-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathquest.py
334 lines (263 loc) · 9.91 KB
/
quest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
""" Quest - An epic journey.
Simple demo that demonstrates PyTMX and pyscroll.
requires pygame and pytmx.
https://github.com/bitcraft/pytmx
pip install pytmx
"""
import os.path
import pygame
from pygame.locals import *
from pytmx.util_pygame import load_pygame
import pyscroll
import pyscroll.data
from pyscroll.group import PyscrollGroup
import random
# define configuration variables here
RESOURCES_DIR = 'levels/town'
HERO_MOVE_SPEED = 200 # pixels per second
GRAVITY = 400 #pixels per second per second
MAP_FILENAME = 'EmilysEpic.tmx'
# simple wrapper to keep the screen resizeable
def init_screen(width, height):
screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
return screen
# make loading maps a little easier
def get_map(filename):
return os.path.join(RESOURCES_DIR, filename)
# make loading images a little easier
def load_image(filename):
return pygame.image.load(os.path.join(RESOURCES_DIR, filename))
class Npc(pygame.sprite.Sprite):
""" Non player character. Similar to hero but moves by itself.
"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('people','circle guy.png')).convert_alpha()
self.velocity = [0.0, 0]
self._position = [0, 0]
self._old_position = self.position
self.rect = self.image.get_rect()
self.wallhit=False
def __del__(self):
print("Destructor called")
@property
def position(self):
return list(self._position)
@position.setter
def position(self, value):
self._position = list(value)
def update(self, dt):
self._position[1] += self.velocity[1] * dt
self.rect.topleft = self._position
idx = self.rect.collidelist(game.walls)
if idx > -1:
self.velocity[0] += HERO_MOVE_SPEED/2.0*dt*(random.random()-0.5)
print("Velocity %d" % self.velocity[0])
if self.velocity[0] > HERO_MOVE_SPEED*1.1:
self.velocity[0] = HERO_MOVE_SPEED*1.1
if self.velocity[0] < -1*HERO_MOVE_SPEED*1.1:
self.velocity[0] = -1*HERO_MOVE_SPEED*1.1
self.rect.bottom = game.walls[idx].top
self._position = list(self.rect.topleft)
if self.wallhit and self.on_platform(): # Hit a wall.
self.wallhit = False
if random.random() > 0.4:
self.velocity[0] = -1*self.velocity[0]
else:
self.velocity[1]= -HERO_MOVE_SPEED*2 #jump
else:
self.velocity[1] += GRAVITY * dt
self._old_position = self._position[:]
self._position[0] = self._position[0] + self.velocity[0] * dt
self.rect.topleft = self._position
print("Position %d, %d" % (self.position[0], self.position[1]) )
def move_back(self):
""" If called after an update, the sprite can move back
"""
#print("move_back")
self._position = self._old_position
self.rect.topleft = self._position
self.wallhit=True
def on_platform(self):
self.rect.y += 2
if self.rect.collidelist(game.walls) > -1:
onplat=True
else:
onplat=False
self.rect.y -= 2
return onplat
class Hero(pygame.sprite.Sprite):
""" Our Hero
The Hero has three collision rects, one for the whole sprite "rect" and
"old_rect"
The position list is used because pygame rects are inaccurate for
positioning sprites; because the values they get are 'rounded down'
as integers, the sprite would move faster moving left or up.
There is also an old_rect that is used to reposition the sprite if it
collides with level walls.
"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = load_image('hero.png').convert_alpha()
self.velocity = [0, 0]
self._position = [0, 0]
self._old_position = self.position
self.rect = self.image.get_rect()
@property
def position(self):
return list(self._position)
@position.setter
def position(self, value):
self._position = list(value)
def update(self, dt):
self._position[1] += self.velocity[1] * dt
self.rect.topleft = self._position
idx = self.rect.collidelist(game.walls)
if idx > -1:
#print("Falling and hit %d" % idx)
#Hit a platform. Need to set ourselves above it.
self.rect.bottom = game.walls[idx].top
self.velocity[1]=0
self._position = list(self.rect.topleft)
else:
self.velocity[1] += GRAVITY * dt
self._old_position = self._position[:]
self._position[0] = self._position[0] + self.velocity[0] * dt
self.rect.topleft = self._position
def move_back(self):
""" If called after an update, the sprite can move back
"""
#print("move_back")
self._position = self._old_position
self.rect.topleft = self._position
def on_platform(self):
self.rect.y += 2
if self.rect.collidelist(game.walls) > -1:
onplat=True
else:
onplat=False
self.rect.y -= 2
return onplat
class QuestGame(object):
""" This class is a basic game.
This class will load data, create a pyscroll group, a hero object.
It also reads input and moves the Hero around the map.
Finally, it uses a pyscroll group to render the map and Hero.
"""
filename = get_map(MAP_FILENAME)
def __init__(self):
# true while running
self.running = False
# load data from pytmx
tmx_data = load_pygame(self.filename)
# setup level geometry with simple pygame rects, loaded from pytmx
self.walls = list()
for object in tmx_data.objects:
self.walls.append(pygame.Rect(
object.x, object.y,
object.width, object.height))
# create new data source for pyscroll
map_data = pyscroll.data.TiledMapData(tmx_data)
# create new renderer (camera)
self.map_layer = pyscroll.BufferedRenderer(map_data, screen.get_size())
self.map_layer.zoom = 2
# pyscroll supports layered rendering. our map has 3 'under' layers
# layers begin with 0, so the layers are 0, 1, and 2.
# since we want the sprite to be on top of layer 1, we set the default
# layer for sprites as 2
self.group = PyscrollGroup(map_layer=self.map_layer, default_layer=2)
self.hero = Hero()
self.npc = list()
self.npc = Npc()
# put the hero in the center of the map
self.hero.position = self.map_layer.map_rect.center
self.npc.position = self.map_layer.map_rect.center
# add our hero to the group
self.group.add(self.hero)
self.group.add(self.npc)
def draw(self, surface):
# center the map/screen on our Hero
self.group.center(self.hero.rect.center)
# draw the map and all sprites
self.group.draw(surface)
def handle_input(self):
""" Handle pygame input events
"""
poll = pygame.event.poll
event = poll()
while event:
if event.type == QUIT:
self.running = False
break
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
self.running = False
break
elif event.key == K_EQUALS:
self.map_layer.zoom += .25
elif event.key == K_MINUS:
value = self.map_layer.zoom - .25
if value > 0:
self.map_layer.zoom = value
# this will be handled if the window is resized
elif event.type == VIDEORESIZE:
init_screen(event.w, event.h)
self.map_layer.set_size((event.w, event.h))
event = poll()
# using get_pressed is slightly less accurate than testing for events
# but is much easier to use.
pressed = pygame.key.get_pressed()
if pressed[K_SPACE]:
# See if there is a platform below us
if self.hero.on_platform():
self.hero.velocity[1] = -HERO_MOVE_SPEED*2
self.hero.falling = True
# elif pressed[K_DOWN]:
# self.hero.velocity[1] = HERO_MOVE_SPEED
# else:
# self.hero.velocity[1] = 0
if pressed[K_LEFT]:
self.hero.velocity[0] = -HERO_MOVE_SPEED
elif pressed[K_RIGHT]:
self.hero.velocity[0] = HERO_MOVE_SPEED
else:
self.hero.velocity[0] = 0
def update(self, dt):
""" Tasks that occur over time should be handled here
"""
self.group.update(dt)
# check if the sprite's colliding with wall
# sprite must have a move_back method,
# otherwise this will fail
for sprite in self.group.sprites():
if sprite.rect.collidelist(self.walls) > -1:
sprite.move_back()
def run(self):
""" Run the game loop
"""
clock = pygame.time.Clock()
self.running = True
from collections import deque
times = deque(maxlen=30)
try:
while self.running:
dt = clock.tick(60) / 1000.
times.append(clock.get_fps())
# print(sum(times) / len(times))
self.handle_input()
self.update(dt)
self.draw(screen)
pygame.display.flip()
except KeyboardInterrupt:
self.running = False
if __name__ == "__main__":
pygame.init()
pygame.font.init()
screen = init_screen(1600, 1200)
pygame.display.set_caption('Quest - An epic journey.')
try:
game = QuestGame()
game.run()
except:
pygame.quit()
raise