本次项目为pygame游戏开发,使用pycharm来进行开发。本项目命名:迷航。
游戏设定:设定一架简略图标飞机。只可前进,和进攻,前进的方向可以由鼠标或者键盘的左右方向键控制,攻击命令可由空格或者单击鼠标左键完成,一次一发子弹。边缘会不停的刷新陨石,偶尔会刷新外星飞船。注:大陨石被子弹击中时会分散成多个小的陨石。
以下为所需的土图片素材
1:开始场景
2:游戏所需场景
3.音效素材省略
项目代码如下
1.alien.py(生成外星目标)
import os
import math
import random
import pygame
from pygame import mixer
import util
import sprite
import bullet
import ship
class Alien(sprite.Sprite):
def __init__(self, world):
super(Alien, self).__init__(world)
self.points = [[1, 0], [-1, 0], [-0.7, 0],
[-0.5, 0.2], [0.5, 0.2],
[0.7, 0],
[0.5, -0.4], [-0.5, -0.4],
[-0.7, 0]]
self.direction = random.randint(1, 2) * 2 - 3
self.position = [world.width / 2 - self.direction * world.width / 2,
random.randint(0, world.height)]
self.angle = 0
self.scale = 10
self.direction_timer = random.randint(10, 50)
self.random_velocity()
self.alien_sound = mixer.Sound(os.path.join("sounds", "alien_engine.ogg"))
self.alien_channel = pygame.mixer.Channel(3)
self.alien_channel.play(self.alien_sound, loops = -1)
def random_velocity(self):
self.velocity = [self.direction * (random.random() * 2 + 1),
random.random() * 6 - 3]
def update(self):
self.direction_timer -= 1
if self.direction_timer < 0:
self.direction_timer = random.randint(10, 50)
self.random_velocity()
if self.angle > 0:
self.angle -= 1
elif self.angle < 0:
self.angle += 1
if self.direction == 1 and self.position[0] > self.world.width - 10:
self.kill = True
elif self.direction == -1 and self.position[0] < 10:
self.kill = True
if self.kill:
self.alien_channel.fadeout(500)
super(Alien, self).update()
def impact(self, other):
self.angle = random.randint(-90, 90)
super(Alien, self).impact(other)
2:asteroid.py(生成陨石)
import pygame
import math
import random
import util
import sprite
import bullet
import ship
import alien
class Asteroid(sprite.Sprite):
def __init__(self, world, scale, max_speed):
super(Asteroid, self).__init__(world)
world.n_asteroids += 1
# spawn on a screen edge
if random.randint(0, 1) == 0:
x = random.randint(0, world.width)
y = random.randint(0, 1) * world.height
else:
x = random.randint(0, 1) * world.width
y = random.randint(0, world.height)
self.position = [x, y]
n_points = random.randint(5, 10)
self.points = []
for i in range(n_points):
angle = i * 360 / n_points + random.randint(-20, 20)
distance = random.random() / 4.0 + 0.75
self.points.append([distance * util.cos(angle),
distance * util.sin(angle)])
self.velocity = [random.random() * max_speed * 2 - max_speed,
random.random() * max_speed * 2 - max_speed]
self.angle = 0
self.scale = scale
self.angular_velocity = random.random() * 4 - 2
def update(self):
self.angle += self.angular_velocity
super(Asteroid, self).update()
def collide(self, other):
if isinstance(other, Asteroid):
# exchange spins
x = other.angular_velocity
other.angular_velocity = self.angular_velocity
self.angular_velocity = x
# calculate point of impact for sparks
dx = self.position[0] - other.position[0]
dy = self.position[1] - other.position[1]
d2 = dx * dx + dy * dy
d = math.sqrt(d2)
if d == 0:
d = 0.0001
u = dx / d
v = dy / d
impact = [other.position[0] + u * other.scale,
other.position[1] + v * other.scale]
self.world.particle.sparks(impact, other.velocity)
super(Asteroid, self).collide(other)
3:bullet.py(发射的子弹)
import pygame
import math
import random
import util
import sprite
import asteroid
import alien
class Bullet(sprite.Sprite):
def __init__(self, world):
super(Bullet, self).__init__(world)
self.points = [[1, 0],
[util.cos(120), util.sin(120)],
[util.cos(240), util.sin(240)]]
self.scale = 2
self.life = 100
self.angle = 0
def update(self):
super(Bullet, self).update()
self.life -= 1
if self.life == 0:
self.kill = True
def collision(self, other):
# don't chain up, we don't want to exchange velocity or anything like
# that
pass
def impact(self, other):
if isinstance(other, alien.Alien):
other.kill = True
self.kill = True
self.world.score += 1000
self.world.particle.explosion(20,
other.position, other.velocity)
elif isinstance(other, asteroid.Asteroid):
other.kill = True
self.kill = True
self.world.score += other.scale
self.world.n_asteroids -= 1
self.world.particle.explosion(other.scale / 3,
other.position, other.velocity)
if other.scale > 15:
n = random.randint(2, max(2, min(5, int(other.scale / 5))))
for i in range(n):
new_asteroid = asteroid.Asteroid(self.world,
other.scale / n, 1)
new_asteroid.position[0] = other.position[0]
new_asteroid.position[1] = other.position[1]
new_asteroid.velocity[0] += other.velocity[0]
new_asteroid.velocity[1] += other.velocity[1]
4:particle.py(微粒特效)
import os
import random
import pygame
from pygame import mixer
import util
# stolen from a PET scanner
colour_table = [[ 15, 0, 30 ],
[ 19, 0, 40 ],
[ 23, 0, 48 ],
[ 28, 0, 57 ],
[ 36, 0, 74 ],
[ 42, 0, 84 ],
[ 46, 0, 93 ],
[ 51, 0, 102 ],
[ 59, 0, 118 ],
[ 65, 0, 130 ],
[ 69, 0, 138 ],
[ 72, 0, 146 ],
[ 81, 0, 163 ],
[ 47, 0, 95 ],
[ 12, 0, 28 ],
[ 64, 0, 144 ],
[ 61, 0, 146 ],
[ 55, 0, 140 ],
[ 52, 0, 137 ],
[ 47, 0, 132 ],
[ 43, 0, 128 ],
[ 38, 0, 123 ],
[ 30, 0, 115 ],
[ 26, 0, 111 ],
[ 23, 0, 108 ],
[ 17, 0, 102 ],
[ 9, 0, 94 ],
[ 6, 0, 91 ],
[ 2, 0, 87 ],
[ 0, 0, 88 ],
[ 0, 0, 100 ],
[ 0, 0, 104 ],
[ 0, 0, 108 ],
[ 0, 0, 113 ],
[ 0, 0, 121 ],
[ 0, 0, 125 ],
[ 0, 0, 129 ],
[ 0, 0, 133 ],
[ 0, 0, 141 ],
[ 0, 0, 146 ],
[ 0, 0, 150 ],
[ 0, 0, 155 ],
[ 0, 0, 162 ],
[ 0, 0, 167 ],
[ 0, 0, 173 ],
[ 0, 0, 180 ],
[ 0, 0, 188 ],
[ 0, 0, 193 ],
[ 0, 0, 197 ],
[ 0, 0, 201 ],
[ 0, 0, 209 ],
[ 0, 0, 214 ],
[ 0, 0, 218 ],
[ 0, 0, 222 ],
[ 0, 0, 230 ],
[ 0, 0, 235 ],
[ 0, 0, 239 ],
[ 0, 0, 243 ],
[ 0, 0, 247 ],
[ 0, 4, 251 ],
[ 0, 10, 255 ],
[ 0, 14, 255 ],
[ 0, 18, 255 ],
[ 0, 24, 255 ],
[ 0, 31, 255 ],
[ 0, 36, 255 ],
[ 0, 39, 255 ],
[ 0, 45, 255 ],
[ 0, 53, 255 ],
[ 0, 56, 255 ],
[ 0, 60, 255 ],
[ 0, 66, 255 ],
[ 0, 74, 255 ],
[ 0, 77, 255 ],
[ 0, 81, 255 ],
[ 0, 88, 251 ],
[ 0, 99, 239 ],
[ 0, 104, 234 ],
[ 0, 108, 230 ],
[ 0, 113, 225 ],
[ 0, 120, 218 ],
[ 0, 125, 213 ],
[ 0, 128, 210 ],
[ 0, 133, 205 ],
[ 0, 141, 197 ],
[ 0, 145, 193 ],
[ 0, 150, 188 ],
[ 0, 154, 184 ],
[ 0, 162, 176 ],
[ 0, 167, 172 ],
[ 0, 172, 170 ],
[ 0, 180, 170 ],
[ 0, 188, 170 ],
[ 0, 193, 170 ],
[ 0, 197, 170 ],
[ 0, 201, 170 ],
[ 0, 205, 170 ],
[ 0, 211, 170 ],
[ 0, 218, 170 ],
[ 0, 222, 170 ],
[ 0, 226, 170 ],
[ 0, 232, 170 ],
[ 0, 239, 170 ],
[ 0, 243, 170 ],
[ 0, 247, 170 ],
[ 0, 251, 161 ],
[ 0, 255, 147 ],
[ 0, 255, 139 ],
[ 0, 255, 131 ],
[ 0, 255, 120 ],
[ 0, 255, 105 ],
[ 0, 255, 97 ],
[ 0, 255, 89 ],
[ 0, 255, 78 ],
[ 0, 255, 63 ],
[ 0, 255, 55 ],
[ 0, 255, 47 ],
[ 0, 255, 37 ],
[ 0, 255, 21 ],
[ 0, 255, 13 ],
[ 0, 255, 5 ],
[ 2, 255, 2 ],
[ 13, 255, 13 ],
[ 18, 255, 18 ],
[ 23, 255, 23 ],
[ 27, 255, 27 ],
[ 35, 255, 35 ],
[ 40, 255, 40 ],
[ 43, 255, 43 ],
[ 48, 255, 48 ],
[ 55, 255, 55 ],
[ 60, 255, 60 ],
[ 64, 255, 64 ],
[ 69, 255, 69 ],
[ 72, 255, 72 ],
[ 79, 255, 79 ],
[ 90, 255, 82 ],
[ 106, 255, 74 ],
[ 113, 255, 70 ],
[ 126, 255, 63 ],
[ 140, 255, 56 ],
[ 147, 255, 53 ],
[ 155, 255, 48 ],
[ 168, 255, 42 ],
[ 181, 255, 36 ],
[ 189, 255, 31 ],
[ 197, 255, 27 ],
[ 209, 255, 21 ],
[ 224, 255, 14 ],
[ 231, 255, 10 ],
[ 239, 255, 7 ],
[ 247, 251, 3 ],
[ 255, 243, 0 ],
[ 255, 239, 0 ],
[ 255, 235, 0 ],
[ 255, 230, 0 ],
[ 255, 222, 0 ],
[ 255, 218, 0 ],
[ 255, 214, 0 ],
[ 255, 209, 0 ],
[ 255, 201, 0 ],
[ 255, 197, 0 ],
[ 255, 193, 0 ],
[ 255, 188, 0 ],
[ 255, 180, 0 ],
[ 255, 176, 0 ],
[ 255, 172, 0 ],
[ 255, 167, 0 ],
[ 255, 156, 0 ],
[ 255, 150, 0 ],
[ 255, 146, 0 ],
[ 255, 142, 0 ],
[ 255, 138, 0 ],
[ 255, 131, 0 ],
[ 255, 125, 0 ],
[ 255, 121, 0 ],
[ 255, 117, 0 ],
[ 255, 110, 0 ],
[ 255, 104, 0 ],
[ 255, 100, 0 ],
[ 255, 96, 0 ],
[ 255, 90, 0 ],
[ 255, 83, 0 ],
[ 255, 78, 0 ],
[ 255, 75, 0 ],
[ 255, 71, 0 ],
[ 255, 67, 0 ],
[ 255, 65, 0 ],
[ 255, 63, 0 ],
[ 255, 59, 0 ],
[ 255, 54, 0 ],
[ 255, 52, 0 ],
[ 255, 50, 0 ],
[ 255, 46, 0 ],
[ 255, 41, 0 ],
[ 255, 39, 0 ],
[ 255, 36, 0 ],
[ 255, 32, 0 ],
[ 255, 25, 0 ],
[ 255, 22, 0 ],
[ 255, 20, 0 ],
[ 255, 17, 0 ],
[ 255, 13, 0 ],
[ 255, 10, 0 ],
[ 255, 7, 0 ],
[ 255, 4, 0 ],
[ 255, 0, 0 ],
[ 252, 0, 0 ],
[ 251, 0, 0 ],
[ 249, 0, 0 ],
[ 248, 0, 0 ],
[ 244, 0, 0 ],
[ 242, 0, 0 ],
[ 240, 0, 0 ],
[ 237, 0, 0 ],
[ 234, 0, 0 ],
[ 231, 0, 0 ],
[ 229, 0, 0 ],
[ 228, 0, 0 ],
[ 225, 0, 0 ],
[ 222, 0, 0 ],
[ 221, 0, 0 ],
[ 219, 0, 0 ],
[ 216, 0, 0 ],
[ 213, 0, 0 ],
[ 212, 0, 0 ],
[ 210, 0, 0 ],
[ 207, 0, 0 ],
[ 204, 0, 0 ],
[ 201, 0, 0 ],
[ 199, 0, 0 ],
[ 196, 0, 0 ],
[ 193, 0, 0 ],
[ 192, 0, 0 ],
[ 190, 0, 0 ],
[ 188, 0, 0 ],
[ 184, 0, 0 ],
[ 183, 0, 0 ],
[ 181, 0, 0 ],
[ 179, 0, 0 ],
[ 175, 0, 0 ]]
n_colour = len(colour_table)
class Particle(object):
def __init__(self, surface):
self.surface = surface
self.width = surface.get_width()
self.height = surface.get_height()
self.show_particles = True
# a row of this hash stores:
#
# 0 - life .. down counter until death
# 1 - x
# 2 - y
# 3 - u horizontal component of velocity
# 4 - v vertical component of velocity
# 5 - colour index ... int index into colour array above
# 6 - colour delta ... add this to index each update
self.particles = {}
self.index = 0
self.explosion1_sound = mixer.Sound(os.path.join("sounds", "planet_crush.ogg"))
self.explosion2_sound = mixer.Sound(os.path.join("sounds", "ship_crush.ogg"))
def show(self, show_particles):
self.show_particles = show_particles
def add(self, position, velocity, colour, colour_delta, life):
if not self.show_particles:
return
particle = [life,
position[0], position[1],
velocity[0], velocity[1],
colour, colour_delta]
self.particles[self.index] = particle
self.index += 1
def n_particles(self):
return len(self.particles)
def remove_all(self):
self.particles = {}
def explosion(self, n_points, position, velocity):
self.explosion1_sound.play()
for i in range(int(n_points)):
delta = 360 / n_points
angle = i * delta + random.randint(int(-delta / 2), int(delta / 2))
speed = random.random() * 2.0
self.add(position,
[velocity[0] + speed * util.cos(angle),
velocity[1] + speed * util.sin(angle)],
n_colour - random.randint(1, 50),
-1,
random.randint(50, 100))
def explosion2(self, n_points, position, velocity):
self.explosion2_sound.play()
for i in range(int(n_points)):
delta = 360.0 / n_points
angle = i * delta + random.randint(int(-delta), int(delta))
speed = random.random() * 4.0
self.add(position,
[velocity[0] + speed * util.cos(angle),
velocity[1] + speed * util.sin(angle)],
n_colour - random.randint(1, 50),
-1,
random.randint(50, 300))
def sparks(self, position, velocity):
n_points = 3
delta = 360 / n_points
for i in range(int(n_points)):
angle = i * delta + random.randint(-delta / 2, delta / 2)
speed = random.random() * 2.0
self.add(position,
[velocity[0] + speed * util.cos(angle),
velocity[1] + speed * util.sin(angle)],
n_colour - random.randint(1, 200),
-113,
random.randint(50, 100))
def jet(self, position, velocity, angle):
angle = angle + random.randint(-10, 10) + 180
u = 2 * util.cos(angle)
v = 2 * util.sin(angle)
self.add([position[0] + 3 * u, position[1] + 3 * v],
[velocity[0] + u, velocity[1] + v],
random.randint(50, 200),
random.randint(20, 200),
random.randint(20, 30))
def starfield(self):
for i in range(30):
self.add([random.randint(0, self.width),
random.randint(0, self.height)],
[0, 0],
random.randint(50, 200),
random.randint(1, 3),
100000000)
def update(self):
if not self.show_particles:
return
keys = list(self.particles.keys())
for i in keys:
part = self.particles[i]
if part[0] > 0:
part[0] -= 1
if part[0] == 0:
del self.particles[i]
continue
part[1] += part[3]
part[2] += part[4]
part[1] %= self.width
part[2] %= self.height
part[5] += part[6]
part[5] %= n_colour
def draw(self):
if not self.show_particles:
return
for i in self.particles:
part = self.particles[i]
rect = [part[1], part[2], 3, 3]
pygame.draw.rect(self.surface, colour_table[part[5]], rect)
5:ship.py(飞船)
import os
import math
import random
import pygame
from pygame import mixer
import util
import sprite
import bullet
import alien
import asteroid
# default shield behavior
# 0 is old regenerating shield behavior, 1 is manual shields
SHIELDMODE = 0
class Ship(sprite.Sprite):
def __init__(self, world):
super(Ship, self).__init__(world)
self.position = [world.width / 2,
world.height / 2]
self.points = [[1, 0],
[util.cos(140), util.sin(140)],
[-0.3, 0],
[util.cos(220), util.sin(220)]]
self.shield_points = []
for i in range(5):
self.shield_points.append([util.cos(i * 360 / 5 - 15),
util.sin(i * 360 / 5 - 15)])
self.shield_points.append([util.cos(i * 360 / 5 + 15),
util.sin(i * 360 / 5 + 15)])
self.velocity = [0, 0]
self.angle = 0
self.scale = 5
self.reload_timer = 0
self.regenerate_timer = 0
self.max_shields = 3
self.shields = self.max_shields
self.shield_tick = 0
self.jet_tick = 0
self.shield_timer = 0
self.shield_mode = SHIELDMODE
self.engines = False
self.fire_sound = mixer.Sound(os.path.join("sounds", "shot.ogg"))
self.engine_sound = mixer.Sound(os.path.join("sounds", "engine.ogg"))
self.fire_channel = pygame.mixer.Channel(2)
self.engine_channel = pygame.mixer.Channel(1)
def rotate_by(self, angle):
self.angle += angle
self.angle %= 360
# power is a bool for thrust on or off
def thrust(self, power):
# engine state changed?
if power != self.engines:
if power:
self.engine_channel.play(self.engine_sound, loops = -1)
else:
self.engine_channel.fadeout(500)
self.engines = power
if power:
u = 0.1 * util.cos(self.angle)
v = 0.1 * util.sin(self.angle)
self.velocity = [self.velocity[0] + u, self.velocity[1] + v]
self.jet_tick -= 1
if self.jet_tick < 0:
self.jet_tick = 3
self.world.particle.jet(self.position, self.velocity, self.angle)
def fire(self):
if self.reload_timer == 0:
self.fire_channel.stop()
self.fire_channel.play(self.fire_sound)
a = util.cos(self.angle)
b = util.sin(self.angle)
projectile = bullet.Bullet(self.world)
projectile.position = [self.position[0] + self.scale * a,
self.position[1] + self.scale * b]
projectile.velocity = [a * 7.0 + self.velocity[0],
b * 7.0 + self.velocity[1]]
projectile.angle = self.angle
self.reload_timer = 10
def update(self):
self.reload_timer = max(0, self.reload_timer - 1)
self.shield_tick += 1
if self.shield_mode == 0:
self.regenerate_timer = max(0, self.regenerate_timer - 1)
if self.regenerate_timer == 0 and self.shields < self.max_shields:
self.regenerate_timer = 500
self.shields += 1
elif self.shield_mode == 1:
self.shield_timer = max(0,self.shield_timer - 1)
if self.shield_timer < 1 and self.shields > 0:
self.shields -= 3
if self.kill:
self.engine_channel.fadeout(500)
super(Ship, self).update()
# used for manual shields
def shield_on(self):
if self.shield_mode == 1:
if self.regenerate_timer == 0 and self.shields < self.max_shields:
# change shield regeneration time below
# actually regenerate time is the difference
# between "regenerate_timer" and "shield_timer"
self.regenerate_timer = 1000
self.shields += 3
if self.shields > 0:
self.shield_timer = 500 #change time shield is on here
def impact(self, other):
if isinstance(other, alien.Alien) or isinstance(other, asteroid.Asteroid):
self.world.particle.sparks(self.position, self.velocity)
self.shields -= 1
self.regenerate_timer = 1000
if self.shields < 0:
self.kill = True
self.world.particle.explosion2(300,
self.position, self.velocity)
super(Ship, self).impact(other)
def draw(self):
super(Ship, self).draw()
for i in range(max(0, self.shields)):
radius = int(self.scale + 5 + 4 * i)
angle = ((i & 1) * 2 - 1) * self.shield_tick
a = radius * util.cos(angle)
b = radius * -util.sin(angle)
c = -b
d = a
screen_points = [[int(a * x + b * y + self.position[0]),
int(c * x + d * y + self.position[1])]
for x, y in self.shield_points]
for i in range(0, len(screen_points), 2):
pygame.draw.line(self.world.surface, util.WHITE,
screen_points[i],
screen_points[i + 1])
6:sprite.py
import pygame
import math
import util
class Sprite(object):
def __init__(self, world):
self.world = world
self.position = [0, 0]
self.velocity = [0, 0]
self.points = []
self.kill = False
self.tested_collision = False
self.continuous = True
self.scale = 10
self.angle = 0
world.add(self)
def test_collisions(self, possible_sprites):
width = self.world.width
height = self.world.height
for other in possible_sprites:
if other == self:
continue
if other.tested_collision:
continue
dx = self.position[0] - other.position[0]
dy = self.position[1] - other.position[1]
# we need to do wrap-around testing
#
# we know that possible_sprites is only other sprites in the
# immediate neighbourhood, therefore if dx > half screen width,
# then this and other must be on opposite sides of the screen and
# must be possibly colliding via warp-around
#
# in this case, notionally move down by a screen width
if dx > width / 2:
dx -= width
elif dx < -width / 2:
dx += width
if dy > height / 2:
dy -= height
elif dy < -height / 2:
dy += height
d2 = dx * dx + dy * dy
t = self.scale + other.scale
t2 = t * t
if d2 > t2:
continue
# unit vector
d = math.sqrt(d2)
if d == 0:
d = 0.0001
u = dx / d
v = dy / d
# amount of overlap
overlap = d - t
# displace by overlap in that direction
other.position[0] += u * overlap
other.position[0] %= width
other.position[1] += v * overlap
other.position[1] %= height
# tell the objects they have collided ... both objects need to be
# told
self.impact(other)
other.impact(self)
# don't do the physics if either object is now dead
if not self.kill and not other.kill:
self.collide(other)
break
# this method is triggered for both objects involved in the collision ... do
# asymmetric things like bullets blowing up aliens
def impact(self, other):
pass
# this is triggered just once, so symmetric things happen in this, like
# physics
def collide(self, other):
x = other.velocity[0]
other.velocity[0] = self.velocity[0]
self.velocity[0] = x
x = other.velocity[1]
other.velocity[1] = self.velocity[1]
self.velocity[1] = x
def update(self):
self.position = [self.position[0] + self.velocity[0],
self.position[1] + self.velocity[1]]
self.position[0] %= self.world.width
self.position[1] %= self.world.height
def draw(self):
a = self.scale * util.cos(self.angle)
b = self.scale * -util.sin(self.angle)
c = -b
d = a
screen_points = [[int(a * x + b * y + self.position[0]),
int(c * x + d * y + self.position[1])]
for x, y in self.points]
if self.continuous:
pygame.draw.lines(self.world.surface, util.WHITE, True,
screen_points)
else:
for i in range(0, len(screen_points), 2):
pygame.draw.line(self.world.surface, util.WHITE,
screen_points[i],
screen_points[i + 1])
7:text.py
import pygame
import math
import random
import util
import sprite
import alien
# character designs from https://github.com/rickwight/meteors, thank you rick
# oh god why
char_points = {
'0': [[-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.25], [-0.25, 0.5], [0.25, -0.5]],
'1': [[0.5, 0.25], [-0.0, 0.5], [-0.0, 0.5], [-0.0, -0.5], [0.5, -0.5], [-0.5, -0.5]],
'2': [[-0.5, -0.5], [0.5, -0.5], [0.5, -0.5], [-0.25, 0.0], [-0.25, 0.0], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25]],
'3': [[0.5, 0.25], [0.25, 0.5], [0.25, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.0], [-0.25, 0.0], [-0.5, -0.25], [-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.25, -0.5], [0.25, -0.5], [0.5, -0.25], [0.25, 0.0], [-0.25, 0.0]],
'4': [[-0.25, -0.5], [-0.25, 0.5], [-0.25, 0.5], [0.5, -0.25], [0.5, -0.25], [-0.5, -0.25]],
'5': [[-0.5, 0.5], [0.5, 0.5], [0.5, 0.5], [0.5, 0.0], [0.5, 0.0], [-0.25, 0.0], [-0.25, 0.0], [-0.5, -0.25], [-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.5, -0.5]],
'6': [[-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.25, 0.0], [-0.25, 0.0], [0.5, 0.0]],
'7': [[0.5, 0.5], [-0.5, 0.5], [-0.5, 0.5], [0.25, -0.5]],
'8': [[0.5, 0.25], [0.25, 0.5], [0.25, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.0], [-0.25, 0.0], [-0.5, -0.25], [-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.25, -0.5], [0.25, -0.5], [0.5, -0.25], [0.5, -0.25], [0.25, 0.0], [0.25, 0.0], [0.5, 0.25], [0.25, 0.0], [-0.25, 0.0]],
'9': [[0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.25, 0.0], [0.25, 0.0], [-0.5, 0.0]],
'A': [[0.5, -0.5], [-0.0, 0.5], [-0.0, 0.5], [-0.5, -0.5], [0.25, 0.0], [-0.25, 0.0]],
'B': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.0], [-0.25, 0.0], [-0.5, -0.25], [-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.5, -0.5], [0.5, 0.0], [-0.25, 0.0]],
'C': [[-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.25, -0.5], [0.25, -0.5], [0.5, -0.25], [0.5, -0.25], [0.5, 0.25], [0.5, 0.25], [0.25, 0.5], [0.25, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25]],
'D': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.5, -0.25], [-0.5, -0.25], [-0.25, -0.5], [-0.25, -0.5], [0.5, -0.5]],
'E': [[0.5, 0.5], [-0.5, 0.5], [0.5, 0.0], [-0.25, 0.0], [0.5, -0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]],
'F': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.5, 0.5], [0.5, 0.0], [-0.25, 0.0]],
'G': [[-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.0], [-0.5, 0.0], [-0.0, 0.0]],
'H': [[0.5, -0.5], [0.5, 0.5], [-0.5, -0.5], [-0.5, 0.5], [0.5, 0.0], [-0.5, 0.0]],
'I': [[0.25, 0.5], [-0.25, 0.5], [0.25, -0.5], [-0.25, -0.5], [-0.0, -0.5], [-0.0, 0.5]],
'J': [[0.5, -0.5], [-0.0, -0.5], [-0.0, -0.5], [-0.0, 0.5], [0.5, 0.5], [-0.5, 0.5]],
'K': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.0], [-0.5, 0.5], [0.5, 0.0], [-0.5, -0.5]],
'L': [[0.5, -0.5], [0.5, 0.5], [0.5, -0.5], [-0.5, -0.5]],
'M': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.0, 0.0], [-0.0, 0.0], [-0.5, 0.5], [-0.5, 0.5], [-0.5, -0.5]],
'N': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.5, -0.5], [-0.5, -0.5], [-0.5, 0.5]],
'O': [[-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.25]],
'P': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.0], [-0.25, 0.0], [0.5, 0.0]],
'Q': [[-0.5, 0.25], [-0.25, 0.5], [-0.25, 0.5], [0.25, 0.5], [0.25, 0.5], [0.5, 0.25], [0.5, 0.25], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.25], [-0.25, -0.25], [-0.5, -0.5]],
'R': [[0.5, -0.5], [0.5, 0.5], [0.5, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25], [-0.5, 0.25], [-0.25, 0.0], [-0.25, 0.0], [0.5, 0.0], [-0.25, 0.0], [-0.5, -0.5]],
'S': [[0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.25, 0.0], [-0.25, 0.0], [0.25, 0.0], [0.25, 0.0], [0.5, 0.25], [0.5, 0.25], [0.25, 0.5], [0.25, 0.5], [-0.25, 0.5], [-0.25, 0.5], [-0.5, 0.25]],
'T': [[0.5, 0.5], [-0.5, 0.5], [-0.0, 0.5], [-0.0, -0.5]],
'U': [[0.5, 0.5], [0.5, -0.25], [0.5, -0.25], [0.25, -0.5], [0.25, -0.5], [-0.25, -0.5], [-0.25, -0.5], [-0.5, -0.25], [-0.5, -0.25], [-0.5, 0.5]],
'V': [[0.5, 0.5], [-0.0, -0.5], [-0.0, -0.5], [-0.5, 0.5]],
'W': [[0.5, 0.5], [0.5, -0.5], [0.5, -0.5], [-0.0, 0.0], [-0.0, 0.0], [-0.5, -0.5], [-0.5, -0.5], [-0.5, 0.5]],
'X': [[0.5, -0.5], [-0.5, 0.5], [-0.5, -0.5], [0.5, 0.5]],
'Y': [[0.5, 0.5], [-0.0, 0.0], [-0.0, 0.0], [-0.5, 0.5], [-0.0, 0.0], [-0.0, -0.5]],
'Z': [[0.5, 0.5], [-0.5, 0.5], [-0.5, 0.5], [0.5, -0.5], [0.3, -0.5], [-0.5, -0.5]],
'-': [[0.3, 0.0], [-0.3, 0.0]],
'm': [[0.5, -0.8], [0.1, -0.4], [0.1, -0.4], [0.5, -0.4],
[0.5, -0.8], [0.5, 0.4], [0.5, 0.4], [-0.5, 0.6],
[0.5, 0.3], [-0.5, 0.5], [-0.5, 0.6], [-0.5, -0.6],
[-0.5, -0.6], [-0.9, -0.2], [-0.9, -0.2], [-0.5, -0.2]]
}
class Character(sprite.Sprite):
# drawable text object
def __init__(self, world, character, position, scale = 20):
super(Character, self).__init__(world)
self.scale = scale
self.angle = 180
self.position = position
self.character = character
self.points = char_points[character]
self.angular_velocity = 0
self.continuous = False
def impact(self, other):
self.angular_velocity = random.random() * 2 - 1
super(Character, self).impact(other)
def update(self):
if abs(self.angular_velocity) > 0.01:
self.angle += self.angular_velocity
super(Character, self).update()
@classmethod
def string(cls, world, string, position, scale = 20):
kern = 2.5
x = position[0] - len(string) * scale * kern / 2.0
y = position[1]
for ch in string:
if ch in char_points:
pos = [x, y]
Character(world, ch, pos, scale)
x += scale * kern
def draw_string(surface, string, colour, scale, position,
centre = False, angle = 0):
kern = 2.5
x = position[0]
y = position[1]
a = scale * util.cos(angle)
b = scale * -util.sin(angle)
c = -b
d = a
if centre:
x -= a * kern * len(string) / 2.0
y -= c * kern * len(string) / 2.0
for ch in string:
if ch in char_points:
screen = [[int(-a * u - b * v + x),
int(-c * u - d * v + y)]
for u, v in char_points[ch]]
for i in range(0, len(screen), 2):
pygame.draw.line(surface, util.WHITE, screen[i], screen[i + 1])
x += a * kern
y += c * kern
8:util.py
import math
# Colors
BLACK = ( 0, 0, 0)
WHITE = ( 255, 255, 255)
BLUE = ( 0, 0, 128)
GREEN = ( 0, 128, 0)
RED = ( 128, 0, 0)
DEG2RAD = 2.0 * math.pi / 360.0
def rad(angle):
return angle * DEG2RAD
# maybe faster on some systems with weak FP hardware?
cos_table = [0] * 360
for i in range(360):
cos_table[i] = math.cos(rad(i))
def cos(angle):
try:
return cos_table[int(angle % 360)]
except:
print("util.cos() of", angle)
def sin(angle):
try:
return cos_table[int((angle - 90) % 360)]
except:
print("util.sin() of", angle)
8:world.py
import os
import math
import random
import sys
import pygame
from pygame import mixer
import util
import sprite
import particle
import text
import alien
import ship
import asteroid
class World(object):
def __init__(self, surface):
self.surface = surface
self.width = surface.get_width()
self.height = surface.get_height()
self.sprites = []
self.particle = particle.Particle(surface)
self.score = 0
self.n_asteroids = 0
self.text_y = 100
# input state
self.quit = False
self.rotate_left = False
self.rotate_right = False
self.rotate_by = 0
self.thrust = False
self.info = False
self.fire = False
self.spawn = False
self.show_particles = True
self.enter = False
self.next_level = False
# the ship ... or none for no ship on screen
self.player = None
# countdown timer until next alien
self.alien_time = random.randint(1000, 2000)
self.music_playing = True
self.background_music = mixer.Sound(os.path.join("sounds", "hoarse_space_cadet.ogg"))
self.background_channel = pygame.mixer.Channel(0)
self.background_channel.play(self.background_music, loops = -1)
def n_objects(self):
return len(self.sprites)
def play_music(self, play):
if play:
self.background_channel.unpause()
else:
self.background_channel.pause()
def reset(self):
self.sprites = []
self.n_asteroids = 0
self.particle.remove_all()
self.text_y = 100
self.score = 0
self.player = None
def remove_asteroids(self):
self.sprites = [x for x in self.sprites
if not isinstance(x, asteroid.Asteroid)]
def add(self, sprite):
self.sprites.append(sprite)
def add_player(self):
if not self.player:
self.player = ship.Ship(self)
def add_text(self, string, scale = 10):
text.Character.string(self, string,
[self.width / 2, self.text_y], scale)
self.text_y += scale * 5
def update(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.quit = True
if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
self.quit = event.type == pygame.KEYDOWN
elif event.key == pygame.K_LEFT:
self.rotate_left = event.type == pygame.KEYDOWN
elif event.key == pygame.K_RIGHT:
self.rotate_right = event.type == pygame.KEYDOWN
elif event.key == pygame.K_UP:
self.thrust = event.type == pygame.KEYDOWN
elif event.key == pygame.K_SPACE:
self.fire = event.type == pygame.KEYDOWN
elif event.key == pygame.K_s:
self.spawn = event.type == pygame.KEYDOWN
elif event.key == pygame.K_n:
self.next_level = event.type == pygame.KEYDOWN
elif event.key == pygame.K_m and event.type == pygame.KEYDOWN:
self.music_playing = not self.music_playing
self.play_music(self.music_playing)
elif event.key == pygame.K_p:
if event.type == pygame.KEYDOWN:
self.show_particles = not self.show_particles
elif event.key == pygame.K_i:
self.info = event.type == pygame.KEYDOWN
elif event.key == pygame.K_RETURN:
self.enter = event.type == pygame.KEYDOWN
elif event.key == pygame.K_f:
if self.player:
self.player.shield_on()
elif event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP:
if event.button == 3:
self.thrust = event.type == pygame.MOUSEBUTTONDOWN
elif event.button == 1:
self.fire = event.type == pygame.MOUSEBUTTONDOWN
self.particle.show(self.show_particles)
x, y = pygame.mouse.get_rel()
self.rotate_by = x / 5.0
if self.rotate_left:
self.rotate_by = -3
elif self.rotate_right:
self.rotate_by = 3
if self.player:
self.player.thrust(self.thrust)
if self.fire:
self.player.fire();
self.player.rotate_by(self.rotate_by)
for i in self.sprites:
i.update()
self.particle.update()
self.alien_time -= 1
if self.alien_time < 0:
self.alien_time = random.randint(1000, 2000)
alien.Alien(self)
if self.player and self.player.kill:
self.player = None
self.sprites = [x for x in self.sprites if not x.kill]
# split the world into a map of 100x100 squares, put a note in each
# square of each sprite which intersects with that square ... then when
# we test for collisions, we only need to test against the sprites in
# that map square
# 100 is the max radius of the asteroids we make
map_spacing = 100
map_width = int(math.ceil(float(self.width) / map_spacing))
map_height = int(math.ceil(float(self.height) / map_spacing))
world_map = []
for x in range(map_width):
map_row = [[] for y in range(map_height)]
world_map.append(map_row)
for i in self.sprites:
i.tested_collision = False
x = int(i.position[0] / map_spacing) % map_width
y = int(i.position[1] / map_spacing) % map_height
for a in range(x - 1, x + 2):
for b in range(y - 1, y + 2):
world_map[a % map_width][b % map_height].append(i)
for i in self.sprites:
x = int(i.position[0] / map_spacing) % map_width
y = int(i.position[1] / map_spacing) % map_height
i.test_collisions(world_map[x][y])
# now we've tested i against everything it could possibly touch,
# we no longer need to test anything against i
i.tested_collision = True
def draw(self):
self.particle.draw()
for i in self.sprites:
i.draw()
9:mian.py
import os
import random
import math
import pygame
from pygame import mixer
import util
import asteroid
import text
import world
import ship
class Game(object):
def __init__(self, surface):
self.surface = surface
self.world = world.World(surface)
self.width = self.world.width
self.height = self.world.height
self.clock = pygame.time.Clock()
self.level = 1
def draw_hud(self):
text.draw_string(self.surface, "SCORE %d" % self.world.score,
util.WHITE, 10, [10, 20])
text.draw_string(self.surface, "LEVEL %d" % self.level,
util.WHITE, 10, [10, 40])
def start_screen(self):
self.world.add_text('ARGH ITS THE ASTEROIDS', scale = 20)
self.world.add_text('PRESS ESC TO QUIT')
self.world.add_text('PRESS LEFT AND RIGHT TO ROTATE')
self.world.add_text('PRESS UP FOR THRUST')
self.world.add_text('PRESS SPACE FOR FIRE')
self.world.add_text('PRESS M TO TURN MUSIC ON OR OFF')
self.world.add_text('OR USE MOUSE CONTROLS')
self.world.add_text('WATCH OUT FOR ALLEN THE ALIEN')
self.world.add_text('PRESS ENTER TO START', scale = 20)
for i in range(4):
asteroid.Asteroid(self.world, random.randint(50, 100), 1)
self.world.particle.starfield()
while not self.world.quit and not self.world.enter:
self.world.update()
self.surface.fill(util.BLACK)
self.draw_info()
self.world.draw()
# set the limit very high, we can use the start screen as a
# benchmark
self.clock.tick(200)
pygame.display.flip()
def draw_info(self):
if self.world.info:
text.draw_string(self.surface,
"FPS %d" % self.clock.get_fps(),
util.WHITE, 10, [10, self.height - 20])
text.draw_string(self.surface,
"OBJECTS %d" % self.world.n_objects(),
util.WHITE, 10, [10, self.height - 40])
text.draw_string(self.surface,
"PARTICLES %d" % self.world.particle.n_particles(),
util.WHITE, 10, [10, self.height - 60])
def level_start(self):
start_animation_frames = 100
start_animation_time = start_animation_frames
while not self.world.quit:
if start_animation_time == 0:
break
self.world.update()
if self.world.spawn:
asteroid.Asteroid(self.world,
random.randint(75, 100),
self.level)
self.surface.fill(util.BLACK)
self.draw_hud()
self.draw_info()
start_animation_time -= 1
t = float(start_animation_time) / start_animation_frames
text.draw_string(self.surface, "LEVEL START", util.WHITE,
t * 150,
[self.width / 2, self.height / 2],
centre = True,
angle = t * 200.0)
self.world.draw()
self.clock.tick(60)
pygame.display.flip()
def play_level(self):
while not self.world.quit:
if self.world.n_asteroids == 0:
break
if not self.world.player:
break
if self.world.next_level:
self.world.remove_asteroids()
break
self.world.update()
self.surface.fill(util.BLACK)
self.draw_hud()
self.draw_info()
self.world.draw()
self.clock.tick(60)
pygame.display.flip()
def game_over(self):
end_animation_frames = 100
end_animation_time = end_animation_frames
while not self.world.quit:
if end_animation_time == 0:
break
self.world.update()
self.surface.fill(util.BLACK)
self.draw_hud()
self.draw_info()
end_animation_time -= 1
t = float(end_animation_time) / end_animation_frames
text.draw_string(self.surface, "GAME OVER", util.WHITE,
math.log(t + 0.001) * 150,
[self.width / 2, self.height / 2],
centre = True,
angle = 180)
self.world.draw()
self.clock.tick(60)
pygame.display.flip()
def epilogue(self):
while not self.world.quit:
if self.world.enter:
break
self.world.update()
self.surface.fill(util.BLACK)
text.draw_string(self.surface, "PRESS ENTER TO PLAY AGAIN",
util.WHITE,
20,
[self.width / 2, self.height / 2],
centre = True,
angle = 0)
self.draw_hud()
self.draw_info()
self.world.draw()
self.clock.tick(60)
pygame.display.flip()
def play_game(self):
self.start_screen()
while not self.world.quit:
self.level = 1
self.world.reset()
self.world.particle.starfield()
while not self.world.quit:
self.level_start()
self.world.add_player()
for i in range(self.level * 2):
asteroid.Asteroid(self.world,
random.randint(75, 100),
0.5 + self.level / 4.0)
self.play_level()
if not self.world.player:
break
self.level += 1
self.game_over()
self.epilogue()
def main():
pygame.init()
mixer.init()
# audio channel allocation:
#
# 0 - background music
# 1 - ship engines
# 2 - ship guns
# 3 - alien
# 4 to 7 - explosions
#
# we reserve the first four channels for us to allocate and let mixer pick
# channels for explosions automatically
mixer.set_reserved(4)
surface = pygame.display.set_mode([0, 0], pygame.FULLSCREEN)
#surface = pygame.display.set_mode([800, 600])
pygame.mouse.set_visible(False)
pygame.display.set_caption("Argh, it's the Asteroids!!")
game = Game(surface)
game.play_game()
pygame.quit()
if __name__ == "__main__":
main()
运行效果:
来源:oschina
链接:https://my.oschina.net/u/4461803/blog/3217949