/
fall_mask.py
209 lines (179 loc) · 7.23 KB
/
fall_mask.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
"""
Demonstrates some simple platforming using mask collision. Shows the benefit
of using pixel perfect collision detection. When using mask collision
techniques, it is generally best to perfom initial rectangle collision tests
before investing in the more expensive mask detection methods.
Note that this code does not allow the sprite to traverse slopes.
That will be addressed in a future example.
-Written by Sean J. McKiernan 'Mekire'
"""
import os
import sys
import random
import pygame as pg
CAPTION = "Basic Platforming: Pixel Perfect Collision"
SCREEN_SIZE = (700, 500)
BACKGROUND_COLOR = (50, 50, 50)
class _Physics(object):
"""
A simplified physics class. Using a 'real' gravity function here, though
it is questionable whether or not it is worth the effort. Compare to the
effect of gravity in fall_rect and decide for yourself.
"""
def __init__(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = self.y_vel_i = 0
self.grav = 20
self.fall = False
self.time = None
def physics_update(self):
"""If the player is falling, calculate current y velocity."""
if self.fall:
time_now = pg.time.get_ticks()
if not self.time:
self.time = time_now
self.y_vel = self.grav*((time_now-self.time)/1000.0)+self.y_vel_i
else:
self.time = None
self.y_vel = self.y_vel_i = 0
class Player(_Physics, pg.sprite.Sprite):
"""Class representing our player."""
def __init__(self,location,speed):
"""
The location is an (x,y) coordinate pair, and speed is the player's
speed in pixels per frame. Speed should be an integer.
"""
_Physics.__init__(self)
pg.sprite.Sprite.__init__(self)
self.image = PLAYER_IMAGE
self.mask = pg.mask.from_surface(self.image)
self.speed = speed
self.jump_power = 10
self.rect = self.image.get_rect(topleft=location)
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
self.rect.move_ip((0,1))
collisions = pg.sprite.spritecollide(self, obstacles, False)
collidable = pg.sprite.collide_mask
if not pg.sprite.spritecollideany(self, collisions, collidable):
self.fall = True
self.rect.move_ip((0,-1))
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect.move_ip(offset)
collisions = pg.sprite.spritecollide(self, obstacles, False)
collidable = pg.sprite.collide_mask
while pg.sprite.spritecollideany(self, collisions, collidable):
self.rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def jump(self):
"""Called when the user presses the jump button."""
if not self.fall:
self.y_vel_i = -self.jump_power
self.fall = True
def update(self, obstacles, keys):
"""Everything we need to stay updated."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def draw(self,surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def __init__(self, location):
"""The location argument is an (x,y) coordinate pair."""
pg.sprite.Sprite.__init__(self)
self.make_image()
self.mask = pg.mask.from_surface(self.image)
self.rect = pg.Rect(location, (50,50))
def make_image(self):
"""Something pretty to look at."""
color = [random.randint(0,255) for _ in range(3)]
self.image = pg.Surface((50,50)).convert_alpha()
self.image.fill(color)
self.image.blit(SHADE_IMG, (0,0))
class Control(object):
"""Class for managing event loop and game states."""
def __init__(self):
"""Nothing to see here folks. Move along."""
self.screen = pg.display.get_surface()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,-25), 4)
self.obstacles = self.make_obstacles()
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
obstacles = [Block((400,400)), Block((300,270)), Block((150,170))]
obstacles += [Block((500+50*i,220)) for i in range(3)]
for i in range(12):
obstacles.append(Block((50+i*50,450)))
obstacles.append(Block((100+i*50,0)))
obstacles.append(Block((0,50*i)))
obstacles.append(Block((650,50*i)))
return pg.sprite.Group(obstacles)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump()
def update(self):
"""Update held keys and the player."""
self.keys = pg.key.get_pressed()
self.player.update(self.obstacles, self.keys)
def draw(self):
"""Draw all necessary objects to the display surface."""
self.screen.fill(BACKGROUND_COLOR)
self.obstacles.draw(self.screen)
self.player.draw(self.screen)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_SIZE)
PLAYER_IMAGE = pg.image.load("smallface.png").convert_alpha()
SHADE_IMG = pg.image.load("shader.png").convert_alpha()
run_it = Control()
run_it.main_loop()
pg.quit()
sys.exit()