Soya/Python game skel-5
import sys, os, os.path, math
import soya
import soya.widget as widget
import soya.sdlconst as sdlconst
soya.init()
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
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)
scene = soya.World()
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)
character = Character(level, KeyboardController())
character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
camera = soya.TravelingCamera(scene)
traveling = soya.ThirdPersonTraveling(character)
traveling.distance = 5.0
camera.add_traveling(traveling)
camera.zap()
camera.back = 70.0
soya.set_root_widget(widget.Group())
soya.root_widget.add(camera)
soya.root_widget.add(widget.FPSLabel())
soya.MainLoop(scene).main_loop()