kopia lustrzana https://github.com/russhughes/st7789py_mpy
384 wiersze
13 KiB
Python
384 wiersze
13 KiB
Python
"""
|
|
roids.py
|
|
========
|
|
|
|
.. figure:: ../_static/roids.jpg
|
|
:align: center
|
|
|
|
Test for polygons.
|
|
|
|
Asteroids style game demo using polygons.
|
|
|
|
.. note:: This example requires the following modules:
|
|
|
|
.. hlist::
|
|
:columns: 3
|
|
|
|
- `st7789py`
|
|
- `tft_config`
|
|
- `tft_buttons`
|
|
|
|
"""
|
|
|
|
import math
|
|
import random
|
|
import utime
|
|
import tft_config
|
|
import tft_buttons as Buttons
|
|
import st7789py as st7789
|
|
|
|
|
|
tft = tft_config.config(tft_config.WIDE)
|
|
buttons = Buttons.Buttons()
|
|
|
|
|
|
def main():
|
|
'''
|
|
Game on!
|
|
'''
|
|
|
|
class Poly():
|
|
'''
|
|
Poly class to keep track of a polygon based sprite
|
|
'''
|
|
|
|
def __init__(
|
|
self,
|
|
# list (x,y) tuples of convex polygon, must be closed
|
|
polygon,
|
|
x=None, # x location of polygon
|
|
y=None, # y location of polygon
|
|
v_x=None, # velocity in x axis
|
|
v_y=None, # velocity in y axis
|
|
angle=None, # angle in radians polygon is facing
|
|
spin=None, # spin in radians per frame_time
|
|
scale=None, # scale factor for polygon
|
|
radius=None, # radius of polygon for collision detection
|
|
max_velocity=10, # max velocity of polygon
|
|
counter=0):
|
|
|
|
# scale the polygon if scale was given
|
|
self.polygon = (
|
|
polygon if scale is None else [(int(scale*x[0]), int(scale*x[1])) for x in polygon])
|
|
|
|
# if no location given assign a random location
|
|
self.x = random.randint(0, width) if x is None else x
|
|
self.y = random.randint(0, width) if y is None else y
|
|
|
|
# set angle if given
|
|
self.angle = float(0) if angle is None else angle
|
|
|
|
# set random spin unless one was given
|
|
self.spin = random.randint(-3, 3) / 16 if spin is None else spin
|
|
|
|
# set random velocity unless one was given
|
|
self.velocity_x = random.uniform(
|
|
0.50, 0.99)*6-3 + 0.75 if v_x is None else v_x
|
|
self.velocity_y = random.uniform(
|
|
0.50, 0.99)*6-3 + 0.75 if v_y is None else v_y
|
|
|
|
# set radius, max_velocity and radius counter
|
|
self.radius = radius
|
|
self.max_velocity = max_velocity
|
|
self.counter = counter
|
|
|
|
def rotate(self, rad):
|
|
'''
|
|
Rotate polygon in radians
|
|
'''
|
|
self.angle += rad
|
|
if self.angle > rad_max:
|
|
self.angle = 0
|
|
elif self.angle < 0:
|
|
self.angle = rad_max
|
|
|
|
def move(self):
|
|
'''
|
|
Rotate and move polygon velocity distance.
|
|
'''
|
|
if self.spin != 0:
|
|
self.rotate(self.spin)
|
|
|
|
self.x += int(self.velocity_x)
|
|
self.y += int(self.velocity_y)
|
|
self.x %= width
|
|
self.y %= height
|
|
|
|
def draw(self, color):
|
|
'''
|
|
Draw the polygon
|
|
'''
|
|
tft.polygon(self.polygon, self.x, self.y, color, self.angle, 0, 0)
|
|
|
|
def collision(self, poly):
|
|
'''
|
|
Detect collisions using overlapping radiuses.
|
|
Returns True on collision.
|
|
'''
|
|
return abs(
|
|
((self.x - poly.x) * (self.x - poly.x) +
|
|
(self.y - poly.y) * (self.y - poly.y))
|
|
< (self.radius + poly.radius) * (self.radius + poly.radius))
|
|
|
|
def create_roid(size, x=None, y=None, v_x=None, v_y=None):
|
|
'''
|
|
Create a new roid with the given parameters.
|
|
'''
|
|
return Poly(
|
|
roid_poly,
|
|
x=x,
|
|
y=y,
|
|
v_x=v_x,
|
|
v_y=v_y,
|
|
scale=roid_scale[size],
|
|
radius=roid_radius[size],
|
|
counter=size)
|
|
|
|
def update_missiles():
|
|
'''
|
|
Update active missiles and handle asteroid hits.
|
|
'''
|
|
for missile in list(missiles): # for each missile
|
|
missile.draw(st7789.BLACK) # erase old missile
|
|
if missile.counter > 0: # if counter > 0 missile is active
|
|
missile.move() # update missile position
|
|
|
|
for roid in list(roids): # for each roid
|
|
if missile.collision(roid): # check if missile collides with roid
|
|
roid.draw(st7789.BLACK) # erase the roid
|
|
if roid.counter > 0: # if roid is not the smallest size
|
|
roids.append( # add first smaller roid
|
|
create_roid(
|
|
roid.counter-1,
|
|
x=roid.x,
|
|
y=roid.y,
|
|
v_x=roid.velocity_x,
|
|
v_y=roid.velocity_y))
|
|
roids.append( # add second smaller roid
|
|
create_roid(
|
|
roid.counter-1,
|
|
x=roid.x,
|
|
y=roid.y,
|
|
v_x=-roid.velocity_x,
|
|
v_y=-roid.velocity_y))
|
|
|
|
roids.remove(roid) # remove the roid that was hit
|
|
missile.counter = 0
|
|
|
|
if missile.counter > 0: # if the missile has life left
|
|
missile.draw(st7789.WHITE) # draw missile
|
|
missile.counter -= 1 # reduce missile life
|
|
else:
|
|
|
|
missiles.remove(missile) # remove exploded missile
|
|
else:
|
|
missiles.remove(missile) # remove expired missile
|
|
|
|
def update_ship():
|
|
'''
|
|
Update ship velocity and limit to max_velocity
|
|
'''
|
|
# apply drag to velocity of ship so it will eventually slow to a stop
|
|
ship.velocity_x -= ship.velocity_x * ship_drag_frame
|
|
ship.velocity_y -= ship.velocity_y * ship_drag_frame
|
|
|
|
if ship.velocity_x > ship.max_velocity: # Limit velocity to +/- max_velocity
|
|
ship.velocity_x = ship.max_velocity
|
|
elif ship.velocity_x < -ship.max_velocity:
|
|
ship.velocity_x = -ship.max_velocity
|
|
|
|
if ship.velocity_y > ship.max_velocity:
|
|
ship.velocity_y = ship.max_velocity
|
|
elif ship.velocity_y < -ship.max_velocity:
|
|
ship.velocity_y = -ship.max_velocity
|
|
|
|
if abs(ship.velocity_x) < 0.1: # if ship is moving very slowly, stop it
|
|
ship.velocity_x = 0.0
|
|
|
|
if abs(ship.velocity_y) < 0.1:
|
|
ship.velocity_y = 0.0
|
|
|
|
ship.move() # move the ship and draw it
|
|
ship.draw(st7789.WHITE)
|
|
|
|
def update_roids():
|
|
'''
|
|
Update roid positions handle ship collisions
|
|
Returns True if not hit, False if hit
|
|
'''
|
|
not_hit = True
|
|
|
|
for roid in roids: # for each roid, erase, move then draw
|
|
roid.draw(st7789.BLACK)
|
|
roid.move()
|
|
roid.draw(st7789.WHITE)
|
|
|
|
if roid.collision(ship): # check for roid/ship collision
|
|
ship.draw(st7789.BLACK) # erase ship
|
|
ship.velocity_x = 0.0 # stop movement
|
|
ship.velocity_y = 0.0
|
|
|
|
not_hit = False
|
|
|
|
return not_hit
|
|
|
|
def explode_ship():
|
|
'''
|
|
Increment explosion step and alternate between drawing
|
|
explosion poly and explosion poly rotated 45 degrees
|
|
|
|
Returns True when explosion is finished
|
|
'''
|
|
ship.counter += 1
|
|
if ship.counter % 2 == 0:
|
|
tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK, 0.785398)
|
|
tft.polygon(explosion_poly, ship.x, ship.y, st7789.WHITE)
|
|
else:
|
|
tft.polygon(explosion_poly, ship.x, ship.y, st7789.WHITE, 0.785398)
|
|
tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK)
|
|
|
|
if ship.counter > 25:
|
|
# erase explosion, move ship to center and stop explosion
|
|
tft.polygon(explosion_poly, ship.x, ship.y, st7789.BLACK)
|
|
# move ship to center
|
|
ship.x = width >> 1
|
|
ship.y = height >> 1
|
|
ship.counter = 0
|
|
return True
|
|
|
|
return False
|
|
|
|
# enable display and clear screen
|
|
tft.fill(st7789.BLACK)
|
|
width = tft.width
|
|
height = tft.height
|
|
|
|
rad_max = 2 * math.pi # 360' in radians
|
|
|
|
# ship variables
|
|
ship_alive = True
|
|
ship_radius = 7
|
|
ship_rad_frame = rad_max / 16 # turning rate per frame
|
|
ship_accel_frame = 0.6 # acceleration per frame
|
|
ship_drag_frame = 0.015 # drag factor per frame
|
|
|
|
ship_poly = [(-7, -7), (7, 0), (-7, 7), (-3, 0), (-7, -7)]
|
|
|
|
ship = Poly(
|
|
ship_poly,
|
|
x=width >> 1,
|
|
y=height >> 1,
|
|
v_x=0,
|
|
v_y=0,
|
|
radius=ship_radius,
|
|
spin=0.0)
|
|
|
|
explosion_poly = [(-4, -4), (-4, 4), (4, 4), (4, -4), (-4, -4)]
|
|
|
|
# asteroid variables
|
|
roid_radius = [5, 10, 16]
|
|
roid_scale = [0.33, 0.66, 1.0]
|
|
|
|
roid_poly = [
|
|
(-5, -15), (-2, -13), (11, -14), (15, -7), (14, 0),
|
|
(16, 5), (11, 16), (7, 16), (-7, 14), (-14, 7),
|
|
(-13, 1), (-14, -8), (-11, -15), (-5, -15)]
|
|
roids = []
|
|
|
|
# missile variables
|
|
missile_velocity = 8
|
|
missile_max = 8
|
|
missile_life = 20
|
|
missile_rate = 200
|
|
missile_last = utime.ticks_ms()
|
|
missile_poly = [(-1, -1), (1, -1), (1, 1), (-1, 1), (-1, -1)]
|
|
missiles = []
|
|
|
|
frame_time = 60 # target frame rate delay
|
|
|
|
# game loop
|
|
while True:
|
|
last_frame = utime.ticks_ms()
|
|
|
|
# add roids if there are none
|
|
if len(roids) == 0:
|
|
roids = [create_roid(2), create_roid(2)]
|
|
|
|
update_missiles()
|
|
|
|
# Erase the ship
|
|
ship.draw(st7789.BLACK)
|
|
|
|
if ship_alive:
|
|
# if left button pressed
|
|
if buttons.left and buttons.left.value() == 0:
|
|
# rotate ship counter clockwise
|
|
ship.rotate(-ship_rad_frame)
|
|
|
|
# if right button pressed
|
|
if buttons.right and buttons.right.value() == 0:
|
|
# rotate ship clockwise
|
|
ship.rotate(ship_rad_frame)
|
|
|
|
# if hyperspace button pressed move ship to random location
|
|
if buttons.hyper and buttons.hyper.value() == 0:
|
|
diameter = ship.radius * 2
|
|
ship.x = random.randint(diameter, width - diameter)
|
|
ship.y = random.randint(diameter, height - diameter)
|
|
|
|
# stop movement
|
|
ship.velocity_x = 0.0
|
|
ship.velocity_y = 0.0
|
|
|
|
# if thrust button pressed
|
|
if buttons.thrust and buttons.thrust.value() == 0:
|
|
# accelerate ship in the direction the ship is facing
|
|
d_y = math.sin(ship.angle) * ship_accel_frame
|
|
d_x = math.cos(ship.angle) * ship_accel_frame
|
|
ship.velocity_x += d_x
|
|
ship.velocity_y += d_y
|
|
|
|
# if the fire button is pressed and less than missile_max active missles
|
|
if buttons.fire and buttons.fire.value() == 0 and len(missiles) < missile_max:
|
|
|
|
# limit missiles firing to once every missile_rate ms
|
|
if last_frame - missile_last > missile_rate:
|
|
|
|
# fire missile in direction ship in facing
|
|
v_y = math.sin(ship.angle) * missile_velocity
|
|
v_x = math.cos(ship.angle) * missile_velocity
|
|
|
|
# create new missile
|
|
missile = Poly(
|
|
missile_poly,
|
|
x=ship.x,
|
|
y=ship.y,
|
|
v_x=v_x,
|
|
v_y=v_y,
|
|
angle=ship.angle,
|
|
radius=1,
|
|
spin=0.0,
|
|
counter=missile_life)
|
|
|
|
# add to to missile list and save last fire time
|
|
missiles.append(missile)
|
|
missile_last = last_frame
|
|
|
|
update_ship()
|
|
|
|
else:
|
|
# explosion animation until returns True
|
|
ship_alive = explode_ship()
|
|
|
|
# update roids and return if ship was not hit
|
|
not_hit = update_roids()
|
|
if ship_alive:
|
|
ship_alive = not_hit
|
|
|
|
# wait until frame time expires
|
|
while utime.ticks_ms() - last_frame < frame_time:
|
|
pass
|
|
|
|
|
|
main()
|