====== Blender Game Engine : Python ======
Pour créer des comportements, des actions, des interactions dans le moteur de jeu de Blender, on a vu qu'on pouvait utiliser les [[logiciels:blender:gamelogic:accueil|briques logiques]]. Nous pouvons également accéder à certaines fonctionnalités par des scripts écrits en langage Python. Le langage Python apporte à Blender modularité et extensibilité : on peut créer des extensions pour Blender (//add-ons//), des scripts pour les jeux et les animations, et utiliser les multiples bibliothèques qui existent pour Python.
La principale difficulté sera de savoir quand utiliser les briques logiques et quand utiliser les scripts Python. Certaines choses sont simples à faire en briques logiques, d'autres pourront être déléguées à des scripts.
[[https://www.blender.org/api/blender_python_api_2_77_release/|Blender Python API]] : documentation de l'interface de programmation avec Python.
Il faut savoir que toutes les propriétés visibles dans le logiciel ne sont pas accessibles en mode **bge** (Blender Game). Elles sont accessibles en mode **bpy** (Application Module) qui est le mode pour créer des //add-ons//. C'est assez déroutant. Nous avons néanmoins deux moyens de savoir ce qu'il est possible de faire en mode jeu, utiliser la fonction **dir()** et consulter la documentation de l'API.
===== Premiers pas =====
Pour commencer, il faut configurer son environnement de travail et le tester avec un premier script.
==== Configuration ====
Afin de débugger et d'afficher des informations utiles, il faut pouvoir les afficher dans une console. Pour Windows, il suffit de cliquer sur **Window > Toggle Python Console**, pour Mac et Linux, il faut ouvrir Blender par le terminal.
Il faut tout d'abord faire apparaître l'éditeur de texte (**Text Editor**) et créer un fichier texte avec l'icône "+", puis nommer ces fichiers avec l'**extension .py**. Au lieu de partir de zéro, vous pouvez aussi vous servir de trois modèles de scripts, en parcourant le menu **Templates > Python > Game Logic**.
L'appel des scripts se fait par une brique logique contrôleur de type Python, qu'il faut associer à un capteur. Il existe deux façons d'appeler un script : soit en appelant le script entier, soit en l'appelant par module.
Attention à la position des lignes en python, on appelle ça l'**indentation**. Il faut une indentation équivalente à 4 espaces pour délimiter les blocs.
Exemple correct :
def test():
print("test")
test()
Exemple incorrect :
def test():
print("test")
test()
==== Premier script ====
* Fichier Blender : {{:logiciels:blender:bge-python:bge-python-00.blend|}}
Exemple d'interface avec un premier script python. Le script est actionné par un capteur "Always". Lorsque le jeu démarre (touche "P"), il affiche simplement le nom de l'objet Cube dans la console. \\
{{:logiciels:blender:bge-python:bge-python-04.png?1000|}}
===script00.py===
# Premier script en Python !
# Importation de la bibliothèque du moteur de jeu (Blender Game Engine ou bge)
# Elle nous permet de manipuler les objets et les briques logiques
import bge
# Premier module : afficher le nom de l'objet
def afficherNomObjet():
# Attention à l'indentation des lignes !
# On récupère le contrôleur
cont = bge.logic.getCurrentController()
# Et ensuite l'objet auquel il appartient
obj = cont.owner
# On affiche le nom de cet objet
print(obj.name)
# Appel du module "afficherNomObjet" dès l'appel du script
afficherNomObjet()
===== Game Object =====
* Fichier Blender : {{:logiciels:blender:bge-python:bge-python-01.blend|bge-python-01.blend}}
Les objets du jeu sont tous les objets que vous avez créé sur vos scènes, ils héritent tous de la classe [[https://www.blender.org/api/blender_python_api_2_77_release/bge.types.KX_GameObject.html#bge.types.KX_GameObject|bge.types.KX_GameObject]].
++++ Paramètres et fonctions d'un objet |
actuators, addDebugProperty, alignAxisToVect, angularDamping, angularVelocity, angularVelocityMax, angularVelocityMin, applyForce, applyImpulse, applyMovement, applyRotation, applyTorque, attrDict, children, childrenRecursive, collisionCallbacks, collisionGroup, collisionMask, color, controllers, currentLodLevel, debug, debugRecursive, disableRigidBody, enableRigidBody, endObject, get, getActionFrame, getActionName, getAngularVelocity, getAxisVect, getDistanceTo, getLinearVelocity, getPhysicsId, getPropertyNames, getReactionForce, getVectTo, getVelocity, groupMembers, groupObject, invalid, isPlayingAction, isSuspendDynamics, life, linVelocityMax, linVelocityMin, linearDamping, linearVelocity, localAngularVelocity, localInertia, localLinearVelocity, localOrientation, localPosition, localScale, localTransform, mass, meshes, name, occlusion, orientation, parent, playAction, position, rayCast, rayCastTo, record_animation, reinstancePhysicsMesh, removeParent, replaceMesh, restoreDynamics, scaling, scene, sendMessage, sensors, setActionFrame, setAngularVelocity, setCollisionMargin, setDamping, setLinearVelocity, setOcclusion, setParent, setVisible, state, stopAction, suspendDynamics, timeOffset, visible, worldAngularVelocity, worldLinearVelocity, worldOrientation, worldPosition, worldScale, worldTransform
++++
==== Rendu ====
Toutes les 10 images, grâce au capteur Always "Tic10", le contrôleur Python lance un module du script "script01.py", qui a pour effet de faire tourner, grossir, avancer le cube "MonJoliCube", ainsi que de changer sa couleur. La touche espace agrandit l'objet en largeur et la flèche du haut active un actionneur qui change d'état et donc arrête ces comportements.
{{:logiciels:blender:bge-python:bge-python-anim-01.gif|}}
==== Réglages ====
{{:logiciels:blender:bge-python:bge-python-05-2.png|}}
==== script01.py ====
++++ Voir le script |
# Deuxième script : GameObject
# Exploration des possibilités d'actions sur les objets au cours du jeu
# Touche "P" pour démarrer le jeu, "Echap" pour l'arrêter
# Touche "Espace" pour agrandir le cube
# Touche "Flèche du haut" pour passer à un autre état et donc arrêter les comportements
# Importation des bibliothèques nécessaires pour ce script
import bge
import random
# Module "getInfos" pour avoir des informations sur l'objet
# Décommenter les lignes pour afficher les informations dans la console
def getInfos(cont):
# Pour utiliser l'objet on a besoin de récupérer la brique contrôleur.
# Celle-ci est accessible directement en paramètre : "(cont)"
obj = cont.owner
# Affiche tous les paramètres et fonctions d'un "GameObject"
# print(dir(obj))
# QUELQUES PROPRIETES
# print(obj.name) # le nom de l'objet
# print(obj.scene) # le nom de la scène
# print(obj.life) # durée de vie de l'objet si il a été créé dynamiquement
# print(obj.visible) # est-ce que l'objet est visible ?
# LES PROPRIETES DE JEU (GAME PROPERTY)
# Appuyer sur la touche "N" dans l'éditeur logique
# print(obj.getPropertyNames()) # renvoie une liste des propriétés de l'objet
# print(obj["majoliepropriete"]) # affiche la valeur de la propriété créée
# obj["majoliepropriete"] = 100 # Affceter une valeur à cette propriété
# CAPTEURS ET ACTIONNEURS
# print(obj.sensors) # renvoie une liste des propriétés de l'objet
# print(obj.actuators) # renvoie une liste des propriétés de l'objet
# AFFICHER LES POSITIONS
# print(obj.worldPosition)
# print(obj.worldOrientation)
# print(obj.worldOrientation.to_euler().x) # La fonction to_euler() simplifie le résultat
# print(obj.localScale)
# print(obj.getVectTo(obj.scene.objects["Sol"]))
# print(obj.getDistanceTo(obj.scene.objects["Sol"]))
# Module randObject() pour manipuler l'objet
def randObject(cont):
# On récupère l'objet
obj = cont.owner
# VISIBILITÉ : afficher ou non l'objet
prob = random.randrange(100) # probabilité que l'objet soit visible
if prob < 20 : # en dessous de 20% il n'est plus visible
obj.setVisible(False)
else:
obj.setVisible(True)
# TAILLE
# On augmente la taille y
obj.localScale.y += 0.1
# TRANSLATION
# On applique un mouvement de translation très simple en ajoutant 1 à sa position
obj.worldPosition.x += 0.1
# Si sa position en X est supérieure à 5, alors on la replace en arrière
if obj.worldPosition.x > 5 :
obj.worldPosition.x = -5
# ROTATION : 0.1 degré sur l'axe X
obj.applyRotation((0.1,0,0))
# COULEURS
# En mode GLSL, aller dans l'éditeur Properties et cocher "Material > Options > Object Color"
r = random.random() # retourne un nombre entre 0 et 1
g = random.random()
b = random.random()
obj.color = (r, g , b, True)
# AUTRES
# alignAxisToVect(), applyMovement(), applyForce(), enableRigidBody(), endObject(), replaceMesh(), sendMessage(), playAction()
# Avec le capteur "ToucheEspace", on change la hauteur du Cube
def changeObject(cont):
obj = cont.owner
sensor = obj.sensors["ToucheEspace"]
if sensor.positive :
obj.localScale.z = 2
else:
obj.localScale.z = 1
# Avec le capteur "ToucheHaut", on active l'actionneur qui nous fait changer d'état
def changeState(cont):
obj = cont.owner
sensor = obj.sensors["ToucheHaut"]
actuator = obj.actuators["ToState2"]
if sensor.positive :
cont.activate(actuator)
else:
cont.deactivate(actuator)
++++
===== Les scènes =====
* Fichier Blender : {{:logiciels:blender:bge-python:bge-python-02.blend|}}
{{:logiciels:blender:bge-python:bge-python-anim-02.gif|}}
Quand on commence à écrire des scripts, il est parfois compliqué de savoir à partir de quelle scène le script est appelé et comment accéder à un objet sur une autre scène. Pour cet exemple, nous allons prendre le principe d'un jeu, avec une scène de référence, toujours présente, la "Scène0", et nous allons changer de scènes avec la touche "Flèche du haut". La touche "Espace" permet de changer la couleur tous les objets qui ont la propriété "objColor". La touche "Suppr" supprime l'objet Text de la scène courante.
Les scènes sont des objets qui héritent de la classe : [[https://www.blender.org/api/blender_python_api_2_77_release/bge.types.KX_Scene.html|bge.types.KX_Scene]]
++++ Paramètres et fonctions pour les scènes |
active_camera, activity_culling, activity_culling_radius, addObject, cameras, dbvt_culling, drawObstacleSimulation, end, get, gravity, invalid, lights, name, objects, objectsInactive, post_draw, pre_draw, pre_draw_setup, replace, restart, resume, suspend, suspended, world
++++
==== Architecture des scènes ====
Pour créer les scènes, nous allons nommer les objets et les scènes de façon à pouvoir les manipuler de façon dynamique. En terminant leurs noms par un chiffre, on peut grâce au Python, retirer ce chiffre et atteindre les objets. Comme les noms des objets doivent être uniques dans tout le projet Blender, cela peut être une bonne pratique de nommer ses objets avec l'identifiant de la scène.
{{:logiciels:blender:bge-python:bge-python-06-outliner.png|}}
==== Scène 0 ====
Sur la scène 0, nous allons placer un objet "Text" pour bien visualiser que la scène est toujours présente en tâche de fond, même si on change de scène. Au démarrage, il ajoute la scène 1 au dessus (//Overlay//). Le module "randColors" est donc toujours accessible par la touche espace, sur toutes les scènes.
{{:logiciels:blender:bge-python:bge-python-06-text0.png|}}
==== script02.py ====
++++ Voir le script |
"""
Troisième script :
Manipulation des scènes et des objets à travers les scènes.
Interactions :
- Appuyer sur "P" pour démarrer, "Echap" pour quitter le jeu
- La touche "Flèche du haut" fait avancer dans les scènes
- La touche "Suppr" supprime l'objet Texte spécifique à chaque scène
- La touche "Espace" pour changer la couleur de l'objet 3D visible
"""
import bge
import random
# Variable globale que l'on peut utiliser partout dans le script
nombre_scenes = 3
# Changer de scène, mais de façon fixe
# L'alternative avec la brique actionneur "Scene > Set Scene" semble plus simple
def goToScene2(cont):
obj = cont.owner
scene = obj.scene
scene.replace("Scene2") # le nom de la scène est en "dur"
# Aller à la scène suivante de façon dynamique
def goToNextScene(cont):
obj = cont.owner
# On récupère l'état du capteur "ToucheHaut"
sensor = obj.sensors["ToucheHaut"]
# La touche "Flèche du haut" est appuyée
if sensor.positive :
# Comme les noms des scènes se terminent par un chiffre
# on récupère le dernier caractère du nom de la scène
# et on le convertit en chiffre
scene = obj.scene
id = int(scene.name[-1:])
# On ajoute 1 pour aller à la scène suivante
id += 1
# S'il dépasse la variable "nombre_scenes", on revient à 1
if id > nombre_scenes :
id = 1
# Changement de scène
scene.replace("Scene" + str(id))
print("Scène suivante : " + str(id))
# Détruire l'objet
def destroyObject(cont):
obj = cont.owner
obj.endObject() # l'objet se détruit lui-même !
# Détruire l'objet Texte de façon dynamique
# car son nom dans chaque scène se termine par le numéro de la scène
def deleteText(cont):
obj = cont.owner
sensor = obj.sensors["ToucheDelete"]
if sensor.positive :
scene = obj.scene
id = scene.name[-1:] # numéro de la scène
textObjName = "Text"+id # nom de l'objet Text
if textObjName in scene.objects :
val = scene.objects[textObjName]
val.endObject()
print("Suppression de " + textObjName)
# Génère des couleurs pour tous les objets de toutes les scènes
# qui ont la propriété "colorObj"
def randColors(cont):
sensor = cont.sensors['ToucheEspace']
if sensor.positive:
# Liste de toutes les scènes
scenes = bge.logic.getSceneList()
# Parcours des scènes
for scene in scenes:
# Parcours des objets de cette scène
for obj in scene.objects:
# Si l'objet a la propriété "colorObj"
if "objColor" in obj:
print("Change colors of " + obj.name)
r = random.random()
g = random.random()
b = random.random()
obj.color = (r, g, b, True)
++++
==== Trouver un objet ====
Le nom des objets doit être unique dans tout le fichier Blender, et les objets avec le //BGE// ne peuvent être trouver qu'en passant par la scène qui le contient. Une façon concise de retrouver un objet dans une scène :
# Liste de toutes les scènes
scenes = bge.logic.getSceneList()
# Trouver la scène "Scene0"
my_scene = [scene for scene in scenes if scene.name=="Scene0"][0]
# Trouver l'objet "MonJoliCube0" de cette scène
my_object = my_scene.objects['MonJoliCube0']
===== Ouïe ! =====
* Fichier Blender : {{:logiciels:blender:bge-python:bge-python-04.blend|}}
{{:logiciels:blender:bge-python:animcollision2.gif|}}
Manipulation des collisions, des messages et des fréquences des boîtes capteurs. Les collisions font parti du groupe [[https://www.blender.org/api/blender_python_api_2_77_release/bge.types.KX_TouchSensor.html|bge.types.KX_TouchSensor]].
===== Objets =====
{{:logiciels:blender:bge-python:bge-python-07.png|}}
==== Contrôler la fréquence des pulses ====
Avec **sensor.skippedTicks**.
{{:logiciels:blender:bge-python:animrandcolors.gif|}}
==== script03.py ====
++++ Voir le script |
"""
Manipulation des collisions, des messages et des fréquences des boîtes capteurs
"""
import bge
import random
# Manipuler la fréquence de la boîte capteur
def randFreqColors(cont):
obj = cont.owner
sensor = obj.sensors["Tic"]
if sensor.positive :
# Activation du mode pulse
if sensor.usePosPulseMode == False:
sensor.usePosPulseMode = True
# Fréquence du prochain déclenchement au hasard
sensor.skippedTicks = random.randrange (1,30)
obj.color = (random.random(),random.random(),random.random(),True)
# Tests de collisions avec envois de messages
def sendBoom(cont):
obj = cont.owner
sensor = obj.sensors["Collision"]
if sensor.positive :
msg = "From " + obj.name
obj.sendMessage("boom", msg , sensor.hitObject.name)
# Réception d'un message et ajout d'un objet texte
def receiveBoom(cont):
obj = cont.owner
sensor = obj.sensors["Message"]
if sensor.positive :
print("Message : " + str(sensor.bodies))
# Récupération du contenu du message et séparation des mots dans un tableau
# message[0] est le premier élément, ici "From"
# message[1] est le deuxième élément, ici le nom de la sphère
message = sensor.bodies[0].split(" ")
# Construction du nom de l'objet Text associé au Cube et au Sol
textName = obj.name + "Text"
# Ajout de cet objet dans la scène
# Arguments : nom de l'objet, nom de l'objet où il va être positionner, durée de vie (ici 100 images)
obj.scene.addObject(textName, textName, 100)
# On modifie ensuite le texte et la position de l'objet Text en question
objText = obj.scene.objects[textName]
objText.text = "Ouïe ! " + message[1] + " !"
objText.worldPosition.z = obj.worldPosition.z + 1
# On change aussi la couleur
obj.color = (random.randrange(10)/10,random.randrange(10)/10,random.randrange(10)/10,True)
++++
===== Autres exemples =====
* Fichier Blender et les textures : {{:logiciels:blender:bge-python:bge-python-05.zip|}}
{{:logiciels:blender:bge-python:animpythonrunanim.gif|}}
Jouer une animation et changer d'images au hasard dans une texture.
==== Objets ====
{{:logiciels:blender:bge-python:bge-python-08.png|}}
==== script04.py ====
++++ Voir le script |
"""
Jouer une animation et changer d'images de textures
"""
import bge
from bge import texture
from bge import logic
import random
# Jouer une animation
def runAnim(cont):
obj = cont.owner
sens = cont.sensors['ToucheEspace']
if sens.positive:
# Déclenchement de l'animation
obj.playAction("MonJoliCube0Action",0,500,play_mode=logic.KX_ACTION_MODE_PLAY,speed=3)
# Changer d'images de texture
# Un dossier "./mestextures/murs/" avec 10 images
# dont le nom des fichiers suit la logique "mur0.jpg", "mur1.jpg", "mur2.jpg", etc.
def randTexture(cont):
# Nombre d'images dans le dossier textures
max=6
obj = cont.owner
# Un capteur quelconque
sens = cont.sensors['Tic']
if sens.positive:
# Image orginale de la texture
ID = texture.materialID(obj, "IMmur0.jpg")
object_texture = texture.Texture(obj, ID)
obj.attrDict["tex1"] = object_texture
# Tirage d'un nombre au hasard et conversion en String pour former le nom de l'image
n = str(random.randrange(max))
# Chemin de l'image
url = logic.expandPath("//textures\mur"+n+".jpg")
new_source = texture.ImageFFmpeg(url)
object_texture.source = new_source
object_texture.refresh(False)
++++
==== Voir aussi ===
Des variables peuvent être accessibles depuis toutes les scènes avec [[https://www.blender.org/api/blender_python_api_2_77_release/bge.logic.html?highlight=globaldict#bge.logic.globalDict|bge.logic.globalDict]].
Manipuler un actionneur "Motion" : move.dLoc = [0.0, speed, 0.0]
Pour les notions de distances entre objet et d'alignement : getDistance, rayCast, actionneur "TrackTo", alignTo...
Pour communiquer avec d'autres logiciels, on peut utiliser la bibliothèque OSC : [[http://wiki.labomedia.org/index.php/Communication_entre_Pure-data_et_Blender_en_OSC|exemple avec Pure Data]] et [[https://github.com/sergeLabo/blenderOSC|blenderOSC]]