-
Notifications
You must be signed in to change notification settings - Fork 0
/
Character.py
280 lines (251 loc) · 12.7 KB
/
Character.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
import constants
import pygame
import os
import random
from Bullet import Bullet
class Character(pygame.sprite.Sprite):
def __init__(self, x, y, scale, character_type, character, speed, health, grenades):
pygame.sprite.Sprite.__init__(self)
self.alive = True
self.character = character
self.character_type = character_type
self.speed = speed
# ammo, might not need now
# TODO if you want uncomment and change constants.TEMPORARY_AMMO to argument
# self.ammo = constants.TEMPORARY_AMMO
# self.start_ammo = constants.TEMPORARY_AMMO
self.shoot_cooldown = 0
self.retreat_time = constants.RETREAT_TIME
self.grenades = grenades
self.coin = 0
self.health = health
self.max_health = self.health # TODO upgrade this by level?
self.direction = 1
self.vel_y = 0
self.flip = self.jump = False
self.in_air = True
self.damage_to_death = False
self.damage_by_bullet = False
self.animation_list = []
self.frame_index = 0
self.action = 0
self.update_time = pygame.time.get_ticks() # next animation
# ai specific variables
self.move_counter = self.idling_counter = 0
self.vision = pygame.Rect(0, 0, 150, 20) # 150 = how far they can look TODO constant
self.idling = False
self.last_shot_time = False
#load all images for players
animation_types = ['idle', 'run', 'jump', 'death']
for animation in animation_types:
temp_list = []
#count number of files in folder
number_of_frames = len(os.listdir(f'images/{self.character_type}/{self.character}/{animation}'))
for i in range(number_of_frames):
img = pygame.image.load(f'images/{self.character_type}/{self.character}/{animation}/{i}.png').convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width() * scale), int(img.get_height() * scale)))
temp_list.append(img)
self.animation_list.append(temp_list)
self.avatar = self.animation_list[self.action][self.frame_index]
self.rect = self.avatar.get_rect()
self.rect.center = (x, y)
self.width = self.avatar.get_width()
self.height = self.avatar.get_height()
def draw(self, screen):
screen.blit(pygame.transform.flip(self.avatar, self.flip, False), self.rect)
# TODO test rect
# pygame.draw.rect(screen, BLACK, self.rect, 1)
def update(self):
self.update_animation()
self.check_alive()
# update cooldown
if self.shoot_cooldown > 0:
# TODO ammo add condition and self.ammo > 0
self.shoot_cooldown -= 1
def move(self, moving_left, moving_right, world, bg_scroll, water_group, exit_group, dead_fx):
#reset movement
screen_scroll = 0
dx = dy = 0
# assign movement variables if moving left or right
if moving_left:
dx = -self.speed
self.flip = True
self.direction = -1
if moving_right:
dx = self.speed
self.flip = False
self.direction = 1
#jump
if self.jump == True and self.in_air == False:
self.vel_y = -11 # jump RANGE TODO put in constant?
self.jump = False
self.in_air = True
# apply gravity
self.vel_y += constants.GRAVITY
if self.vel_y > 10: # TODO current landing
self.vel_y
dy += self.vel_y
# Check for collision
for tile in world.obstacle_list:
# If there is a collision, move out of the way and stop the player from moving.
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height): # check x direction
dx = 0
# if the ai has hit the wall then make it turn around
if self.character_type == 'enemy':
self.direction *= -1
self.move_counter = 0
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height): # check y direction
# check if below the ground, (i.e: if the player is jumping)
if self.vel_y < 0:
self.vel_y = 0
dy = tile[1].bottom - self.rect.top
# check if above the ground, (i.e: if the player is falling)
if self.vel_y > 0:
self.vel_y = 0
self.in_air = False
dy = tile[1].top - self.rect.bottom
# check for collision with water / lava
if pygame.sprite.spritecollide(self, water_group, False):
self.health = 0
if self.character_type == 'player':
dead_fx.play()
# check for collision with exit
level_complete = False
if pygame.sprite.spritecollide(self, exit_group, False):
level_complete = True
# check if fallen off the map
if self.rect.bottom > constants.SCREEN_HEIGHT:
self.health = 0
if self.character_type == 'player':
dead_fx.play()
# check if going of the edges of the screen
if self.character_type == 'player':
if self.rect.left + dx < 0 or self.rect.right + dx > constants.SCREEN_WIDTH:
dx = 0
# update rectangle position
self.rect.x += dx
self.rect.y += dy
# update scroll based on player position
if self.character_type == 'player':
if (self.rect.right > constants.SCREEN_WIDTH - constants.SCROLL_THRESH and bg_scroll < (world.level_length * constants.TILE_SIZE) - constants.SCREEN_WIDTH)\
or (self.rect.left < constants.SCROLL_THRESH and bg_scroll > abs(dx)):
self.rect.x -= dx
screen_scroll = -dx
return screen_scroll, level_complete
def shoot(self, bullet_group, shot_fx):
if self.shoot_cooldown == 0:
self.shoot_cooldown = constants.SHOOT_COOLDOWN
bullet = Bullet(self.rect.centerx + (constants.BULLET_RANGE * self.rect.size[0] * self.direction ), self.rect.centery, self.direction, self.character_type)
bullet_group.add(bullet)
shot_fx.play()
if self.retreat_time > 0:
self.retreat_time -= 1
def ai(self, player, bullet_group, world, screen_scroll, bg_scroll, water_group, exit_group, shot_fx, dead_fx): # TODO SHOULD BE DIFFERENT DEPENDS ON ENEMY, LIKE SOME WILL ALWAYS BE IDLING
if self.alive and player.alive:
if self.rect.colliderect(player.rect): # player die if touch enemy
player.health = 0
dead_fx.play()
# pass #TODO uncomment
if self.idling == False and random.randint(1, 200) == 1: # sometimes stopped
self.update_action(constants.ACTION_IDLE)
self.idling = True
self.idling_counter = 50
# check if the ai in near the player
#TODO vision lava?
if self.vision.colliderect(player.rect):
if self.retreat_time > 0:
# TODO different depend on type of enemy, might not shoot but explode
# stop running and face the player
self.update_action(constants.ACTION_IDLE)
# shoot
# TODO fixbug player change the direction then have to change direction as well
self.shoot(bullet_group, shot_fx)
else:
# if player take damage within 10 secs monster won't
if player.last_shot_time != False and pygame.time.get_ticks() - player.last_shot_time <= constants.RETREAT_COOLDOWN: #retreat cooldown
self.retreat_time = constants.RETREAT_TIME
else:
if self.direction == 1:
ai_moving_right = True
else:
ai_moving_right = False
ai_moving_left = not ai_moving_right
self.move(ai_moving_left, ai_moving_right, world, bg_scroll, water_group, exit_group, dead_fx)
self.update_action(constants.ACTION_RUN)
self.move_counter += 1
# update ai vision as the enemy moves
self.vision.center = (self.rect.centerx + 75 * self.direction, self.rect.centery) # TODO constant
# draw vision:
# pygame.draw.rect(screen, constants.RED, self.vision)
if self.move_counter > constants.TILE_SIZE:
self.direction *= -1
self.move_counter *= -1
else:
self.retreat_time = constants.RETREAT_TIME
if self.idling == False:
if self.direction == 1:
ai_moving_right = True
else:
ai_moving_right = False
ai_moving_left = not ai_moving_right
self.move(ai_moving_left, ai_moving_right, world, bg_scroll, water_group, exit_group, dead_fx)
self.update_action(constants.ACTION_RUN)
# TODO maybe upgrade to find and run to player
self.move_counter += 1
# update ai vision as the enemy moves
# TODO should still have when idle depend on the type of enemy
self.vision.center = (self.rect.centerx + 75 * self.direction, self.rect.centery) # TODO constant
# draw vision:
# pygame.draw.rect(screen, constants.RED, self.vision)
if self.move_counter > constants.TILE_SIZE:
self.direction *= -1
self.move_counter *= -1
else: # and then move again after stopped at sometime
self.idling_counter -=1
if self.idling_counter <= 0:
self.idling = False
# scroll
self.rect.x += screen_scroll
def update_animation(self):
# update animation
# update image depending on current frame
self.avatar = self.animation_list[self.action][self.frame_index]
# check if enough time has passed since the last update
if pygame.time.get_ticks() - self.update_time > constants.ANIMATION_COOLDOWN:
self.update_time = pygame.time.get_ticks()
self.frame_index += 1
# if animation run out then reset back to start
if self.frame_index >= len(self.animation_list[self.action]):
if self.action == 3: #death action
self.frame_index = len(self.animation_list[self.action]) - 1
else:
self.frame_index = 0
def update_action(self, new_action):
# check if the new action is different to the previous one
if new_action != self.action:
self.action = new_action
# update animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def check_alive(self):
if self.health <= 0:
self.health = 0
self.speed = 0
self.alive = False
if (self.damage_by_bullet == True):
self.damage_to_death = True
self.update_action(constants.ACTION_DEATH) # death TODO do we need to kill object for enemy type as well?
class HealthBar():
def __init__(self, x, y, health, max_health):
self.x = x
self.y = y
self.health = health
self.max_health = max_health
def draw(self, screen, health):
# update with new health
self.health = health
# calculate health ratio
ratio = self.health / self.max_health
pygame.draw.rect(screen, constants.BLACK, (self.x-2, self.y-2, constants.HEALTH_BAR_WIDTH, constants.HEALTH_BAR_HEIGHT), 1)
pygame.draw.rect(screen, constants.RED, (self.x, self.y, constants.HEALTH_BAR_WIDTH, constants.HEALTH_BAR_HEIGHT))
pygame.draw.rect(screen, constants.GREEN, (self.x, self.y, constants.HEALTH_BAR_WIDTH * ratio, constants.HEALTH_BAR_HEIGHT))