-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathplayer.py
295 lines (257 loc) · 11.2 KB
/
player.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
import pygame
from pygame.locals import *
from constants import *
import globals
WIDTH=100
HEIGHT=100
HITBOX_WIDTH = 60
HITBOX_HEIGHT = 100
ASSET_SIZE = 48
JUMP_POWER=12
class Player(pygame.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
#Aqui si explico lo de las animaciones.
#Aqui definimos cosas simples, que estamos en la animacion 'run',
#un diccionario que contendra el detalle de las animaciones,
#y otro que indica, por cada una, un 'tipo'.
#continuous: Se repite, o 'loopea'. once: No lo hace (e.g. para brincar)
#que se queda en el ultimo frame como 'cayendo'
self.current_animation = "run"
self.animations = {}
self.animation_behaviour = {
"run": "continuous",
"jump": "once",
"doublejump": "once"
}
self.load_assets()
#Indice del frame de la animacion
self.idxAnimation = 0
self.animationSpeed = .15
self.surf = self.animations[self.current_animation][self.idxAnimation]
self.surf = pygame.transform.scale(self.surf, (WIDTH,HEIGHT))
self.rect = self.surf.get_rect()
self.update_hitbox()
self.update_mask()
self.vel = pygame.math.Vector2(0, 0)
self.pos = pygame.math.Vector2(100, 300)
self.acc = pygame.math.Vector2(0, .022)
#Cuantas veces has brincado
self.jumpCount=0
self.canJump = False
self.doJump = False
self.wantedToJump = False
self.wantToJumpTime = -1
self.lastJumpTime = pygame.time.get_ticks()
#Cosas para mejorar el manejo de brincos (e.g. si quisiste brincar un poquito)
#antes de tocar el suelo, al tocarlo brinca automaticamente, etc
self.timeToHonorJumpAttempt = 100
self.dead = False
self.addScore = 0
self.colliding_with_floor = False
self.shield = True
self.shieldSurf = pygame.image.load("my-sprites/shield-big.png")
self.shieldSurf = pygame.transform.scale(self.shieldSurf, (WIDTH*1.1, HEIGHT*1.1))
self.shieldRect = self.shieldSurf.get_rect()
#Al salvar con el escudo, a veces te acaba de salvar y se gasta
#un salto si lo haces sin querer, esto lo previene
self.canOnlyJumpGoingDown = False
#Cargar una animacion especifica en base al nombre
#Como usamos al 'Punk', podemos usar solo las que estan en esa carpeta.
#Toda la animacion esta en un solo PNG, asi que itera en la imagen
#segun el ancho esperado de cada cuadro de la animacion, y lo carga
#en self.animations[nombre]
def load_animation(self, name):
self.animations[name] = []
asset = pygame.image.load("sprites/2 Punk/Punk_" + name + ".png").convert_alpha()
width = asset.get_width()
idx = 0
while (idx*ASSET_SIZE < width):
frame = pygame.Surface((ASSET_SIZE, ASSET_SIZE), pygame.SRCALPHA)
frame.blit(asset, asset.get_rect(), Rect(idx*ASSET_SIZE, 0, ASSET_SIZE, ASSET_SIZE))
self.animations[name].append(
frame
)
idx+=1
#Cargas las animaciones a usar
def load_assets(self):
self.load_animation("run")
self.load_animation("jump")
self.load_animation("doublejump")
#Cambiar la animacion actual, reseteando el indice del frame
def changeAnimation(self, name):
if self.current_animation is not name:
self.current_animation = name
self.idxAnimation = 0
#Brincar
def jump(self):
#Solo poder brincar si ya pasaron ciertos MS para evitar
#gastar dos saltos de inmediato
if pygame.time.get_ticks() - self.lastJumpTime < 100:
return
self.lastJumpTime = pygame.time.get_ticks()
#Si me salvo el escudo y voy hacia arriba todavia, no permitir brincar
if self.canOnlyJumpGoingDown and self.vel.y < 0:
return
#Solo brincar cuando se puede, etc
if self.canJump:
self.doJump = True
self.wantedToJump = False
if self.jumpCount == 2:
self.canJump = False
else:
self.wantedToJump = True
self.wantToJumpTime = pygame.time.get_ticks()
#'Cancelar' el brinco para no brincar tan alto
def cancel_jump(self):
if self.vel.y < -3:
self.vel.y = -3
#Revisar colision con el piso. Para eso uso un 'hitbox', entonces
#cambio temporalmente el rect por el hitbox, reviso usando spritecollide,
#y luego lo regreso HOH
def check_collisions_floor(self, spritegroup):
oldRect = self.rect
self.rect = self.hitbox
hits = pygame.sprite.spritecollide(self, spritegroup, False)
self.rect = oldRect
self.colliding_with_floor = False
if hits:
#Ver si ponemos al jugador arriba de la plataforma (cuando le pega de lado)
#y no esta mucho mas abajo que la plataforma, "salvandolo"...
if self.rect.bottom - hits[0].rect.top < (ASSET_SIZE/2) and self.vel.y >= 0:
self.vel.y = 0
self.jumpCount=0
self.canJump = True
self.pos.y = hits[0].rect.top + 1
self.changeAnimation("run")
if self.wantedToJump:
self.jump()
self.colliding_with_floor = True
else:
if self.jumpCount == 2:
self.canJump = False
#Colision con enemigos, si tengo escudo me lo quita, etc
def check_collisions_enemies(self, enemies_group):
hits = pygame.sprite.spritecollide(self, enemies_group, False, pygame.sprite.collide_mask)
if hits:
if self.shield:
self.shield = False
hits[0].kill()
else:
self.dead = True
#Tomar un escudo. Aqui usamos el hitbox que es mas grande a los lados y arriba
#para agarrarlos mas facil
def check_collisions_shields(self, group):
oldRect = self.rect
self.rect = self.hitbox
hits = pygame.sprite.spritecollide(self, group, True, pygame.sprite.collide_rect_ratio(1))
self.rect = oldRect
if hits:
for hit in hits:
self.shield = True
self.addScore += 5
hit.kill()
#Tomar dinero. Aqui usamos el hitbox que es mas grande a los lados y arriba
#para agarrarlos mas facil
def check_collisions_powerups(self, group):
oldRect = self.rect
self.rect = self.hitbox
hits = pygame.sprite.spritecollide(self, group, True, pygame.sprite.collide_rect_ratio(1))
self.rect = oldRect
if hits:
for hit in hits:
self.addScore += 10
hit.kill()
#En cada frame del juego se llama esto para cambiar el cuadro de la animacion
#y revisar si se va a repetir (continuous) o si nos quedamos en la ultima y ya
def animate(self,delta_time):
self.animationSpeed = .008 * delta_time
self.idxAnimation += self.animationSpeed
if int(self.idxAnimation)+1 >= len(self.animations[self.current_animation]):
if self.animation_behaviour[self.current_animation] == "continuous":
self.idxAnimation = 0
else:
self.idxAnimation = len(self.animations[self.current_animation])-1
self.surf = self.animations[self.current_animation][int(self.idxAnimation)]
self.surf = pygame.transform.scale(self.surf, (WIDTH,HEIGHT))
self.rect = self.surf.get_rect()
self.update_hitbox()
self.update_mask()
def update(self, delta_time, collision_floor_group, collision_group_powerups, collision_group_enemies, collision_group_shields):
self.check_collisions_floor(collision_floor_group)
self.check_collisions_powerups(collision_group_powerups)
self.check_collisions_enemies(collision_group_enemies)
self.check_collisions_shields(collision_group_shields)
if self.dead:
return
self.animate(delta_time)
now = pygame.time.get_ticks()
if self.wantedToJump and now - self.wantToJumpTime > self.timeToHonorJumpAttempt:
self.wantedToJump = False
if self.doJump:
#Si no esta en el piso, ya "gastamos" un salto
#esto es un tema cuando vas rapido y brincas
#al final de la plataforma
if self.jumpCount == 0 and not self.colliding_with_floor:
self.jumpCount += 1
if self.jumpCount == 0:
self.vel.y = JUMP_POWER * -1
self.changeAnimation("jump")
elif self.jumpCount == 1:
self.vel.y = JUMP_POWER * -1 * .8
self.changeAnimation("doublejump")
self.jumpCount += 1
self.doJump = False
#Gravedad
self.vel += self.acc * delta_time
self.pos += self.vel
#Nos fuimos hasta abajo? Escudo = Salvar. No escudo = MORIR
if self.rect.top > SCREEN_HEIGHT:
if self.shield:
self.shield = False
self.pos.y = 50
else:
self.dead = True
self.rect.midbottom = self.pos
self.update_hitbox()
self.update_mask()
self.update_shield()
if self.canOnlyJumpGoingDown and self.vel.y > 0:
self.canOnlyJumpGoingDown = False
#El escudo nos debe salvar de morir cayendo hacia abajo
#Se hace un megasalto automatico
def shield_save(self):
if self.shield:
self.shield = False
self.canOnlyJumpGoingDown = True
self.vel.y = JUMP_POWER * -1.5
self.pos.y -= 20
self.jumpCount = 0
self.canJump = True
self.changeAnimation("jump")
else:
self.dead = True
def update_shield(self):
self.shieldRect.center = (
self.rect.center[0]-20,
self.rect.center[1]+10,
)
#HITBOX para colisiones con el piso, dinero, escudos
#Aumento .1 de ancho con cada game_speed
def update_hitbox(self):
self.hitbox = pygame.Rect(
self.rect.x * 1 + (.1 * globals.game_speed), self.rect.y,
HITBOX_WIDTH, HITBOX_HEIGHT
)
#MASK (mascara) para colisiones con enemigos (un poco mas pequenas que
# el jugador para que sea mas 'justo' o 'facil')
def update_mask(self):
self.maskSurface = self.surf
self.maskSurface = pygame.transform.scale(self.maskSurface, (WIDTH*.8,HEIGHT*.8))
self.mask = pygame.mask.from_surface(self.maskSurface)
#Mostrar el hitbox en pantalla, para debug
def display_hitbox(self):
debugRect = pygame.Surface((self.hitbox.width,self.hitbox.height))
debugRect.set_alpha(128)
debugRect.fill((255,0,0))
pygame.display.get_surface().blit(debugRect, (self.hitbox.x, self.hitbox.y))