Soya/Version imprimable
Apparence
< Soya
Soya
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Soya
Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la Licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans Texte de dernière page de couverture. Une copie de cette licence est incluse dans l'annexe nommée « Licence de documentation libre GNU ».
Python base 1
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2001-2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Base 1 : paramétrer Soya et afficher un élément 3D.
# Ceci est la première leçon du tutorial de Soya.
# Dans cette leçon, vous apprendrez à paramétrer Soya et à afficher un modèle 3D. Dans l'ordre des choses,
# nous avons besoin de :
# - un modèle 3D
# - une lampe (ou lumière)
# - une caméra
# - une scène pour grouper les objets 3D.
# Importation de : sys, os et soya.
import sys, os, os.path, soya
# Initialisation de Soya (création et affichage de la fenêtre graphique).
soya.init()
# Ajout d'un répertoire "tutorial/data" à la liste des répertoires de données de Soya. Quand Soya charge toutes les données,
# lorsque c'est un modèle 3D ou une texture, il va toujours chercher les données dans soya.path.
# soya.path fonctionne comme sys.path.
# Le répertoire des données de Soya doivent être organisés de la manière suivante :
# ./images : les images
# ./materials : les materiaux (inclue les textures dans les formats d'image optimisés)
# ./worlds : les modèles 3D
# ./models : les modèles optimisés.
# Notez l'utilisation de sys.argv pour aller dans le répertoire où sont stockés les scripts actifs.
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création d'une scène. La scène est un Monde qui contient tous les éléments 3D que l'on va
# afficher. Un Monde est un objet 3D qui peut contenir d'autres objets 3D (et donc, d'autres mondes),
# retenez qu'un Monde est un groupe d'objets 3D. (World = Monde)
scene = soya.World()
# Chargement du modèle de l'épée (provenant de "tutorial/data/models/sword.data").
# Le modèle de l'épée que nous utilisons a été fait avec [[Blender]].
# Model.get est une méthode statique qui retoure un objet correspondant à un nom de fichier et
# le charge si besoin. Note : si vous avez besoin de cette objet une seconde fois, il retournera le même objet
# sauf si vous l'avez rechargé
# Toutes les dépendances du modèle seront chargés (Par exemple, les materiaux).
# (sword = épée)
sword_model = soya.Model.get("sword")
# Création du modèle.
# Un Body (corps) affiche un modèle 3D. Le premier argument lors de la construction du Body est le parent du
# nouveau Body ; ici, nous plaçons le Body dans la scène. Le parent doit être un World ou un dérivé d'un World (Ou # rien)
# (Ceci est une convention similaire à Tkinter, où le premier argument lors de la construction d'un widget est
# son maître).
# Le second argument est un modèle : ici notre modèle de l'épée.
sword = soya.Body(scene, sword_model)
# La position par défaut est 0.0, 0.0, 0.0
# Pour mieux la voir, nous déplaçons l'épée vers la droite.
sword.x = 1.0
# Nous effectuons ensuite une rotation de l'épée sur l'axe Y de 60,0 degrés.
# (Dans Soya, tous les angles sont en degrés)
sword.rotate_y(90.0)
# Création d'une lampe dans la scène (par convention, le premier argument est le parent).
# Ensuite nous déplaçons cette lampe de vers les coordonées (1.5, 2.0, 0.2).
light = soya.Light(scene)
light.set_xyz(0.5, 0.0, 2.0)
# Création d'une caméra dans la scène et nous la déplaçons vers z = 5.0. Cette caméra montre dans la direction de
# -Z, donc, dans ce cas vers l'épée.
#
# Soya considère toujours la direction X par rapport à la droite, la direction Y par rapport au haut et le -Z par # rapport à l'avant.
#<!-- (Using -Z for front seems odd, but using Z for front makes all coordinate systems
# indirect, which is a mathematical nightmare !)-->
camera = soya.Camera(scene)
camera.z = 2.0
# Demandons à Soya que la caméra soit celle que l'on utilise pour faire le rendu de l'image.
soya.set_root_widget(camera)
# Pour faire une image du rendu en 320x240 dans le répertoire "résultat", décommentez cette ligne :
#soya.render(); soya.screenshot().resize((320, 240)).save(os.path.join(os.path.dirname(sys.argv[0]), "resultat", os.path.basename(sys.argv[0])[:-3] + ".jpeg"))
# Créons d'une boucle principale pour la scène et lançons là.
# La boucle principale est l'objet qui gère toutes les boucles de Soya. Ces boucles gèrent :
# - les autres boucles
# - la régularité d'image par seconde : 40 FPS
# - le lissage d'une animation
# - calculer les FPS
# - faire un rendu à l'écran.
soya.MainLoop(scene).main_loop()
Python base 2
# -*- indent-tabs-mode: t -*-
# Tutorial Soya.
# Copyright (C) 2001-2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# base 2 : gestion du temps et rotation d'un modèle 3D.
# Cette leçon est la même que [[base-1]].py excepté la rotation du modèle.
# Vous allez apprendre la gestion du temps et la rotation.
# Les fonctions basiques servant à la rotation :
#
# rotate_x : tourne dans l'axe X
# rotate_y : tourne dans l'axe Y (= dans le plan horizontal)
# rotate_z : tourne dans l'axe Z
# Importation et initialisation de Soya (voir 1ère leçon).
import sys, os, os.path, soya
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création de la scène.
scene = soya.World()
# Chargement du modèle de l'épée.
sword_model = soya.Model.get("sword")
# Création de la classe servant à la rotation des Body. Soya, puisque il est entièrement orienté objet, presque
# toutes les classes Soya peuvent être complétés.
# Ici notre classe provenant de soya.Body, et alors on peut afficher un modèle.
class RotatingBody(soya.Body):
# La classe principale appelle répétitivement la méthode advance_time pour tous les objets dans la scène.
# Pour tourner un Body, nous avons juste à modifier cette méthode.
# Dans Soya, l'unité de temps est le "Round", un Round équivaut à 30 millisecondes (par défaut).
# Donc l'argument de advance_time est la proportion de temps dans un round ce qui produit :
# par exemple, 0.3 fait que 30% d'un Round s'est passé entre 2appels soit 9 millisecondes.
# advance_time doit être limité pour l'animation de code mais pas pour le code de décision. Nous verrons
# d'autres méthodes pour ça dans Base 3.
def advance_time(self, proportion):
# Appel de advance_time. Ceci est indispensable, comme tous les objets de Soya
# que nous avons déjà dans la méthode advance_time.
soya.Body.advance_time(self, proportion)
# Tourner un objet sur l'axe Y. L'angle est proportionnel car
# le plus gros du temps a été épuisé, nous voulons tourner dans l'ordre du lissage de l'animation.
# Presque toutes les rotations ou les mouvements se produisent dans advance_time qui a besoin
# d'être proportionnel.[1]
self.rotate_y(proportion * 5.0)
# On fait la rotation du Body dans la scène, utilisation du modèle de l'épée.
sword = RotatingBody(scene, sword_model)
# Création de la lampe.
light = soya.Light(scene)
light.set_xyz(0.5, 0.0, 2.0)
# Création de la caméra.
camera = soya.Camera(scene)
camera.z = 3.0
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
[1]
# Rotates the object around Y axis. The angle is proportional to proportion because # the more time has been spent, the more we want to rotate, in order to achieve a # smooth animation. # Almost every rotations or moves that occurs in advance_time should be proportional # to proportion.
Python base 3
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Base 3 : gestion du temps : un mouvement de sphère aléatoire.
# Dans cette leçon, nous allons créer une sphère qui bougera aléatoirement.
# Vous allez en apprendre plus sur la gestion du temps (seconde partie), ainsi que sur les vecteurs et le
# système de conversion de coordonées.
# Importation des modules.
import sys, os, os.path, random, soya
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création d'une scène.
scene = soya.World()
# Création d'une classe qui gèrera les mouvements aléatoires de la sphère. Nous appellons ça "Head" ou tête car
# nous allons utiliser un modèle de tête.
# Cette classe a soya.Body comme classe parente, maintenant nous pouvons avoir un modèle (la tête).
class Head(soya.Body):
# Voyons le constructeur de la classe.
def __init__(self, parent):
# Appel du constructeur soya.Body (rappelez vous, l'appel de cette super implémentation
# est toujours une bonne idée), et utilisation du modèle appelé, 'caterpillar_head'.
soya.Body.__init__(self, parent, soya.Model.get("caterpillar_head"))
# Ajouter un attribut rapide pour notre nouvel objet.
# La rapidité est un vecteur de l'objet. Un vecteur est un objet mathématique, utilisé pour
# le calcul ; contrairement à d'autres objets (Lampe, Caméra, Body, World,...) qui ne sont pas
# modifiés durant le rendu.
# Un vecteur est défini par un système de 3 coordonnées (X, Y, Z) ; ici la
# vitesse est défini dans 'self', exemple : le Head, et avec les coordonnées 0.0, 0.0, -0.2.
# Souvenez vous que dans Soya, la direction -Z est le devant.
# Ceci signifie que le vecteur de vitesse est parallèle à la direction de Head
# qui a une longueur de 0.2.
self.speed = soya.Vector(self, 0.0, 0.0, -0.2)
# La vitesse de rotation actuelle (sur l'axe Y)
self.rotation_speed = 0.0
# Tel que advance_time, begin_round est appellé par la boucle principale.
# Mais contrairement à advance_time, begin_round est appellé régulièrement, au début de chaque round
# ; celui ci recoit un argument non "proportionnel".
# Les décisions doivent être prises dans le begin_round.
def begin_round(self):
# Appel de la super implémentation.
soya.Body.begin_round(self)
# Calcul de la nouvelle vitesse de rotation : un angle aléatoire entre -25.0 et 25.0 degrés.
self.rotation_speed = random.uniform(-25.0, 25.0)
# La vitesse des vecteurs n'a pas besoin d'être recalculée, tel qu'il est exprimé dans le
# système de coordonnées de Head
# CoordSyst.
# Dans advance_time, nous allons faire un head amélioré.
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
# Execution de la rotation, il faut tenir compte de l'argument de proportion.
self.rotate_y(proportion * self.rotation_speed)
# Déplaçons le Head grâce au vecteur de vitesse.
# add_mul_vector est identique à self.add_vector(proportion * self.speed), mais plus rapide.
# Le vecteur de vitesse et le Head ne sont pas dans le même système de coordonnées, mais
# Soya fait automatiquement la conversion nécéssaire.
self.add_mul_vector(proportion, self.speed)
# Création du Head dans la scène.
head = Head(scene)
# Création d'une lampe.
light = soya.Light(scene)
light.set_xyz(2.0, 5.0, 0.0)
# Création d'une camera.
camera = soya.Camera(scene)
soya.set_root_widget(camera)
camera.set_xyz(0.0, 15.0, 15.0)
# On fait en sorte que la caméra montre la position initiale du Head.
# La méthode look_at est une autre méthode de rotation ; ca marche avec n'importe quel objet 3D
# auquel nous donnons la position (un objet 3D ou un point), ou nous lui donnons une direction (si l'argument est
# un vecteur).
camera.look_at(head)
#import time; main_loop = soya.MainLoop(scene)
#for i in range(3):
# for j in range(5):
# time.sleep(0.1); main_loop.update()
# soya.render(); soya.screenshot().resize((320, 240)).save(os.path.join(os.path.dirname(sys.argv[0]), "results", os.path.basename(sys.argv[0])[:-3] + "_%s.jpeg" % i))
soya.MainLoop(scene).main_loop()
Python base 4
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# basic-4: Gestion du temps : un mouvement aléatoire de chenille.
# Dans cette leçon, nous allons créer une chenille composée d'une tête (Nous avons déjà vu ça
# dans une précédente leçon ; maintenant vous comprenez pourquoi j'ai appelé ça 'head' (= tête))
# et 10 sphères d'un corps. Chaque pièce du corps suit le précédent, ou le head pour
# première pièce.
# Démarrez le tutorial si vous ne comprennez pas ce qu'est la chenille.
# Importation du module de Soya.
import sys, os, os.path, random, soya
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création de la scène.
scene = soya.World()
# CaterpillarHead est la classe pour la tête de la chenille.
# C'est identique à la classe Head de la leçon base 3, excepté le nom de la classe.
# Alors je ne commente pas !
class CaterpillarHead(soya.Body):
def __init__(self, parent):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar_head"))
self.speed = soya.Vector(self, 0.0, 0.0, -0.2)
def begin_round(self):
soya.Body.begin_round(self)
self.rotate_y((random.random() - 0.5) * 50.0)
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# Une CaterpillarPiece est une pièce du corps de la chenille.
# Ça suit d'autres objets -- la pièce précédente, ou là tête pour la première.
class CaterpillarPiece(soya.Body):
# Le constructeur prend 2 arguments : le parent et la précédente pièce du corps
# que nous devons suivre.
# Similaire à la tête, nous définissons un vecteur de vitesse.
def __init__(self, parent, previous):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar"))
self.previous = previous
self.speed = soya.Vector(self, 0.0, 0.0, -0.2)
def begin_round(self):
soya.Body.begin_round(self)
# Nous tournons une pièce de la chenille pour qu'il regarde le morceau précédent.
self.look_at(self.previous)
# La méthode distance_to retourne une distance entre 2 positions.
# Si nous sommes trop près du morceau précédent du corps, nous paramétrons la vitesse à Z = 0.0,
# et ainsi la vitesse du vecteur est nulle : cette pièce n'a pas de longs mouvements.
# Sinon, nous remettons la vitesse à Z = -0.2.
if self.distance_to(self.previous) < 1.5: self.speed.z = 0.0
else: self.speed.z = -0.2
# advance_time est identique a CaterpillarHead.
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# Création de la tête de la chenille.
caterpillar_head = CaterpillarHead(scene)
caterpillar_head.rotate_y(90.0)
# Création de 10 pièces de corps de la chenille.
previous_caterpillar_piece = caterpillar_head
for i in range(10):
previous_caterpillar_piece = CaterpillarPiece(scene, previous_caterpillar_piece)
previous_caterpillar_piece.x = i + 1
# Création de la lampe.
light = soya.Light(scene)
light.set_xyz(2.0, 5.0, 0.0)
# Création de la caméra.
camera = soya.Camera(scene)
camera.set_xyz(0.0, 15.0, 15.0)
camera.look_at(caterpillar_head)
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
# Pour information, la texture de la chenille à été réalisée avec Gimp et le modèle a été généré
# avec ce code (regardez le tutoriel sur le modelage pour le comprendre) :
# import soya.sphere
# caterpillar_material = soya.Material(soya.Image.get("chenille.png"))
# caterpillar_head_material = soya.Material(soya.Image.get("chenille_tete.png"))
# caterpillar_material .filename = "caterpillar"
# caterpillar_head_material.filename = "caterpillar_head"
# caterpillar_material .save()
# caterpillar_head_material.save()
# caterpillar = soya.sphere.Sphere(slices = 12, stacks = 12, material = caterpillar_material)
# caterpillar_head = soya.sphere.Sphere(slices = 12, stacks = 12, material = caterpillar_head_material)
# caterpillar_head.scale(1.2, 1.2, 1.2)
# caterpillar .filename = "caterpillar"
# caterpillar_head.filename = "caterpillar_head"
# caterpillar .save()
# caterpillar_head.save()
# XXX put this elsewhere
# Lots of Soya methods have also an operator :
#
# Position + Vector => Point
# Position += Vector Position.add_vector(Vector)
# Position >> Position Position.vector_to (Position) => Vector
# Position %= CoordSyst Position.convert_to(CoordSyst)
# Vector * float
# Position + Vector => Point
# Position += Vector
# Position.add_vector(Vector)
# Translation or vectorial addition (if the Position is a Vector).
# Position >> Position => Vector
# Position.vector_to (Position) => Vector
# Creates a vector from a strating and an ending position.
# Position %= CoordSyst Position.convert_to(CoordSyst)
# Vector * float
Python base 5
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# basic-5: Event management : un controle de la chenille controllé par le clavier
# Dans cette leçon notre chenille va nous obéir !
# Vous allez apprendre comment utiliser les évenements de SDL avec Soya.
# Utilisez les touches directionelles pour contrôler la chenille.
# Importation des modules de Soya.
# Le module soya.sdlconst contient toutes les constantes de SDL.
import sys, os, os.path, soya, soya.sdlconst
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création de la scene.
scene = soya.World()
# La classe CaterpillarHead est similaire à la classe CaterpillarHead de la précédente leçon.
class CaterpillarHead(soya.Body):
def __init__(self, parent):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar_head"))
self.speed = soya.Vector(self, 0.0, 0.0, 0.0)
self.rotation_y_speed = 0.0
def begin_round(self):
soya.Body.begin_round(self)
# Boucle de tous les évenements Soya / SDL.
# Chaque évenement est un tuple ; la première valeur indique les types d'évènements
# et les autres valeurs dépendant du type. Les évenements existants sont :
# - (KEYDOWN, keysym, modifier) où keysym est un code de touche (une constante K_*)
# et un modifieur est un drapeau combinant plusieurs constantes MOD_* (pour tester la présence du
# modifieur, do e.g. for left shift: modifier & soya.sdlconst.MOD_LSHIFT).
# - (KEYUP, keysym, modifier)
# - (MOUSEMOTION, x, y, xrel, yrel) où x et y sont les coordonnées de la souris (en pixels)
# , xrel et yrel sont des coordonnées relatives de la souris (la différence entre le prochain évenement).
# - (MOUSEBUTTONDOWN, button, x, y) où button est le numéro de bouton de souris et x et y sont les coordonnées de la souris.
# Les numéros de boutons de la souris sont :
# - 1 : gauche
# - 2 : milieu
# - 3 : droite
# - 4 : molette haut
# - 5 : molette bas
# - (MOUSEBUTTONUP, button, x, y)
# - (JOYAXISMOTION, axis, value) XXX
# - (JOYBUTTONDOWN, button) XXX
# - (VIDEORESIZE, new_width, new_height)
for event in soya.process_event():
# Verification des touches préssées.
if event[0] == soya.sdlconst.KEYDOWN:
# Les flèches du haut et du bas met la vitesse (speed) de la chenille à une valeur positive ou négative.
if event[1] == soya.sdlconst.K_UP: self.speed.z = -0.2
elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 0.1
# Les flèches gauche et droite modifient la vitesse de rotation.
elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 10.0
elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = -10.0
# En appuyant sur échap ou 'q'la boucle principale main_loop sera quittée, et termine ainsi le programme
# soya.MAIN_LOOP.stop() est la bonne manière de terminer l'application, et cause
# le retournement de la méthode MainLoop.main_loop().
elif event[1] == soya.sdlconst.K_q: soya.MAIN_LOOP.stop()
elif event[1] == soya.sdlconst.K_ESCAPE: soya.MAIN_LOOP.stop()
# On regarde les touches qui ne sont pas appuiées.
elif event[0] == soya.sdlconst.KEYUP:
# Quand les touches haut ou bas sont libérés, la vitesse (speed) est remise à zero.
if event[1] == soya.sdlconst.K_UP: self.speed.z = 0.0
elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 0.0
# Quand les flèches gauche et droite sont libérés, la vitesse de rotation est mise à zero.
elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 0.0
elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = 0.0
elif event[0] == soya.sdlconst.QUIT:
soya.MAIN_LOOP.stop()
# On fait la rotation.
self.rotate_y(self.rotation_y_speed)
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# La classe CaterpillarPiece n'a pas changé depuis le dernier tutorial.
class CaterpillarPiece(soya.Body):
def __init__(self, parent, previous):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar"))
self.previous = previous
self.speed = soya.Vector(self, 0.0, 0.0, -0.2)
def begin_round(self):
soya.Body.begin_round(self)
self.look_at(self.previous)
if self.distance_to(self.previous) < 1.5: self.speed.z = 0.0
else: self.speed.z = -0.2
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# Création de la tête de la chenille et de 10 pièces de son corps.
caterpillar_head = CaterpillarHead(scene)
caterpillar_head.rotate_y(90.0)
previous_caterpillar_piece = caterpillar_head
for i in range(10):
previous_caterpillar_piece = CaterpillarPiece(scene, previous_caterpillar_piece)
previous_caterpillar_piece.x = i + 1
# Création de la lampe.
light = soya.Light(scene)
light.set_xyz(2.0, 5.0, 0.0)
# Création de la caméra.
camera = soya.Camera(scene)
camera.set_xyz(0.0, 15.0, 15.0)
camera.look_at(caterpillar_head)
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
Python base 6
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# base 6 : gestion des évenements, controler la chenille avec la souris
# Nous allons voir comment contrôler la chenille avec la souris.
# Importation des modules.
import sys, os, os.path, soya, soya.sdlconst
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création d'une scène.
scene = soya.World()
# La classe CaterpillarHead est similaire à celle du tutorial précédent.
class CaterpillarHead(soya.Body):
def __init__(self, parent):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar_head"))
self.speed = soya.Vector(self, 0.0, 0.0, 0.0)
self.rotation_y_speed = 0.0
self.mouse_x = 0
self.mouse_y = 0
def begin_round(self):
soya.Body.begin_round(self)
# Boucle pour tous les évenements Soya / SDL.
for event in soya.process_event():
# Verification des évenements de la souris, et conservation des coordonnées X et Y du curseur.
if event[0] == soya.sdlconst.MOUSEMOTION:
self.mouse_x = event[1]
self.mouse_y = event[2]
# Le calcul des coordonnées de la souris est en 3D, Camera.coord2d_to_3d renvoie les coordonnées X et Y d'une souris en 2D
# et une coordonnée optionnel Z (puisqu'il ne peut pas bien le gérer, la valeur de Z par défaut est -1.0).
# Maintenant, nous utilisons pour Z les coordonnées Z de la chenille dans les coordonnées du système de la caméra :
# nous considérons que le curseur est à la même hauteur que la chenille.
# l'opérateur % est utilisé pour la conversion des coordonnées :
# position % coordinate_system
# retourne la position convertie dans coordinate_system (possibly position itself if it
# is already in the right coordinate system).
mouse_pos = camera.coord2d_to_3d(self.mouse_x, self.mouse_y, (self % camera).z)
# Alors ca convertit la position de la souris en système de coordonnées pour la scène, et met les
# coordonnées de Y à 0.0, car nous ne voulons pas d'une chenille qui vole
# (souvenez vous que Y est l'axe de la hauteur).
mouse_pos.convert_to(scene)
mouse_pos.y = 0.0
# Calcul de la vitesse de coordonnée Z ; nous ne voulons pas une vitesse constante : plus le curseur est loin
# et plus la chenille va vite.
# Ainsi, la vitesse de coordonnée Z est la distance entre la chenille et la souris, et doit être négative
# (car -Z est vers le haut).
self.speed.z = -self.distance_to(mouse_pos)
# Rotations vers la souris.
self.look_at(mouse_pos)
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# Nous changeons CaterpillarPiece, afin qu'il puisse gérer la variable de vitesse du Head.
class CaterpillarPiece(soya.Body):
def __init__(self, parent, previous):
soya.Body.__init__(self, parent, soya.Model.get("caterpillar"))
self.previous = previous
self.speed = soya.Vector(self, 0.0, 0.0, -0.2)
def begin_round(self):
soya.Body.begin_round(self)
# Comme la vitesse peut être très élevée, nous avons besoin de prendre en compte
# la vitesse précédente (celle que l'on avait lors du mouvement).
# On calcule ensuite la future position de la pièce par la translation de la piece
# par le vecteur vitesse de cette même pièce.
previous_next_pos = self.previous + self.previous.speed
# La chenille regarde vers la futur position de la pièce.
self.look_at(previous_next_pos)
# Calcul de la coordonnée Z de vitesse. Nous utilisons un espace entre une pièce et
# sa prochaine position, et nous supprimons 1.5 car nous avons besoin que chaque piece
# soit séparé par une distance de 1.5 unités.
self.speed.z = -(self.distance_to(previous_next_pos) - 1.5)
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
# Creation du Head de la chenille ainsi que 10 pièces de son corps.
caterpillar_head = CaterpillarHead(scene)
caterpillar_head.rotate_y(90.0)
previous_caterpillar_piece = caterpillar_head
for i in range(10):
previous_caterpillar_piece = CaterpillarPiece(scene, previous_caterpillar_piece)
previous_caterpillar_piece.x = i + 1
# Création d'une lampe.
light = soya.Light(scene)
light.set_xyz(2.0, 5.0, 1.0)
# Création d'une caméra.
camera = soya.Camera(scene)
camera.set_xyz(0.0, 15.0, 15.0)
camera.look_at(caterpillar_head)
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
Python base de chargement de fichier 1
# -*- indent-tabs-mode: t -*- # Soya 3D tutorial # Copyright (C) 2001-2004 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # base du chargement de fichier 1 : chargement de fichier : chargement d'un corps en rotation # Cette leçon charge un modèle tournant provenant d'un fichier, en utilisant Cerealizer ou cPickle. # # Ce fichier devra être créé en exécutant le tutorial basic-savingfile-cerealizer-1 # ou basic-savingfile-pickle-1 tutorial. Notez alors que le chargement d'un fichier est INDÉPENDANT # de son format de fichier (Cerealizer ou Pickle); Soya est fait pour déterminer le format qu'il utilise. # Importation de Soya et cerealizer. import sys, os, os.path, soya import cerealizer # L'import du module est défini par la classe RotatingBody. Cerealizer ou Pickle sauvent # les données de l'objet, mais PAS la source ou le code executable de leur classe. # Maintenant, nous utilisons "execfile" depuis le fichier "basic-savingfile-cerealizer-1.py" ce qui n'est pas # un nom de module python valide, à cause du caractère "-". # Cependant, vous devez normalement utiliser ceci : # #import basic-savingfile-cerealizer-1 # # Ici, l'importation de "basic-savingfile-cerealizer-1.py" ou "basic-savingfile-pickle-1.py" # n'a aucune importance avec 'module part' (= avant le "if __name__ == "__main__"), vous retrouvez # les mêmes dans les deux tutorial. execfile(os.path.join(os.path.dirname(sys.argv[0]), "basic-savingfile-cerealizer-1.py")) # Initialisation de Soya et paramétrage des répertoires. soya.init() soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data")) # Chargement de la scène que nous avons sauvé précédemment. "a_scene_with_a_rotating_body" est le nom de fichier # que nous avons donné à cette scène. # La méthode "World.load" est similaire à "Model.get", sauf que "load" retourne toujours un # nouvel objet lu sur un fichier, alors que get cache son résultat. scene = soya.World.load("a_scene_with_a_rotating_body") # Création d'une caméra. # Pour des raisons techniques, la caméra ne peut pas être sauvée. Ceci n'est pas un réel problème depuis que # la camera n'est pas réellement une scène dépendante par sa configuration. # Nous ajoutons ainsi la caméra APRÈS le chargement de la scène. camera = soya.Camera(scene) camera.z = 3.0 soya.set_root_widget(camera) soya.MainLoop(scene).main_loop()
Python sauvegarde avec cerealizer
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2001-2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# base de sauvegarde de fichier avec céréalizer : Sauvez un fichier avec cerealizer :
# sauvez un corps en rotation.
# Cette leçon enregistre le moèle tournant de la leçon base 2 dans un fichier en utilisant Cerealizer.
# Pour commencer, vous devez installer Cerealizer (un module semblable à Pickle),
# il est disponible sur cette page :
#
# http://home.gna.org/oomadness/en/cerealizer/index.html
# Importation de Soya et Cerealizer.
import sys, os, os.path, soya
import cerealizer
# Création de la classe du corps en rotation, regardez le tutorial base 2 pour plus d'information.
# Notez que vous pouvez ajouter un attribut à votre classe, et il sera automatiquement sauvé.
class RotatingBody(soya.Body):
def advance_time(self, proportion):
soya.Body.advance_time(self, proportion)
self.rotate_y(proportion * 5.0)
# Enregistrement de la classe RotatingBody en utilisant Cerealizer.
cerealizer.register(RotatingBody)
# cerealizer.register accepte toutes les classes de Python. Une classe peut hériter d'une classe Soya
# (tel que, ici, Body) ou non, ce n'est pas important.
# Cependant, si votre classe hérite de World, Image, Model, Material, vous devez faire quelque chose
# de spécial si vous voulez utiliser YourClass.get() ou YourClass.save(). Ceci implique
# l'objet SavedInAPath, comprendre ici que tous les objets Soya sont sauvés
# dans un répertoire spécial de soya.path.
#
# Par contre, pour World, vous devez faire ceci :
#
#import soya.cerealizer4soya
#class YourWorld(soya.World):
# pass
#cerealizer.register(YourWorld, soya.cerealizer4soya.SavedInAPathHandler(YourWorld))
#
# Et vous pouvez utiliser YourWorld.get("filename").
#
# Si vous désirez qu'un fichier soit sauvé dans un sous répertoire différent de soya.path, par exemple
# data/your_worlds/ plutot que data/worlds/, faites:
#
#class YourWorld(soya.World):
# DIRNAME = "your_worlds"
# The rest of the file is executed ONLY if this file is run as a script.
# This allows to import this file as a module, for defining the RotatingBody class.
if sys.argv[0].endswith("basic-savingfile-cerealizer-1.py"):
# Inits Soya and sets the data directory.
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Sets the file format soya uses for saving file.
#
# The default configuration is to use cPickle for saving files, and to support loading
# files saved either by cPickle or Cerealizer (if available).
#
# The complete syntax is set_file_format(saving_format, loading_format), where loading_format
# is optional and can be either a single format, or a list of format.
# There are currently 2 supported formats: cPickle and Cerealizer.
# See set_file_format's __doc__ for more information.
soya.set_file_format(cerealizer)
# Creates the scene.
scene = soya.World()
# Loads the sword model.
sword_model = soya.Model.get("sword")
# Creates a rotating body in the scene, using the sword model.
sword = RotatingBody(scene, sword_model)
# Creates a light.
light = soya.Light(scene)
light.set_xyz(0.5, 0.0, 2.0)
# Set the scene filename. It is just the name of the file, soya adds automatically
# a directory path as well as a ".data" extention.
scene.filename = "a_scene_with_a_rotating_body"
# Saves the scene. The file is created in the <soya.path[0]>/worlds/ directory, here:
#
# tutorial/data/worlds/a_scene_with_a_rotating_body.data
#
# The file is ALWAYS saved in the FIRST path listed in soya.path (which is, as sys.path,
# a list of path).
#
# Soya separates the "set filename" and the "save" step, because it allows you to set the
# filename once, and then to save the object several times without having to remind its
# filename.
#
# Notice that, while saving the scene, Soya will save a reference to the "sword" Model we
# have used above. However, the data of this Model are NOT dupplicated.
scene.save()
# Creates a camera.
#
# For technical reasons, camera are not saveable yet. This is not really a problem since the
# camera is not really scene-dependent by rather configuration-dependent.
# We thus add the camera AFTER saving the scene.
camera = soya.Camera(scene)
camera.z = 3.0
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
# That's all -- after running this tutorial, you should have a
# tutorial/data/worlds/a_scene_with_a_rotating_body.data file. The easiest way to verify
# that this file is REALLY a Cerealizer file, is to open the file and check if it begins
# by the magic string "cereal1".
Python animation de personnage 1
# -*- indent-tabs-mode: t -*- # Soya 3D tutorial # Copyright (C) 2001-2004 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # character-animation-1: annimation d'un personnage avec Cal3D : ici, nous utilisons Balazar le sorcier ! # Ou comment écrire un viewer pour Cal3D en moins de 20lignes... # Pour information, le personnage a été créé avec blender et exporté avec Cal3D # , avec mon script Blender2Cal3D : http://oomadness.nekeme.net/en/blender2cal3d/index.html # Regardez la documentation Cal3D pour plus d'informations sur le sujet et sur l'annimation de modèles et aussi # pour les Body, ou vous aurez des informations poussées pour le chargement de materiaux # (comment se passer des materiaux Cal3D avec ceux pour Soya). # Import et initialisation de Soya. import sys, os, os.path, soya, soya.widget as widget soya.init() soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data")) # Création d'une scène. scene = soya.World() # Chargement d'un modèle Cal3D. # Les modèles Cal3D sont sauvés dans le sous répertoir 'models' de soya.path ; chaque modèle cal3d # est un sous repertoire et non un fichier (voir tutorial/data/models/balazar pour ceux ayant # la version complète du tutorial). Il contient les squelettes, animations, mesh et materiaux, et # un fichier ".cfg" ayant le même nom que le sous repertoire. # Vous pouvez utiliser cal3d.parse_cfg_file(filename). sorcerer_model = soya.AnimatedModel.get("balazar") # Vous pouvez avoir une liste des meshs disponibles et les noms des animations ainsi : print "Available meshes :", sorcerer_model.meshes .keys() print "Available animations:", sorcerer_model.animations.keys() # Création du body Cal3D, utilisant le sorcerer_model. # Voir la docstrings du module soya.cal3d pour en apprendre plus sur les attachement et détachement des mesh # possibles (voir Body.__init__, Body.attach et Body.detach). # Il est aussi possible de l'utiliser, pour le démembrement, ou le changement d'une arme d'un personnage. sorcerer = soya.Body(scene, sorcerer_model) # Rotation de Balazar le sorcier sorcerer.rotate_y(-120.0) # Début de l'animation cyclique (qui se répète) appellée "marche". sorcerer.animate_blend_cycle("marche") # Pour arrêter l'animation : # #sorcerer.animate_clear_cycle("marche") # # Pour un mouvement non répetitif : # #sorcerer.animate_execute_action("marche") # # Pour plus d'infos sur les arguments optionnels, voir le docstring de cal3d.Body.animate* # Ajout d'une camera, d'un label montrant les fps, d'une lampe et execution de la boucle principale. camera = soya.Camera(scene) camera.set_xyz(0.0, 1.5, 3.0) soya.set_root_widget(widget.Group()) soya.root_widget.add(camera) soya.root_widget.add(widget.FPSLabel()) soya.Light(scene).set_xyz(5.0, 5.0, 2.0) # import time # main_loop = soya.MainLoop(scene) # for i in range(3): # for j in range(10): # time.sleep(0.1) # main_loop.update() # soya.render() # print "." # soya.screenshot().resize((320, 240)).save(os.path.join(os.path.dirname(sys.argv[0]), "results", os.path.basename(sys.argv[0])[:-3] + "_%s.jpeg" % i)) soya.MainLoop(scene).main_loop()
Python animation de personnage 2
# -*- indent-tabs-mode: t -*- # Soya 3D tutorial # Copyright (C) 2001-2002 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # character-animation-2: Mixage entre Cal3D et Soya : Balazar avec une épée # Dans cette leçon, nous ajoutons une épée dans la main droite de Balazar, le sorcier # de la précédente leçon. # L'épée est un objet Soya, auquel nous ajoutons un personnage Cal3D, et il bougera # avec elle ! # Importation et initialisation de Soya. import sys, os, os.path, soya, soya.widget as widget soya.init() soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data")) # Création de la scène. scene = soya.World() # Chargement du modèle du sorcier. sorcerer_model = soya.AnimatedModel.get("balazar") # Création du sorcier. # Nous avons besoin que le sorcier soit un World, de façon à ajouter d'autres objets à l'interieur (l'épée). # Le corps Cal3D est désormais ajouté dans le World du sorcier. sorcerer = soya.World(scene) sorcerer.rotate_y(-120.0) sorcerer.model = sorcerer_model sorcerer.animate_blend_cycle("marche") # Création de la main droite du sorcier dans son World, et on l'appelle 'mainD' # (abréviation de main droite). right_hand = soya.World(sorcerer) #sorcerer_body.attach_to_bone(right_hand, "mainD") sorcerer.attach_to_bone(right_hand, "mainD") # Création du Body right_hand_item, avec un modèle d'épée, dans la main droite. #sword = soya.World() #soya.Face(sword, [soya.Vertex(sword, 0.0, 0.0, 0.0), soya.Vertex(sword, 0.0, 1.0, 0.0), soya.Vertex(sword, 1.0, 0.0, 0.0)]) #epee = soya.Body(scene, sword.to_model()) right_hand_item = soya.Body(right_hand, soya.Model.get("sword")) right_hand_item.rotate_z(180.0) right_hand_item.set_xyz(0.05, 0.1, 0.0) # En utilisant right_hand_item.set_model(...), vous pouvez facilement remplacer l'épée par un marteau # ou une arme à feu ! # Utilisation de la conversion du système de coordonnées afin de détecter les collisions # (par exemple, Point(right_hand_item, 0.0, 0.0, -3.0) est la fin de l'épée) camera = soya.Camera(scene) camera.set_xyz(0.0, 1.5, 3.0) soya.set_root_widget(widget.Group()) soya.root_widget.add(camera) soya.root_widget.add(widget.FPSLabel()) soya.Light(scene).set_xyz(5.0, 5.0, 8.0) soya.MainLoop(scene).main_loop()
Python animation de personnage, ombres et cell-shading
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2001-2002 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# character-animation-shadow-cellshading-1: ombrage et cellshading pour les personnages animés Cal3D
# Dans cette leçon, nous ajoutons des ombres et du cell-shading à Balazar.
#
# Voir les leçons modeling-shadow et modeling-cellshading pour plus d'information sur les ombres et le
# cell-shading.
# Importation et initialisation de Soya.
import sys, os, os.path, soya, soya.widget as widget
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Création de la scène.
scene = soya.World()
# Ajout d'une atmosphère, pour que le background soit gris (in order to see the black outline
# of the cell-shading).
scene.atmosphere = soya.Atmosphere()
scene.atmosphere.bg_color = (0.6, 0.6, 0.6, 1.0)
# Chargement du modèle du sorcier.
sorcerer_model = soya.AnimatedModel.get("balazar")
# Activation de l'ombre sur Balazar !
sorcerer_model.shadow = 1
# Activation du cell-shading sur Balazar !
# Arguments (ils sont tous optionnels):
# - shader (par défaut : soya.SHADER_DEFAULT_MATERIAL)
# - outline_color (par défaut : defaults to black)
# - outline_width (par défaut : 4.0)
# - outline_attenuation (par défaut : 0.3)
#
# Voir la leçon modeling-cellshading-1
# pour plus d'informations sur ces arguments
sorcerer_model.set_cellshading()
# Notez que vous pouvez ajouter ces attributs dans le fichier .cfg du Cal3D
# (par exemple : balazar/balazar.cfg) :
#
#double_sided=1
#shadow=1
#cellshading=1
#cellshading_outline_color=1.0, 1.0, 1.0, 1.0
#cellshading_outline_width=4.0
#cellshading_outline_attenuation=0.0
#
# double_sided dessine un modèle Cal3D avec une double face
# (équivalent à Face.double_sided). Les autres attributs ont une signification évidente
#
# Et souvenez vous : ne pas mettre d'espace avant ou après le "=" !
# Création du Body du sorcier
sorcerer = soya.Body(scene, sorcerer_model)
# Rotation du sorcier Balazar
sorcerer.rotate_y(-120.0)
# Début de l'animation "marche" en cycle.
sorcerer.animate_blend_cycle("marche")
# Création d'un mur pour recevoir l'ombre.
# Notez que le mur peut être n'importe quel objet (tel que un terrain ou autre) : recevoir
# une ombre ne require pas de propriété spéciales.
wall_model = soya.World()
wall_face = soya.Face(wall_model, [
soya.Vertex(wall_model, -5.0, 0.0, -5.0),
soya.Vertex(wall_model, -5.0, 0.0, 5.0),
soya.Vertex(wall_model, 5.0, 0.0, 5.0),
soya.Vertex(wall_model, 5.0, 0.0, -5.0),
])
wall_face.double_sided = 1
wall = soya.Body(scene, wall_model.to_model())
wall.set_xyz(-1.0, 0.0, 0.0)
# Ajouter une caméra et une lampe puis début de la boucle principale.
light = soya.Light(scene)
light.set_xyz(2.0, 5.0, -1.0)
light.cast_shadow = 1
light.shadow_color = (0.0, 0.0, 0.0, 0.5)
camera = soya.Camera(scene)
camera.set_xyz(0.0, 1.0, 4.0)
soya.set_root_widget(camera)
soya.MainLoop(scene).main_loop()
Python déformation
# -*- indent-tabs-mode: t -*- # Soya 3D tutorial # Copyright (C) 2001-2004 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # deform-1: déformation d'un modèle : # Importation de sys, os modules et de Soya. import sys, os, os.path, math, soya # Initialisation de Soya (création et affichage de la fenêtre). soya.init() soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data")) scene = soya.World() sword_model = soya.Model.get("sword") sword = soya.Body(scene, sword_model) sword.x = 1.0 sword.rotate_y(90.0) class MyDeform(soya.PythonDeform): def deform_point(self, x, y, z): return x, y, z + 0.3 * math.sin(0.1 * self.time + 5.0 * y) deform = MyDeform() sword.add_deform(deform) light = soya.Light(scene) light.set_xyz(0.5, 0.0, 2.0) camera = soya.Camera(scene) camera.z = 2.0 soya.set_root_widget(camera) soya.MainLoop(scene).main_loop()
Python plein écran
# -*- indent-tabs-mode: t -*- # Soya 3D tutorial # Copyright (C) 2001-2004 Jean-Baptiste LAMY # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # fullscreen-1: Paramétrage : affichage d'un modèle 3D # Soya peut afficher en plein écran. # Attention : vous DEVEZ prévoir une manière pour quitter le programme, par un évenement SDL par exemple. # Importation de sys, os modules et Soya. import sys, os, os.path, soya, soya.sdlconst # Initialisation de Soya en plein écran en 800x600 (le dernier argument, 1, commande le plein écran). # Le premier argument est le nom de la fenêtre, qui est visible en mode fenêtré. soya.init("Soya fullscreen tutorial", 800, 600, 1) soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data")) # Le reste du tutorial est identique à basic-5.py scene = soya.World() class CaterpillarHead(soya.Body): def __init__(self, parent): soya.Body.__init__(self, parent, soya.Model.get("caterpillar_head")) self.speed = soya.Vector(self, 0.0, 0.0, 0.0) self.rotation_y_speed = 0.0 def begin_round(self): soya.Body.begin_round(self) for event in soya.process_event(): if event[0] == soya.sdlconst.KEYDOWN: if event[1] == soya.sdlconst.K_UP: self.speed.z = -0.2 elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 0.1 elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 10.0 elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = -10.0 elif event[1] == soya.sdlconst.K_q: soya.MAIN_LOOP.stop() elif event[1] == soya.sdlconst.K_ESCAPE: soya.MAIN_LOOP.stop() if event[0] == soya.sdlconst.KEYUP: if event[1] == soya.sdlconst.K_UP: self.speed.z = 0.0 elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 0.0 elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 0.0 elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = 0.0 self.rotate_y(self.rotation_y_speed) def advance_time(self, proportion): soya.Body.advance_time(self, proportion) self.add_mul_vector(proportion, self.speed) class CaterpillarPiece(soya.Body): def __init__(self, parent, previous): soya.Body.__init__(self, parent, soya.Model.get("caterpillar")) self.previous = previous self.speed = soya.Vector(self, 0.0, 0.0, -0.2) def begin_round(self): soya.Body.begin_round(self) self.look_at(self.previous) if self.distance_to(self.previous) < 1.5: self.speed.z = 0.0 else: self.speed.z = -0.2 def advance_time(self, proportion): soya.Body.advance_time(self, proportion) self.add_mul_vector(proportion, self.speed) # Création de la tête de la chenille et de 10 pièces de son corps. caterpillar_head = CaterpillarHead(scene) caterpillar_head.rotate_y(90.0) previous_caterpillar_piece = caterpillar_head for i in range(10): previous_caterpillar_piece = CaterpillarPiece(scene, previous_caterpillar_piece) previous_caterpillar_piece.x = i + 1 # Création d'une lampe. light = soya.Light(scene) light.set_xyz(2.0, 5.0, 0.0) # Création d'une camera. camera = soya.Camera(scene) camera.set_xyz(0.0, 15.0, 15.0) camera.look_at(caterpillar_head) soya.set_root_widget(camera) soya.MainLoop(scene).main_loop()
Python game skel-3
# -*- indent-tabs-mode: t -*-
#! /usr/bin/python -O
# Game Skeleton
# Soya gaming tutorial, lesson 3
# Adding character-level interaction (collision, ...)
# New stuff is in the Character class
# A bunch of import
import sys, os, os.path, math
import soya
import soya.widget as widget
import soya.sdlconst as sdlconst
# Inits Soya
soya.init()
# Definition du répertoire (=ou se trouve models, textures, ...)
HERE = os.path.dirname(sys.argv[0])
soya.path.append(os.path.join(HERE, "data"))
class Level(soya.World):
"""Un niveau de jeu.
Level est une sous classe de soya.World."""
class Action:
"""Les actions que le personnage peut faire."""
def __init__(self, action):
self.action = action
# Actions possibles.
ACTION_WAIT = 0
ACTION_ADVANCE = 1
ACTION_ADVANCE_LEFT = 2
ACTION_ADVANCE_RIGHT = 3
ACTION_TURN_LEFT = 4
ACTION_TURN_RIGHT = 5
ACTION_GO_BACK = 6
ACTION_GO_BACK_LEFT = 7
ACTION_GO_BACK_RIGHT = 8
class KeyboardController:
"""Un controleur est un objet donnant des ordres à un personnage.
Ici, nous utilisons un controlleur clavier, mais il pourrait très bien être controlé par
la souris ou l'IA.Notez que la seul méthode s'appelle "next", avec laquelle nous utilisons le
controleur."""
def __init__(self):
self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0
def next(self):
"""retourne l'action futur."""
for event in soya.process_event():
if event[0] == sdlconst.KEYDOWN:
if (event[1] == sdlconst.K_q) or (event[1] == sdlconst.K_ESCAPE):
sys.exit() # Quit the game
elif event[1] == sdlconst.K_LEFT: self.left_key_down = 1
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 1
elif event[1] == sdlconst.K_UP: self.up_key_down = 1
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 1
elif event[0] == sdlconst.KEYUP:
if event[1] == sdlconst.K_LEFT: self.left_key_down = 0
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 0
elif event[1] == sdlconst.K_UP: self.up_key_down = 0
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 0
# Les gens disant que Python n'a pas de switch/select se trompent...
# Souvenez vous en quand vous coderez un jeu de combat !
return Action({
(0, 0, 1, 0) : ACTION_ADVANCE,
(1, 0, 1, 0) : ACTION_ADVANCE_LEFT,
(0, 1, 1, 0) : ACTION_ADVANCE_RIGHT,
(1, 0, 0, 0) : ACTION_TURN_LEFT,
(0, 1, 0, 0) : ACTION_TURN_RIGHT,
(0, 0, 0, 1) : ACTION_GO_BACK,
(1, 0, 0, 1) : ACTION_GO_BACK_LEFT,
(0, 1, 0, 1) : ACTION_GO_BACK_RIGHT,
}.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT))
print "foo"
class Character(soya.World):
print "foo"
"""Un personnage dans votre jeu."""
def __init__(self, parent, controller):
soya.World.__init__(self, parent)
self.set_model(soya.Model.get("cube"))
# Désactivation du raypicking sur le personnage !!!
self.solid = 0
self.controller = controller
self.speed = soya.Vector(self)
self.rotation_speed = 0.0
# Cette variable est utilisé pour la détection des collisions.
# radius est le rayon approximative d'une sphère autour du personnage,
# et radius_y est pareil mais dans l'axe Y (vertical) seulement (le personnage
# est souvent plus grand que large ;-).
# Nous avons besoin de radius * sqrt(2)/2 < vitesse maximale (ici, 0.35)
self.radius = 0.5
self.radius_y = 1.0
# Center est le point central du personnage (centré sur la sphère mentionnée
# précédemment)
self.center = soya.Point(self, 0.0, self.radius_y, 0.0)
# Ces vecteurs sont les directions -X, X, -Y, Y, -Z, Z du personnage.
self.left = soya.Vector(self, -1.0, 0.0, 0.0)
self.right = soya.Vector(self, 1.0, 0.0, 0.0)
self.down = soya.Vector(self, 0.0, -1.0, 0.0)
self.up = soya.Vector(self, 0.0, 1.0, 0.0)
self.front = soya.Vector(self, 0.0, 0.0, -1.0)
self.back = soya.Vector(self, 0.0, 0.0, 1.0)
def begin_round(self):
self.begin_action(self.controller.next())
soya.World.begin_round(self)
def begin_action(self, action):
"""Cette méthode amorce l'action ACTION. Il n'effectue pas l'action
(pour cela, voir advance_time). Mais ceci ne fait que décoder l'action et maintenant, il vérifie
les collisions du personnage. Il ne fait que vérifier si le personnage est bien sur le sol
ground, et sinon, il le replace au bon endroit."""
# Reset
self.rotation_speed = self.speed.x = self.speed.z = 0.0
if self.speed.y > 0.0: self.speed.y = 0.0
# Si self.speed.y < 0.0, le personnage tombe et nous cherchons si il doit
# continuer à tomber, et tombe de plus en plus vite,
#mais nous ne remettons pas self.speed.y à 0.0.
# Détermination de la rotation du personnage
if action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
self.rotation_speed = 5.0
elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
self.rotation_speed = -5.0
# Détermination de la vitesse du personnage
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
self.speed.z = -0.35
elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
self.speed.z = 0.2
# Calcul du nouveau centre de la sphère du personnage après le mouvement
# (C'est un point et une addition de vecteur, une translation)
new_center = self.center + self.speed
# Création d'un contexte 'raypicking', provenant d'un centre et d'un rayon.
# Le contexte Raypicking est plus rapide si vous en avez plusieurs
# dans la même région, en selectionnant tous les objets succeptibles
# de se heurter au rayon.
context = scene.RaypickContext(new_center, max(self.radius, 0.1 + self.radius_y))
# Création du sol, et on verifie si le personnage tombe.
# Dans ce cas, nous faisons un raypicking vers le bas.
# Si totu se passe bien, la méthode raypick retourne un
# tuple : (collision point, collision normal).
r = context.raypick(new_center, self.down, 0.1 + self.radius_y, 1, 1)
if r:
# Le personnage a ses pieds sur ou en dessous du sol
# donc nous le positionnons sur le sol.
ground, ground_normal = r
ground.convert_to(self)
self.speed.y = ground.y
else:
# Pas de sol => début de la chute
# Testez la chute avec la crevasse derrière la deuxième maison
# Nous incrémentons self.speed.y de 0.0 à -0.5 avec -0.05 par étapes.
self.speed.y = max(self.speed.y - 0.05, -0.5)
# Le mouvement (défini par le vecteur de vitesse) peut être impossible si
# le personnage rencontre un mur.
# collisions personnage - level
for vec in (self.left, self.right, self.front, self.back, self.up):
r = context.raypick(new_center, vec, self.radius, 1, 1)
if r:
# Le rayon recontre un mur => le personnage ne peut pas continuer le mouvement
# planifié. Nous calculons le nouveau vecteur et nous l'ajoutons au vecteur
# vitesse si bien que new_center (pour le raypick suivant, souvenez vous que
# new_center = self.center + self.speed, ainsi si la vitesse a changé,
# nous devons l'actualiser.
collision, wall_normal = r
hypo = vec.length() * self.radius - new_center.distance_to(collision)
correction = wall_normal * hypo
# Formule théorique, mais assez complexe et identique au résultat
# angle = math.radians(180.0 - vec.angle_to(wall_normal))
#correction = wall_normal * hypo * math.cos(angle)
# Ajout du vecteur de correction au vecteur de vitesse et à new_center
# (Point + addition vectorielle, donc translation)
self.speed += correction
new_center += correction
def advance_time(self, proportion):
soya.World.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
self.rotate_y(proportion * self.rotation_speed)
# Création de la scène (un World sans parent)
scene = soya.World()
# Chargement du level, puis on le place dans la scène
try:
level = soya.World.get("level_demo")
except ValueError:
print>>sys.stderr, 'Le level n\'est pas encoregénéré, veuillez lancer le tutorial game_skel-1.py'
sys.exit(1)
scene.add(level)
# Création du personnage dans le level avec le control par le clavier
character = Character(level, KeyboardController())
character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
# Création d'une camera à la Tomb Raider dans la scène
camera = soya.TravelingCamera(scene)
traveling = soya.ThirdPersonTraveling(character)
traveling.distance = 5.0
camera.add_traveling(traveling)
camera.zap()
camera.back = 70.0
# Création d'un groupe de widget contenant la camera et un label montrant les FPS.
soya.set_root_widget(widget.Group())
soya.root_widget.add(camera)
soya.root_widget.add(widget.FPSLabel())
# Création et execution de "main_loop" (=objet gérant le temps et régulant les FPS)
# Par défaut, les FPS sont lockés à 40.
soya.MainLoop(scene).main_loop()
Python game skel-2
# -*- indent-tabs-mode: t -*-
#! /usr/bin/python -O
# Game Skeleton
# Copyright (C) 2003-2004 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Soya gaming tutorial, lesson 2
# Ajout d'un controlleur clavier pour le cube.
# Importations
import sys, os, os.path
import soya
import soya.widget as widget
import soya.sdlconst as sdlconst
# Initialisation Soya
soya.init()
# Definition du répertoire de donnée (=où l'on trouve les modèles, textures ...)
HERE = os.path.dirname(sys.argv[0])
soya.path.append(os.path.join(HERE, "data"))
class Level(soya.World):
"""Un niveau de jeu.
Level est une sous classe de soya.World."""
class Action:
"""Une action que le personnage peut faire."""
def __init__(self, action):
self.action = action
# Les actions disponibles
# Pour plus d'actions complexe, vous devez faire une sous classe "Action".
ACTION_WAIT = 0
ACTION_ADVANCE = 1
ACTION_ADVANCE_LEFT = 2
ACTION_ADVANCE_RIGHT = 3
ACTION_TURN_LEFT = 4
ACTION_TURN_RIGHT = 5
ACTION_GO_BACK = 6
ACTION_GO_BACK_LEFT = 7
ACTION_GO_BACK_RIGHT = 8
class KeyboardController:
"""Un controleur est un objet qui donne les ordres au personnage.
Ici, nous définissons un controlleur basé sur le clavuer, mais il existe aussi des controleurs basé sur un controleur basé sur la souris, ou l'IA. Notez que l'unique méthode appellée est "next", avec laquelle nous utilisons le générateur de python."""
def __init__(self):
self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0
def next(self):
"""Retourne l'action suivante"""
for event in soya.process_event():
if event[0] == sdlconst.KEYDOWN:
if (event[1] == sdlconst.K_q) or (event[1] == sdlconst.K_ESCAPE):
sys.exit() # Quit the game
elif event[1] == sdlconst.K_LEFT: self.left_key_down = 1
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 1
elif event[1] == sdlconst.K_UP: self.up_key_down = 1
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 1
elif event[0] == sdlconst.KEYUP:
if event[1] == sdlconst.K_LEFT: self.left_key_down = 0
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 0
elif event[1] == sdlconst.K_UP: self.up_key_down = 0
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 0
# Les gens disent que Python n'a pas de switch/select case et c'est faux !
# Souvenez vous en lorsque vous codez un jeu de combat.
return Action({
(0, 0, 1, 0) : ACTION_ADVANCE,
(1, 0, 1, 0) : ACTION_ADVANCE_LEFT,
(0, 1, 1, 0) : ACTION_ADVANCE_RIGHT,
(1, 0, 0, 0) : ACTION_TURN_LEFT,
(0, 1, 0, 0) : ACTION_TURN_RIGHT,
(0, 0, 0, 1) : ACTION_GO_BACK,
(1, 0, 0, 1) : ACTION_GO_BACK_LEFT,
(0, 1, 0, 1) : ACTION_GO_BACK_RIGHT,
}.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT))
class Character(soya.World):
"""Un personnage dans le jeu.
J'ai toujours considéré character.x, character.y, character.z (par exemple, les points de coordonées
(0.0, 0.0, 0.0) donne dans le système de coordonnée du personnage :
Point(character, 0.0, 0.0, 0.0)) pour être la position du personnage et non son centre.
Similairement je considère que le vecteur X du personnage (par exemple
Vector(character, 1.0, 0.0, 0.0)) pour être la direction de droite du vecteur y
(par exemple Vector(character, 0.0, 1.0, 0.0)) pour être la direction du haut et le vecteur -Z
(par exemple Vector(character, 0.0, 0.0, -1.0)) pour être la direction du 'proche'.
(Pourquoi -Z et non Z ? Juste pour avoir indirectement le système de coordonnées !!!)"""
def __init__(self, parent, controller):
soya.World.__init__(self, parent)
# Pour maintenant, le personnage est un simple cube
self.set_model(soya.Model.get("cube"))
# Désactivation du raypicking sur le personnage !!!
# Nous avons besoin d'une caméra à la Tomb-Raider,
# et pour la detection des collisions (voir prochaine leçon).
self.solid = 0
# Le controleur du personnage
self.controller = controller
# La vitesse du personnage : un vecteur défini dans le système de coordonées du personnage.
# Exemple, si vous voulez que le personnage avance, faire character.speed.z = -1.0
self.speed = soya.Vector(self)
# La vitesse de rotation du personnage.(Donc l'axe Y)
self.rotation_speed = 0.0
def begin_round(self):
"""Cette méthode est appellée par la MainLoop chaque fois qu'un round commence. Soya gère des rounds de 30ms par défaut, ceci signifie que le personnage sera -perform- à chaque action durant 30ms.
Actuellement, tous les round ne durent pas exactement 30ms, mais il retourne True à un point global de la vue. Exemple, 1000 rounds feront 1000 * 30ms."""
# Appel d'une nouvelle action venant d'un controlleur, et la commence.
self.begin_action(self.controller.next())
# Délégation du World, pour commencer le round.
# Ceci doit être appellé après begin_action pour un meilleur effet visuel,
# chaque appel de begin_action peut influencer World.begin_round (exemple, si begin_action
# débute une animation, voir leçon 4)
soya.World.begin_round(self)
def begin_action(self, action):
"""Cette méthode commence l'action ACTION. Il ne doit pas executer l'action
(voir advance_time pour ça). Mais il doit "décoder" cette action, et verifier pour tout collision qui peut se produire (pas encore, mais nous verrons ça dans la leçon 4).
begin_action se met dans le vecteur de vitesse et dans la variable flottante rotation_speed du personnage (sa vitesse et sa vitesse de rotation (axe Y))."""
# Réinitialsation
self.rotation_speed = self.speed.x = self.speed.y = self.speed.z = 0.0
# Determination de la rotation du personnage
if action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
self.rotation_speed = 5.0
elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
self.rotation_speed = -5.0
# Determination de la vitesse du personnage
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
self.speed.z = -0.35
elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
self.speed.z = 0.2
# Vous pouvez utiliser speed.x pour les pas/ mouvement latéraux,
# et speed.y pour sauter ou tomber (voir leçon 5)
def advance_time(self, proportion):
"""Cette méthode appelle une ou plusieurs fois entre 2 rounds.
PROPORTION est la proportion du round qui a été épuisé
(Exemple, 1.0 pour un round complet, 0.5 pour une moitié, ...).
Tous lse personnages bougeant DOIVENT se produire dans la méthode, dans l'odre pour prendre l'avantage de la gestion du temps de la MainLoop et prend le meilleur effet graphique."""
soya.World.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
self.rotate_y(proportion * self.rotation_speed)
# Création de la Scène (un World sans parent)
scene = soya.World()
# Chargement du Level, et met ça dans la scène.
try:
level = soya.World.get("level_demo")
except ValueError:
print>>sys.stderr, 'the level of this demo is not yet generated, please run the game_skel-1.py tutorial'
sys.exit(1)
scene.add(level)
# Création du personnage dans le Level, avec un controleur clavier
character = Character(level, KeyboardController())
character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
# Création du caméra à la Tomb Raider dans la scène
camera = soya.TravelingCamera(scene)
traveling = soya.ThirdPersonTraveling(character)
traveling.distance = 5.0
camera.add_traveling(traveling)
camera.zap()
camera.back = 70.0
# Creéation d'un groupe de widget contenant la caméra et un label qui montre les FPS.
soya.set_root_widget(widget.Group())
soya.root_widget.add(camera)
soya.root_widget.add(widget.FPSLabel())
#soya.render(); soya.screenshot().resize((320, 240)).save(os.path.join(os.path.dirname(sys.argv[0]), "results", os.path.basename(sys.argv[0])[:-3] + ".jpeg"))
# Création et exécution de "main_loop" (=un objet qui gère et régule les FPS)
# Par défaut, les FPS sont bloqués à 40.
soya.MainLoop(scene).main_loop()
Python game skel-5
# -*- indent-tabs-mode: t -*-
#! /usr/bin/python -O
# Game Skeleton
# Copyright (C) 2003-2004 Jean-Baptiste LAMY
# Un jeu avec Soya, leçon 5
# Ajout de la fonction saut.
# Nouvel élément dans les classes Controller Character
# Importations
import sys, os, os.path, math
import soya
import soya.widget as widget
import soya.sdlconst as sdlconst
# Initialisation de Soya
soya.init()
# On définit le répertoire des données (lieux où l'on trouve les modèles, textures...)
HERE = os.path.dirname(sys.argv[0])
soya.path.append(os.path.join(HERE, "data"))
class Level(soya.World):
"""Un niveau de jeu. Level est une sous classe de soya.World."""
class Action:
"""Une action que le personnage peut faire."""
def __init__(self, action):
self.action = action
# Actions disponibles
ACTION_WAIT = 0
ACTION_ADVANCE = 1
ACTION_ADVANCE_LEFT = 2
ACTION_ADVANCE_RIGHT = 3
ACTION_TURN_LEFT = 4
ACTION_TURN_RIGHT = 5
ACTION_GO_BACK = 6
ACTION_GO_BACK_LEFT = 7
ACTION_GO_BACK_RIGHT = 8
ACTION_JUMP = 9
class KeyboardController:
"""Un controlleur est un objet qui donne des ordres aux personnages.
Ici, nous définissons le clavier comme controleur de base, mais il y a aussi les controleurs basés sur la souris ou sur une Intelligence Artificielle (IA).
Notez que l'unique méthode appellée est "next", avec laquelle nous allons utiliser le générateur de controlleur python."""
def __init__(self):
self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0
def next(self):
"""Returns the next action"""
jump = 0
for event in soya.process_event():
if event[0] == sdlconst.KEYDOWN:
if (event[1] == sdlconst.K_q) or (event[1] == sdlconst.K_ESCAPE):
sys.exit() # Quit the game
elif event[1] == sdlconst.K_LSHIFT:
# La touche shift déclanche le saut
# Contrairement aux autres actions, le saut est seulment appellé au début du saut.
jump = 1
elif event[1] == sdlconst.K_LEFT: self.left_key_down = 1
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 1
elif event[1] == sdlconst.K_UP: self.up_key_down = 1
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 1
elif event[0] == sdlconst.KEYUP:
if event[1] == sdlconst.K_LEFT: self.left_key_down = 0
elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 0
elif event[1] == sdlconst.K_UP: self.up_key_down = 0
elif event[1] == sdlconst.K_DOWN: self.down_key_down = 0
if jump: return Action(ACTION_JUMP)
# Tout le monde dit que Python n'a pas de switch/select case, c'est absolument faux...
# Souvenez vous en quand vous programmez un jeu de combat !
return Action({
(0, 0, 1, 0) : ACTION_ADVANCE,
(1, 0, 1, 0) : ACTION_ADVANCE_LEFT,
(0, 1, 1, 0) : ACTION_ADVANCE_RIGHT,
(1, 0, 0, 0) : ACTION_TURN_LEFT,
(0, 1, 0, 0) : ACTION_TURN_RIGHT,
(0, 0, 0, 1) : ACTION_GO_BACK,
(1, 0, 0, 1) : ACTION_GO_BACK_LEFT,
(0, 1, 0, 1) : ACTION_GO_BACK_RIGHT,
}.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT))
class Character(soya.World):
"""A character in the game."""
def __init__(self, parent, controller):
soya.World.__init__(self, parent)
# Chargement d'un modèle Cal3D
balazar = soya.AnimatedModel.get("balazar")
# Création d'un Body Cal3D affichant le modèle "balazar"
# (NB: Balazar est le nom d'un sorcier).
self.perso = soya.Body(self, balazar)
# Appel de l'animation du personnage qui attend.
self.perso.animate_blend_cycle("attente")
# Animation actuelle
self.current_animation = "attente"
# Désactivation du raypicking du personnage !!!
self.solid = 0
self.controller = controller
self.speed = soya.Vector(self)
self.rotation_speed = 0.0
# Nous avons besoin de radius * sqrt(2)/2 < max speed (ici, 0.35)
self.radius = 0.5
self.radius_y = 1.0
self.center = soya.Point(self, 0.0, self.radius_y, 0.0)
self.left = soya.Vector(self, -1.0, 0.0, 0.0)
self.right = soya.Vector(self, 1.0, 0.0, 0.0)
self.down = soya.Vector(self, 0.0, -1.0, 0.0)
self.up = soya.Vector(self, 0.0, 1.0, 0.0)
self.front = soya.Vector(self, 0.0, 0.0, -1.0)
self.back = soya.Vector(self, 0.0, 0.0, 1.0)
# Vrai si le personnage saute, par exemple, speed.y > 0.0
self.jumping = 0
def play_animation(self, animation):
if self.current_animation != animation:
# Arrêt de l'animation précédente
self.perso.animate_clear_cycle(self.current_animation, 0.2)
# Début de la nouvelle animation
self.perso.animate_blend_cycle(animation, 1.0, 0.2)
self.current_animation = animation
def begin_round(self):
self.begin_action(self.controller.next())
soya.World.begin_round(self)
def begin_action(self, action):
# Réinitialisation
self.speed.x = self.speed.z = self.rotation_speed = 0.0
# Si le personnage saute, nous ne pouvons pas remettre speed.y à 0.0 !!!
if (not self.jumping) and self.speed.y > 0.0: self.speed.y = 0.0
animation = "attente"
# Détermination de la rotation du personnage
if action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
self.rotation_speed = 4.0
animation = "tourneG"
elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
self.rotation_speed = -4.0
animation = "tourneD"
# Determination de la vitesse du personnage
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
self.speed.z = -0.25
animation = "marche"
elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
self.speed.z = 0.06
animation = "recule"
new_center = self.center + self.speed
context = scene.RaypickContext(new_center, max(self.radius, 0.1 + self.radius_y))
# On prend le sol, et on vérifie si le personnage tombe.
r = context.raypick(new_center, self.down, 0.1 + self.radius_y, 1, 1)
if r and not self.jumping:
# Placement du personnage sur le sol
# Si le personnage saute, nous ne pouvons pas le mettre SUR le sol.
ground, ground_normal = r
ground.convert_to(self)
self.speed.y = ground.y
# Le saut est possible seulement si le personnage est sur le sol.
if action.action == ACTION_JUMP:
self.jumping = 1
self.speed.y = 0.5
else:
# Pas de sol => le personnage tombe
# Vous pouvez tester en allant sur le toit de la 2ème maison.
self.speed.y = max(self.speed.y - 0.02, -0.25)
animation = "chute"
# Si la vitesse verticale est négative, le saut est arrêté.
if self.speed.y < 0.0: self.jumping = 0
new_center = self.center + self.speed
# Le mouvement (définit par le vecteur de vitesse) peut être impossible si le
# personnage rencontre un mur.
for vec in (self.left, self.right, self.front, self.back, self.up):
r = context.raypick(new_center, vec, self.radius, 1, 1)
if r:
# Si le ray rencontre un mur => le personnage ne peut pas avancer.
# Nous effectuons une correction sur le vecteur, et nous l'ajoutons au vecteur de vitesse, pour obtenir un
# nouveau centre : new_center (pour le raypicking suivant ; souvenez vous en)
# new_center = self.center + self.speed, alors si speed est changé, nous devons le mettre à jour).
collision, wall_normal = r
hypo = vec.length() * self.radius - (new_center >> collision).length()
correction = wall_normal * hypo
# Formules théoriques, mais plus complexes avec un résultat identique.
#angle = math.radians(180.0 - vec.angle_to(wall_normal))
#correction = wall_normal * hypo * math.cos(angle)
self.speed.add_vector(correction)
new_center.add_vector(correction)
self.play_animation(animation)
def advance_time(self, proportion):
soya.World.advance_time(self, proportion)
self.add_mul_vector(proportion, self.speed)
self.rotate_y(proportion * self.rotation_speed)
# Création d'une scène (un World sans parent)
scene = soya.World()
# Chargement du Level, on le place ensuite sur la scène.
try:
level = soya.World.get("level_demo")
except ValueError:
print>>sys.stderr, 'the level of this demo is not yet generated, please run the game_skel-1.py tutorial'
sys.exit(1)
scene.add(level)
# Création d'un personnage dans le Leavel, avec un controlleur clavier.
character = Character(level, KeyboardController())
character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
# Création d'une caméra à la Tomb Raider dans la scène
camera = soya.TravelingCamera(scene)
traveling = soya.ThirdPersonTraveling(character)
traveling.distance = 5.0
camera.add_traveling(traveling)
camera.zap()
camera.back = 70.0
# Création d'un groupe de widget, contenant la caméra et un label qui montre les FPS.
soya.set_root_widget(widget.Group())
soya.root_widget.add(camera)
soya.root_widget.add(widget.FPSLabel())
# Création et démarrage de la "main_loop" (=un objet qui gère le temps et qui régule les FPS)
# Par défaut, le nombre de FPS est bloqué à 40.
soya.MainLoop(scene).main_loop()
GFDL | Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. |