Add sound options and semi-separate front-end from engine
This commit is contained in:
parent
8852a9f678
commit
bc47fb3f30
8 changed files with 107 additions and 67 deletions
|
@ -24,7 +24,6 @@ from random import choice, randrange, shuffle
|
|||
from sys import modules
|
||||
|
||||
import pygame
|
||||
from pygame.mixer import Sound
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .constants import *
|
||||
|
@ -50,7 +49,7 @@ class Hero:
|
|||
spin_speed (float): speed of spinning (in frames per slash)
|
||||
spin_queue (float): frames left to finish spinning
|
||||
wound (float): amount of wound
|
||||
sfx_heart (Sound): heart beat sound effect
|
||||
sfx_heart (pygame.mixer.Sound): heart beat sound effect
|
||||
"""
|
||||
def __init__(self, surface, fps):
|
||||
self.surface = surface
|
||||
|
@ -64,7 +63,7 @@ class Hero:
|
|||
self.spin_speed = fps / HERO_HP
|
||||
self.spin_queue = self.wound = 0.0
|
||||
|
||||
self.sfx_heart = Sound(SFX_HEART)
|
||||
self.sfx_heart = SFX_HEART
|
||||
|
||||
def update(self, fps):
|
||||
"""Update the hero."""
|
||||
|
@ -92,6 +91,9 @@ class Hero:
|
|||
x, y = pygame.mouse.get_pos()
|
||||
self.angle = atan2(y - self.y, x - self.x)
|
||||
self.spin_queue = 0.0
|
||||
|
||||
def draw(self):
|
||||
"""Draw the hero."""
|
||||
trigon = regpoly(3, self.R, self.angle, self.x, self.y)
|
||||
fill_aapolygon(self.surface, trigon, self.color[int(self.wound)])
|
||||
|
||||
|
@ -117,7 +119,7 @@ class Enemy:
|
|||
spin_speed (float): speed of spinning (in frames per slash)
|
||||
spin_queue (float): frames left to finish spinning
|
||||
wound (float): amount of wound
|
||||
sfx_slash (Sound): sound effect indicating close-range attack damage
|
||||
sfx_slash (pygame.mixer.Sound): sound effect of slashed hero
|
||||
"""
|
||||
def __init__(self, maze, x, y, color):
|
||||
self.maze = maze
|
||||
|
@ -132,7 +134,7 @@ class Enemy:
|
|||
self.spin_speed = self.maze.fps / ENEMY_HP
|
||||
self.spin_queue = self.wound = 0.0
|
||||
|
||||
self.sfx_slash = Sound(SFX_SLASH_HERO)
|
||||
self.sfx_slash = SFX_SLASH_HERO
|
||||
|
||||
def get_pos(self):
|
||||
"""Return coordinate of the center of the enemy."""
|
||||
|
@ -238,6 +240,7 @@ class Enemy:
|
|||
|
||||
def draw(self):
|
||||
"""Draw the enemy."""
|
||||
if get_ticks() < self.maze.next_move and not self.awake: return
|
||||
radious = self.maze.distance/SQRT2 - self.awake*2
|
||||
square = regpoly(4, radious, self.angle, *self.get_pos())
|
||||
color = TANGO[self.color][int(self.wound)] if self.awake else FG_COLOR
|
||||
|
@ -257,7 +260,6 @@ class Enemy:
|
|||
self.spin_queue -= sign(self.spin_queue)
|
||||
else:
|
||||
self.angle, self.spin_queue = pi / 4, 0.0
|
||||
if self.awake or get_ticks() >= self.maze.next_move: self.draw()
|
||||
|
||||
def hit(self, wound):
|
||||
"""Handle the enemy when it's attacked."""
|
||||
|
@ -290,8 +292,7 @@ class Chameleon(Enemy):
|
|||
|
||||
def draw(self):
|
||||
"""Draw the Chameleon."""
|
||||
if (not self.awake or self.spin_queue
|
||||
or get_ticks() < max(self.visible, self.maze.next_move)):
|
||||
if not self.awake or get_ticks() < self.visible or self.spin_queue:
|
||||
Enemy.draw(self)
|
||||
|
||||
def hit(self, wound):
|
||||
|
|
|
@ -19,21 +19,25 @@
|
|||
|
||||
__doc__ = 'brutalmaze module for shared constants'
|
||||
|
||||
from pkg_resources import resource_filename
|
||||
from pygame import image
|
||||
from pkg_resources import resource_filename as pkg_file
|
||||
import pygame
|
||||
from pygame.mixer import Sound
|
||||
|
||||
SETTINGS = resource_filename('brutalmaze', 'settings.ini')
|
||||
ICON = image.load(resource_filename('brutalmaze', 'icon.png'))
|
||||
MUSIC = resource_filename('brutalmaze', 'soundfx/music.ogg')
|
||||
SFX_SPAWN = resource_filename('brutalmaze', 'soundfx/spawn.ogg')
|
||||
SFX_SLASH_ENEMY = resource_filename('brutalmaze', 'soundfx/slash-enemy.ogg')
|
||||
SFX_SLASH_HERO = resource_filename('brutalmaze', 'soundfx/slash-hero.ogg')
|
||||
SFX_SHOT_ENEMY = resource_filename('brutalmaze', 'soundfx/shot-enemy.ogg')
|
||||
SFX_SHOT_HERO = resource_filename('brutalmaze', 'soundfx/shot-hero.ogg')
|
||||
SFX_MISSED = resource_filename('brutalmaze', 'soundfx/missed.ogg')
|
||||
SFX_HEART = resource_filename('brutalmaze', 'soundfx/heart.ogg')
|
||||
SFX_LOSE = resource_filename('brutalmaze', 'soundfx/lose.ogg')
|
||||
SETTINGS = pkg_file('brutalmaze', 'settings.ini')
|
||||
ICON = pygame.image.load(pkg_file('brutalmaze', 'icon.png'))
|
||||
MUSIC = pkg_file('brutalmaze', 'soundfx/music.ogg')
|
||||
|
||||
mixer = pygame.mixer.get_init()
|
||||
if mixer is None: pygame.mixer.init(frequency=44100)
|
||||
SFX_SPAWN = Sound(pkg_file('brutalmaze', 'soundfx/spawn.ogg'))
|
||||
SFX_SLASH_ENEMY = Sound(pkg_file('brutalmaze', 'soundfx/slash-enemy.ogg'))
|
||||
SFX_SLASH_HERO = Sound(pkg_file('brutalmaze', 'soundfx/slash-hero.ogg'))
|
||||
SFX_SHOT_ENEMY = Sound(pkg_file('brutalmaze', 'soundfx/shot-enemy.ogg'))
|
||||
SFX_SHOT_HERO = Sound(pkg_file('brutalmaze', 'soundfx/shot-hero.ogg'))
|
||||
SFX_MISSED = Sound(pkg_file('brutalmaze', 'soundfx/missed.ogg'))
|
||||
SFX_HEART = Sound(pkg_file('brutalmaze', 'soundfx/heart.ogg'))
|
||||
SFX_LOSE = Sound(pkg_file('brutalmaze', 'soundfx/lose.ogg'))
|
||||
if mixer is None: pygame.mixer.quit()
|
||||
|
||||
SQRT2 = 2 ** 0.5
|
||||
INIT_SCORE = 5**0.5/2 + 0.5 # golden mean
|
||||
|
|
|
@ -35,7 +35,7 @@ from pygame import DOUBLEBUF, KEYDOWN, OPENGL, QUIT, RESIZABLE, VIDEORESIZE
|
|||
from pygame.time import Clock, get_ticks
|
||||
from appdirs import AppDirs
|
||||
|
||||
from .constants import *
|
||||
from .constants import SETTINGS, ICON, MUSIC, HERO_SPEED
|
||||
from .maze import Maze
|
||||
from .misc import sign
|
||||
|
||||
|
@ -44,7 +44,8 @@ class ConfigReader:
|
|||
"""Object reading and processing INI configuration file for
|
||||
Brutal Maze.
|
||||
"""
|
||||
CONTROL_ALIASES = (('New game', 'new'), ('Pause', 'pause'),
|
||||
CONTROL_ALIASES = (('New game', 'new'), ('Toggle pause', 'pause'),
|
||||
('Toggle mute', 'mute'),
|
||||
('Move left', 'left'), ('Move right', 'right'),
|
||||
('Move up', 'up'), ('Move down', 'down'),
|
||||
('Long-range attack', 'shot'),
|
||||
|
@ -57,12 +58,14 @@ class ConfigReader:
|
|||
self.config.read(SETTINGS) # default configuration
|
||||
self.config.read(filenames)
|
||||
|
||||
def parse_graphics(self):
|
||||
"""Parse graphics configurations."""
|
||||
def parse_output(self):
|
||||
"""Parse graphics and sound configurations."""
|
||||
self.size = (self.config.getint('Graphics', 'Screen width'),
|
||||
self.config.getint('Graphics', 'Screen height'))
|
||||
self.opengl = self.config.getboolean('Graphics', 'OpenGL')
|
||||
self.max_fps = self.config.getint('Graphics', 'Maximum FPS')
|
||||
self.muted = self.config.getboolean('Sound', 'Muted')
|
||||
self.musicvol = self.config.getfloat('Sound', 'Music volume')
|
||||
|
||||
def parse_control(self):
|
||||
"""Parse control configurations."""
|
||||
|
@ -84,27 +87,31 @@ class ConfigReader:
|
|||
|
||||
def read_args(self, arguments):
|
||||
"""Read and parse a ArgumentParser.Namespace."""
|
||||
if arguments.size is not None: self.size = arguments.size
|
||||
if arguments.opengl is not None: self.opengl = arguments.opengl
|
||||
if arguments.max_fps is not None: self.max_fps = arguments.max_fps
|
||||
for option in 'size', 'opengl', 'max_fps', 'muted', 'musicvol':
|
||||
value = getattr(arguments, option)
|
||||
if value is not None: setattr(self, option, value)
|
||||
|
||||
|
||||
class Game:
|
||||
"""Object handling main loop and IO."""
|
||||
def __init__(self, size, scrtype, max_fps, key, mouse):
|
||||
def __init__(self, size, scrtype, max_fps, muted, musicvol, key, mouse):
|
||||
pygame.mixer.pre_init(frequency=44100)
|
||||
pygame.init()
|
||||
pygame.mixer.music.load(MUSIC)
|
||||
pygame.mixer.music.play(-1)
|
||||
if muted:
|
||||
pygame.mixer.quit()
|
||||
else:
|
||||
pygame.mixer.music.load(MUSIC)
|
||||
pygame.mixer.music.set_volume(musicvol)
|
||||
pygame.mixer.music.play(-1)
|
||||
pygame.display.set_icon(ICON)
|
||||
pygame.fastevent.init()
|
||||
self.clock = Clock()
|
||||
# self.fps is a float to make sure floordiv won't be used in Python 2
|
||||
self.max_fps, self.fps = max_fps, float(max_fps)
|
||||
self.musicvol = musicvol
|
||||
self.key, self.mouse = key, mouse
|
||||
self.maze = Maze(max_fps, size, scrtype)
|
||||
self.hero = self.maze.hero
|
||||
self.paused = False
|
||||
self.clock, self.paused = Clock(), False
|
||||
|
||||
def __enter__(self): return self
|
||||
|
||||
|
@ -145,6 +152,14 @@ class Game:
|
|||
self.maze.__init__(self.fps)
|
||||
elif event.key == self.key['pause'] and not self.hero.dead:
|
||||
self.paused ^= True
|
||||
elif event.key == self.key['mute']:
|
||||
if pygame.mixer.get_init() is None:
|
||||
pygame.mixer.init(frequency=44100)
|
||||
pygame.mixer.music.load(MUSIC)
|
||||
pygame.mixer.music.set_volume(self.musicvol)
|
||||
pygame.mixer.music.play(-1)
|
||||
else:
|
||||
pygame.mixer.quit()
|
||||
|
||||
if not self.hero.dead:
|
||||
keys = pygame.key.get_pressed()
|
||||
|
@ -181,7 +196,7 @@ def main():
|
|||
parents.append(dirs.user_config_dir)
|
||||
filenames = [join(parent, 'settings.ini') for parent in parents]
|
||||
config = ConfigReader(filenames)
|
||||
config.parse_graphics()
|
||||
config.parse_output()
|
||||
|
||||
# Parse command-line arguments
|
||||
parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
|
||||
|
@ -206,6 +221,14 @@ def main():
|
|||
parser.add_argument(
|
||||
'-f', '--max-fps', type=int, metavar='FPS',
|
||||
help='the desired maximum FPS (fallback: {})'.format(config.max_fps))
|
||||
parser.add_argument(
|
||||
'--mute', '-m', action='store_true', default=None,
|
||||
help='mute all sounds (fallback: {})'.format(config.muted))
|
||||
parser.add_argument('--unmute', action='store_false', dest='muted',
|
||||
help='unmute sound')
|
||||
parser.add_argument(
|
||||
'--music-volume', type=float, metavar='VOL', dest='musicvol',
|
||||
help='between 0.0 and 1.0 (fallback: {})'.format(config.musicvol))
|
||||
args = parser.parse_args()
|
||||
if args.defaultcfg is not None:
|
||||
with open(SETTINGS) as settings: args.defaultcfg.write(settings.read())
|
||||
|
@ -215,10 +238,11 @@ def main():
|
|||
# Manipulate config
|
||||
if args.config: config.config.read(args.config)
|
||||
config.read_args(args)
|
||||
config.parse_graphics()
|
||||
config.parse_output()
|
||||
config.parse_control()
|
||||
|
||||
# Main loop
|
||||
scrtype = (config.opengl and DOUBLEBUF|OPENGL) | RESIZABLE
|
||||
with Game(config.size, scrtype, config.max_fps, config.key, config.mouse) as game:
|
||||
with Game(config.size, scrtype, config.max_fps, config.muted,
|
||||
config.musicvol, config.key, config.mouse) as game:
|
||||
while game.loop(): pass
|
||||
|
|
|
@ -25,7 +25,6 @@ from random import choice, getrandbits, uniform
|
|||
|
||||
import pygame
|
||||
from pygame import RESIZABLE
|
||||
from pygame.mixer import Sound
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .characters import Hero, new_enemy
|
||||
|
@ -76,9 +75,8 @@ class Maze:
|
|||
next_move (int): the tick that the hero gets mobilized
|
||||
next_slashfx (int): the tick to play next slash effect of the hero
|
||||
slashd (float): minimum distance for slashes to be effective
|
||||
sfx_slash (Sound): sound effect indicating an enemy get slashed
|
||||
sfx_shot (Sound): sound effect indicating an enemy get shot
|
||||
sfx_lose (Sound): sound effect to be played when you lose
|
||||
sfx_slash (pygame.mixer.Sound): sound effect of slashed enemy
|
||||
sfx_lose (pygame.mixer.Sound): sound effect to be played when you lose
|
||||
"""
|
||||
def __init__(self, fps, size=None, scrtype=None):
|
||||
self.fps = fps
|
||||
|
@ -109,10 +107,9 @@ class Maze:
|
|||
self.next_move = self.next_slashfx = 0
|
||||
self.slashd = self.hero.R + self.distance/SQRT2
|
||||
|
||||
self.sfx_spawn = Sound(SFX_SPAWN)
|
||||
self.sfx_slash = Sound(SFX_SLASH_ENEMY)
|
||||
self.sfx_shot = Sound(SFX_SHOT_ENEMY)
|
||||
self.sfx_lose = Sound(SFX_LOSE)
|
||||
self.sfx_spawn = SFX_SPAWN
|
||||
self.sfx_slash = SFX_SLASH_ENEMY
|
||||
self.sfx_lose = SFX_LOSE
|
||||
|
||||
def add_enemy(self):
|
||||
"""Add enough enemies."""
|
||||
|
@ -140,13 +137,21 @@ class Maze:
|
|||
def draw(self):
|
||||
"""Draw the maze."""
|
||||
self.surface.fill(BG_COLOR)
|
||||
if get_ticks() < self.next_move: return
|
||||
for i in self.rangex:
|
||||
for j in self.rangey:
|
||||
if self.map[i][j] != WALL: continue
|
||||
x, y = self.get_pos(i, j)
|
||||
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
|
||||
fill_aapolygon(self.surface, square, FG_COLOR)
|
||||
if get_ticks() >= self.next_move:
|
||||
for i in self.rangex:
|
||||
for j in self.rangey:
|
||||
if self.map[i][j] != WALL: continue
|
||||
x, y = self.get_pos(i, j)
|
||||
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
|
||||
fill_aapolygon(self.surface, square, FG_COLOR)
|
||||
|
||||
for enemy in self.enemies: enemy.draw()
|
||||
self.hero.draw()
|
||||
bullet_radius = self.distance / 4
|
||||
for bullet in self.bullets: bullet.draw(bullet_radius)
|
||||
pygame.display.flip()
|
||||
pygame.display.set_caption('Brutal Maze - Score: {}'.format(
|
||||
int(self.score - INIT_SCORE)))
|
||||
|
||||
def rotate(self):
|
||||
"""Rotate the maze if needed."""
|
||||
|
@ -266,7 +271,7 @@ class Maze:
|
|||
self.score += enemy.wound
|
||||
enemy.die()
|
||||
self.enemies.pop(j)
|
||||
play(self.sfx_shot, wound, bullet.angle)
|
||||
play(bullet.sfx_hit, wound, bullet.angle)
|
||||
fallen.append(i)
|
||||
break
|
||||
elif bullet.get_distance(self.x, self.y) < self.distance:
|
||||
|
@ -310,15 +315,12 @@ class Maze:
|
|||
for enemy in self.enemies: enemy.wake()
|
||||
for bullet in self.bullets: bullet.place(dx, dy)
|
||||
|
||||
self.draw()
|
||||
for enemy in self.enemies: enemy.update()
|
||||
if not self.hero.dead:
|
||||
self.hero.update(fps)
|
||||
self.slash()
|
||||
self.track_bullets()
|
||||
pygame.display.flip()
|
||||
pygame.display.set_caption('Brutal Maze - Score: {}'.format(
|
||||
int(self.score - INIT_SCORE)))
|
||||
self.draw()
|
||||
|
||||
def resize(self, size):
|
||||
"""Resize the maze."""
|
||||
|
|
|
@ -76,6 +76,7 @@ def choices(d):
|
|||
|
||||
def play(sound, volume=1.0, angle=None):
|
||||
"""Play a pygame.mixer.Sound at the given volume."""
|
||||
if pygame.mixer.get_init() is None: return
|
||||
if pygame.mixer.find_channel() is None:
|
||||
pygame.mixer.set_num_channels(pygame.mixer.get_num_channels() + 1)
|
||||
|
||||
|
|
|
@ -6,6 +6,11 @@ OpenGL: no
|
|||
# FPS should not be greater than refresh rate.
|
||||
Maximum FPS: 60
|
||||
|
||||
[Sound]
|
||||
Muted: no
|
||||
# Volume must be between 0.0 and 1.0
|
||||
Music volume: 1.0
|
||||
|
||||
[Control]
|
||||
# Input values should be either from Mouse1 to Mouse3 or a keyboard key
|
||||
# and they are case-insensitively read.
|
||||
|
@ -13,7 +18,8 @@ Maximum FPS: 60
|
|||
# http://www.pygame.org/docs/ref/key.html
|
||||
# Key combinations are not supported.
|
||||
New game: F2
|
||||
Pause: p
|
||||
Toggle pause: p
|
||||
Toggle mute: m
|
||||
Move left: Left
|
||||
Move right: Right
|
||||
Move up: Up
|
||||
|
|
|
@ -22,7 +22,6 @@ __doc__ = 'brutalmaze module for weapon classes'
|
|||
from math import cos, sin
|
||||
|
||||
from pygame.time import get_ticks
|
||||
from pygame.mixer import Sound
|
||||
|
||||
from .constants import *
|
||||
from .misc import regpoly, fill_aapolygon
|
||||
|
@ -37,25 +36,28 @@ class Bullet:
|
|||
angle (float): angle of the direction the bullet pointing (in radians)
|
||||
color (str): bullet's color name
|
||||
fall_time (int): the tick that the bullet will fall down
|
||||
sfx_hit (Sound): sound effect indicating the bullet hits the target
|
||||
sfx_missed (Sound): sound effect indicating the bullet hits the target
|
||||
sfx_hit (pygame.mixer.Sound): sound effect indicating target was hit
|
||||
sfx_missed (pygame.mixer.Sound): sound effect indicating a miss shot
|
||||
"""
|
||||
def __init__(self, surface, x, y, angle, color):
|
||||
self.surface = surface
|
||||
self.x, self.y, self.angle, self.color = x, y, angle, color
|
||||
self.fall_time = get_ticks() + BULLET_LIFETIME
|
||||
# Sound effects of bullets shot by hero are stored in Maze to avoid
|
||||
# unnecessary duplication
|
||||
if color != 'Aluminium':
|
||||
self.sfx_hit = Sound(SFX_SHOT_HERO)
|
||||
self.sfx_missed = Sound(SFX_MISSED)
|
||||
if color == 'Aluminium':
|
||||
self.sfx_hit = SFX_SHOT_ENEMY
|
||||
else:
|
||||
self.sfx_hit = SFX_SHOT_HERO
|
||||
self.sfx_missed = SFX_MISSED
|
||||
|
||||
def update(self, fps, distance):
|
||||
"""Update the bullet."""
|
||||
s = distance * BULLET_SPEED / fps
|
||||
self.x += s * cos(self.angle)
|
||||
self.y += s * sin(self.angle)
|
||||
pentagon = regpoly(5, distance // 4, self.angle, self.x, self.y)
|
||||
|
||||
def draw(self, radius):
|
||||
"""Draw the bullet."""
|
||||
pentagon = regpoly(5, radius, self.angle, self.x, self.y)
|
||||
value = int((1-(self.fall_time-get_ticks())/BULLET_LIFETIME)*ENEMY_HP)
|
||||
try:
|
||||
fill_aapolygon(self.surface, pentagon, TANGO[self.color][value])
|
||||
|
|
2
wiki
2
wiki
|
@ -1 +1 @@
|
|||
Subproject commit 3b014564eb185bb525a59378fd2d9ede65bf0d33
|
||||
Subproject commit 34af1cf8b3e3ea8272d6793a794484a239794d50
|
Loading…
Reference in a new issue