Soya/Python game skel-2

Un livre de Wikilivres.
# -*- 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()