====== 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]]