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
7
# it under the terms of the GNU General Public License as published by
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
21
# Adding a keyboard-controlled cube
22
23
# A bunch of import
24
import sys, os, os.path
25
import soya
26
import soya.widget as widget
27
import soya.sdlconst as sdlconst
28
29
# Inits Soya
30
soya.init()
31
32
# Define data path (=where to find models, textures, ...)
33
HERE = os.path.dirname(sys.argv[0])
34
soya.path.append(os.path.join(HERE, "data"))
35
36
class Level(soya.World):
37
"""A game level.
38
Level is a subclass of soya.World."""
39
40
41
class 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.
48
ACTION_WAIT = 0
49
ACTION_ADVANCE = 1
50
ACTION_ADVANCE_LEFT = 2
51
ACTION_ADVANCE_RIGHT = 3
52
ACTION_TURN_LEFT = 4
53
ACTION_TURN_RIGHT = 5
54
ACTION_GO_BACK = 6
55
ACTION_GO_BACK_LEFT = 7
56
ACTION_GO_BACK_RIGHT = 8
57
58
59
class KeyboardController:
60
"""A controller is an object that gives orders to a character.
61
Here, we define a keyboard based controller, but there may be mouse-based or IA-based
62
controllers.
63
Notice that the unique method is called "next", which allows to use Python generator
64
as 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
99
class Character(soya.World):
100
"""A character in the game.
101
I always consider character.x, character.y, character.z (i.e. the point with
102
coordinates (0.0, 0.0, 0.0) in the character coordinates system :
103
Point(character, 0.0, 0.0, 0.0)) to be the position of the feet of the character
104
(and not the center).
105
106
Similarly, i consider the X vector of the character (i.e.
107
Vector(character, 1.0, 0.0, 0.0)) to be the right direction, the Y vector (i.e.
108
Vector(character, 0.0, 1.0, 0.0)) to be the up direction and the -Z vector (i.e.
109
Vector(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
134
round of 30ms by default, this means that the character will perform each action
135
during 30ms.
136
137
Actually, all round obviously does not last exactely 30ms, but it is true on a
138
global 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
151
collision that may occur (not yet, but it will do in lesson 4).
152
begin_action puts in the speed vector and the rotation_speed floating variable the
153
character 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
164
if action.action in (ACTION_ADVANCE, ACTION_ADVANCE_LEFT, ACTION_ADVANCE_RIGHT):
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
172
def advance_time(self, proportion):
173
"""This method is called one or more times between 2 rounds.
174
PROPORTION 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
177
ALL character moves MUST occur in the method, in order to take avantage
178
of the Idler time management system and get the best visual effect."""
179
soya.World.advance_time(self, proportion)
180
181
self.add_mul_vector(proportion, self.speed)
182
self.rotate_lateral(proportion * self.rotation_speed)
183
184
185
# Create the scene (a world with no parent)
186
scene = soya.World()
187
188
# Loads the level, and put it in the scene
189
level = soya.World.get("level_demo")
190
scene.add(level)
191
192
# Creates a character in the level, with a keyboard controller
193
character = Character(level, KeyboardController())
194
character.set_xyz(216.160568237, -7.93332195282, 213.817764282)
195
196
# Creates a Tomb Raider-like camera in the scene
197
camera = soya.TravelingCamera(scene)
198
traveling = soya.ThirdPersonTraveling(character)
199
traveling.distance = 5.0
200
camera.add_traveling(traveling)
201
camera.zap()
202
camera.back = 70.0
203
204
# Creates a widget group, containing the camera and a label showing the FPS.
205
soya.set_root_widget(widget.Group())
206
soya.root_widget.add(camera)
207
soya.root_widget.add(widget.FPSLabel())
208
209
#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"))
210
211
# Creates and run an "idler" (=an object that manage time and regulate FPS)
212
# By default, FPS is locked at 40.
213
soya.Idler(scene).idle()
214