-
Notifications
You must be signed in to change notification settings - Fork 1
/
ship.py
359 lines (286 loc) · 10.4 KB
/
ship.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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
from __future__ import division
from OpenGL.GL import *
from OpenGL.GLUT import *
import numpy
import math
import entity
import bullets
import bezier
import model
from asteroids import WIDTH, HEIGHT, distance
import particle
SHIP_ACCEL = 0.1
SHIP_ROTSPEED = 4
SHIP_SHIELD_FADE = 0.08
# Ship states
SHIP_DEAD = 4
SHIP_ACTIVE = 1
SHIP_FLYING_IN = 2
SHIP_FLYING_OUT = 3
SHIP_SCALE = 15
class Ship(entity.Entity):
"""Represents a player ship.
"""
WRAPDIST = 25
modelfile = "ship.obj"
def __init__(self, hud, playernum=0):
self.scale = SHIP_SCALE
super(Ship, self).__init__(model.ObjModel(self.modelfile), 2*self.scale)
self.pos = numpy.array((WIDTH/2, HEIGHT/2, 0),dtype=float)
self.lives = 3
hud.set_lives(self.lives)
if playernum != 0:
# Change the body color of the ship
pass # TODO
self.hud = hud
# States:
# 0 - control disabled, nothing happening
# 1 - ship under normal control for in game
# 2 - flying in
# 3 - flying out
# 4 - Blown up
self._state = 0
# Store the ship's orientation as three angles:
# theta is the rotation about the Z axis. When phi is 0, this is the
# angle the ship is pointing on the X/Y plane. 0 is straight up, and
# increasing angles turn the ship clockwise.
self.theta = 30
# phi is the counterclockwise rotation about the X axis (out of the
# page) Together with theta they describe the ship's direction
self.phi = 0
# rot is the rotation about the ship's axis (Y). When phi is 90, this
# variable is equivilant to theta
self.rot = 0
# translational velocity
self.speed = numpy.array([0,0,0], dtype=float)
# Acceleration, in world units per frame^2
self.accel = 1
# The player's bullets
self.bullets = bullets.Bullets()
self.shieldmax = 5
self.shields = self.shieldmax
self.hud.set_shields_max(self.shieldmax)
self.hud.set_shields(self.shields)
# Initialize movement state vars
self._reset()
# Shield visibility
self._shield_vis = 0
# Automatic trigger?
self.autofire = False
def direction(self):
"""Computes the unit vector representing the ship's direction"""
# Start with the ship's un-rotated direction, as a column vector
direction = numpy.matrix("[0;1;0]", dtype=float)
# Apply a rotation about the X axis
cosphi = math.cos(self.phi*math.pi/180)
sinphi = math.sin(self.phi*math.pi/180)
xrot = numpy.matrix([
[1, 0, 0],
[0, cosphi, -sinphi],
[0, sinphi, cosphi],
], dtype=float)
# Apply a rotation about the Z axis
costheta = math.cos(self.theta*math.pi/180)
sintheta = math.sin(self.theta*math.pi/180)
zrot = numpy.matrix([
[costheta, -sintheta, 0],
[sintheta, costheta, 0],
[0, 0, 1],
], dtype=float)
return numpy.array(zrot * xrot * direction).squeeze()
def update(self):
# Choose the appropriate update function based on the current state
p = lambda: None
[p, self._update_normal, # 0 and 1
self._update_bezier, self._update_bezier, # 2 and 3
p][self._state]()
# Thrusting?
if self._thrusting:
direction = self.direction()
self.speed += SHIP_ACCEL * direction
particle.thrust(self.pos-direction*4, self.speed - direction*2)
if self._turning:
self.theta += SHIP_ROTSPEED * self._turning
self.rot = self.theta
if self.autofire and self._trigger:
self.fire()
# update bullets
self.bullets.update()
if self._shield_vis > 0:
self._shield_vis -= SHIP_SHIELD_FADE
def _update_normal(self):
# Update position based on current speed
self.pos += self.speed
# Looping around:
if self.pos[0] > WIDTH + self.WRAPDIST:
self.pos[0] = -self.WRAPDIST
elif self.pos[0] < -self.WRAPDIST:
self.pos[0] = WIDTH + self.WRAPDIST
if self.pos[1] > HEIGHT + self.WRAPDIST:
self.pos[1] = -self.WRAPDIST
elif self.pos[1] < -self.WRAPDIST:
self.pos[1] = HEIGHT + self.WRAPDIST
def _update_bezier(self):
direction = self.direction()
particle.thrust(self.pos-direction*4, self.speed - direction*2)
self._t += 1
self.rot += 2
current = self._bezier.B(self._t)
self.pos = current[:3]
self.theta = current[3]
self.phi = current[4]
if self._t >= self._bezier.tmax:
if self._state == 2:
self._state = 1
else:
self._state = 0
def thrust(self, on):
"""Turns thrust on or off"""
if self._state != 1:
return
self._thrusting = on
def turn(self, dir):
"""Turns left or right"""
if self._state != 1:
return
self._turning = dir
def draw(self):
"""Our own draw method, for alternate rotation"""
# Draw our bullets
self.bullets.draw()
if self._state == 4:
return
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glTranslated(*self.pos)
glScaled(self.scale, self.scale, self.scale)
# Do the rotations. The ship normally faces (0,1,0) into the page
# normal turn
glRotated(self.theta, 0,0,1)
phi = self.phi
# skip common case: phi is 0
if phi:
glRotated(phi, 1,0,0)
# ship's axis rotation. Do this last, so it's always along the ship's
# axis, not the world's Y axis
glRotated(self.rot, 0,1,0)
self.model.draw()
# Draw shields
if self._shield_vis > 0:
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (0,1,0,self._shield_vis,))
glDepthMask(GL_FALSE)
glEnable(GL_BLEND)
glutSolidSphere(2, 6, 6)
glDepthMask(GL_TRUE)
glDisable(GL_BLEND)
glPopMatrix()
def _reset(self):
"""Resets movement parameters"""
self.speed[:] = 0
self._turning = 0
self._thrusting = 0
self._trigger = 0
def fly_in(self):
"""Initiates a fly-in"""
self._state = 2
self._reset()
# Set up a quadratic bezier curve with p0 just behind the camera, p1 at
# some point near the x/y plane, and p2 at our destination: the center
# of the screen on the x/y plane
# first 3 are position, second two are theta and phi
p0 = numpy.array([
WIDTH*0.6,
HEIGHT/2,
distance * 1.1,
90.0,
-90.0], dtype=float)
p1 = numpy.array([
WIDTH*0.6,
HEIGHT/2,
distance * 0.3,
90.0,
-80.0], dtype=float)
p2 = numpy.array([
WIDTH/2,
HEIGHT/2,
0,
90,
0], dtype=float)
# Set the ship at p0
self.pos[:] = p0[:3]
self.theta = p0[3]
self.phi = p0[4]
self._t = 0
# Specify the number of frames to take as the 4th parameter
self._bezier = bezier.Quadratic(p0,p1,p2,100)
def fly_out(self):
self._state = 3
self._reset()
# Starting point: the ship's current position
p0 = numpy.empty((5,))
p0[:3] = self.pos
p0[3] = self.theta
p0[4] = self.phi
# Ending point: behind the camera
p2 = numpy.array([
WIDTH*0.6,
HEIGHT/2,
distance * 1.1,
0.0,
90.0], dtype=float)
# Compute the mid-point by computing the ship's current direction and
# going forward a bit
p1 = p0.copy()
p1[:3] += self.direction() * 200
# adjust the angles a bit too
p1[3:] = (0.2*p0[3:] + 1.8*p2[3:]) / 2
self._t = 0
self._bezier = bezier.Quadratic(p0,p1,p2,200)
def damage(self, damage_amt):
"""The ship has taken damage.
Base damage amount is 1, the amount that hitting an asteroid does, and
the amount that it takes to split an asteroid
"""
if not self.is_active():
return
if self.shields == 0:
# KABOOM
particle.explosion(self.pos, (0,10,0), self.speed)
self.lives -= 1
self.hud.set_lives(self.lives)
self._state = 4
self._reset()
else:
# Be generous, any shields will keep the ship from blowing up
self.shields = max(0, self.shields-damage_amt)
self.hud.set_shields(self.shields)
self._shield_vis = 1
def new_ship(self):
"""Resets stats and such"""
self.shields = self.shieldmax
self.hud.set_shields(self.shields)
def is_active(self):
"""Returns true if the state of this ship is active in the game, able
to fire and take damage."""
return self._state == 1
def is_flying(self):
return self._state == 2 or self._state == 3
def is_dead(self):
return self._state == SHIP_DEAD
def trigger(self, on):
"""Trigger is on or off"""
if not self.autofire and on and not self._trigger:
self.fire()
self._trigger = on
def fire(self):
"""Fire its primary weapon"""
if not self.bullets.can_fire() or not self.is_active():
return
# First calculate the trajectory of the bullet
shipdirection = self.direction()
# Fire the bullet from the ship's tip
position = self.pos + shipdirection*20
# Velocity has a base speed in the direction of the ship, and a
# component from the ship's current velocity
velocity = self.bullets.speed * shipdirection + self.speed
self.bullets.fire(self.pos, velocity)