Merge branch 'master' of makar:/var/www/git/pgfw

This commit is contained in:
Frank DeMarco 2017-07-13 14:32:05 -04:00
commit 316d354f07
14 changed files with 349 additions and 116 deletions

View File

@ -1,92 +1,50 @@
from os import listdir
from os.path import join
from pygame.mixer import Channel, Sound, music, find_channel
from pygame.mixer import Channel, Sound, music, find_channel, get_num_channels
from GameChild import *
from Input import *
class Audio(GameChild):
current_channel = None
paused = False
muted = False
UP, DOWN = .1, -.1
BASE_VOLUME = .8
def __init__(self, game):
GameChild.__init__(self, game)
self.delegate = self.get_delegate()
self.load_fx()
self.original_volumes = {}
self.volume = self.BASE_VOLUME
if self.check_command_line("-mute"):
self.volume = 0
self.subscribe(self.respond)
def load_fx(self):
fx = {}
if self.get_configuration().has_option("audio", "sfx-path"):
root = self.get_resource("audio", "sfx-path")
if root:
for name in listdir(root):
fx[name.split(".")[0]] = Sound(join(root, name))
self.fx = fx
def set_volume(self, volume=None, increment=None, mute=False):
if mute:
self.volume = 0
elif increment:
self.volume += increment
if self.volume > 1:
self.volume = 1.0
elif self.volume < 0:
self.volume = 0
else:
self.volume = volume
def respond(self, event):
if self.delegate.compare(event, "mute"):
self.mute()
compare = self.get_game().delegate.compare
if compare(event, "volume-mute"):
self.set_volume(mute=True)
elif compare(event, "volume-up"):
self.set_volume(increment=self.UP)
elif compare(event, "volume-down"):
self.set_volume(increment=self.DOWN)
def mute(self):
self.muted = True
self.set_volume()
def unmute(self):
self.muted = False
self.set_volume()
def set_volume(self):
volume = int(not self.muted)
music.set_volume(volume)
if self.current_channel:
self.current_channel.set_volume(volume)
def play_bgm(self, path, stream=False):
self.stop_current_channel()
if stream:
music.load(path)
music.play(-1)
else:
self.current_channel = Sound(path).play(-1)
self.set_volume()
def stop_current_channel(self):
music.stop()
if self.current_channel:
self.current_channel.stop()
self.current_channel = None
self.paused = False
def play_fx(self, name, panning=.5):
if not self.muted:
channel = find_channel(True)
if panning != .5:
offset = 1 - abs(panning - .5) * 2
if panning < .5:
channel.set_volume(1, offset)
else:
channel.set_volume(offset, 1)
channel.play(self.fx[name])
def pause(self):
channel = self.current_channel
paused = self.paused
if paused:
music.unpause()
if channel:
channel.unpause()
else:
music.pause()
if channel:
channel.pause()
self.paused = not paused
def is_bgm_playing(self):
current = self.current_channel
if current and current.get_sound():
return True
return music.get_busy()
def update(self):
for ii in xrange(get_num_channels()):
channel = Channel(ii)
sound = channel.get_sound()
if sound is not None:
if sound not in self.original_volumes.keys():
self.original_volumes[sound] = sound.get_volume()
sound.set_volume(self.original_volumes[sound] * self.volume)

View File

