-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathentities.py
More file actions
275 lines (209 loc) · 12.8 KB
/
entities.py
File metadata and controls
275 lines (209 loc) · 12.8 KB
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
import object_types
import pygame
import math
# classe de torpedos e submarinos
class WaterObject:
def __init__(self, image: pygame.Surface, position: object_types.Vector2d, speed: float, bearing: object_types.Angle, radius: float, tag: str, invencibility_time: float) -> None:
# imagem para demo
self.image = image
# informações comuns para qualquer objeto
self.position = position
self.speed = speed
self.bearing = bearing
self.radius = radius
# tag usada para indentificar contatos
self.tag = tag
# tempo de invencibilidade depois de spawnar para evitar colisões ao criar torpedos
self.invencibility_time = invencibility_time
# variavel para definir se o objeto está ativo
self.alive = True
def die(self):
# objetos mortos não se movem
self.alive = False
self.speed = 0
def check_collision(self, all_water_objects: list['WaterObject']):
# checa todo objeto aquatico (menos ele mesmo) e calcula a distancia usando pitagoras
for water_object in all_water_objects:
if water_object != self:
delta_x = water_object.position.x - self.position.x
delta_y = water_object.position.y - self.position.y
distance = math.sqrt(delta_x ** 2 + delta_y ** 2)
# se a distancia for menor que a soma do proprio raio e do raio do objeto, então há colisão
if distance <= self.radius + water_object.radius:
# se o o objeto em questão está invencivel, a colisão é ignorada, caso contrário, ambos os objetos morrem e o objeto pra de verificar se há colisão
if water_object.invencibility_time <= 0:
self.die()
water_object.die()
break
# função principal, que atualiza varias caracteristicas do objeto
def update(self, time_span: float, all_water_objects: list['WaterObject']) -> None:
# mover o objeto baseado na velocidade e angulo atual
self.position.x += self.speed * self.bearing.cos() * time_span
self.position.y += self.speed * self.bearing.sin() * time_span
# se está invencível, diminuir o tempo de invencibilidade, caso contrário checar por colisões
if self.invencibility_time > 0:
self.invencibility_time -= time_span
else:
self.check_collision(all_water_objects)
# função que consegue o angulo entre o proprio objeto e outro, relativo a direção norte (0º)
def check_bearing_global(self, other_water_object: 'WaterObject') -> object_types.Angle:
delta_x = other_water_object.position.x - self.position.x
delta_y = other_water_object.position.y - self.position.y
distance = math.sqrt(delta_x ** 2 + delta_y ** 2)
if distance == 0:
return object_types.Angle(0)
cos = delta_x / distance
if delta_y >= 0:
return object_types.Angle(math.acos(cos) * 180 / math.pi)
else:
return object_types.Angle(-math.acos(cos) * 180 / math.pi)
# função que consegue o angulo entre o proprio objeto e outro, relativo a direção atual do submarino (self.bearing)
def check_bearing_relative(self, other_water_object: 'WaterObject') -> object_types.Angle:
global_bearing = self.check_bearing_global(other_water_object)
relative_bearing = global_bearing.subtract(self.bearing)
return relative_bearing
# função que dá a distancia para um objeto
def get_distance(self, other_water_object: 'WaterObject') -> float:
delta_x = other_water_object.position.x - self.position.x
delta_y = other_water_object.position.y - self.position.y
distance = math.sqrt(delta_x ** 2 + delta_y ** 2)
return distance
# classe submarino controlável
class PlayerSub(WaterObject):
def __init__(self, image: pygame.Surface, position: object_types.Vector2d, speed: float, bearing: object_types.Angle, radius: float, tag: str, invencibility_time: float, max_speed: float, acceleration: float, turn_rate: object_types.Angle, torpedo_reload_time: float, max_torpedos: int, torpedo_recharge_time: float, max_contact_time) -> None:
# variaveis extra para controle do submarino
self.acceleration = acceleration
self.max_speed = max_speed
self.turn_rate = turn_rate
# variaveis responsável por lidar com o sonar
self.contacts = list[WaterObject]()
self.contacts_graph = list[list[tuple[float, object_types.Angle, object_types.Angle]]]()
self.max_contact_time = max_contact_time
# limites para o numero de torpedos que podem ser lançados e o tempo entre os lançamentos e o tempo para recuperar torpedos
self.reload_time = torpedo_reload_time
self.time_to_reload = 0
self.recharge_time = torpedo_recharge_time
self.time_to_recharge = self.recharge_time
self.max_torpedos = max_torpedos
self.amount_torpedo = self.max_torpedos
# chamar a função de inicialização da classe parente
super().__init__(image, position, speed, bearing, radius, tag, invencibility_time)
# função que atira um torpedo
def fire(self, image: pygame.Surface, torpedo_speed: float, torpedo_radius: float, torpedo_tag: str, torpedo_invencibility_time: float, torpedo_turn_rate: object_types.Angle, torpedo_detection_angle: object_types.Angle, torpedo_neutral_angle: object_types.Angle, target: 'PlayerSub', torpedo_burn_time, torpedos: list['Torpedo'], all_water_objects: list['WaterObject']):
# checar se não ultrapassou o limite de torpedos e se passou o tempo de reload entre um torpedo e outro, e se o submarino atual não está morto
if (self.amount_torpedo > 0 and self.time_to_reload <= 0 and self.alive):
# adicionar o torpedo na lista de torpedos
torpedos.append(Torpedo(image, self.position.copy(), torpedo_speed, self.bearing.copy(), torpedo_radius, torpedo_tag, torpedo_invencibility_time, torpedo_turn_rate, torpedo_detection_angle, torpedo_neutral_angle, target, torpedo_burn_time))
all_water_objects.append(torpedos[-1]) # adicionar o ultimo torpedo na lista de todos os objetos
# atualizar os limites para lançar outro torpedo
self.amount_torpedo -= 1
self.time_to_reload = self.reload_time
# função de morte especial que impede o submarino de se mover e acelerar novamente
def die(self):
self.max_speed = 0
# chamar a função de inicialização da classe parente
super().die()
# função que adiciona um contato de sonar para a lista de contatos
def add_contact(self, other_thing: 'WaterObject') -> None:
self.contacts.append(other_thing)
new_graph = list[tuple[float, object_types.Angle, object_types.Angle]]()
self.contacts_graph.append(new_graph)
def remove_contact(self, contact_index: int) -> None:
self.contacts.pop(contact_index)
self.contacts_graph.pop(contact_index)
# função que atualiza contatos
def update_contacts(self, time_span: float) -> None:
# para cada contato na lista de contatos
contact_index = 0
while contact_index < len(self.contacts):
# criar um objeto referencia ao contato da lista
contact = self.contacts[contact_index]
# para cada ponto do grafico do contato
graph_index = 0
while graph_index < len(self.contacts_graph[contact_index]):
# adicionar a variação de tempo no termo tempo
time = self.contacts_graph[contact_index][graph_index][0]
relative_angle = self.contacts_graph[contact_index][graph_index][1]
global_angle = self.contacts_graph[contact_index][graph_index][2]
self.contacts_graph[contact_index][graph_index] = (time + time_span, relative_angle, global_angle)
# se o termo tempo for maior que o tempo maximo, remover o ponto do grafico
if self.contacts_graph[contact_index][graph_index][0] > self.max_contact_time:
self.contacts_graph[contact_index].pop(graph_index)
else:
graph_index += 1
# se o contato está vivo, adicionar um novo ponto com termo tempo 0
if contact.alive:
contact_new_tuple = (float(0), self.check_bearing_relative(contact), self.check_bearing_global(contact))
self.contacts_graph[contact_index].append(contact_new_tuple)
contact_index += 1
else:
# se o contato morto não tem mais pontos no grafico então é hora de remover o contato
if len(self.contacts_graph[contact_index]) <= 0:
self.remove_contact(contact_index)
else:
contact_index += 1
# função de uptade especial para submarino, tem como parametros controles que podem ser usados pelo jogador
def update(self, time_span: float, accelerating: int, turning: int, all_water_objects: list['WaterObject']) -> None:
# adicionar novos contatos se houverem objetos que não são o proprio submarino, não já estão na lista de contatos e estão vivos
for water_object in all_water_objects:
if water_object != self:
if water_object not in self.contacts and water_object.alive:
self.add_contact(water_object)
# atualizar os contatos
self.update_contacts(time_span)
# recarregar os torpedos
if self.amount_torpedo < self.max_torpedos:
if self.time_to_recharge <= 0:
self.amount_torpedo += 1
self.time_to_recharge = self.recharge_time
else:
self.time_to_recharge -= time_span
# diminui o tempo de recarga para poder atirar torpedos novamente
if self.time_to_reload > 0:
self.time_to_reload -= time_span
else:
self.time_to_reload = 0
# usar o controle de aceleração para acelerar ou desacelerar o submarino
self.speed += accelerating * self.acceleration * time_span
# a velocidade não pode ultrapassar o limite
if self.speed > self.max_speed:
self.speed = self.max_speed
elif self.speed < -self.max_speed:
self.speed = -self.max_speed
# usar o controle de giro para mudar a direção do submarino
delta_angle = self.turn_rate.multiply(turning * time_span)
self.bearing = self.bearing.add(delta_angle)
# chamar a função de inicialização da classe parente
super().update(time_span, all_water_objects)
# classe de torpedos
class Torpedo(WaterObject):
def __init__(self, image: pygame.Surface, position: object_types.Vector2d, speed: float, bearing: object_types.Angle, radius: float, tag: str, invencibility_time: float, turn_rate: object_types.Angle, angle_detection: object_types.Angle, angle_neutral: object_types.Angle, target: WaterObject, burn_time: float) -> None:
# variaveis extra para auto-controle do torpedo
self.turn_rate = turn_rate
# variaveis para o sistema de localização do alvo do torpedo
self.angle_detection = angle_detection
self.angle_neutral = angle_neutral
self.target = target
# tempo de queima para evitar que o torpedo dure para sempre
self.burn_time = burn_time
# chamar a função de inicialização da classe parente
super().__init__(image, position, speed, bearing, radius, tag, invencibility_time)
# função de update especial para torpedos
def update(self, time_span: float, all_water_objects: list['WaterObject']) -> None:
# diminuir o tempo de queima e matar o torpedo quando o tempo chegar a 0
self.burn_time -= time_span
if self.burn_time <= 0:
self.die()
# verificar o angulo até o alvo
target_angle = self.check_bearing_relative(self.target)
# controle automático de giro baseado se o alvo está na zona de reconhecimento do torpedo
turning = 0
if target_angle.degree > self.angle_neutral.degree and target_angle.degree <= self.angle_detection.degree:
turning = 1
elif target_angle.degree >= 360 - self.angle_detection.degree and target_angle.degree < 360 - self.angle_neutral.degree:
turning = -1
# girar o torpedo baseado no resultado do processamento anterior
delta_angle = self.turn_rate.multiply(turning * time_span)
self.bearing = self.bearing.add(delta_angle)
# chamar a função de inicialização da classe parente
return super().update(time_span, all_water_objects)