Sharedwww / talks / 2006-05-09-sage-digipen / tutorial / game_skel-2.pyOpen in CoCalc
Author: William A. Stein
1#! /usr/bin/python -O
2
3# Game Skeleton
4# Copyright (C) 2003-2004 Jean-Baptiste LAMY
5#
6# This program is free software; you can redistribute it and/or modify
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20# Soya gaming tutorial, lesson 2
22
23# A bunch of import
24import sys, os, os.path
25import soya
26import soya.widget as widget
27import soya.sdlconst as sdlconst
28
29# Inits Soya
30soya.init()
31
32# Define data path (=where to find models, textures, ...)
33HERE = os.path.dirname(sys.argv[0])
34soya.path.append(os.path.join(HERE, "data"))
35
36class Level(soya.World):
37  """A game level.
38Level is a subclass of soya.World."""
39
40
41class Action:
42  """An action that the character can do."""
43  def __init__(self, action):
44    self.action = action
45
46# The available actions
47# For more complex actions, you may want to subclass Action.
48ACTION_WAIT          = 0
52ACTION_TURN_LEFT     = 4
53ACTION_TURN_RIGHT    = 5
54ACTION_GO_BACK       = 6
55ACTION_GO_BACK_LEFT  = 7
56ACTION_GO_BACK_RIGHT = 8
57
58
59class KeyboardController:
60  """A controller is an object that gives orders to a character.
61Here, we define a keyboard based controller, but there may be mouse-based or IA-based
62controllers.
63Notice that the unique method is called "next", which allows to use Python generator
64as controller."""
65  def __init__(self):
66    self.left_key_down = self.right_key_down = self.up_key_down = self.down_key_down = 0
67
68  def next(self):
69    """Returns the next action"""
70    for event in soya.process_event():
71      if   event[0] == sdlconst.KEYDOWN:
72        if   (event[1] == sdlconst.K_q) or (event[1] == sdlconst.K_ESCAPE):
73          sys.exit() # Quit the game
74        elif event[1] == sdlconst.K_LEFT:  self.left_key_down  = 1
75        elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 1
76        elif event[1] == sdlconst.K_UP:    self.up_key_down    = 1
77        elif event[1] == sdlconst.K_DOWN:  self.down_key_down  = 1
78
79      elif event[0] == sdlconst.KEYUP:
80        if   event[1] == sdlconst.K_LEFT:  self.left_key_down  = 0
81        elif event[1] == sdlconst.K_RIGHT: self.right_key_down = 0
82        elif event[1] == sdlconst.K_UP:    self.up_key_down    = 0
83        elif event[1] == sdlconst.K_DOWN:  self.down_key_down  = 0
84
85    # People saying that Python doesn't have switch/select case are wrong...
86    # Remember this if you are coding a fighting game !
87    return Action({
88      (0, 0, 1, 0) : ACTION_ADVANCE,
89      (1, 0, 1, 0) : ACTION_ADVANCE_LEFT,
90      (0, 1, 1, 0) : ACTION_ADVANCE_RIGHT,
91      (1, 0, 0, 0) : ACTION_TURN_LEFT,
92      (0, 1, 0, 0) : ACTION_TURN_RIGHT,
93      (0, 0, 0, 1) : ACTION_GO_BACK,
94      (1, 0, 0, 1) : ACTION_GO_BACK_LEFT,
95      (0, 1, 0, 1) : ACTION_GO_BACK_RIGHT,
96      }.get((self.left_key_down, self.right_key_down, self.up_key_down, self.down_key_down), ACTION_WAIT))
97
98
99class Character(soya.World):
100  """A character in the game.
101I always consider character.x, character.y, character.z (i.e. the point with
102coordinates (0.0, 0.0, 0.0) in the character coordinates system :
103Point(character, 0.0, 0.0, 0.0)) to be the position of the feet of the character
104(and not the center).
105
106Similarly, i consider the X vector of the character (i.e.
107Vector(character, 1.0, 0.0, 0.0)) to be the right direction, the Y vector (i.e.
108Vector(character, 0.0, 1.0, 0.0)) to be the up direction and the -Z vector (i.e.
109Vector(character, 0.0, 0.0, -1.0)) to be the front direction.
110(Why -Z and not Z ? just to avoid indirect coordinate systems !!!)"""
111  def __init__(self, parent, controller):
112    soya.World.__init__(self, parent)
113
114    # For now, the character is simply a cube.
115    self.set_shape(soya.Shape.get("cube"))
116
117    # Disable raypicking on the character itself !!!
118    # This is needed for the Tomb-Raider like camera (see below),
119    # and for collision detection too (see next lesson).
120    self.solid = 0
121
122    # The character's controller
123    self.controller = controller
124
125    # The character's speed : a vector defined in the character coordinates system.
126    # E.g. if you want the character to move forward, do character.speed.z = -1.0
127    self.speed = soya.Vector(self)
128
129    # The character's rotation speed (around the Y axis)
130    self.rotation_speed = 0.0
131
132  def begin_round(self):
133    """This method is called by the Idler each time a round starts. Soya manages
134round of 30ms by default, this means that the character will perform each action
135during 30ms.
136
137Actually, all round obviously does not last exactely 30ms, but it is true on a
138global point of view. E.g., 1000 rounds will last 1000 * 30ms."""
139    # Gets the new action from the controller, and begins it
140    self.begin_action(self.controller.next())
141
142    # Delegate to World, for beginning round of inner children.
143    # This should be called after begin_action for a better visual effect,
144    # since begin_action may influence World.begin_round (e.g. if begin_action
145    # starts an animation, see lesson 4)
146    soya.World.begin_round(self)
147
148  def begin_action(self, action):
149    """This method begins the action ACTION. It DOES NOT perform the action
150(see advance_time for that). But it does "decode" the action, and check for any
151collision that may occur (not yet, but it will do in lesson 4).
152begin_action puts in the speed vector and the rotation_speed floating variable the
153character speed and rotation speed (amon the Y axis)."""
154    # Reset
155    self.rotation_speed = self.speed.x = self.speed.y = self.speed.z = 0.0
156
157    # Determine the character rotation
158    if   action.action in (ACTION_TURN_LEFT, ACTION_ADVANCE_LEFT, ACTION_GO_BACK_LEFT):
159      self.rotation_speed = 5.0
160    elif action.action in (ACTION_TURN_RIGHT, ACTION_ADVANCE_RIGHT, ACTION_GO_BACK_RIGHT):
161      self.rotation_speed = -5.0
162
163    # Determine the character speed
165      self.speed.z = -0.35
166    elif action.action in (ACTION_GO_BACK, ACTION_GO_BACK_LEFT, ACTION_GO_BACK_RIGHT):
167      self.speed.z = 0.2
168
169    # You can use speed.x for stride/lateral movement,
170    # and speed.y for jumping/falling (see lesson 5)
171
173    """This method is called one or more times between 2 rounds.
174PROPORTION is the proportion of the round that has been spent
175(e.g. 1.0 for a full round, 0.5 for a half, ...).
176
177ALL character moves MUST occur in the method, in order to take avantage
180
182    self.rotate_lateral(proportion * self.rotation_speed)
183
184
185# Create the scene (a world with no parent)
186scene = soya.World()
187
188# Loads the level, and put it in the scene
189level = soya.World.get("level_demo")
191
192# Creates a character in the level, with a keyboard controller
193character = Character(level, KeyboardController())
194character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
195
196# Creates a Tomb Raider-like camera in the scene
197camera = soya.TravelingCamera(scene)
198traveling = soya.ThirdPersonTraveling(character)
199traveling.distance = 5.0
201camera.zap()
202camera.back = 70.0
203
204# Creates a widget group, containing the camera and a label showing the FPS.
205soya.set_root_widget(widget.Group())