@ -65,6 +65,7 @@ class Configuration(RawConfigParser):
set_option(section, "windows-dist-path", "dist/win/", False)
set_option(section, "windows-icon-path", "", False)
set_option(section, "lowercase-boolean-true", "yes", False)
set_option(section, "osx-includes", "", False)
section = "display"
add_section(section)
set_option(section, "dimensions", "480, 360", False)
@ -114,7 +115,9 @@ class Configuration(RawConfigParser):
set_option(section, "toggle-fullscreen", "K_F11", False)
set_option(section, "reset-game", "K_F8", False)
set_option(section, "record-video", "K_F10", False)
set_option(section, "mute", "K_F12", False)
set_option(section, "volume-down", "K_F1", False)
set_option(section, "volume-up", "K_F2", False)
set_option(section, "volume-mute", "K_F3", False)
set_option(section, "toggle-interpolator", "K_F7", False)
section = "joy"
add_section(section)
@ -130,6 +133,7 @@ class Configuration(RawConfigParser):
section = "audio"
add_section(section)
set_option(section, "sfx-path", "aud/fx/", False)
set_option(section, "sfx-volume", "1.0", False)
section = "interpolator-gui"
add_section(section)
set_option(section, "margin", "80", False)
@ -295,10 +299,11 @@ class Configuration(RawConfigParser):
exclude = []
if self.has_option(section, option):
exclude = self.get(section, option)
exclude += [".git", ".gitignore", "README", "build/", "dist/",
"setup.py", "MANIFEST", "PKG-INFO",
exclude += [".git*", "README", "build/", "dist/", "*.egg-info",
"*.py", "MANIFEST*", "PKG-INFO", "*.pyc", "*.swp", "*~",
self.get("setup", "changelog"),
self.get("setup", "package-root")]
self.get("setup", "package-root"),
self.get("setup", "init-script")]
for location in self.get("setup", "additional-packages"):
exclude.append(location)
self.set(section, option, exclude, False)
@ -395,7 +400,7 @@ class TypeDeclarations(dict):
"setup": {"list": ["classifiers", "resource-search-path",
"requirements", "data-exclude",
"additional-packages"],
"additional-packages", "osx-includes"],
"path": ["installation-dir", "changelog", "description-file",
"main-object", "icon-path", "windows-dist-path",
@ -409,7 +414,9 @@ class TypeDeclarations(dict):
"joy": {"int": ["advance", "pause", "select"]},
"audio": {"path": "sfx-path"},
"audio": {"path": "sfx-path",
"float": "sfx-volume"},
"event": {"int": "command-id-offset"},

View File

@ -79,7 +79,7 @@ class Delegate(GameChild):
return self.get_command_attribute(evt) in commands
def get_command_attribute(self, evt):
return evt.dict[self.command_key]
return evt.dict.has_key(self.command_key) and evt.dict[self.command_key]
def post(self, command=None, cancel=False, **attributes):
attributes[self.command_key] = command

View File

@ -54,6 +54,7 @@ class Game(GameChild):
self.update()
else:
self.interpolator.gui.update()
self.audio.update()
if self.video_recorder.requested:
self.video_recorder.update()

View File

@ -91,7 +91,7 @@ class Input(GameChild):
self.post_any_command(event.button, cancel)
def translate_axis_motion(self, event):
if not self.suppressed:
if not self.suppressed and not self.check_command_line("-disable-joy-axis"):
axis = event.axis
value = event.value
if -.01 < value < .01:
@ -150,6 +150,11 @@ class Input(GameChild):
post("mouse-double-click-left", pos=pos)
last = get_secs()
self.last_mouse_down_left = last
if "mouse" not in self.any_press_ignored_keys:
self.post_any_command(event.button)
if event.type == MOUSEBUTTONUP:
if "mouse" not in self.any_press_ignored_keys:
self.post_any_command(event.button, True)
def get_axes(self):
axes = {}

View File

@ -404,7 +404,7 @@ class GUI(Animation):
def deactivate(self):
self.active = False
self.time_filter.open()
self.audio.muted = self.saved_mute_state
# self.audio.muted = self.saved_mute_state
self.display.set_mouse_visibility(self.saved_mouse_state)
def respond_to_command(self, event):
@ -427,8 +427,8 @@ class GUI(Animation):
def activate(self):
self.active = True
self.time_filter.close()
self.saved_mute_state = self.audio.muted
self.audio.mute()
# self.saved_mute_state = self.audio.muted
# self.audio.mute()
self.draw()
self.saved_mouse_state = self.display.set_mouse_visibility(True)

View File

@ -124,3 +124,21 @@ class Note(Samples):
if channel and panning:
channel.set_volume(*panning)
return channel
class Chord:
def __init__(self, *args):
self.notes = args
def play(self, maxtime=0, fadeout=[None], panning=None, fade_in=[0]):
if isinstance(fadeout, int):
fadeout = [fadeout]
if isinstance(fade_in, int):
fade_in = [fade_in]
for ii, note in enumerate(self.notes):
note.play(maxtime, fadeout[ii % len(fadeout)], panning, fade_in[ii % len(fade_in)])
def stop(self):
for note in self.notes:
note.stop()

View File

@ -1,5 +1,5 @@
from os import walk, remove
from os.path import sep, join, exists, normpath
from os.path import sep, join, exists, normpath, basename
from re import findall, sub
from distutils.core import setup
from distutils.command.install import install
@ -30,7 +30,8 @@ class Setup:
for location in locations:
if exists(location):
for root, dirs, files in walk(location, followlinks=True):
packages.append(root.replace(sep, "."))
if exists(join(root, "__init__.py")):
packages.append(root.replace(sep, "."))
return packages
def build_data_map(self):
@ -49,13 +50,16 @@ class Setup:
def remove_excluded(self, paths, root, exclude):
removal = []
for path in paths:
for pattern in exclude:
if fnmatch(normpath(join(root, path)), pattern):
removal.append(path)
if self.contains_path(join(root, path), exclude):
removal.append(path)
for path in removal:
paths.remove(path)
if path in paths:
paths.remove(path)
return paths
def contains_path(self, path, container):
return any(fnmatch(path, rule) or fnmatch(basename(path), rule) for rule in container)
def translate_title(self):
config = self.config.get_section("setup")
title = config["title"].replace(" ", config["whitespace-placeholder"])

44
pgfw/SetupOSX.py Normal file
View File

@ -0,0 +1,44 @@
from os.path import exists
from re import match
from setuptools import setup, find_packages
from Configuration import Configuration
from Setup import Setup
class SetupOSX(Setup):
def __init__(self, launcher_path, data_file_paths,
config_file_path="config"):
Setup.__init__(self)
self.launcher_path = launcher_path
self.data_file_paths = data_file_paths
self.config_file_path = config_file_path
def setup(self):
config = Configuration()
setup_obj = Setup()
version = config.get_section("setup")["version"]
name = setup_obj.translate_title()
plist = dict(
CFBundleIconFile=name,
CFBundleName=name,
CFBundleShortVersionString=version,
CFBundleGetInfoString=' '.join([name, version]),
CFBundleExecutable=name,
CFBundleIdentifier='org.' + name.lower())
setup(name=name,
version=version,
app=[dict(script=self.launcher_path, plist=plist)],
setup_requires=["py2app"],
options=dict(py2app=dict(arch="i386",)),
data_files=self.data_file_paths)
config_path = "dist/%s.app/Contents/Resources/%s" % \
(name, self.config_file_path)
if exists(config_path):
lines = open(config_path).readlines()
fp = open(config_path, "w")
for line in lines:
if match("^\W*fullscreen\W*=\W*yes\W*", line):
fp.write("fullscreen = no\n")
else:
fp.write(line)

View File

@ -9,21 +9,25 @@ from pygame.transform import flip
from pygame.locals import *
from Animation import Animation
from Vector import Vector
from Vector import Vector, EVector
from extension import get_hue_shifted_surface, get_step
class Sprite(Animation):
def __init__(self, parent, framerate=None):
def __init__(self, parent, framerate=None, neighbors=[], mass=None):
Animation.__init__(self, parent, self.shift_frame, framerate)
self.frames = []
self.mirrored = False
self.alpha = 255
self.locations = []
self.framesets = [Frameset(self, framerate=framerate)]
self.frameset_index = 0
self.neighbors = neighbors
self.mass = mass
self.step = EVector()
self.set_frameset(0)
self.locations.append(Location(self))
self.motion_overflow = Vector()
self.stop()
self.display_surface = self.get_display_surface()
def __getattr__(self, name):
@ -39,11 +43,13 @@ class Sprite(Animation):
if frameset.name == identifier:
identifier = ii
break
self.frameset_index = identifier
self.register_interval()
self.update_location_size()
if self.get_current_frameset().length() > 1:
self.play()
if self.frameset_index != identifier:
self.frameset_index = identifier
self.register_interval()
self.update_location_size()
if self.get_current_frameset().length() > 1:
self.play()
self.get_current_frameset().reset()
def register_interval(self):
self.register(self.shift_frame,
@ -204,21 +210,26 @@ class Sprite(Animation):
for frameset in self.framesets:
frameset.reverse()
def go(self, dx=0, dy=0):
self.go_vector = Vector(dx, dy)
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
self.step.set_step(dx, dy, magnitude, angle)
def stop(self):
self.go_vector = Vector()
def cancel_step(self):
self.step.set_step(0, 0)
def is_going(self):
return self.go_vector != [0, 0]
def is_stepping(self):
return bool(self.step)
def update(self, areas=None, substitute=None):
Animation.update(self)
if self.is_going():
self.move(*self.go_vector)
if self.is_stepping():
self.move(self.step.dx, self.step.dy)
for neighbor in self.neighbors:
self.move(*get_step(self.location.center, neighbor.location.center,
neighbor.mass))
if self.get_current_frameset().length():
self.draw(areas, substitute)
# for location in self.locations:
# location.update()
def draw(self, areas=None, substitute=None):
for location in self.locations:
@ -249,6 +260,10 @@ class Location(Rect):
overflow[1] -= int(overflow[1])
return excess
def move_to(self, x, y, base=None):
ox, oy = self.apply_motion_overflow(base)
self.move_ip(x - ox, y - oy)
def reset_motion_overflow(self):
self.motion_overflow.place_at_origin()
@ -269,6 +284,18 @@ class Location(Rect):
def is_hidden(self):
return self.hidden
def update(self):
pass
# for neighbor in self.sprite.neighbors:
# if neighbor.mass:
# closest = neighbor.location
# for location in neighbor.locations[1:]:
# if get_distance(self.center, location.center) < \
# get_distance(self.center, closest.center):
# closest = location
# self.move_ip(get_step(self.center, closest.center,
# neighbor.mass))
class Fader(Surface):
@ -427,3 +454,25 @@ class Frameset:
def reverse(self):
self.reversed = not self.reversed
class BlinkingSprite(Sprite):
def __init__(self, parent, blink_rate, framerate=None):
Sprite.__init__(self, parent, framerate)
self.register(self.blink, interval=blink_rate)
self.play(self.blink)
def reset(self):
self.unhide()
def blink(self):
self.toggle_hidden()
class RainbowSprite(Sprite):
def __init__(self, parent, image, hue_shift=8, framerate=None):
Sprite.__init__(self, parent, framerate)
for hue in xrange(0, 360, hue_shift):
self.add_frame(get_hue_shifted_surface(image, hue))

View File

@ -6,9 +6,12 @@ class TimeFilter(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.ticks = self.unfiltered_ticks = self.last_ticks = get_ticks()
self.reset_ticks()
self.open()
def reset_ticks(self):
self.ticks = self.unfiltered_ticks = self.last_ticks = get_ticks()
def close(self):
self.closed = True

View File

@ -1,8 +1,18 @@
from math import pi, degrees
from extension import get_delta, get_distance, get_angle
class Vector(list):
def __init__(self, x=0, y=0):
list.__init__(self, (x, y))
def __repr__(self):
message = "<%f" % self[0]
for value in self[1:]:
message += ", %f" % value
return message + ">"
def __getattr__(self, name):
if name == "x":
return self[0]
@ -48,6 +58,24 @@ class Vector(list):
self.y *= other
return self
def __eq__(self, other):
for sv, ov in zip(self, other):
if value != other[ii]:
return False
return True
def __ne__(self, other):
for sv, ov in zip(self, other):
if value == other[ii]:
return False
return True
def __nonzero__(self):
for value in self:
if bool(value):
return True
return False
def apply_to_components(self, function):
self.x = function(self.x)
self.y = function(self.y)
@ -67,3 +95,37 @@ class Vector(list):
def place_at_origin(self):
self.x = 0
self.y = 0
class EVector(Vector):
def __init__(self, x=0, y=0, dx=0, dy=0, magnitude=None, angle=0):
Vector.__init__(self, x, y)
self.set_step(dx, dy, magnitude, angle)
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
"""specify angle in radians, counter-clockwise, 0 is up"""
if magnitude is not None:
self.magnitude = magnitude
self.angle = angle
self.dx, self.dy = get_delta(angle, magnitude, False)
else:
self.dx = dx
self.dy = dy
if dx == 0 and dy == 0:
self.magnitude = 0
self.angle = 0
else:
end = self.x + dx, self.y + dy
self.magnitude = get_distance(self, end)
self.angle = -get_angle(self, end) - pi
def __repr__(self):
return "<dx=%.2f, dy=%.2f, m=%.2f, ang=%.2f>" % \
(self.dx, self.dy, self.magnitude, self.angle)
def __nonzero__(self):
return bool(self.magnitude)
def move(self):
self += self.dx, self.dy

View File

@ -1,7 +1,7 @@
from random import randint
from math import sin, cos, atan2, radians, sqrt
from random import randint, random
from math import sin, cos, atan2, radians, sqrt, pi
from pygame import Surface, PixelArray
from pygame import Surface, PixelArray, Color
from pygame.mixer import get_num_channels, Channel
from pygame.locals import *
@ -11,14 +11,21 @@ def get_step(start, end, speed):
angle = atan2(x1 - x0, y1 - y0)
return speed * sin(angle), speed * cos(angle)
def get_endpoint(start, angle, magnitude):
def get_angle(start, end, transpose=False):
angle = atan2(end[1] - start[1], end[0] - start[0])
if transpose:
angle = -angle - pi
return angle
def get_endpoint(start, angle, magnitude, translate_angle=True):
"""clockwise, 0 is up"""
x0, y0 = start
dx, dy = get_delta(angle, magnitude)
dx, dy = get_delta(angle, magnitude, translate_angle)
return x0 + dx, y0 + dy
def get_delta(angle, magnitude):
angle = radians(angle)
def get_delta(angle, magnitude, translate_angle=True):
if translate_angle:
angle = radians(angle)
return sin(angle) * magnitude, -cos(angle) * magnitude
def rotate_2d(point, center, angle, translate_angle=True):
@ -104,6 +111,14 @@ def collide_line_with_rect(rect, p0, p1):
if get_intersection(p0, p1, *line):
return True
def get_random_number_in_range(start, end):
return random() * (end - start) + start
def get_value_in_range(start, end, position, reverse=False):
if reverse:
position = 1 - position
return (end - start) * position + start
def render_box(font, text, antialias, color, background=None, border=None,
border_width=1, padding=0):
surface = font.render(text, antialias, color, background)
@ -142,3 +157,56 @@ def get_busy_channel_count():
for index in xrange(get_num_channels()):
count += Channel(index).get_busy()
return count
def get_hue_shifted_surface(base, offset):
surface = base.copy()
pixels = PixelArray(surface)
color = Color(0, 0, 0)
for x in xrange(surface.get_width()):
for y in xrange(surface.get_height()):
h, s, l, a = Color(*surface.unmap_rgb(pixels[x][y])).hsla
if a:
color.hsla = (h + offset) % 360, s, l, a
pixels[x][y] = color
del pixels
return surface
def get_inverted_surface(base):
surface = base.copy()
pixels = PixelArray(surface)
for x in xrange(surface.get_width()):
for y in xrange(surface.get_height()):
color = Color(*surface.unmap_rgb(pixels[x][y]))
if color.hsla[3]:
color.r = 255 - color.r
color.g = 255 - color.g
color.b = 255 - color.b
pixels[x][y] = color
del pixels
return surface
def fill_tile(surface, tile):
for x in xrange(0, surface.get_width(), tile.get_width()):
for y in xrange(0, surface.get_height(), tile.get_height()):
surface.blit(tile, (x, y))
def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0),
colorkey=(255, 0, 255)):
foreground = font.render(text, antialias, color)
background = font.render(text, antialias, shadow_color)
alpha = SRCALPHA if antialias else 0
surface = Surface((foreground.get_width() + abs(offset[0]),
foreground.get_height() + abs(offset[1])), alpha)
if not antialias:
surface.set_colorkey(colorkey)
surface.fill(colorkey)
surface.blit(background, ((abs(offset[0]) + offset[0]) / 2,
(abs(offset[1]) + offset[1]) / 2))
surface.blit(foreground, ((abs(offset[0]) - offset[0]) / 2,
(abs(offset[1]) - offset[1]) / 2))
return surface
def get_hsla_color(hue, saturation=100, lightness=50, alpha=100):
color = Color(0, 0, 0, 0)
color.hsla = hue % 360, saturation, lightness, alpha
return color

14
pgfw/gfx_extension.py Normal file
View File

@ -0,0 +1,14 @@
from pygame.gfxdraw import (aacircle, filled_circle, aatrigon, filled_trigon,
aapolygon, filled_polygon)
def aa_filled_circle(surface, cx, cy, radius, color):
aacircle(surface, cx, cy, radius, color)
filled_circle(surface, cx, cy, radius, color)
def aa_filled_trigon(surface, x1, y1, x2, y2, x3, y3, color):
aatrigon(surface, x1, y1, x2, y2, x3, y3, color)
filled_trigon(surface, x1, y1, x2, y2, x3, y3, color)
def aa_filled_polygon(surface, points, color):
aapolygon(surface, points, color)
filled_polygon(surface, points, color)