-
Notifications
You must be signed in to change notification settings - Fork 0
/
character.py
219 lines (192 loc) · 8.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
import pygame
import math
import weapon
import constants as const
from pygame import mixer
pygame.init()
mixer.init()
mob_ded_fx=pygame.mixer.Sound("assets/audio/mob_ded.wav")
game_won_fx=pygame.mixer.Sound("assets/audio/won.wav")
class Character():
def __init__(self, x, y, health, mob_animations, char_type, boss, size):
self.char_type = char_type
self.boss = boss
self.score = 0
self.frame_index = 0
self.action = 0 # 0:idle, 1:run
self.animation_list = mob_animations[char_type]
self.update_time = pygame.time.get_ticks()
self.running = False
self.flip = False
self.health = health
self.alive = True
self.hit = False
self.last_hit = pygame.time.get_ticks()
self.last_attack = pygame.time.get_ticks()
self.stunned = False
# can use self.animation_list instead of mob_animations[char_type]
self.img = mob_animations[char_type][self.action][self.frame_index]
self.rect = pygame.Rect(0, 0, const.TILE_SIZE *
size, const.TILE_SIZE*size)
self.rect.center = (x, y)
def move(self, dx, dy, obstacle_tiles, exit_tile=None):
screen_scroll = [0, 0]
level_complete = False
self.running = False
if dx != 0 or dy != 0:
self.running = True
if dx < 0:
self.flip = True
if dx > 0:
self.flip = False
# control diagonal speed
if dx != 0 and dy != 0:
dx = dx*(math.sqrt(2)/2)
dy = dy*(math.sqrt(2)/2)
# check for collision with map in x direction
self.rect.x += dx
for obstacle in obstacle_tiles:
# check for collision, 1 indicates image_rect in tile_data
if obstacle[1].colliderect(self.rect):
# check which side (wrt x axis) collision is from
if dx > 0:
self.rect.right = obstacle[1].left
if dx < 0:
self.rect.left = obstacle[1].right
# check for collision with map in y direction
self.rect.y += dy
for obstacle in obstacle_tiles:
# check for collision, 1 indicates image_rect in tile_data
if obstacle[1].colliderect(self.rect):
# check which side (wrt x axis) collision is from
if dy > 0:
self.rect.bottom = obstacle[1].top
if dy < 0:
self.rect.top = obstacle[1].bottom
# logic only applicable to player
if self.char_type == 0:
# check collision with exit ladder
if exit_tile[1].colliderect(self.rect):
# ensure player is close to the center of the exit ladder
exit_dist = math.sqrt(
(self.rect.centerx-exit_tile[1].centerx)**2 +
(self.rect.centery-exit_tile[1].centery)**2)
if exit_dist < 20:
level_complete = True
# update scroll based on player position
# move camera left and right
if self.rect.right > (const.SCREEN_WIDTH-const.SCROLL_THRESH):
screen_scroll[0] = (const.SCREEN_WIDTH -
const.SCROLL_THRESH)-self.rect.right
self.rect.right = (const.SCREEN_WIDTH-const.SCROLL_THRESH)
if self.rect.left < const.SCROLL_THRESH:
screen_scroll[0] = const.SCROLL_THRESH-self.rect.left
self.rect.left = const.SCROLL_THRESH
# move camera up and down
if self.rect.bottom > (const.SCREEN_HEIGHT-const.SCROLL_THRESH):
screen_scroll[1] = (const.SCREEN_HEIGHT -
const.SCROLL_THRESH)-self.rect.bottom
self.rect.bottom = (const.SCREEN_HEIGHT-const.SCROLL_THRESH)
if self.rect.top < const.SCROLL_THRESH:
screen_scroll[1] = const.SCROLL_THRESH-self.rect.top
self.rect.top = const.SCROLL_THRESH
return screen_scroll, level_complete
def ai(self, player, obstacle_tiles, screen_scroll, fireball_img):
clipped_line = ()
stun_cooldown = 100
ai_dx = 0
ai_dy = 0
fireball = None
# reposition the mobs on screen scroll
self.rect.x += screen_scroll[0]
self.rect.y += screen_scroll[1]
# create a line of sight from the enemy to the player
line_of_sight = ((self.rect.centerx, self.rect.centery),
(player.rect.centerx, player.rect.centery))
# check if line of sight passes through an obstacle tile
for obstacle in obstacle_tiles:
if obstacle[1].clipline(line_of_sight):
clipped_line = obstacle[1].clipline(line_of_sight)
# check distance to player
dist = math.sqrt((self.rect.centerx-player.rect.centerx) ** 2
+ (self.rect.centery-player.rect.centery)**2)
if not clipped_line and dist > const.RANGE:
if self.rect.centerx > player.rect.centerx:
ai_dx = -const.ENEMY_SPEED
if self.rect.centerx < player.rect.centerx:
ai_dx = const.ENEMY_SPEED
if self.rect.centery > player.rect.centery:
ai_dy = -const.ENEMY_SPEED
if self.rect.centery < player.rect.centery:
ai_dy = const.ENEMY_SPEED
if self.alive:
if not self.stunned:
# move towards player
self.move(ai_dx, ai_dy, obstacle_tiles)
# attack player
if dist < const.ATTACK_RANGE and player.hit == False:
player.health -= 10
player.hit = True
player.last_hit = pygame.time.get_ticks()
# boss enemies shoot fireballs
fireball_cooldown = 700
if self.boss:
if dist < 500:
if pygame.time.get_ticks()-self.last_attack >= fireball_cooldown:
fireball = weapon.Fireball(fireball_img, self.rect.centerx,
self.rect.centery, player.rect.centerx,
player.rect.centery)
self.last_attack = pygame.time.get_ticks()
# check if enemy is hit
if self.hit == True:
self.hit = False
self.last_hit = pygame.time.get_ticks()
self.stunned = True
self.running = False
self.update_action(0)
if (pygame.time.get_ticks()-self.last_hit) > stun_cooldown:
self.stunned = False
return fireball
def update(self):
# check if character has died
if self.health <= 0:
self.health = 0
if self.char_type==6:
game_won_fx.play()
elif self.char_type!=0:
mob_ded_fx.play()
self.alive = False
# timer to reset player taking a hit
hit_cooldown = 1000
if self.char_type == 0:
if self.hit == True and (pygame.time.get_ticks()-self.last_hit) > hit_cooldown:
self.hit = False
# check what action the player is performing
if self.running:
self.update_action(1) # 1:run
else:
self.update_action(0) # 0:idle
animation_cooldown = 70
# handle animation
# update img
self.img = 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 > animation_cooldown:
self.frame_index += 1
self.update_time = pygame.time.get_ticks()
if self.frame_index >= (len(self.animation_list[self.action])):
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 the animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def draw(self, surface):
flipped_img = pygame.transform.flip(self.img, self.flip, False)
if self.char_type == 0:
surface.blit(flipped_img, (self.rect.x,
self.rect.y-const.OFFSET*const.SCALE))
else:
surface.blit(flipped_img, self.rect)