Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Frank DeMarco | c54ebd914d |
|
@ -1,4 +1 @@
|
|||
*.pyc
|
||||
build/
|
||||
MANIFEST
|
||||
*~
|
||||
|
|
19
LICENSE.txt
19
LICENSE.txt
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2022 shampoo.ooo <mailbox@shampoo.ooo>
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In
|
||||
no event will the authors be held liable for any damages arising from the use of
|
||||
this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including
|
||||
commercial applications, and to alter it and redistribute it freely, subject to
|
||||
the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source distribution.
|
|
@ -1 +0,0 @@
|
|||
include sample.py
|
|
@ -0,0 +1,50 @@
|
|||
----------------
|
||||
Pygame Framework
|
||||
----------------
|
||||
|
||||
Classes that facilitate the creation of Pygame projects
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Create a project which draws a square every second:
|
||||
|
||||
from time import sleep
|
||||
from random import randint
|
||||
|
||||
from pgfw.Game import Game
|
||||
|
||||
# inheriting from Game allows you to customize your project
|
||||
class SampleGame(Game):
|
||||
|
||||
square_width = 30
|
||||
|
||||
# update runs every frame, you can think of it as the mainloop
|
||||
def update(self):
|
||||
sleep(1)
|
||||
screen = self.get_screen()
|
||||
bounds = screen.get_size()
|
||||
screen.fill((0, 0, 0))
|
||||
screen.fill((255, 255, 255),
|
||||
(randint(0, bounds[0]), randint(0, bounds[1]),
|
||||
self.square_width, self.square_width))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# the play method begins the project's animation
|
||||
SampleGame().play()
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This software is dedicated to the public domain. See
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ for details.
|
||||
|
||||
|
||||
Contact
|
||||
=======
|
||||
|
||||
frank dot s dot demarco at gmail
|
||||
http://usethematrixze.us
|
60
README.md
60
README.md
|
@ -1,60 +0,0 @@
|
|||
Pygame Framework
|
||||
================
|
||||
|
||||
PGFW is a game framework that facilitates the creation and development of [Pygame][] projects. It contains a class `Game` which can be inherited and used as a project skeleton. There are classes for features such as sprites, animations, audio, b-splines, geometry, and screen capture. It is a collection of code used in previous projects with a focus on creating 2D games. Some examples of games using it are [Picture Processing][], [Scrapeboard][], and [Cakefoot][].
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* Python 3+
|
||||
* [Pygame][] 1.9+
|
||||
|
||||
Start a project
|
||||
---------------
|
||||
|
||||
Clone the repository (or [download](https://open.shampoo.ooo/shampoo/pgfw/archive/main.zip) and unzip it)
|
||||
|
||||
git clone https://open.shampoo.ooo/shampoo/pgfw
|
||||
|
||||
Save the following script at the root of the repository to create a project that redraws a square at a random location every second. The project can be run with `python3 [SCRIPT]`. Maybe some cats may even like this game. This script is also available in [sample.py][].
|
||||
|
||||
from time import sleep
|
||||
from random import randint
|
||||
|
||||
from pgfw.Game import Game
|
||||
|
||||
class SampleGame(Game):
|
||||
|
||||
square_width = 30
|
||||
|
||||
# instructions in the update method automatically run once every frame
|
||||
def update(self):
|
||||
sleep(1)
|
||||
screen = self.get_screen()
|
||||
bounds = screen.get_size()
|
||||
screen.fill((0, 0, 0))
|
||||
screen.fill((255, 255, 255),
|
||||
(randint(0, bounds[0]), randint(0, bounds[1]),
|
||||
self.square_width, self.square_width))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
SampleGame().run()
|
||||
|
||||
To further build this example, the [Pygame API][] and classes in [pgfw/][] could be imported and used directly in the update function.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Unrestricted use, under the zlib license, see [LICENSE.txt][]
|
||||
|
||||
![Pygame powered](https://www.pygame.org/docs/_static/pygame_powered.png)
|
||||
|
||||
[Pygame]: https://pygame.org
|
||||
[Pygame API]: https://www.pygame.org/docs
|
||||
[Picture Processing]: https://ohsqueezy.itch.io/ppu
|
||||
[Scrapeboard]: https://scrape.nugget.fun
|
||||
[Cakefoot]: https://ohsqueezy.itch.io/cakefoot
|
||||
[sample.py]: sample.py
|
||||
[pgfw/]: pgfw/
|
||||
[LICENSE.txt]: LICENSE.txt
|
|
@ -1,151 +1,62 @@
|
|||
import collections
|
||||
import pygame
|
||||
|
||||
from .GameChild import GameChild
|
||||
class Animation:
|
||||
|
||||
class Animation(GameChild):
|
||||
def __init__(self, frame_duration, skip_frames=False):
|
||||
self.reset_ticks()
|
||||
self.updates_this_cycle = 1
|
||||
self.overflow = 0
|
||||
self.update_count = 1
|
||||
self.actual_frame_duration = 0
|
||||
self.target_frame_duration = frame_duration
|
||||
self.skip_frames = skip_frames
|
||||
self.stopping = False
|
||||
|
||||
def __init__(self, parent, method=None, interval=None, unfiltered=False):
|
||||
GameChild.__init__(self, parent)
|
||||
self.unfiltered = unfiltered
|
||||
self.default_method = method or self.build_frame
|
||||
self.accounts = collections.OrderedDict()
|
||||
self.register(self.default_method, interval=interval)
|
||||
self.last_update = 0
|
||||
|
||||
def build_frame(self):
|
||||
pass
|
||||
|
||||
def register(self, *args, **kwargs):
|
||||
interval = None
|
||||
if "interval" in kwargs:
|
||||
interval = kwargs["interval"]
|
||||
for method in args:
|
||||
if method not in self.accounts:
|
||||
self.accounts[method] = Account(interval, self)
|
||||
else:
|
||||
self.accounts[method].set_interval(interval)
|
||||
|
||||
def play(self, method=None, interval=None, delay=0, play_once=False, **kwargs):
|
||||
account = self.accounts[self.get_default(method)]
|
||||
account.set_delay(delay)
|
||||
account.set_args(kwargs)
|
||||
account.set_play_once(play_once)
|
||||
if interval:
|
||||
account.set_interval(interval)
|
||||
account.play()
|
||||
|
||||
def get_default(self, method=None):
|
||||
if not method:
|
||||
method = self.default_method
|
||||
elif isinstance(method, str):
|
||||
return getattr(self, method)
|
||||
return method
|
||||
|
||||
def halt(self, method=None):
|
||||
if not method:
|
||||
for account in self.accounts.values():
|
||||
account.halt()
|
||||
else:
|
||||
if method in self.accounts:
|
||||
self.accounts[method].halt()
|
||||
|
||||
def is_playing(self, method=None, check_all=False, include_delay=False):
|
||||
if check_all:
|
||||
return any(self.is_account_playing(account, include_delay) for method, account in self.accounts.items())
|
||||
return self.is_account_playing(self.accounts[self.get_default(method)], include_delay)
|
||||
|
||||
def is_account_playing(self, account, include_delay):
|
||||
return account.playing and (include_delay or not account.delay)
|
||||
|
||||
def reset_timer(self, method=None):
|
||||
if not method:
|
||||
for account in self.accounts.values():
|
||||
account.reset_timer()
|
||||
else:
|
||||
self.accounts[method].reset_timer()
|
||||
|
||||
def update(self):
|
||||
for method, account in self.accounts.items():
|
||||
if account.update():
|
||||
method(**account.args)
|
||||
|
||||
|
||||
class Account:
|
||||
|
||||
def __init__(self, interval, animation):
|
||||
self.animation = animation
|
||||
self.time_filter = animation.get_game().time_filter
|
||||
self.set_interval(interval)
|
||||
self.set_delay(0)
|
||||
self.set_play_once(False)
|
||||
self.interval_index = 0
|
||||
self.last_frame = 0
|
||||
self.halt()
|
||||
|
||||
def set_interval(self, interval):
|
||||
if isinstance(interval, int) or isinstance(interval, str):
|
||||
interval = [interval]
|
||||
self.interval = interval
|
||||
|
||||
def set_delay(self, delay):
|
||||
self.delay = delay
|
||||
|
||||
def set_play_once(self, play_once):
|
||||
self.play_once = play_once
|
||||
|
||||
def set_args(self, args):
|
||||
self.args = args
|
||||
def reset_ticks(self):
|
||||
self.last_ticks = self.get_ticks()
|
||||
|
||||
def play(self):
|
||||
self.playing = True
|
||||
while not self.stopping:
|
||||
self.advance_frame()
|
||||
self.update_frame_duration()
|
||||
self.update_overflow()
|
||||
self.stopping = False
|
||||
|
||||
def halt(self):
|
||||
self.last_update = None
|
||||
self.playing = False
|
||||
def advance_frame(self):
|
||||
while self.update_count > 0:
|
||||
self.sequence()
|
||||
self.update_count -= 1
|
||||
if not self.skip_frames:
|
||||
break
|
||||
|
||||
def update(self):
|
||||
if self.playing:
|
||||
if self.animation.unfiltered:
|
||||
ticks = self.time_filter.get_unfiltered_ticks()
|
||||
else:
|
||||
ticks = self.time_filter.get_ticks()
|
||||
self.update_delay(ticks)
|
||||
if not self.delay:
|
||||
interval = self.interval
|
||||
if interval:
|
||||
if ticks - self.last_frame < self.get_current_interval():
|
||||
return False
|
||||
self.last_frame = ticks
|
||||
self.increment_interval_index()
|
||||
if self.play_once:
|
||||
self.halt()
|
||||
return True
|
||||
def update_frame_duration(self):
|
||||
last_ticks = self.last_ticks
|
||||
actual_frame_duration = self.get_ticks() - last_ticks
|
||||
last_ticks = self.get_ticks()
|
||||
wait_duration = self.get_configuration().get("display", "wait-duration",
|
||||
"int")
|
||||
while actual_frame_duration < self.target_frame_duration:
|
||||
pygame.time.wait(wait_duration)
|
||||
actual_frame_duration += self.get_ticks() - last_ticks
|
||||
last_ticks = self.get_ticks()
|
||||
self.actual_frame_duration = actual_frame_duration
|
||||
self.last_ticks = last_ticks
|
||||
|
||||
def get_current_interval(self):
|
||||
return self.interval[self.interval_index]
|
||||
def get_ticks(self):
|
||||
return pygame.time.get_ticks()
|
||||
|
||||
def increment_interval_index(self, increment=1):
|
||||
index = self.interval_index + increment
|
||||
while index >= len(self.interval):
|
||||
index -= len(self.interval)
|
||||
self.interval_index = index
|
||||
def update_overflow(self):
|
||||
self.update_count = 1
|
||||
target_frame_duration = self.target_frame_duration
|
||||
overflow = self.overflow
|
||||
overflow += self.actual_frame_duration - target_frame_duration
|
||||
while overflow > target_frame_duration:
|
||||
self.update_count += 1
|
||||
overflow -= target_frame_duration
|
||||
overflow = self.overflow
|
||||
|
||||
def reset_interval(self):
|
||||
self.interval_index = 0
|
||||
def stop(self):
|
||||
self.stopping = True
|
||||
|
||||
def reset_timer(self):
|
||||
if self.animation.unfiltered:
|
||||
ticks = self.time_filter.get_unfiltered_ticks()
|
||||
else:
|
||||
ticks = self.time_filter.get_ticks()
|
||||
self.last_frame = ticks
|
||||
|
||||
def update_delay(self, ticks):
|
||||
delay = self.delay
|
||||
if delay > 0:
|
||||
last_update = self.last_update or ticks
|
||||
delay -= ticks - last_update
|
||||
if delay < 0:
|
||||
delay = 0
|
||||
self.last_update = ticks
|
||||
self.delay = delay
|
||||
def clear_queue(self):
|
||||
self.update_count = 1
|
||||
|
|
963
pgfw/Audio.py
963
pgfw/Audio.py
|
@ -1,911 +1,70 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
|
||||
import os, re, shutil, pygame, sys, collections
|
||||
from pygame.mixer import Channel, Sound, music
|
||||
|
||||
from .GameChild import *
|
||||
from .Sprite import *
|
||||
from .Input import *
|
||||
from .Animation import *
|
||||
from .extension import *
|
||||
from GameChild import *
|
||||
from Input import *
|
||||
|
||||
class Audio(Animation):
|
||||
class Audio(GameChild):
|
||||
|
||||
UP, DOWN = .1, -.1
|
||||
CONFIG_SEPARATOR = ","
|
||||
current_channel = None
|
||||
paused = False
|
||||
muted = False
|
||||
|
||||
def __init__(self, game):
|
||||
Animation.__init__(self, game)
|
||||
self.current_bgm = None
|
||||
self.volume = 1.0
|
||||
self.pre_muted_volume = 1.0
|
||||
if self.check_command_line("-mute"):
|
||||
self.get_configuration().set("audio", "volume", 0)
|
||||
self.register(self.play_sfx)
|
||||
if self.get_configuration("audio", "panel-enabled"):
|
||||
self.audio_panel = AudioPanel(self)
|
||||
GameChild.__init__(self, game)
|
||||
self.load_fx()
|
||||
self.subscribe_to(self.get_user_event_id(), self.mute)
|
||||
|
||||
def load_fx(self):
|
||||
fx = {}
|
||||
if self.get_configuration().has_option("audio", "sfx-path"):
|
||||
root = self.get_resource("audio", "sfx-path")
|
||||
for name in listdir(root):
|
||||
fx[name.split(".")[0]] = Sound(join(root, name))
|
||||
self.fx = fx
|
||||
|
||||
def mute(self, event):
|
||||
if self.is_command(event, "mute"):
|
||||
self.muted = not self.muted
|
||||
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.audio_panel = None
|
||||
self.subscribe(self.respond)
|
||||
self.sfx = {}
|
||||
self.bgm = {}
|
||||
if self.get_configuration("audio", "auto-load"):
|
||||
self.load_sfx()
|
||||
self.load_bgm()
|
||||
self.set_volume(self.get_configuration("audio", "volume"))
|
||||
self.current_channel = Sound(path).play(-1)
|
||||
self.set_volume()
|
||||
|
||||
def set_volume(self, volume=None, increment=None, mute=False, unmute=False):
|
||||
if mute:
|
||||
self.pre_muted_volume = self.volume
|
||||
self.volume = 0
|
||||
elif unmute and self.pre_muted_volume is not None:
|
||||
self.volume = self.pre_muted_volume
|
||||
self.pre_muted_volume = None
|
||||
elif increment:
|
||||
self.volume = clamp(self.volume + increment, 0, 1.0)
|
||||
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):
|
||||
if not self.muted:
|
||||
self.fx[name].play()
|
||||
|
||||
def pause(self):
|
||||
channel = self.current_channel
|
||||
paused = self.paused
|
||||
if paused:
|
||||
music.unpause()
|
||||
if channel:
|
||||
channel.unpause()
|
||||
else:
|
||||
self.volume = volume
|
||||
self.get_configuration().set("audio", "volume", self.volume)
|
||||
if pygame.mixer.music.get_busy():
|
||||
pygame.mixer.music.set_volume(self.current_bgm.volume * self.volume)
|
||||
for ii in range(pygame.mixer.get_num_channels()):
|
||||
channel = pygame.mixer.Channel(ii)
|
||||
if channel.get_busy():
|
||||
channel.set_volume(channel.get_sound().get_volume() * self.volume)
|
||||
|
||||
def get_volume(self):
|
||||
return self.volume
|
||||
|
||||
def set_channel_volume(self, channel, *args):
|
||||
'''
|
||||
Set channel volume taking global volume into account. One or two values can be passed into *args.
|
||||
A single value will affect both left and right speakers. Two values will be used as the left and right
|
||||
speakers separately. This is the behavior of pygame's Channel.set_volume method
|
||||
'''
|
||||
for ii in range(len(args)):
|
||||
args[ii] *= self.volume
|
||||
channel.set_volume(*args)
|
||||
|
||||
def respond(self, event):
|
||||
compare = self.get_game().delegate.compare
|
||||
if compare(event, "volume-mute"):
|
||||
if self.volume > 0:
|
||||
self.set_volume(mute=True)
|
||||
else:
|
||||
self.set_volume(unmute=True)
|
||||
elif compare(event, "volume-up"):
|
||||
self.set_volume(increment=self.UP)
|
||||
elif compare(event, "volume-down"):
|
||||
self.set_volume(increment=self.DOWN)
|
||||
|
||||
def load_sfx(self, sfx_location=None):
|
||||
"""
|
||||
Load SFX from paths defined in config. This can be run without arguments, and it will attempt to auto find
|
||||
SFX following the below procedure. If sfx_location is set, paths for SFX files in the config are overridden.
|
||||
|
||||
Auto-loading SFX procedure:
|
||||
|
||||
* load config file name/path definitions at init
|
||||
* check project specific sfx paths at init, load any that don't conflict
|
||||
* check default sfx paths at init, load any that don't conflict
|
||||
* repository paths are not loaded at init but can replace loaded paths
|
||||
and get written to config file
|
||||
"""
|
||||
for name, sfx_definition in self.get_configuration("sfx").items():
|
||||
sfx_definition_members = sfx_definition.split(self.CONFIG_SEPARATOR)
|
||||
# default values for everything besides path in case those aren't included in the config definition
|
||||
path, volume, fade_out, loops, maxtime = sfx_definition_members[0], 1.0, 0, 0, 0
|
||||
# format for an SFX defintion in config is: "name = path[, volume][, fade out][, loops][, maxtime]"
|
||||
for ii, member in enumerate(sfx_definition_members[1:]):
|
||||
if ii == 0:
|
||||
volume = float(member)
|
||||
elif ii == 1:
|
||||
fade_out = float(member)
|
||||
elif ii == 2:
|
||||
loops = int(member)
|
||||
elif ii == 3:
|
||||
maxtime = float(member)
|
||||
self.load_sfx_file(
|
||||
path, name, True, volume=volume, fade_out=fade_out, loops=loops, maxtime=maxtime)
|
||||
# override config definitions of SFX paths
|
||||
if sfx_location is None:
|
||||
sfx_location = self.get_configuration("audio", "sfx-project-path") + \
|
||||
self.get_configuration("audio", "sfx-default-path")
|
||||
if isinstance(sfx_location, str):
|
||||
sfx_location = [sfx_location]
|
||||
for root in sfx_location:
|
||||
prefix = ""
|
||||
root = self.get_resource(root)
|
||||
if root:
|
||||
print("checking {} for sound effects".format(root))
|
||||
if os.path.isfile(root):
|
||||
self.load_sfx_file(root)
|
||||
else:
|
||||
for node, branches, leaves in os.walk(root, followlinks=True):
|
||||
for leaf in leaves:
|
||||
# use path elements to prepend subdirectories to the SFX name
|
||||
print(r"{}".format(re.escape(root)), r"{}".format(re.escape(node)), rf"{root}", rf"{node}")
|
||||
prefix = re.sub(r"{}".format(re.escape(root)), r"", r"{}".format(node))
|
||||
print(prefix)
|
||||
prefix = re.sub(rf"^\{os.path.sep}", r"", prefix)
|
||||
if prefix:
|
||||
prefix = re.sub(rf"\{os.path.sep}", r"_", prefix) + "_"
|
||||
self.load_sfx_file(os.path.join(node, leaf,), prefix=prefix)
|
||||
|
||||
def load_sfx_file(self, path, name=None, replace=False, prefix="", volume=1.0, fade_out=0, loops=0, maxtime=0):
|
||||
path = self.get_resource(path)
|
||||
if path and self.is_loadable(path):
|
||||
if name is None:
|
||||
name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path))
|
||||
if not replace and name in self.sfx:
|
||||
print("skipping existing sound effect for {}: {}".format(name, path))
|
||||
else:
|
||||
print("loading sound effect {} into {}".format(path, name))
|
||||
self.sfx[name] = SoundEffect(self, path, volume, loops, fade_out, maxtime=maxtime)
|
||||
return True
|
||||
else:
|
||||
print("couldn't load sound effect, path is not loadable {}".format(path))
|
||||
return False
|
||||
|
||||
def get_sfx(self, name):
|
||||
'''
|
||||
Get a SoundEffect object (which inherits pygame's Sound) from this object's dictonary of loaded sfx
|
||||
'''
|
||||
return self.sfx[name]
|
||||
|
||||
def play_sfx(self, name, loops=None, maxtime=None, fade_ms=None, position=None, x=None):
|
||||
return self.sfx[name].play(loops, maxtime, fade_ms, position, x)
|
||||
|
||||
def load_bgm(self):
|
||||
"""
|
||||
Loading BGM procedure:
|
||||
|
||||
- Check project specific BGM paths and load files found in those paths.
|
||||
- Load config file name/path definitions, overwriting existing. This means the config file
|
||||
definitions have precedence over the automatic loading of files placed in folders.
|
||||
|
||||
Further editing of BGM while the game is running can be done through the AudioPanel object.
|
||||
"""
|
||||
# First load BGM files found in the BGM path set in the configuration
|
||||
for root in self.get_configuration("audio", "bgm-project-path"):
|
||||
# look for path in resource folders
|
||||
root = self.get_resource(root)
|
||||
if root is not None and os.path.exists(root):
|
||||
print("checking {} for background music files".format(root))
|
||||
if os.path.isfile(root):
|
||||
self.set_bgm(root)
|
||||
else:
|
||||
for node, branches, leaves in os.walk(root, followlinks=True):
|
||||
for leaf in leaves:
|
||||
prefix = re.sub(root, "", node)
|
||||
prefix = re.sub("^/", "", prefix)
|
||||
if prefix:
|
||||
prefix = re.sub("/", "_", prefix) + "_"
|
||||
self.set_bgm(os.path.join(node, leaf), prefix=prefix)
|
||||
# Next load BGM paths defined in the configuration. If any of these have the same name as
|
||||
# BGM loaded by the previous code block, they will be overwritten to give the config file
|
||||
# precedence over automatic BGM detection.
|
||||
print("checking configuration for background music definitions".format(root))
|
||||
for name, bgm_definition in self.get_configuration("bgm").items():
|
||||
bgm_definition_members = bgm_definition.split(self.CONFIG_SEPARATOR)
|
||||
path, volume = bgm_definition_members[0], 1.0
|
||||
for ii, member in enumerate(bgm_definition_members[1:]):
|
||||
if ii == 0:
|
||||
volume = float(member)
|
||||
self.set_bgm(path, name, volume=volume)
|
||||
|
||||
def set_bgm(self, path, name=None, prefix="", volume=1.0):
|
||||
path = self.get_resource(path)
|
||||
try:
|
||||
pygame.mixer.music.load(path)
|
||||
except:
|
||||
print("can't load {} as music".format(path))
|
||||
return False
|
||||
if name is None:
|
||||
name = os.path.basename(path).split(".")[0]
|
||||
print("setting {} background music to {}".format(name, path))
|
||||
self.bgm[prefix + name] = BGM(self, path, volume)
|
||||
if self.current_bgm is None:
|
||||
self.current_bgm = self.bgm[prefix + name]
|
||||
return True
|
||||
|
||||
def play_bgm(self, name=None, store_as_current=True, start=0):
|
||||
if name is None:
|
||||
bgm = self.current_bgm
|
||||
else:
|
||||
bgm = self.bgm[name]
|
||||
pygame.mixer.music.load(bgm.get_path())
|
||||
try:
|
||||
pygame.mixer.music.play(-1, start)
|
||||
except pygame.error:
|
||||
pygame.mixer.music.play(-1)
|
||||
pygame.mixer.music.set_volume(bgm.get_volume() * self.get_configuration("audio", "volume"))
|
||||
if store_as_current:
|
||||
self.current_bgm = bgm
|
||||
|
||||
def is_sound_file(self, path):
|
||||
return path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions")
|
||||
|
||||
def is_loadable(self, path):
|
||||
try:
|
||||
pygame.mixer.Sound(path)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_streamable(self, path):
|
||||
try:
|
||||
pygame.mixer.music.load(path)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_audio_panel_active(self):
|
||||
return self.audio_panel and self.audio_panel.active
|
||||
|
||||
def update(self):
|
||||
Animation.update(self)
|
||||
if self.audio_panel:
|
||||
self.audio_panel.update()
|
||||
|
||||
|
||||
class BGM(GameChild):
|
||||
|
||||
def __init__(self, parent, path, volume=1.0):
|
||||
GameChild.__init__(self, parent)
|
||||
self.path = path
|
||||
self.volume = volume
|
||||
|
||||
def get_path(self):
|
||||
return self.path
|
||||
|
||||
def adjust_volume(self, increment):
|
||||
self.volume = clamp(self.volume + increment, 0, 1.0)
|
||||
if self.parent.current_bgm == self:
|
||||
pygame.mixer.music.set_volume(self.volume)
|
||||
return self.volume
|
||||
|
||||
def get_volume(self):
|
||||
return self.volume
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == other.path
|
||||
|
||||
|
||||
class SoundEffect(GameChild, pygame.mixer.Sound):
|
||||
|
||||
def __init__(self, parent, path, volume=1.0, loops=0, fade_out_length=0, fade_in_length=0, maxtime=0):
|
||||
self.path = path
|
||||
GameChild.__init__(self, parent)
|
||||
pygame.mixer.Sound.__init__(self, path)
|
||||
self.display_surface = self.get_display_surface()
|
||||
self.local_volume = volume
|
||||
self.loops = loops
|
||||
self.fade_out_length = fade_out_length
|
||||
self.fade_in_length = fade_in_length
|
||||
self.maxtime = maxtime
|
||||
|
||||
def play(self, loops=None, maxtime=None, fade_ms=None, position=None, x=None):
|
||||
self.set_volume(self.local_volume * self.get_configuration("audio", "volume"))
|
||||
if loops is None:
|
||||
loops = self.loops
|
||||
if maxtime is None:
|
||||
maxtime = int(self.maxtime * 1000)
|
||||
if fade_ms is None:
|
||||
fade_ms = int(self.fade_in_length * 1000)
|
||||
channel = pygame.mixer.Sound.play(self, loops, maxtime, fade_ms)
|
||||
if x is not None:
|
||||
position = float(x) / self.display_surface.get_width()
|
||||
if position is not None and channel is not None:
|
||||
channel.set_volume(*self.get_panning(position))
|
||||
if self.fade_out_length > 0:
|
||||
self.fadeout(int(self.fade_out_length * 1000))
|
||||
return channel
|
||||
|
||||
def get_panning(self, position):
|
||||
return 1 - max(0, ((position - .5) * 2)), 1 + min(0, ((position - .5) * 2))
|
||||
|
||||
def adjust_volume(self, increment):
|
||||
self.local_volume += increment
|
||||
if self.local_volume > 1.0:
|
||||
self.local_volume = 1.0
|
||||
elif self.local_volume < 0:
|
||||
self.local_volume = 0
|
||||
return self.local_volume
|
||||
|
||||
def adjust_loop_count(self, increment):
|
||||
self.loops += increment
|
||||
if self.loops < -1:
|
||||
self.loops = -1
|
||||
return self.loops
|
||||
|
||||
def adjust_fade_in_length(self, increment):
|
||||
self.fade_in_length += increment
|
||||
limit = self.get_length() * (self.loops + 1)
|
||||
if self.fade_in_length < 0:
|
||||
self.fade_in_length = 0
|
||||
elif self.loops > -1 and self.fade_in_length > limit:
|
||||
self.fade_in_length = limit
|
||||
return self.fade_in_length
|
||||
|
||||
def adjust_fade_out_length(self, increment):
|
||||
self.fade_out_length += increment
|
||||
limit = self.get_length() * (self.loops + 1)
|
||||
if self.fade_out_length < 0:
|
||||
self.fade_out_length = 0
|
||||
elif self.loops > -1 and self.fade_out_length > limit:
|
||||
self.fade_out_length = limit
|
||||
return self.fade_out_length
|
||||
|
||||
def adjust_maxtime(self, increment):
|
||||
self.maxtime += increment
|
||||
limit = self.get_length() * (self.loops + 1)
|
||||
if self.maxtime < 0:
|
||||
self.maxtime = 0
|
||||
elif self.loops > -1 and self.maxtime > limit:
|
||||
self.maxtime = limit
|
||||
return self.maxtime
|
||||
|
||||
|
||||
class AudioPanel(Animation):
|
||||
|
||||
MARGIN = 6
|
||||
|
||||
def __init__(self, parent):
|
||||
Animation.__init__(self, parent)
|
||||
self.rows = []
|
||||
self.bgm_elapsed = None
|
||||
font_path = self.get_resource(self.get_configuration("audio", "panel-font"))
|
||||
self.font_large = pygame.font.Font(font_path, 15)
|
||||
self.font_medium = pygame.font.Font(font_path, 12)
|
||||
self.font_small = pygame.font.Font(font_path, 8)
|
||||
self.file_browser = AudioPanelFileBrowser(self)
|
||||
self.subscribe(self.respond)
|
||||
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.row_offset = 0
|
||||
self.deactivate()
|
||||
|
||||
def get_selected(self):
|
||||
for row in self.rows:
|
||||
if row.selected:
|
||||
return row
|
||||
|
||||
def activate(self):
|
||||
pygame.mouse.set_visible(True)
|
||||
self.active = True
|
||||
if pygame.mixer.music.get_busy():
|
||||
self.bgm_elapsed = pygame.mixer.music.get_pos() / 1000
|
||||
pygame.mixer.music.stop()
|
||||
pygame.mixer.stop()
|
||||
# self.build()
|
||||
|
||||
def deactivate(self):
|
||||
pygame.mouse.set_visible(self.get_configuration("mouse", "visible"))
|
||||
self.active = False
|
||||
if self.bgm_elapsed is not None:
|
||||
self.get_audio().play_bgm(start=self.bgm_elapsed)
|
||||
self.file_browser.hide()
|
||||
|
||||
def respond(self, event):
|
||||
if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx:
|
||||
if self.active:
|
||||
self.deactivate()
|
||||
else:
|
||||
self.activate()
|
||||
if not self.rows:
|
||||
self.build()
|
||||
elif self.active:
|
||||
if event.type == pygame.MOUSEBUTTONDOWN and self.file_browser.is_hidden():
|
||||
if event.button == 5:
|
||||
self.row_offset += 1
|
||||
elif event.button == 4:
|
||||
self.row_offset -= 1
|
||||
elif event.button == 3:
|
||||
self.deactivate()
|
||||
|
||||
def build(self):
|
||||
for row in self.rows:
|
||||
row.unsubscribe()
|
||||
del row
|
||||
self.rows = []
|
||||
for key in sorted(self.parent.bgm):
|
||||
self.rows.append(AudioPanelRow(self, key, True))
|
||||
for key in sorted(self.parent.sfx):
|
||||
self.rows.append(AudioPanelRow(self, key))
|
||||
|
||||
def update(self):
|
||||
if self.active:
|
||||
Animation.update(self)
|
||||
ds = self.get_display_surface()
|
||||
dsr = ds.get_rect()
|
||||
ds.fill((0, 0, 0))
|
||||
corner = Vector(self.MARGIN, self.MARGIN)
|
||||
index = self.row_offset
|
||||
for row in self.rows:
|
||||
row.location.bottom = 0
|
||||
row.update()
|
||||
while corner.y < dsr.height - self.MARGIN:
|
||||
row = self.rows[index % len(self.rows)]
|
||||
row.location.topleft = corner.copy()
|
||||
row.update()
|
||||
corner.y += row.location.height + self.MARGIN
|
||||
index += 1
|
||||
self.file_browser.update()
|
||||
|
||||
|
||||
class AudioPanelRow(BlinkingSprite):
|
||||
|
||||
BACKGROUND = pygame.Color(128, 192, 255, 255)
|
||||
FOREGROUND = pygame.Color(0, 0, 0, 255)
|
||||
WIDTH = .5
|
||||
HEIGHT = 30
|
||||
INDENT = 4
|
||||
MAX_NAME_WIDTH = .7
|
||||
SLIDER_W = 60
|
||||
BUTTON_W = 30
|
||||
|
||||
def __init__(self, parent, key, is_bgm=False):
|
||||
BlinkingSprite.__init__(self, parent, 500)
|
||||
self.key = key
|
||||
self.selected = False
|
||||
self.font = self.parent.font_large
|
||||
self.is_bgm = is_bgm
|
||||
self.build()
|
||||
font_medium = self.parent.font_medium
|
||||
font_small = self.parent.font_small
|
||||
if self.is_bgm:
|
||||
volume = self.get_bgm().volume
|
||||
volume_function = self.get_bgm().adjust_volume
|
||||
else:
|
||||
volume = self.get_sound_effect().local_volume
|
||||
volume_function = self.get_sound_effect().adjust_volume
|
||||
self.volume_spinner = AudioPanelSpinner(
|
||||
self, font_medium, font_small, self.SLIDER_W, self.location.h, .05,
|
||||
volume, volume_function, self.FOREGROUND, self.BACKGROUND, 2, "vol")
|
||||
if not self.is_bgm:
|
||||
self.fade_out_spinner = AudioPanelSpinner(
|
||||
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
|
||||
self.get_sound_effect().fade_out_length,
|
||||
self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND,
|
||||
self.BACKGROUND, 1, "fade")
|
||||
self.loops_spinner = AudioPanelSpinner(
|
||||
self, font_medium, font_small, self.SLIDER_W, self.location.h, 1,
|
||||
self.get_sound_effect().loops,
|
||||
self.get_sound_effect().adjust_loop_count, self.FOREGROUND,
|
||||
self.BACKGROUND, 0, "loops")
|
||||
self.maxtime_spinner = AudioPanelSpinner(
|
||||
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
|
||||
self.get_sound_effect().maxtime,
|
||||
self.get_sound_effect().adjust_maxtime, self.FOREGROUND,
|
||||
self.BACKGROUND, 1, "cutoff")
|
||||
if self.is_bgm:
|
||||
callback, kwargs = self.get_game().get_audio().play_bgm, {"name": self.key, "store_as_current": False}
|
||||
else:
|
||||
callback, kwargs = self.get_sound_effect().play, {}
|
||||
self.play_button = AudioPanelButton(self, callback, kwargs)
|
||||
frame = pygame.Surface((self.BUTTON_W, self.location.h), SRCALPHA)
|
||||
frame.fill(self.BACKGROUND)
|
||||
stop_button_frame = frame.copy()
|
||||
w, h = frame.get_size()
|
||||
pygame.draw.polygon(frame, self.FOREGROUND, ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
|
||||
self.play_button.add_frame(frame)
|
||||
if self.is_bgm:
|
||||
callback = pygame.mixer.music.stop
|
||||
else:
|
||||
callback = self.get_sound_effect().stop
|
||||
self.stop_button = AudioPanelButton(self, callback)
|
||||
stop_button_frame.fill(self.FOREGROUND, (w * .25, h * .25, w * .5, h * .5))
|
||||
self.stop_button.add_frame(stop_button_frame)
|
||||
self.stop_blinking()
|
||||
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
|
||||
def respond(self, event):
|
||||
if self.parent.active and event.button == 1:
|
||||
if self.parent.file_browser.is_hidden() and self.location.collidepoint(event.pos):
|
||||
if not self.selected:
|
||||
self.parent.file_browser.visit(self.parent.file_browser.HOME)
|
||||
self.selected = True
|
||||
self.start_blinking()
|
||||
self.parent.file_browser.unhide()
|
||||
elif self.parent.file_browser.is_hidden():
|
||||
if self.selected:
|
||||
self.selected = False
|
||||
self.stop_blinking()
|
||||
|
||||
def unsubscribe(self, callback=None, kind=None):
|
||||
if callback is None:
|
||||
callback = self.respond
|
||||
kind = pygame.MOUSEBUTTONDOWN
|
||||
GameChild.unsubscribe(self, self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
self.play_button.unsubscribe()
|
||||
self.stop_button.unsubscribe()
|
||||
if not self.is_bgm:
|
||||
for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner:
|
||||
spinner.unsubscribe()
|
||||
|
||||
def build(self):
|
||||
ds = self.get_display_surface()
|
||||
dsr = ds.get_rect()
|
||||
surface = pygame.Surface((dsr.w * self.WIDTH, self.HEIGHT), pygame.SRCALPHA)
|
||||
surface.fill(self.BACKGROUND)
|
||||
self.add_frame(surface)
|
||||
name_sprite = Sprite(self)
|
||||
name = self.font.render(self.key + ":", True, self.FOREGROUND)
|
||||
if name.get_width() > int(self.location.w * self.MAX_NAME_WIDTH):
|
||||
crop = pygame.Rect(0, 0, int(self.location.w * self.MAX_NAME_WIDTH), name.get_height())
|
||||
crop.right = name.get_rect().right
|
||||
name = name.subsurface(crop)
|
||||
name_sprite.add_frame(name)
|
||||
name_sprite.display_surface = surface
|
||||
name_sprite.location.midleft = self.INDENT, self.location.centery
|
||||
name_sprite.update()
|
||||
file_sprite = Sprite(self)
|
||||
box = get_boxed_surface(
|
||||
pygame.Surface((self.location.w - name_sprite.location.w - self.INDENT * 3,
|
||||
self.location.height - 4), pygame.SRCALPHA),
|
||||
border=self.FOREGROUND)
|
||||
file_sprite.add_frame(box)
|
||||
file_sprite.location.midright = self.location.right - self.INDENT, self.location.centery
|
||||
file_sprite.display_surface = surface
|
||||
file_name_sprite = Sprite(self)
|
||||
if self.is_bgm:
|
||||
file_name = self.get_bgm().path
|
||||
else:
|
||||
file_name = self.get_sound_effect().path
|
||||
file_name_text = self.font.render(file_name, True, self.FOREGROUND)
|
||||
file_name_sprite.add_frame(file_name_text)
|
||||
file_name_sprite.display_surface = box
|
||||
file_name_sprite.location.midright = file_sprite.location.w - self.INDENT, file_sprite.location.h / 2
|
||||
file_name_sprite.update()
|
||||
file_sprite.update()
|
||||
|
||||
def get_sound_effect(self):
|
||||
return self.get_game().get_audio().sfx[self.key]
|
||||
|
||||
def get_bgm(self):
|
||||
return self.get_game().get_audio().bgm[self.key]
|
||||
|
||||
def update_config(self):
|
||||
if self.is_bgm:
|
||||
section_name = "bgm"
|
||||
else:
|
||||
section_name = "sfx"
|
||||
if not self.get_configuration().has_section(section_name):
|
||||
self.get_configuration().add_section(section_name)
|
||||
if self.is_bgm:
|
||||
bgm = self.get_bgm()
|
||||
config_value = "{}, {:.2f}".format(bgm.path, bgm.volume)
|
||||
else:
|
||||
sound_effect = self.get_sound_effect()
|
||||
config_value = "{}, {:.2f}, {:.2f}, {}, {:.2f}".format(
|
||||
sound_effect.path, sound_effect.local_volume, sound_effect.fade_out_length,
|
||||
sound_effect.loops, sound_effect.maxtime)
|
||||
self.get_configuration().set(section_name, self.key, config_value)
|
||||
config_path = self.get_configuration().locate_project_config_file()
|
||||
backup_path = config_path + ".backup"
|
||||
shutil.copyfile(config_path, backup_path)
|
||||
self.get_configuration().write(open(config_path, "w"))
|
||||
|
||||
def set_clickable(self, clickable=True):
|
||||
self.play_button.set_clickable(clickable)
|
||||
self.stop_button.set_clickable(clickable)
|
||||
self.volume_spinner.set_clickable(clickable)
|
||||
if not self.is_bgm:
|
||||
self.fade_out_spinner.set_clickable(clickable)
|
||||
self.loops_spinner.set_clickable(clickable)
|
||||
self.maxtime_spinner.set_clickable(clickable)
|
||||
|
||||
def update(self):
|
||||
self.play_button.location.midleft = self.location.move(5, 0).midright
|
||||
self.stop_button.location.midleft = self.play_button.location.midright
|
||||
self.volume_spinner.location.midleft = self.stop_button.location.move(5, 0).midright
|
||||
if not self.is_bgm:
|
||||
self.fade_out_spinner.location.midleft = self.volume_spinner.location.midright
|
||||
self.loops_spinner.location.midleft = self.fade_out_spinner.location.midright
|
||||
self.maxtime_spinner.location.midleft = self.loops_spinner.location.midright
|
||||
Sprite.update(self)
|
||||
self.volume_spinner.update()
|
||||
if not self.is_bgm:
|
||||
self.fade_out_spinner.update()
|
||||
self.loops_spinner.update()
|
||||
self.maxtime_spinner.update()
|
||||
self.play_button.update()
|
||||
self.stop_button.update()
|
||||
|
||||
|
||||
class AudioPanelFileBrowser(Sprite):
|
||||
|
||||
WIDTH = .75
|
||||
HEIGHT = .75
|
||||
COLORS = pygame.Color(255, 255, 255), pygame.Color(0, 0, 0)
|
||||
HOME, UP = "[HOME]", "[UP]"
|
||||
|
||||
def __init__(self, parent):
|
||||
Sprite.__init__(self, parent)
|
||||
self.rows = []
|
||||
self.font = self.parent.font_large
|
||||
self.previewing_sound = None
|
||||
self.previewing_sound_row = None
|
||||
ds = self.get_display_surface()
|
||||
dsr = ds.get_rect()
|
||||
surface = pygame.Surface((dsr.w * self.WIDTH - 2, dsr.h * self.HEIGHT - 2), SRCALPHA)
|
||||
surface.fill(self.COLORS[0])
|
||||
self.background = get_boxed_surface(surface, self.COLORS[0], self.COLORS[1])
|
||||
self.add_frame(self.background.copy())
|
||||
self.location.center = dsr.center
|
||||
self.reset()
|
||||
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
|
||||
def reset(self):
|
||||
if self.previewing_sound is not None:
|
||||
self.previewing_sound.stop()
|
||||
# self.visit(self.HOME)
|
||||
self.hide()
|
||||
|
||||
def respond(self, event):
|
||||
if not self.is_hidden():
|
||||
if event.button == 1:
|
||||
if self.collide(event.pos):
|
||||
for row in self.rows:
|
||||
pos = Vector(*event.pos).get_moved(-self.location.left, -self.location.top)
|
||||
if (not row.has_child("button") or pos.x < row.get_child("button").location.left) and row.collide(pos):
|
||||
full_path = os.path.join(os.path.sep.join(self.trail), row.path)
|
||||
if row.path == self.HOME or row.path == self.UP or \
|
||||
os.path.isdir(full_path) and os.access(full_path, os.R_OK):
|
||||
self.visit(row.path)
|
||||
elif os.path.isfile(full_path) and os.access(full_path, os.R_OK):
|
||||
loaded = False
|
||||
selected = self.parent.get_selected()
|
||||
if selected.is_bgm:
|
||||
loaded = self.get_audio().set_bgm(full_path, selected.key)
|
||||
else:
|
||||
loaded = self.get_audio().load_sfx_file(full_path, selected.key, True)
|
||||
if loaded:
|
||||
selected.update_config()
|
||||
self.hide()
|
||||
self.get_delegate().cancel_propagation()
|
||||
self.parent.build()
|
||||
else:
|
||||
self.hide()
|
||||
self.get_delegate().cancel_propagation()
|
||||
elif event.button == 4:
|
||||
self.row_offset -= 1
|
||||
elif event.button == 5:
|
||||
self.row_offset += 1
|
||||
|
||||
def hide(self):
|
||||
for row in self.parent.rows:
|
||||
row.selected = False
|
||||
row.stop_blinking()
|
||||
row.set_clickable(True)
|
||||
for row in self.rows:
|
||||
if row.has_child("button"):
|
||||
row.get_child("button").set_clickable(False)
|
||||
if self.previewing_sound is not None:
|
||||
self.previewing_sound.stop()
|
||||
Sprite.hide(self)
|
||||
|
||||
def unhide(self):
|
||||
for row in self.parent.rows:
|
||||
row.set_clickable(False)
|
||||
for row in self.rows:
|
||||
if row.has_child("button"):
|
||||
row.get_child("button").set_clickable()
|
||||
Sprite.unhide(self)
|
||||
|
||||
def visit(self, path):
|
||||
if path == self.UP and len(self.trail) > 1:
|
||||
path = self.trail[-2]
|
||||
self.trail = self.trail[:-2]
|
||||
self.visit(path)
|
||||
elif path != self.UP:
|
||||
self.row_offset = 0
|
||||
if path == self.HOME:
|
||||
self.trail = []
|
||||
self.paths = ["/"]
|
||||
for option in "sfx-repository-path", "sfx-default-path", "sfx-project-path", \
|
||||
"bgm-repository-path", "bgm-project-path":
|
||||
for sfx_location in self.get_configuration("audio", option):
|
||||
if self.get_resource(sfx_location):
|
||||
self.paths.append(self.get_resource(sfx_location))
|
||||
else:
|
||||
self.paths = [self.HOME]
|
||||
self.trail.append(path)
|
||||
if len(self.trail) > 1:
|
||||
self.paths.append(self.UP)
|
||||
self.paths.extend(sorted(os.listdir(os.path.sep.join(self.trail))))
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
for row in self.rows:
|
||||
if row.has_child("button"):
|
||||
row.get_child("button").unsubscribe()
|
||||
del row
|
||||
self.rows = []
|
||||
for path in self.paths:
|
||||
row = Sprite(self)
|
||||
row.path = path
|
||||
text = self.font.render(path, True, self.COLORS[1])
|
||||
surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA)
|
||||
surface.blit(text, (8, 0))
|
||||
surface.fill(self.COLORS[1], (0, surface.get_height() - 1, self.location.w, 1))
|
||||
row.add_frame(surface)
|
||||
row.display_surface = self.get_current_frame()
|
||||
row.location.bottom = 0
|
||||
self.rows.append(row)
|
||||
full_path = os.path.join(os.path.sep.join(self.trail), path)
|
||||
if self.get_audio().is_sound_file(full_path):
|
||||
button = AudioPanelButton(self, self.preview, {"path": full_path, "row": row}, [row, self])
|
||||
row.set_child("button", button)
|
||||
frame = pygame.Surface([text.get_height()] * 2, SRCALPHA)
|
||||
w, h = frame.get_size()
|
||||
pygame.draw.polygon(
|
||||
frame, self.COLORS[1], ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
|
||||
button.add_frame(frame)
|
||||
button.display_surface = row.get_current_frame()
|
||||
button.location.right = self.location.w - 10
|
||||
|
||||
def preview(self, path, row):
|
||||
is_bgm = self.parent.get_selected().is_bgm
|
||||
audio = self.get_audio()
|
||||
if is_bgm and audio.is_streamable(path) or not is_bgm and audio.is_loadable(path):
|
||||
if self.previewing_sound is not None:
|
||||
self.previewing_sound.stop()
|
||||
pygame.mixer.music.stop()
|
||||
if is_bgm:
|
||||
pygame.mixer.music.load(path)
|
||||
pygame.mixer.music.play(-1)
|
||||
else:
|
||||
self.previewing_sound = SoundEffect(self, path)
|
||||
self.previewing_sound.play()
|
||||
self.previewing_sound_row = row
|
||||
|
||||
def update(self):
|
||||
self.get_current_frame().blit(self.background, (0, 0))
|
||||
if not self.is_hidden():
|
||||
corner = Vector(1, 1)
|
||||
index = self.row_offset
|
||||
for row in self.rows:
|
||||
row.remove_locations()
|
||||
row.location.bottom = 0
|
||||
while corner.y < self.location.h:
|
||||
row = self.rows[index % len(self.rows)]
|
||||
if index - self.row_offset >= len(self.rows):
|
||||
row.add_location(corner.copy())
|
||||
else:
|
||||
row.location.topleft = corner.copy()
|
||||
corner.y += row.location.height
|
||||
index += 1
|
||||
for row in self.rows:
|
||||
row.update()
|
||||
for location in row.locations:
|
||||
if location.collidepoint(*Vector(*pygame.mouse.get_pos()).get_moved(
|
||||
-self.location.left, -self.location.top)) or \
|
||||
row == self.previewing_sound_row:
|
||||
self.get_current_frame().fill(self.COLORS[1], (
|
||||
location.topleft, (6, location.h)))
|
||||
self.get_current_frame().fill(self.COLORS[1], (
|
||||
location.move(-8, 0).topright, (6, location.h)))
|
||||
Sprite.update(self)
|
||||
|
||||
|
||||
class AudioPanelSpinner(Sprite):
|
||||
|
||||
def __init__(self, parent, font, label_font, width=80, height=48,
|
||||
magnitude=1, value=0, callback=None,
|
||||
foreground=pygame.Color(0, 0, 0),
|
||||
background=pygame.Color(255, 255, 255), precision=0,
|
||||
label_text=""):
|
||||
Sprite.__init__(self, parent)
|
||||
self.magnitude, self.value = magnitude, value
|
||||
self.background, self.foreground = background, foreground
|
||||
self.precision = precision
|
||||
self.callback = callback
|
||||
self.font = font
|
||||
self.label_font = label_font
|
||||
surface = pygame.Surface((width, height), SRCALPHA)
|
||||
surface.fill(background)
|
||||
self.add_frame(surface)
|
||||
self.label = Sprite(self)
|
||||
self.label.add_frame(self.label_font.render(label_text, True, foreground))
|
||||
self.label.display_surface = self.get_current_frame()
|
||||
self.display = Sprite(self)
|
||||
self.display.display_surface = self.get_current_frame()
|
||||
self.update_display()
|
||||
self.up_button = Sprite(self)
|
||||
self.up_button.add_frame(render_box(
|
||||
width=width - self.display.location.w - 2, height=int(height * .5) - 2,
|
||||
color=foreground, border=foreground, font=self.font, text="+"))
|
||||
self.up_button.location.left = self.display.location.right - 1
|
||||
self.up_button.display_surface = self.get_current_frame()
|
||||
self.down_button = Sprite(self)
|
||||
self.down_button.add_frame(render_box(
|
||||
width=self.up_button.location.w - 2, height=self.up_button.location.h - 1,
|
||||
border=foreground, font=self.font, text="-"))
|
||||
self.down_button.location.topleft = self.display.location.right - 1, \
|
||||
self.up_button.location.bottom - 1
|
||||
self.down_button.display_surface = self.get_current_frame()
|
||||
self.set_clickable()
|
||||
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
|
||||
def unsubscribe(self, callback=None, kind=None):
|
||||
if callback is None:
|
||||
callback = self.respond
|
||||
kind = pygame.MOUSEBUTTONDOWN
|
||||
GameChild.unsubscribe(self, callback, kind)
|
||||
|
||||
def update_display(self):
|
||||
self.display.clear_frames()
|
||||
self.display.add_frame(render_box(
|
||||
width=int(self.location.w * .7) - 2,
|
||||
border=self.foreground, font=self.font, text="{:.{precision}f}".format(
|
||||
self.value, precision=self.precision)))
|
||||
self.display.location.bottomleft = 0, self.location.h
|
||||
|
||||
def increment(self, up=True):
|
||||
step = self.magnitude * [-1, 1][up]
|
||||
self.value += step
|
||||
if self.callback is not None:
|
||||
response = self.callback(step)
|
||||
if response is not None:
|
||||
self.value = response
|
||||
self.update_display()
|
||||
|
||||
def respond(self, event):
|
||||
if self.clickable and event.button == 1:
|
||||
relative_position = Vector(*event.pos).get_moved(
|
||||
-self.location.left, -self.location.top)
|
||||
up_collides = self.up_button.collide(relative_position)
|
||||
down_collides = self.down_button.collide(relative_position)
|
||||
if up_collides or down_collides:
|
||||
if up_collides:
|
||||
self.increment()
|
||||
else:
|
||||
self.increment(False)
|
||||
self.parent.update_config()
|
||||
|
||||
def set_clickable(self, clickable=True):
|
||||
self.clickable = clickable
|
||||
|
||||
def update(self):
|
||||
self.get_current_frame().fill(self.background)
|
||||
self.label.update()
|
||||
self.up_button.update()
|
||||
self.down_button.update()
|
||||
self.display.update()
|
||||
Sprite.update(self)
|
||||
|
||||
|
||||
class AudioPanelButton(Sprite):
|
||||
|
||||
def __init__(self, parent, callback, callback_kwargs={}, containers=[], pass_mods=False):
|
||||
Sprite.__init__(self, parent)
|
||||
self.callback = callback
|
||||
self.callback_kwargs = callback_kwargs
|
||||
self.containers = containers
|
||||
self.pass_mods = pass_mods
|
||||
self.set_clickable()
|
||||
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
|
||||
|
||||
def unsubscribe(self, callback=None, kind=None):
|
||||
if callback is None:
|
||||
callback = self.respond
|
||||
kind = pygame.MOUSEBUTTONDOWN
|
||||
Sprite.unsubscribe(self, callback, kind)
|
||||
|
||||
def respond(self, event):
|
||||
if self.get_audio().audio_panel.active and self.clickable and event.button == 1:
|
||||
pos = Vector(*event.pos)
|
||||
for container in self.containers:
|
||||
pos.move(-container.location.left, -container.location.top)
|
||||
if self.collide(pos):
|
||||
if self.pass_mods:
|
||||
kwargs = collections.ChainMap(self.callback_kwargs, {"mods": pygame.key.get_mods()})
|
||||
else:
|
||||
kwargs = self.callback_kwargs
|
||||
self.callback(**kwargs)
|
||||
|
||||
def set_clickable(self, clickable=True):
|
||||
self.clickable = clickable
|
||||
music.pause()
|
||||
if channel:
|
||||
channel.pause()
|
||||
self.paused = not paused
|
||||
|
|
|
@ -1,505 +0,0 @@
|
|||
import argparse
|
||||
from os import sep, getcwd
|
||||
from os.path import join, exists, basename, dirname, expanduser
|
||||
from sys import argv, version_info
|
||||
from re import match
|
||||
from pprint import pformat
|
||||
|
||||
if version_info[0] >= 3:
|
||||
from configparser import RawConfigParser
|
||||
else:
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
class Configuration(RawConfigParser):
|
||||
|
||||
default_project_file_rel_path = "config"
|
||||
default_resource_paths = [".", "resource"]
|
||||
|
||||
def __init__(self, project_file_rel_path=None, resource_path=None, type_declarations=None):
|
||||
RawConfigParser.__init__(self)
|
||||
self.project_file_rel_path = project_file_rel_path
|
||||
self.resource_path = resource_path
|
||||
self.modifiable = {}
|
||||
self.order = []
|
||||
self.set_type_declarations(type_declarations)
|
||||
self.set_defaults()
|
||||
self.read_project_config_file()
|
||||
self.merge_command_line()
|
||||
self.modify_defaults()
|
||||
self.print_debug(self)
|
||||
|
||||
def set_type_declarations(self, type_declarations):
|
||||
if type_declarations is None:
|
||||
type_declarations = TypeDeclarations()
|
||||
self.type_declarations = type_declarations
|
||||
|
||||
def translate_path(self, path):
|
||||
new = ""
|
||||
if path and path[0] == sep:
|
||||
new += sep
|
||||
return expanduser("{0}{1}".format(new, join(*path.split(sep))))
|
||||
|
||||
def set_defaults(self):
|
||||
add_section = self.add_section
|
||||
set_option = self.set
|
||||
section = "setup"
|
||||
add_section(section)
|
||||
set_option(section, "package-root", basename(getcwd()), False)
|
||||
set_option(section, "additional-packages", "", False)
|
||||
set_option(section, "title", "", False)
|
||||
set_option(section, "classifiers", "", False)
|
||||
set_option(section, "resource-search-path", "./, resource/", False)
|
||||
set_option(section, "installation-dir", "/usr/local/share/games/", False)
|
||||
set_option(section, "changelog", "changelog", False)
|
||||
set_option(section, "description-file", "", False)
|
||||
set_option(section, "init-script", "", False)
|
||||
set_option(section, "version", "", False)
|
||||
set_option(section, "summary", "", False)
|
||||
set_option(section, "license", "", False)
|
||||
set_option(section, "platforms", "", False)
|
||||
set_option(section, "contact-name", "", False)
|
||||
set_option(section, "contact-email", "", False)
|
||||
set_option(section, "url", "", False)
|
||||
set_option(section, "requirements", "", False)
|
||||
set_option(section, "main-object", "pgfw/Game.py", False)
|
||||
set_option(section, "resource-path-identifier", "resource_path", False)
|
||||
set_option(section, "special-char-placeholder", "_", False)
|
||||
set_option(section, "whitespace-placeholder", "-", False)
|
||||
set_option(section, "windows-dist-path", "dist/win/", False)
|
||||
set_option(section, "windows-icon-path", "", False)
|
||||
set_option(section, "boolean-true-lowercase", "yes, true, t, 1", False)
|
||||
set_option(section, "osx-includes", "", False)
|
||||
section = "display"
|
||||
add_section(section)
|
||||
set_option(section, "dimensions", "480, 360", False)
|
||||
set_option(section, "frame-duration", "40", False)
|
||||
set_option(section, "wait-duration", "2", False)
|
||||
set_option(section, "caption", "", False)
|
||||
set_option(section, "centered", "yes", False)
|
||||
set_option(section, "icon-path", "", False)
|
||||
set_option(section, "skip-frames", "no", False)
|
||||
set_option(section, "fullscreen", "no", False)
|
||||
set_option(section, "windowed-flag", "wi", False)
|
||||
set_option(section, "show-framerate", "no", False)
|
||||
set_option(section, "framerate-display-flag", "fr", False)
|
||||
set_option(section, "framerate-text-size", "16", False)
|
||||
set_option(section, "framerate-text-color", "0, 0, 0", False)
|
||||
set_option(section, "framerate-text-background", "255, 255, 255", False)
|
||||
set_option(section, "framerate-position", "-1, 0", False)
|
||||
set_option(section, "use-framebuffer", "no", False)
|
||||
section = "input"
|
||||
add_section(section)
|
||||
set_option(section, "release-suffix", "-release", False)
|
||||
set_option(section, "confirm-quit", "no", False)
|
||||
section = "sprite"
|
||||
add_section(section)
|
||||
set_option(section, "transparent-color", "magenta", False)
|
||||
section = "screen-captures"
|
||||
add_section(section)
|
||||
set_option(section, "rel-path", "caps", False)
|
||||
set_option(section, "file-name-format", "%Y%m%d%H%M%S", False)
|
||||
set_option(section, "file-extension", "png", False)
|
||||
section = "video-recordings"
|
||||
add_section(section)
|
||||
set_option(section, "enable", "no", False)
|
||||
set_option(section, "rel-path", "vids", False)
|
||||
set_option(section, "directory-name-format", "%Y%m%d%H%M%S", False)
|
||||
set_option(section, "file-extension", "png", False)
|
||||
set_option(section, "frame-format", "RGB", False)
|
||||
set_option(section, "framerate", "40", False)
|
||||
set_option(section, "temp-directory", "", False)
|
||||
set_option(section, "record-audio", "yes", False)
|
||||
set_option(section, "filename-digits", "6", False)
|
||||
section = "mouse"
|
||||
add_section(section)
|
||||
set_option(section, "visible", "yes", False)
|
||||
set_option(section, "double-click-time-limit", ".5", False)
|
||||
section = "keys"
|
||||
add_section(section)
|
||||
set_option(section, "up", "K_UP", False)
|
||||
set_option(section, "right", "K_RIGHT", False)
|
||||
set_option(section, "down", "K_DOWN", False)
|
||||
set_option(section, "left", "K_LEFT", False)
|
||||
set_option(section, "capture-screen", "K_F9", False)
|
||||
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, "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)
|
||||
set_option(section, "toggle-audio-panel", "K_F6", False)
|
||||
section = "joy"
|
||||
add_section(section)
|
||||
set_option(section, "advance", "7", False)
|
||||
set_option(section, "pause", "7", False)
|
||||
set_option(section, "select", "6", False)
|
||||
set_option(section, "single-xy", "no", False)
|
||||
set_option(section, "delay-axis", "0", False)
|
||||
set_option(section, "vertical-axis", "1", False)
|
||||
set_option(section, "horizontal-axis", "0", False)
|
||||
section = "event"
|
||||
add_section(section)
|
||||
set_option(section, "user-event-id", "USEREVENT", False)
|
||||
set_option(section, "command-id-offset", "1", False)
|
||||
set_option(section, "command-key", "command", False)
|
||||
set_option(section, "cancel-flag-key", "cancel", False)
|
||||
section = "audio"
|
||||
add_section(section)
|
||||
set_option(section, "sfx-default-path", "~/storage/audio/sfx/default", False)
|
||||
set_option(section, "sfx-repository-path", "~/storage/audio/sfx/all", False)
|
||||
set_option(section, "sfx-project-path", "sfx", False)
|
||||
set_option(section, "sfx-extensions", "wav, ogg, mp3", False)
|
||||
set_option(section, "bgm-repository-path", "~/storage/audio/bgm", False)
|
||||
set_option(section, "bgm-project-path", "bgm", False)
|
||||
set_option(section, "sfx-volume", "1.0", False)
|
||||
set_option(section, "bgm-volume", "1.0", False)
|
||||
set_option(section, "volume", "1.0", False)
|
||||
set_option(section, "panel-enabled", "no", False)
|
||||
set_option(section, "panel-font", None, False)
|
||||
set_option(section, "auto-load", "yes", False)
|
||||
section = "interpolator-gui"
|
||||
add_section(section)
|
||||
set_option(section, "margin", "80", False)
|
||||
set_option(section, "marker-color", "255, 0, 0", False)
|
||||
set_option(section, "marker-size", "11", False)
|
||||
set_option(section, "curve-color", "0, 255, 0", False)
|
||||
set_option(section, "label-size", "16", False)
|
||||
set_option(section, "label-precision", "2", False)
|
||||
set_option(section, "axis-label-count", "8", False)
|
||||
set_option(section, "prompt-size", "380, 60", False)
|
||||
set_option(section, "prompt-border-color", "255, 0, 0", False)
|
||||
set_option(section, "prompt-border-width", "3", False)
|
||||
set_option(section, "prompt-character-limit", "21", False)
|
||||
set_option(section, "prompt-text-size", "42", False)
|
||||
set_option(section, "template-nodeset", "L 0 0, 1000 1", False)
|
||||
set_option(section, "template-nodeset-name", "template", False)
|
||||
set_option(section, "flat-y-range", "1", False)
|
||||
|
||||
def add_section(self, name):
|
||||
if name not in self.order:
|
||||
self.order.append(name)
|
||||
RawConfigParser.add_section(self, name)
|
||||
|
||||
def set(self, section, option, value, modifiable=True):
|
||||
if modifiable:
|
||||
if section not in self.order:
|
||||
self.order.append(section)
|
||||
if section not in self.modifiable:
|
||||
self.modifiable[section] = []
|
||||
if option not in self.modifiable[section]:
|
||||
self.modifiable[section].append(option)
|
||||
RawConfigParser.set(self, section, option, value)
|
||||
|
||||
def read_project_config_file(self):
|
||||
path = self.locate_project_config_file()
|
||||
if path:
|
||||
fp = open(path)
|
||||
self.set_modifiable(fp)
|
||||
fp.seek(0)
|
||||
self.readfp(fp)
|
||||
fp.seek(0)
|
||||
self.set_order(fp)
|
||||
fp.close()
|
||||
else:
|
||||
self.print_debug("No configuration file found")
|
||||
|
||||
def locate_project_config_file(self):
|
||||
rel_path = self.project_file_rel_path
|
||||
if not rel_path:
|
||||
rel_path = self.default_project_file_rel_path
|
||||
if exists(rel_path) and not self.is_shared_mode():
|
||||
return rel_path
|
||||
if self.resource_path:
|
||||
installed_path = join(self.resource_path, rel_path)
|
||||
if exists(installed_path):
|
||||
return installed_path
|
||||
|
||||
def set_order(self, fp):
|
||||
self.order = order = []
|
||||
for line in open(self.locate_project_config_file()):
|
||||
result = match("^\s*\[(.*)\]\s*$", line)
|
||||
if result:
|
||||
order.append(result.group(1))
|
||||
|
||||
def set_modifiable(self, fp):
|
||||
config = RawConfigParser()
|
||||
config.readfp(fp)
|
||||
modifiable = self.modifiable
|
||||
for section in config._sections:
|
||||
if section not in modifiable:
|
||||
modifiable[section] = []
|
||||
for option in config._sections[section]:
|
||||
if option != "__name__" and option not in modifiable[section]:
|
||||
modifiable[section].append(option)
|
||||
|
||||
def is_shared_mode(self):
|
||||
return "-s" in argv
|
||||
|
||||
def print_debug(self, statement):
|
||||
if self.is_debug_mode():
|
||||
print(statement)
|
||||
|
||||
def is_debug_mode(self):
|
||||
return "-d" in argv
|
||||
|
||||
def modify_defaults(self):
|
||||
self.set_installation_path()
|
||||
self.set_resource_search_path()
|
||||
self.set_screen_captures_path()
|
||||
self.set_video_recordings_path()
|
||||
self.set_data_exclusion_list()
|
||||
self.set_requirements()
|
||||
|
||||
def set_installation_path(self):
|
||||
self.set("setup", "installation-path",
|
||||
join(self.get("setup", "installation-dir"),
|
||||
self.get("setup", "package-root")), False)
|
||||
|
||||
def set_resource_search_path(self):
|
||||
section, option = "setup", "resource-search-path"
|
||||
search_path = self.get(section, option)
|
||||
if self.resource_path:
|
||||
search_path.append(self.resource_path)
|
||||
else:
|
||||
search_path.append(self.get("setup", "installation-path"))
|
||||
self.set(section, option, search_path, False)
|
||||
|
||||
def get(self, section, option):
|
||||
value = RawConfigParser.get(self, section, option)
|
||||
if value is None:
|
||||
value = self.get_substitute(section, option)
|
||||
return self.cast_value(section, option, value)
|
||||
|
||||
def get_substitute(self, section, option):
|
||||
if section == "display":
|
||||
if option == "caption":
|
||||
return self.get("setup", "title")
|
||||
|
||||
def cast_value(self, section, option, value):
|
||||
pair = section, option
|
||||
types = self.type_declarations
|
||||
# if type(value) == str or type(value) == unicode:
|
||||
if type(value) == str:
|
||||
if pair in types["bool"]:
|
||||
return value.lower() in self.get("setup", "boolean-true-lowercase")
|
||||
elif pair in types["int"]:
|
||||
return int(value)
|
||||
elif pair in types["float"]:
|
||||
return float(value)
|
||||
elif pair in types["path"]:
|
||||
return self.translate_path(value)
|
||||
elif pair in types["list"]:
|
||||
if value == "":
|
||||
return []
|
||||
else:
|
||||
return [member.strip() for member in value.split(types.list_member_sep)]
|
||||
elif pair in types["int-list"]:
|
||||
return [int(member) for member in value.split(types.list_member_sep)]
|
||||
elif pair in types["float-list"]:
|
||||
return [float(member) for member in value.split(types.list_member_sep)]
|
||||
elif pair in types["path-list"]:
|
||||
return [self.translate_path(member.strip()) for member in value.split(types.list_member_sep)]
|
||||
return value
|
||||
|
||||
def set_screen_captures_path(self):
|
||||
section, option = "screen-captures", "path"
|
||||
if not self.has_option(section, option):
|
||||
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
|
||||
|
||||
def build_home_path(self):
|
||||
return join("~", "." + self.get("setup", "package-root"))
|
||||
|
||||
def set_video_recordings_path(self):
|
||||
section, option = "video-recordings", "path"
|
||||
if not self.has_option(section, option):
|
||||
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
|
||||
|
||||
def set_data_exclusion_list(self):
|
||||
section, option = "setup", "data-exclude"
|
||||
exclude = []
|
||||
if self.has_option(section, option):
|
||||
exclude = self.get(section, option)
|
||||
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", "init-script")]
|
||||
for location in self.get("setup", "additional-packages"):
|
||||
exclude.append(location)
|
||||
self.set(section, option, exclude, False)
|
||||
|
||||
def set_requirements(self):
|
||||
section, option = "setup", "requirements"
|
||||
requirements = []
|
||||
if self.has_option(section, option):
|
||||
requirements = self.get(section, option)
|
||||
if "pygame" not in requirements:
|
||||
requirements.append("pygame")
|
||||
self.set(section, option, requirements, False)
|
||||
|
||||
def get_section(self, section):
|
||||
assignments = {}
|
||||
for option in self.options(section):
|
||||
assignments[option] = self.get(section, option)
|
||||
return assignments
|
||||
|
||||
def __repr__(self):
|
||||
config = {}
|
||||
for section in self.sections():
|
||||
config[section] = self.get_section(section)
|
||||
return pformat(config, 2, 1)
|
||||
|
||||
def items(self, section):
|
||||
items = []
|
||||
for option in self.options(section):
|
||||
items.append((option, self.get(section, option)))
|
||||
return items
|
||||
|
||||
def write(self, fp=None):
|
||||
modifiable = self.modifiable
|
||||
use_main = fp is None
|
||||
if use_main:
|
||||
path = self.locate_project_config_file()
|
||||
if not path:
|
||||
path = join(self.resource_path or "", self.default_project_file_rel_path)
|
||||
fp = open(path, "w")
|
||||
break_line = False
|
||||
for section in self.order:
|
||||
if section in modifiable:
|
||||
if break_line:
|
||||
fp.write("\n")
|
||||
fp.write("[%s]\n" % section)
|
||||
for option in modifiable[section]:
|
||||
if self.has_option(section, option):
|
||||
raw_value = self.get_raw_value(self.get(section, option))
|
||||
# handle multiline entries
|
||||
if "\n" in raw_value:
|
||||
lines = raw_value.split("\n")
|
||||
fp.write("{} = {}\n".format(option, lines[0]))
|
||||
for line in lines[1:]:
|
||||
# have to indent lines after the first to be compatible with configparser
|
||||
fp.write(" {}\n".format(line))
|
||||
else:
|
||||
fp.write("{} = {}\n".format(option, raw_value))
|
||||
break_line = True
|
||||
if use_main:
|
||||
fp.close()
|
||||
|
||||
def get_raw_value(self, value):
|
||||
if isinstance(value, list):
|
||||
raw = ""
|
||||
for ii, value in enumerate(value):
|
||||
if ii:
|
||||
raw += ", "
|
||||
raw += str(value)
|
||||
else:
|
||||
raw = str(value)
|
||||
return raw
|
||||
|
||||
def clear_section(self, section):
|
||||
if self.has_section(section):
|
||||
for option in self.options(section):
|
||||
self.remove_option(section, option)
|
||||
|
||||
def merge_command_line(self, command_line=None):
|
||||
"""
|
||||
Instantiate an argument parser to parse the command line. Load all settings submitted through the `--config` flag. Each setting
|
||||
is a string starting with the chosen delimiter, followed by the section name, option name, and option value each separated by the
|
||||
chosen delimiter.
|
||||
|
||||
For example, set the screen dimensions using "|" as the delimiter and set the Q key to the quit command using "," as the
|
||||
delimiter,
|
||||
|
||||
$ ./OPEN-GAME --config "|display|dimensions|960,540" ",keys,quit,K_q"
|
||||
|
||||
@param command_line A list of strings to use as the command line. If this is `None`, `sys.argv` is used instead.
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--config", nargs="*", default=[])
|
||||
# Loop over all assignment strings found
|
||||
for assignment in parser.parse_known_args(command_line)[0].config:
|
||||
# Split each assigment string by the first character in the string and pass along the parsed values as arguments
|
||||
# to the set member function
|
||||
self.set(*assignment.split(assignment[0])[1:])
|
||||
|
||||
|
||||
class TypeDeclarations(dict):
|
||||
|
||||
list_member_sep = ','
|
||||
|
||||
defaults = {
|
||||
|
||||
"display": {"int": ["frame-duration", "wait-duration", "framerate-text-size"],
|
||||
|
||||
"bool": ["centered", "skip-frames", "fullscreen", "show-framerate", "use-framebuffer"],
|
||||
|
||||
"int-list": ["dimensions", "framerate-text-color", "framerate-text-background", "framerate-position"]},
|
||||
|
||||
"input": {"bool": "confirm-quit"},
|
||||
|
||||
"screen-captures": {"path": ["rel-path", "path"]},
|
||||
|
||||
"video-recordings": {"path": ["rel-path", "path"],
|
||||
|
||||
"int": ["framerate", "filename-digits"],
|
||||
|
||||
"bool": ["enable", "record-audio"]},
|
||||
|
||||
"setup": {"list": ["classifiers", "resource-search-path", "requirements", "data-exclude", "additional-packages", "osx-includes",
|
||||
"boolean-true-lowercase"],
|
||||
|
||||
"path": ["installation-dir", "changelog", "description-file", "main-object", "icon-path", "windows-dist-path", "package-root"]},
|
||||
|
||||
"mouse": {"float": "double-click-time-limit",
|
||||
|
||||
"bool": "visible"},
|
||||
|
||||
"keys": {"list": ["up", "right", "down", "left"]},
|
||||
|
||||
"joy": {"int": ["advance", "pause", "select", "vertical-axis", "horizontal-axis"],
|
||||
|
||||
"float": "delay-axis",
|
||||
|
||||
"bool": "single-xy"},
|
||||
|
||||
"audio": {
|
||||
|
||||
"list": "sfx-extensions",
|
||||
|
||||
"path": "panel-font",
|
||||
|
||||
"path-list": ["sfx-default-path", "sfx-repository-path", "sfx-project-path", "bgm-repository-path", "bgm-project-path"],
|
||||
|
||||
"float": ["sfx-volume", "bgm-volume", "volume"],
|
||||
|
||||
"bool": ["panel-enabled", "auto-load"]
|
||||
|
||||
},
|
||||
|
||||
"event": {"int": "command-id-offset"},
|
||||
|
||||
"interpolator-gui": {"int": ["margin", "marker-size", "label-size", "axis-label-count", "label-precision", "prompt-border-width",
|
||||
"prompt-character-limit", "prompt-text-size", "flat-y-range"],
|
||||
|
||||
"int-list": ["marker-color", "curve-color", "prompt-size", "prompt-border-color"]},
|
||||
|
||||
}
|
||||
|
||||
additional_defaults = {}
|
||||
|
||||
def __init__(self):
|
||||
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [], "list": [], "int-list": [], "float-list": [], "path-list": []})
|
||||
self.add_chart(self.defaults)
|
||||
self.add_chart(self.additional_defaults)
|
||||
|
||||
def add(self, cast, section, option):
|
||||
self[cast].append((section, option))
|
||||
|
||||
def add_chart(self, chart):
|
||||
for section, declarations in chart.items():
|
||||
for cast, options in declarations.items():
|
||||
if type(options) != list:
|
||||
options = [options]
|
||||
for option in options:
|
||||
self.add(cast, section, option)
|
|
@ -1,92 +0,0 @@
|
|||
import collections
|
||||
|
||||
from pygame.event import get, pump, Event, post
|
||||
from pygame.locals import *
|
||||
|
||||
from .GameChild import GameChild
|
||||
from .Input import Input
|
||||
|
||||
class Delegate(GameChild):
|
||||
|
||||
def __init__(self, game):
|
||||
GameChild.__init__(self, game)
|
||||
self.subscribers = collections.OrderedDict()
|
||||
self.load_configuration()
|
||||
self.disable()
|
||||
self.cancelling_propagation = False
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("event")
|
||||
self.cancel_flag_key = config["cancel-flag-key"]
|
||||
self.command_key = config["command-key"]
|
||||
self.command_event_id = config["command-id-offset"] + globals()[config["user-event-id"]]
|
||||
|
||||
def disable(self):
|
||||
self.enabled = False
|
||||
|
||||
def enable(self):
|
||||
self.enabled = True
|
||||
self.interpolator = self.get_game().interpolator
|
||||
|
||||
def dispatch(self):
|
||||
if self.enabled:
|
||||
subscribers = self.subscribers
|
||||
for evt in get():
|
||||
kind = evt.type
|
||||
if kind in subscribers:
|
||||
for subscriber in subscribers[kind]:
|
||||
if not self.interpolator.is_gui_active() or hasattr(subscriber, "im_class") and \
|
||||
(subscriber.im_class == Input or subscriber.im_class == \
|
||||
self.interpolator.gui.__class__):
|
||||
self.print_debug("Passing %s to %s" % (evt, subscriber))
|
||||
if not self.cancelling_propagation:
|
||||
subscriber(evt)
|
||||
else:
|
||||
self.cancelling_propagation = False
|
||||
break
|
||||
else:
|
||||
pump()
|
||||
|
||||
def cancel_propagation(self):
|
||||
self.cancelling_propagation = True
|
||||
|
||||
def add_subscriber(self, callback, kind=None):
|
||||
self.print_debug("Subscribing %s to %s" % (callback, kind))
|
||||
if kind is None:
|
||||
kind = self.command_event_id
|
||||
subscribers = self.subscribers
|
||||
if kind not in subscribers:
|
||||
subscribers[kind] = list()
|
||||
subscribers[kind].append(callback)
|
||||
|
||||
def is_command(self, event):
|
||||
return event.type == self.command_event_id
|
||||
|
||||
def remove_subscriber(self, callback, kind=None):
|
||||
if kind is None:
|
||||
kind = self.command_event_id
|
||||
self.subscribers[kind].remove(callback)
|
||||
|
||||
def compare(self, evt, commands=None, cancel=False, **attributes):
|
||||
if self.is_command(evt):
|
||||
self.add_cancel_flag_to_attributes(attributes, cancel)
|
||||
if commands is not None and not isinstance(commands, list):
|
||||
commands = [commands]
|
||||
if commands is not None:
|
||||
if not self.command_in_list(evt, commands):
|
||||
return False
|
||||
return all(key in evt.dict and evt.dict[key] == value for key, value in attributes.items())
|
||||
|
||||
def add_cancel_flag_to_attributes(self, attributes, cancel):
|
||||
attributes[self.cancel_flag_key] = cancel
|
||||
|
||||
def command_in_list(self, evt, commands):
|
||||
return self.get_command_attribute(evt) in commands
|
||||
|
||||
def get_command_attribute(self, evt):
|
||||
return (self.command_key in evt.dict) and evt.dict[self.command_key]
|
||||
|
||||
def post(self, command=None, cancel=False, **attributes):
|
||||
attributes[self.command_key] = command
|
||||
self.add_cancel_flag_to_attributes(attributes, cancel)
|
||||
post(Event(self.command_event_id, attributes))
|
104
pgfw/Display.py
104
pgfw/Display.py
|
@ -1,112 +1,30 @@
|
|||
from os import environ
|
||||
from sys import maxsize, platform
|
||||
from pygame import display, image
|
||||
|
||||
import pygame
|
||||
from pygame import image, mouse
|
||||
from pygame.locals import *
|
||||
|
||||
from .GameChild import *
|
||||
from GameChild import *
|
||||
|
||||
class Display(GameChild):
|
||||
|
||||
def __init__(self, game):
|
||||
GameChild.__init__(self, game)
|
||||
print(f"available fullscreen modes: {pygame.display.list_modes()}")
|
||||
self.delegate = self.get_delegate()
|
||||
self.load_configuration()
|
||||
self.align_window()
|
||||
self.init_screen()
|
||||
self.config = self.get_configuration()
|
||||
self.set_screen()
|
||||
self.set_caption()
|
||||
self.set_icon()
|
||||
self.set_mouse_visibility()
|
||||
self.subscribe(self.toggle_fullscreen)
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("display")
|
||||
self.centered = config["centered"]
|
||||
self.fullscreen_enabled = config["fullscreen"]
|
||||
self.caption = config["caption"]
|
||||
self.windowed_flag = config["windowed-flag"]
|
||||
self.icon_path = self.get_resource("display", "icon-path")
|
||||
self.mouse_visibility = self.get_configuration("mouse", "visible")
|
||||
|
||||
def align_window(self):
|
||||
if self.centered:
|
||||
environ["SDL_VIDEO_CENTERED"] = "1"
|
||||
|
||||
def init_screen(self):
|
||||
full = False
|
||||
if self.fullscreen_requested():
|
||||
full = True
|
||||
|
||||
# Added pygame.SCALED to avoid fullscreen bug
|
||||
# <https://www.pygame.org/docs/ref/display.html#pygame.display.toggle_fullscreen>
|
||||
try:
|
||||
self.set_screen(pygame.SCALED, fs=full)
|
||||
except AttributeError:
|
||||
self.set_screen(fs=full)
|
||||
|
||||
def fullscreen_requested(self):
|
||||
return not self.check_command_line(self.windowed_flag) and self.fullscreen_enabled
|
||||
|
||||
def set_screen(self, flags=0x0, dimensions=None, fs=None):
|
||||
"""
|
||||
Initialize the pygame display, passing along any flags, and assign the returned display surface to `self.screen`. If no
|
||||
dimensions are passed, use the dimensions of the current screen surface if it exists, otherwise get dimensions from the
|
||||
configuration. If `fs` is `None`, leave the fullscreen state as is, otherwise read it as a boolean and set fullscreen
|
||||
accordingly.
|
||||
|
||||
@param flags Flags from https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode. To pass multiple flags,
|
||||
join them with the OR operator.
|
||||
@param dimensions Dimensions of the screen surface as (width, height)
|
||||
@param fs Set to True or False or omit to keep current fullscreen state
|
||||
"""
|
||||
# Try to auto discover dimensions if not provided
|
||||
if dimensions is None:
|
||||
if pygame.display.get_surface():
|
||||
dimensions = pygame.display.get_surface().get_size()
|
||||
else:
|
||||
dimensions = self.get_configuration("display", "dimensions")
|
||||
|
||||
if fs is None:
|
||||
# Get the current fullscreen state
|
||||
fs = bool(pygame.display.get_surface().get_flags() & pygame.FULLSCREEN)
|
||||
|
||||
# Get a display surface with specified fullscreen state
|
||||
if fs:
|
||||
self.screen = pygame.display.set_mode(dimensions, flags | pygame.FULLSCREEN)
|
||||
else:
|
||||
self.screen = pygame.display.set_mode(dimensions, flags)
|
||||
|
||||
# Redraw the spline interpolator interface if enabled
|
||||
if self.get_game().interpolator.gui_enabled:
|
||||
self.get_game().interpolator.gui.rearrange()
|
||||
|
||||
def rotate(self):
|
||||
"""
|
||||
Rotate the screen surface 90 degrees by resetting it with swapped W and H dimensions.
|
||||
"""
|
||||
current_width, current_height = self.get_display_surface().get_size()
|
||||
self.set_screen(dimensions=(current_height, current_width))
|
||||
def set_screen(self):
|
||||
self.screen = display.set_mode(self.config.get("display", "dimensions",
|
||||
"int-list"))
|
||||
|
||||
def set_caption(self):
|
||||
pygame.display.set_caption(self.caption)
|
||||
display.set_caption(self.config.get("display", "caption"))
|
||||
|
||||
def set_icon(self):
|
||||
if self.icon_path:
|
||||
pygame.display.set_icon(image.load(self.icon_path).convert_alpha())
|
||||
|
||||
def set_mouse_visibility(self, visibility=None):
|
||||
if visibility is None:
|
||||
visibility = self.mouse_visibility
|
||||
return mouse.set_visible(visibility)
|
||||
if self.get_configuration().has_option("display", "icon-path"):
|
||||
path = self.get_resource("display", "icon-path")
|
||||
display.set_icon(image.load(path).convert_alpha())
|
||||
|
||||
def get_screen(self):
|
||||
return self.screen
|
||||
|
||||
def get_size(self):
|
||||
return self.screen.get_size()
|
||||
|
||||
def toggle_fullscreen(self, event):
|
||||
if self.delegate.compare(event, "toggle-fullscreen"):
|
||||
pygame.display.toggle_fullscreen()
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
from pygame import event
|
||||
from pygame.locals import *
|
||||
|
||||
from GameChild import *
|
||||
|
||||
class EventDelegate(GameChild):
|
||||
|
||||
def __init__(self, game):
|
||||
GameChild.__init__(self, game)
|
||||
self.subscribers = dict()
|
||||
self.disable()
|
||||
|
||||
def enable(self):
|
||||
self.enabled = True
|
||||
|
||||
def disable(self):
|
||||
self.enabled = False
|
||||
|
||||
def dispatch_events(self):
|
||||
if self.enabled:
|
||||
subscribers = self.subscribers
|
||||
for evt in event.get():
|
||||
kind = evt.type
|
||||
if kind in subscribers:
|
||||
for subscriber in subscribers[kind]:
|
||||
self.print_debug("Passing {0} to {1}".\
|
||||
format(evt, subscriber))
|
||||
subscriber(evt)
|
||||
else:
|
||||
event.pump()
|
||||
|
||||
def add_subscriber(self, kind, callback):
|
||||
self.print_debug("Subscribing {0} to {1}".\
|
||||
format(callback, kind))
|
||||
subscribers = self.subscribers
|
||||
if kind not in subscribers:
|
||||
subscribers[kind] = list()
|
||||
subscribers[kind].append(callback)
|
||||
|
||||
def remove_subscriber(self, kind, callback):
|
||||
self.subscribers[kind].remove(callback)
|
|
@ -0,0 +1,9 @@
|
|||
from pygame import font
|
||||
|
||||
from GameChild import *
|
||||
|
||||
class Font(font.Font, GameChild):
|
||||
|
||||
def __init__(self, parent, size):
|
||||
GameChild.__init__(self, parent)
|
||||
font.Font.__init__(self, self.get_resource("font-path"), size)
|
166
pgfw/Game.py
166
pgfw/Game.py
|
@ -1,129 +1,81 @@
|
|||
import os, pygame, sys
|
||||
from os import environ
|
||||
|
||||
import pygame
|
||||
from pygame import display
|
||||
from pygame.locals import *
|
||||
|
||||
from .Animation import Animation
|
||||
from .Mainloop import Mainloop
|
||||
from .Audio import Audio
|
||||
from .Display import Display
|
||||
from .Configuration import Configuration
|
||||
from .Delegate import Delegate
|
||||
from .Input import Input
|
||||
from .ScreenGrabber import ScreenGrabber
|
||||
from .Profile import Profile
|
||||
from .VideoRecorder import VideoRecorder
|
||||
from .Interpolator import Interpolator
|
||||
from .TimeFilter import TimeFilter
|
||||
from .Sprite import Sprite
|
||||
from .confirm_quit_message import CONFIRM_QUIT_MESSAGE
|
||||
from .extension import *
|
||||
from GameChild import *
|
||||
from Animation import *
|
||||
from Audio import *
|
||||
from Display import *
|
||||
from configuration.Configuration import *
|
||||
from EventDelegate import *
|
||||
from Input import *
|
||||
from ScreenGrabber import *
|
||||
|
||||
class Game(Animation):
|
||||
class Game(GameChild, Animation):
|
||||
|
||||
resources_path = None
|
||||
|
||||
resource_path = None
|
||||
|
||||
def __init__(self, config_rel_path=None, type_declarations=None):
|
||||
self.confirming_quit = False
|
||||
self.profile = Profile(self)
|
||||
self.time_filter = TimeFilter(self)
|
||||
Animation.__init__(self, self)
|
||||
self.print_debug(pygame.version.ver)
|
||||
print(sys.version_info)
|
||||
def __init__(self, config_rel_path=None):
|
||||
self.init_gamechild()
|
||||
self.config_rel_path = config_rel_path
|
||||
self.type_declarations = type_declarations
|
||||
self.set_configuration()
|
||||
if self.get_configuration("display", "use-framebuffer"):
|
||||
os.putenv("SDL_FBDEV", "/dev/fb0")
|
||||
os.putenv("SDL_VIDEODRIVER", "fbcon")
|
||||
os.putenv("SDL_NOMOUSE", "1")
|
||||
self.init_animation()
|
||||
self.align_window()
|
||||
pygame.init()
|
||||
self.set_children()
|
||||
self.register(self.get_input().unsuppress)
|
||||
self.subscribe_to(QUIT, self.end)
|
||||
self.subscribe_to(self.get_user_event_id(), self.end)
|
||||
self.clear_queue()
|
||||
self.delegate.enable()
|
||||
|
||||
def init_gamechild(self):
|
||||
GameChild.__init__(self)
|
||||
|
||||
def set_configuration(self):
|
||||
self.configuration = Configuration(self.config_rel_path, self.resource_path, self.type_declarations)
|
||||
self.configuration = Configuration(self.config_rel_path,
|
||||
self.resources_path)
|
||||
|
||||
def init_animation(self):
|
||||
Animation.__init__(self,
|
||||
self.configuration.get("display", "frame-duration",
|
||||
"int"))
|
||||
|
||||
def align_window(self):
|
||||
if self.configuration.get("display", "centered", "bool"):
|
||||
environ["SDL_VIDEO_CENTERED"] = "1"
|
||||
|
||||
def set_children(self):
|
||||
"""
|
||||
Create the child objects that encapsulate other parts of the framework. They're usually used as singletons or managers. Subscribe
|
||||
to the pygame.QUIT event.
|
||||
"""
|
||||
self.delegate = Delegate(self)
|
||||
self.subscribe(self.end, QUIT)
|
||||
self.subscribe(self.end)
|
||||
self.interpolator = Interpolator(self)
|
||||
self.set_display()
|
||||
self.set_delegate()
|
||||
self.set_input()
|
||||
self.set_audio()
|
||||
self.set_screen_grabber()
|
||||
|
||||
def set_display(self):
|
||||
self.display = Display(self)
|
||||
self.mainloop = Mainloop(self)
|
||||
|
||||
def set_delegate(self):
|
||||
self.delegate = EventDelegate(self)
|
||||
|
||||
def set_input(self):
|
||||
self.input = Input(self)
|
||||
|
||||
def set_audio(self):
|
||||
self.audio = Audio(self)
|
||||
|
||||
def set_screen_grabber(self):
|
||||
self.screen_grabber = ScreenGrabber(self)
|
||||
self.video_recorder = VideoRecorder(self)
|
||||
|
||||
# Call this separately because it needs the display to be initialized previously
|
||||
self.interpolator.init_gui()
|
||||
|
||||
def frame(self):
|
||||
self.time_filter.update()
|
||||
self.delegate.dispatch()
|
||||
if not self.interpolator.is_gui_active() and not self.get_audio().is_audio_panel_active():
|
||||
Animation.update(self)
|
||||
if self.confirming_quit:
|
||||
self.time_filter.close()
|
||||
self.draw_confirm_quit_dialog()
|
||||
else:
|
||||
self.time_filter.open()
|
||||
self.update()
|
||||
elif self.interpolator.is_gui_active():
|
||||
self.interpolator.gui.update()
|
||||
self.audio.update()
|
||||
if self.video_recorder.requested:
|
||||
self.video_recorder.update()
|
||||
|
||||
def draw_confirm_quit_dialog(self):
|
||||
dialog = Sprite(self)
|
||||
dialog.add_frame(self.get_confirm_quit_surface())
|
||||
dialog.location.center = self.get_display_surface().get_rect().center
|
||||
dialog.update()
|
||||
|
||||
def get_confirm_quit_surface(self):
|
||||
lines = CONFIRM_QUIT_MESSAGE.strip().split("\n")
|
||||
w, h = len(lines[0]), len(lines)
|
||||
text = pygame.Surface((w, h), SRCALPHA)
|
||||
for y, line in enumerate(lines):
|
||||
for x, char in enumerate(line.strip()):
|
||||
if char == "#":
|
||||
text.set_at((x, y), (0, 0, 0))
|
||||
text = pygame.transform.scale2x(text)
|
||||
text = pygame.transform.scale2x(text)
|
||||
return get_boxed_surface(
|
||||
text, background=(255, 255, 255), border=(0, 0, 0), border_width=24, padding=12)
|
||||
|
||||
def run(self):
|
||||
self.mainloop.run()
|
||||
def sequence(self):
|
||||
self.delegate.dispatch_events()
|
||||
self.update()
|
||||
display.update()
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def blit(self, source, destination, area=None, special_flags=0):
|
||||
self.get_screen().blit(source, destination, area, special_flags)
|
||||
|
||||
def get_rect(self):
|
||||
return self.get_screen().get_rect()
|
||||
|
||||
def suppress_input_temporarily(self, length=700):
|
||||
self.get_input().suppress()
|
||||
self.play(self.get_input().unsuppress, delay=length, play_once=True)
|
||||
|
||||
def end(self, event):
|
||||
if event.type == QUIT or self.delegate.compare(event, "quit"):
|
||||
if self.confirming_quit or not self.get_configuration("input", "confirm-quit"):
|
||||
self.mainloop.stop()
|
||||
self.profile.end()
|
||||
else:
|
||||
print("press quit again to confirm or press any other button to cancel...")
|
||||
self.confirming_quit = True
|
||||
elif self.delegate.compare(event, "any"):
|
||||
if self.confirming_quit and self.delegate.compare(event, "any"):
|
||||
print("cancelled quit...")
|
||||
self.confirming_quit = False
|
||||
self.delegate.cancel_propagation()
|
||||
def end(self, evt):
|
||||
if evt.type == QUIT or self.is_command(evt, "quit"):
|
||||
self.stop()
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
from os.path import exists, join, basename, normpath, abspath, commonprefix, sep
|
||||
from sys import argv, version_info
|
||||
from os.path import exists, join, basename, normpath, abspath
|
||||
from sys import argv
|
||||
|
||||
from pygame import mixer, event, time
|
||||
from pygame import mixer
|
||||
from pygame.locals import *
|
||||
|
||||
if version_info[0] >= 3:
|
||||
from . import Game
|
||||
else:
|
||||
import Game
|
||||
import Game
|
||||
|
||||
class GameChild:
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
self.game = self.get_game()
|
||||
|
||||
def get_game(self):
|
||||
current = self
|
||||
|
@ -21,74 +17,57 @@ class GameChild:
|
|||
current = current.parent
|
||||
return current
|
||||
|
||||
def get_configuration(self, section=None, option=None, linebreaks=True):
|
||||
config = self.game.configuration
|
||||
if option is None and section is None:
|
||||
return config
|
||||
elif option and section:
|
||||
if config.has_option(section, option):
|
||||
rvalue = config.get(section, option)
|
||||
if not linebreaks and isinstance(rvalue, str):
|
||||
rvalue = rvalue.replace("\n", " ")
|
||||
return rvalue
|
||||
elif option is None:
|
||||
if config.has_section(section):
|
||||
return config.get_section(section)
|
||||
else:
|
||||
return {}
|
||||
def get_configuration(self):
|
||||
return self.get_game().configuration
|
||||
|
||||
def get_input(self):
|
||||
return self.game.input
|
||||
return self.get_game().input
|
||||
|
||||
def get_screen(self):
|
||||
return self.game.display.get_screen()
|
||||
|
||||
def get_display_surface(self):
|
||||
current = self
|
||||
attribute = "display_surface"
|
||||
while not isinstance(current, Game.Game):
|
||||
if hasattr(current, attribute):
|
||||
return getattr(current, attribute)
|
||||
current = current.parent
|
||||
return current.display.get_screen()
|
||||
return self.get_game().display.get_screen()
|
||||
|
||||
def get_audio(self):
|
||||
return self.game.audio
|
||||
return self.get_game().audio
|
||||
|
||||
def get_delegate(self):
|
||||
return self.game.delegate
|
||||
return self.get_game().delegate
|
||||
|
||||
def get_resource(self, path_or_section, option=None):
|
||||
def get_resource(self, section, option):
|
||||
config = self.get_configuration()
|
||||
rel_path = path_or_section
|
||||
if option is not None:
|
||||
rel_path = config.get(path_or_section, option)
|
||||
if rel_path:
|
||||
for root in config.get("setup", "resource-search-path"):
|
||||
if self.is_shared_mode() and not self.is_absolute_path(root):
|
||||
continue
|
||||
path = join(root, rel_path)
|
||||
if exists(path):
|
||||
return normpath(path)
|
||||
|
||||
def is_shared_mode(self):
|
||||
return self.check_command_line("s")
|
||||
|
||||
def check_command_line(self, flag):
|
||||
return "-" + flag in argv
|
||||
rel_path = config.get(section, option, "path")
|
||||
for root in config.get("setup", "resources-search-path", "list"):
|
||||
if self.is_shared_mode() and not self.is_absolute_path(root):
|
||||
continue
|
||||
path = join(root, rel_path)
|
||||
if (exists(path)):
|
||||
return path
|
||||
self.print_debug("Couldn't find resource: {0}, {1}".\
|
||||
format(section, option))
|
||||
|
||||
def print_debug(self, statement):
|
||||
if self.is_debug_mode():
|
||||
print(statement)
|
||||
|
||||
def is_debug_mode(self):
|
||||
return self.check_command_line("d")
|
||||
print statement
|
||||
|
||||
def is_absolute_path(self, path):
|
||||
return normpath(path) == abspath(path)
|
||||
|
||||
def subscribe(self, callback, kind=None):
|
||||
self.game.delegate.add_subscriber(callback, kind)
|
||||
def is_shared_mode(self):
|
||||
return "-s" in argv
|
||||
|
||||
def unsubscribe(self, callback, kind=None):
|
||||
self.game.delegate.remove_subscriber(callback, kind)
|
||||
def subscribe_to(self, kind, callback):
|
||||
self.get_game().delegate.add_subscriber(kind, callback)
|
||||
|
||||
def unsubscribe_from(self, kind, callback):
|
||||
self.get_game().delegate.remove_subscriber(kind, callback)
|
||||
|
||||
def is_debug_mode(self):
|
||||
return "-d" in argv
|
||||
|
||||
def get_user_event_id(self):
|
||||
return globals()[self.get_configuration().get("event",
|
||||
"user-event-id")]
|
||||
|
||||
def is_command(self, evt, cmd):
|
||||
name = self.get_configuration().get("event", "command-event-name")
|
||||
return evt.type == self.get_user_event_id() and evt.name == name and \
|
||||
evt.command == cmd
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
from .GameChild import GameChild
|
||||
from .extension import get_random_hsla_color, get_range_steps, get_hsla_color
|
||||
|
||||
class Gradient(GameChild):
|
||||
|
||||
def __init__(self, parent, rect, granularity, start=None, end=None):
|
||||
GameChild.__init__(self, parent)
|
||||
if start is None:
|
||||
start = get_random_hsla_color()
|
||||
self.start = start
|
||||
if end is None:
|
||||
end = get_random_hsla_color()
|
||||
self.end = end
|
||||
self.rect = rect
|
||||
self.granularity = granularity
|
||||
|
||||
def update(self):
|
||||
ds = self.get_display_surface()
|
||||
y = self.rect.top
|
||||
hues = list(get_range_steps(self.start.hsla[0], self.end.hsla[0],
|
||||
self.granularity))
|
||||
saturations = list(get_range_steps(self.start.hsla[1], self.end.hsla[1],
|
||||
self.granularity))
|
||||
lightnesses = list(get_range_steps(self.start.hsla[2], self.end.hsla[2],
|
||||
self.granularity))
|
||||
for index in range(len(hues)):
|
||||
h = self.rect.h // self.granularity
|
||||
ds.fill(get_hsla_color(int(hues[index]), int(saturations[index]), int(lightnesses[index])),
|
||||
(self.rect.left, y, self.rect.w, h))
|
||||
y += h
|
227
pgfw/Input.py
227
pgfw/Input.py
|
@ -1,40 +1,21 @@
|
|||
from time import time as get_secs
|
||||
|
||||
from pygame import joystick as joy
|
||||
from pygame.key import get_pressed, get_mods
|
||||
from pygame import event, joystick as joy, key as keyboard
|
||||
from pygame.locals import *
|
||||
|
||||
from .GameChild import *
|
||||
from GameChild import *
|
||||
|
||||
class Input(GameChild):
|
||||
|
||||
def __init__(self, game):
|
||||
GameChild.__init__(self, game)
|
||||
self.last_mouse_down_left = None
|
||||
self.axes_cancelled = {"up": True, "right": True, "down": True, "left": True}
|
||||
self.last_command_post_time = get_secs()
|
||||
self.joystick = Joystick()
|
||||
self.delegate = self.get_delegate()
|
||||
self.load_configuration()
|
||||
self.set_any_press_ignore_list()
|
||||
self.unsuppress()
|
||||
self.unsuppress_any_on_mods()
|
||||
self.subscribe_to_events()
|
||||
self.build_key_map()
|
||||
self.build_joy_button_map()
|
||||
|
||||
def load_configuration(self):
|
||||
self.release_suffix = self.get_configuration("input", "release-suffix")
|
||||
self.key_commands = self.get_configuration().items("keys")
|
||||
self.double_click_time_limit = self.get_configuration(
|
||||
"mouse", "double-click-time-limit")
|
||||
|
||||
def set_any_press_ignore_list(self):
|
||||
self.any_press_ignored = set(["capture-screen", "toggle-fullscreen",
|
||||
"reset-game", "record-video", "quit",
|
||||
"toggle-interpolator", "toggle-audio-panel",
|
||||
"volume-down", "volume-up", "volume-mute"])
|
||||
self.any_press_ignored_keys = set()
|
||||
def subscribe_to_events(self):
|
||||
self.subscribe_to(KEYDOWN, self.translate_key_press)
|
||||
self.subscribe_to(JOYBUTTONDOWN, self.translate_joy_button)
|
||||
self.subscribe_to(JOYAXISMOTION, self.translate_axis_motion)
|
||||
|
||||
def suppress(self):
|
||||
self.suppressed = True
|
||||
|
@ -42,135 +23,55 @@ class Input(GameChild):
|
|||
def unsuppress(self):
|
||||
self.suppressed = False
|
||||
|
||||
def is_suppressed(self):
|
||||
'''
|
||||
Return True if input is suppressed
|
||||
'''
|
||||
return self.suppressed
|
||||
|
||||
def unsuppress_any_on_mods(self):
|
||||
'''
|
||||
Prevent modifier keys from triggering an any key event
|
||||
'''
|
||||
self.suppressed_any_key_on_mods = False
|
||||
|
||||
def suppress_any_key_on_mods(self):
|
||||
'''
|
||||
Allow modifier keys to trigger an any key event
|
||||
'''
|
||||
self.suppressed_any_key_on_mods = True
|
||||
|
||||
def subscribe_to_events(self):
|
||||
'''
|
||||
Tell Delegate to send keyboard, joystick and mouse buttons
|
||||
'''
|
||||
self.subscribe(self.translate_key, KEYDOWN)
|
||||
self.subscribe(self.translate_key, KEYUP)
|
||||
self.subscribe(self.translate_joy_button, JOYBUTTONDOWN)
|
||||
self.subscribe(self.translate_joy_button, JOYBUTTONUP)
|
||||
self.subscribe(self.translate_axis_motion, JOYAXISMOTION)
|
||||
self.subscribe(self.translate_mouse_input, MOUSEBUTTONDOWN)
|
||||
self.subscribe(self.translate_mouse_input, MOUSEBUTTONUP)
|
||||
|
||||
def build_key_map(self):
|
||||
key_map = {}
|
||||
for command, keys in self.key_commands:
|
||||
config = self.get_configuration()
|
||||
for command, keys in config.get_section("keys", "list").iteritems():
|
||||
key_map[command] = []
|
||||
if type(keys) == str:
|
||||
keys = [keys]
|
||||
for key in keys:
|
||||
key_map[command].append(globals()[key])
|
||||
self.key_map = key_map
|
||||
|
||||
def build_joy_button_map(self):
|
||||
self.joy_button_map = self.get_configuration("joy")
|
||||
|
||||
def translate_key(self, event):
|
||||
def translate_key_press(self, evt):
|
||||
if not self.suppressed:
|
||||
cancel = event.type == KEYUP
|
||||
posted = None
|
||||
key = event.key
|
||||
for cmd, keys in self.key_map.items():
|
||||
key = evt.key
|
||||
for cmd, keys in self.key_map.iteritems():
|
||||
if key in keys:
|
||||
self.post_command(cmd, cancel=cancel)
|
||||
posted = cmd
|
||||
if (not posted or posted not in self.any_press_ignored) and \
|
||||
key not in self.any_press_ignored_keys and \
|
||||
(not self.suppressed_any_key_on_mods or \
|
||||
(not get_mods() & KMOD_CTRL and not get_mods() & KMOD_ALT)):
|
||||
self.post_any_command(key, cancel)
|
||||
self.post_command(cmd)
|
||||
|
||||
def post_command(self, cmd, **attributes):
|
||||
self.delegate.post(cmd, **attributes)
|
||||
def post_command(self, cmd):
|
||||
eid = self.get_user_event_id()
|
||||
name = self.get_configuration().get("event", "command-event-name")
|
||||
event.post(event.Event(eid, name=name, command=cmd))
|
||||
|
||||
def post_any_command(self, id, cancel=False):
|
||||
self.post_command("any", id=id, cancel=cancel)
|
||||
|
||||
def translate_joy_button(self, event):
|
||||
def translate_joy_button(self, evt):
|
||||
if not self.suppressed:
|
||||
cancel = event.type == JOYBUTTONUP
|
||||
posted = None
|
||||
for command, button in self.joy_button_map.items():
|
||||
if int(button) == event.button:
|
||||
self.post_command(command, cancel=cancel)
|
||||
posted = command
|
||||
if not posted or posted not in self.any_press_ignored:
|
||||
self.post_any_command(event.button, cancel)
|
||||
button = evt.button
|
||||
config = self.get_configuration()
|
||||
if button == config["joy-advance"]:
|
||||
self.post_command("advance")
|
||||
if button == config["joy-pause"]:
|
||||
self.post_command("pause")
|
||||
|
||||
def translate_axis_motion(self, event):
|
||||
if not self.suppressed and not self.check_command_line("-disable-joy-axis"):
|
||||
axis = event.axis
|
||||
value = event.value
|
||||
single_xy = self.get_configuration("joy", "single-xy")
|
||||
command = None
|
||||
if -.01 < value < .01:
|
||||
for direction in "up", "right", "down", "left":
|
||||
if not self.axes_cancelled[direction]:
|
||||
self.post_command(direction, cancel=True)
|
||||
if direction not in self.any_press_ignored:
|
||||
self.post_any_command(direction, True)
|
||||
self.axes_cancelled[direction] = True
|
||||
if single_xy:
|
||||
if direction == "up" and self.joystick.is_direction_pressed(Joystick.down):
|
||||
command = "down"
|
||||
elif direction == "down" and self.joystick.is_direction_pressed(Joystick.up):
|
||||
command = "up"
|
||||
elif direction == "right" and self.joystick.is_direction_pressed(Joystick.left):
|
||||
command = "left"
|
||||
elif direction == "left" and self.joystick.is_direction_pressed(Joystick.right):
|
||||
command = "right"
|
||||
def translate_axis_motion(self, evt):
|
||||
if not self.suppressed:
|
||||
axis = evt.axis
|
||||
value = evt.value
|
||||
if axis == 1:
|
||||
if value < 0:
|
||||
self.post_command("up")
|
||||
elif value > 0:
|
||||
self.post_command("down")
|
||||
else:
|
||||
if axis == self.get_configuration("joy", "vertical-axis"):
|
||||
if value < 0:
|
||||
if not single_xy or not self.joystick.is_direction_pressed(Joystick.down):
|
||||
command = "up"
|
||||
elif value > 0:
|
||||
if not single_xy or not self.joystick.is_direction_pressed(Joystick.up):
|
||||
command = "down"
|
||||
elif axis == self.get_configuration("joy", "horizontal-axis"):
|
||||
if value > 0:
|
||||
if not single_xy or not self.joystick.is_direction_pressed(Joystick.left):
|
||||
command = "right"
|
||||
elif value < 0:
|
||||
if not single_xy or not self.joystick.is_direction_pressed(Joystick.right):
|
||||
command = "left"
|
||||
if command is not None:
|
||||
delay = self.get_configuration("joy", "delay-axis")
|
||||
secs = get_secs()
|
||||
if not delay or secs - self.last_command_post_time > delay:
|
||||
self.post_command(command)
|
||||
if command not in self.any_press_ignored:
|
||||
self.post_any_command(command)
|
||||
self.axes_cancelled[command] = False
|
||||
self.last_command_post_time = secs
|
||||
if value > 0:
|
||||
self.post_command("right")
|
||||
elif value < 0:
|
||||
self.post_command("left")
|
||||
|
||||
def is_command_active(self, command):
|
||||
if not self.suppressed:
|
||||
if self.is_key_pressed(command):
|
||||
return True
|
||||
joystick = self.joystick
|
||||
joy_map = self.joy_button_map
|
||||
if command in joy_map and joystick.get_button(int(joy_map[command])):
|
||||
if self.is_key_pressed(command):
|
||||
return True
|
||||
if command == "up":
|
||||
return joystick.is_direction_pressed(Joystick.up)
|
||||
|
@ -180,55 +81,21 @@ class Input(GameChild):
|
|||
return joystick.is_direction_pressed(Joystick.down)
|
||||
elif command == "left":
|
||||
return joystick.is_direction_pressed(Joystick.left)
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_key_pressed(self, command):
|
||||
poll = get_pressed()
|
||||
poll = keyboard.get_pressed()
|
||||
for key in self.key_map[command]:
|
||||
if poll[key]:
|
||||
return True
|
||||
|
||||
def translate_mouse_input(self, event):
|
||||
button = event.button
|
||||
pos = event.pos
|
||||
post = self.post_command
|
||||
if event.type == MOUSEBUTTONDOWN:
|
||||
if button == 1:
|
||||
last = self.last_mouse_down_left
|
||||
if last:
|
||||
limit = self.double_click_time_limit
|
||||
if get_secs() - last < limit:
|
||||
post("mouse-double-click-left", pos=pos)
|
||||
last = get_secs()
|
||||
self.last_mouse_down_left = last
|
||||
if "mouse" not in self.any_press_ignored:
|
||||
self.post_any_command(event.button)
|
||||
if event.type == MOUSEBUTTONUP:
|
||||
if "mouse" not in self.any_press_ignored:
|
||||
self.post_any_command(event.button, True)
|
||||
|
||||
def get_axes(self):
|
||||
axes = {}
|
||||
for direction in "up", "right", "down", "left":
|
||||
axes[direction] = self.is_command_active(direction)
|
||||
return axes
|
||||
|
||||
def register_any_press_ignore(self, *args, **attributes):
|
||||
self.any_press_ignored.update(args)
|
||||
self.any_press_ignored_keys.update(self.extract_keys(attributes))
|
||||
|
||||
def extract_keys(self, attributes):
|
||||
keys = []
|
||||
if "keys" in attributes:
|
||||
keys = attributes["keys"]
|
||||
if type(keys) == int:
|
||||
keys = [keys]
|
||||
return keys
|
||||
|
||||
def unregister_any_press_ignore(self, *args, **attributes):
|
||||
self.any_press_ignored.difference_update(args)
|
||||
self.any_press_ignored_keys.difference_update(
|
||||
self.extract_keys(attributes))
|
||||
|
||||
|
||||
class Joystick:
|
||||
|
||||
|
@ -241,19 +108,15 @@ class Joystick:
|
|||
js.init()
|
||||
self.js = js
|
||||
|
||||
def is_direction_pressed(self, direction, margin=0.01):
|
||||
def is_direction_pressed(self, direction):
|
||||
js = self.js
|
||||
if not js or direction > 4:
|
||||
return False
|
||||
if direction == 0:
|
||||
return js.get_axis(1) < 0 - margin
|
||||
return js.get_axis(1) < 0
|
||||
elif direction == 1:
|
||||
return js.get_axis(0) > 0 + margin
|
||||
return js.get_axis(0) > 0
|
||||
elif direction == 2:
|
||||
return js.get_axis(1) > 0 + margin
|
||||
return js.get_axis(1) > 0
|
||||
elif direction == 3:
|
||||
return js.get_axis(0) < 0 - margin
|
||||
|
||||
def get_button(self, id):
|
||||
if self.js:
|
||||
return self.js.get_button(id)
|
||||
return js.get_axis(0) < 0
|
||||
|
|
|
@ -1,741 +0,0 @@
|
|||
from re import match
|
||||
from os.path import join
|
||||
from tempfile import gettempdir
|
||||
|
||||
from pygame import Surface
|
||||
from pygame.font import Font
|
||||
from pygame.draw import aaline
|
||||
from pygame.locals import *
|
||||
|
||||
from .GameChild import GameChild
|
||||
from .Sprite import Sprite
|
||||
from .Animation import Animation
|
||||
|
||||
class Interpolator(list, GameChild):
|
||||
|
||||
def __init__(self, parent):
|
||||
GameChild.__init__(self, parent)
|
||||
self.set_nodesets()
|
||||
self.gui_enabled = False
|
||||
|
||||
def init_gui(self):
|
||||
"""
|
||||
If the interpolator is requested on the command line, create a GUI object. This runs separately from `pgfw.Interpolator.__init__` because
|
||||
the display is not available when the main `pgfw.Interpolator` is created by `pgfw.Game`.
|
||||
"""
|
||||
self.gui_enabled = self.check_command_line("-interpolator")
|
||||
if self.gui_enabled:
|
||||
self.gui = GUI(self)
|
||||
|
||||
def set_nodesets(self):
|
||||
config = self.get_configuration()
|
||||
if config.has_section("interpolate"):
|
||||
for name, value in config.get_section("interpolate").items():
|
||||
self.add_nodeset(name, value)
|
||||
|
||||
def add_nodeset(self, name, value, method=None):
|
||||
self.append(Nodeset(name, value, method))
|
||||
return len(self) - 1
|
||||
|
||||
def is_gui_active(self):
|
||||
return self.gui_enabled and self.gui.active
|
||||
|
||||
def get_nodeset(self, name):
|
||||
for nodeset in self:
|
||||
if nodeset.name == name:
|
||||
return nodeset
|
||||
|
||||
def remove(self, outgoing):
|
||||
for ii, nodeset in enumerate(self):
|
||||
if nodeset.name == outgoing.name:
|
||||
self.pop(ii)
|
||||
break
|
||||
|
||||
|
||||
class Nodeset(list):
|
||||
|
||||
LINEAR, CUBIC = range(2)
|
||||
|
||||
def __init__(self, name, nodes, method=None):
|
||||
list.__init__(self, [])
|
||||
self.name = name
|
||||
if isinstance(nodes, str):
|
||||
self.parse_raw(nodes)
|
||||
else:
|
||||
self.interpolation_method = method
|
||||
self.parse_list(nodes)
|
||||
self.set_splines()
|
||||
|
||||
def parse_raw(self, raw):
|
||||
raw = raw.strip()
|
||||
if raw[0].upper() == "L":
|
||||
self.set_interpolation_method(self.LINEAR, False)
|
||||
else:
|
||||
self.set_interpolation_method(self.CUBIC, False)
|
||||
for node in raw[1:].strip().split(","):
|
||||
self.add_node(list(map(float, node.strip().split())), False)
|
||||
|
||||
def set_interpolation_method(self, method, refresh=True):
|
||||
self.interpolation_method = method
|
||||
if refresh:
|
||||
self.set_splines()
|
||||
|
||||
def add_node(self, coordinates, refresh=True):
|
||||
x = coordinates[0]
|
||||
inserted = False
|
||||
index = 0
|
||||
for ii, node in enumerate(self):
|
||||
if x < node.x:
|
||||
self.insert(ii, Node(coordinates))
|
||||
inserted = True
|
||||
index = ii
|
||||
break
|
||||
elif x == node.x:
|
||||
return None
|
||||
if not inserted:
|
||||
self.append(Node(coordinates))
|
||||
index = len(self) - 1
|
||||
if refresh:
|
||||
self.set_splines()
|
||||
return index
|
||||
|
||||
def parse_list(self, nodes):
|
||||
for node in nodes:
|
||||
self.add_node(node)
|
||||
|
||||
def set_splines(self):
|
||||
if self.interpolation_method == self.LINEAR:
|
||||
self.set_linear_splines()
|
||||
else:
|
||||
self.set_cubic_splines()
|
||||
|
||||
def set_linear_splines(self):
|
||||
self.splines = splines = []
|
||||
for ii in range(len(self) - 1):
|
||||
x1, y1, x2, y2 = self[ii] + self[ii + 1]
|
||||
m = float(y2 - y1) / (x2 - x1)
|
||||
splines.append(LinearSpline(x1, y1, m))
|
||||
|
||||
def set_cubic_splines(self):
|
||||
n = len(self) - 1
|
||||
a = [node.y for node in self]
|
||||
b = [None] * n
|
||||
d = [None] * n
|
||||
h = [self[ii + 1].x - self[ii].x for ii in range(n)]
|
||||
alpha = [None] + [(3.0 / h[ii]) * (a[ii + 1] - a[ii]) - \
|
||||
(3.0 / h[ii - 1]) * (a[ii] - a[ii - 1]) \
|
||||
for ii in range(1, n)]
|
||||
c = [None] * (n + 1)
|
||||
l = [None] * (n + 1)
|
||||
u = [None] * (n + 1)
|
||||
z = [None] * (n + 1)
|
||||
l[0] = 1
|
||||
u[0] = z[0] = 0
|
||||
for ii in range(1, n):
|
||||
l[ii] = 2 * (self[ii + 1].x - self[ii - 1].x) - \
|
||||
h[ii - 1] * u[ii - 1]
|
||||
u[ii] = h[ii] / l[ii]
|
||||
z[ii] = (alpha[ii] - h[ii - 1] * z[ii - 1]) / l[ii]
|
||||
l[n] = 1
|
||||
z[n] = c[n] = 0
|
||||
for jj in range(n - 1, -1, -1):
|
||||
c[jj] = z[jj] - u[jj] * c[jj + 1]
|
||||
b[jj] = (a[jj + 1] - a[jj]) / h[jj] - \
|
||||
(h[jj] * (c[jj + 1] + 2 * c[jj])) / 3
|
||||
d[jj] = (c[jj + 1] - c[jj]) / (3 * h[jj])
|
||||
self.splines = [CubicSpline(self[ii].x, a[ii], b[ii], c[ii],
|
||||
d[ii]) for ii in range(n)]
|
||||
|
||||
def get_y(self, t, loop=False, reverse=False, natural=False):
|
||||
if loop or reverse:
|
||||
if reverse and int(t) / int(self[-1].x) % 2:
|
||||
t = self[-1].x - t
|
||||
t %= self[-1].x
|
||||
elif not natural:
|
||||
if t < self[0].x:
|
||||
t = self[0].x
|
||||
elif t > self[-1].x:
|
||||
t = self[-1].x
|
||||
splines = self.splines
|
||||
for ii in range(len(splines) - 1):
|
||||
if t < splines[ii + 1].x:
|
||||
return splines[ii].get_y(t)
|
||||
return splines[-1].get_y(t)
|
||||
|
||||
def remove(self, node, refresh=True):
|
||||
list.remove(self, node)
|
||||
if refresh:
|
||||
self.set_splines()
|
||||
|
||||
def resize(self, left, length, refresh=True):
|
||||
old_left = self[0].x
|
||||
old_length = self.get_length()
|
||||
for node in self:
|
||||
node.x = left + length * (node.x - old_left) / old_length
|
||||
if refresh:
|
||||
self.set_splines()
|
||||
|
||||
def get_length(self):
|
||||
return self[-1].x - self[0].x
|
||||
|
||||
|
||||
class Node(list):
|
||||
|
||||
def __init__(self, coordinates):
|
||||
list.__init__(self, coordinates)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "x":
|
||||
return self[0]
|
||||
elif name == "y":
|
||||
return self[1]
|
||||
return list.__get__(self, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == "x":
|
||||
list.__setitem__(self, 0, value)
|
||||
elif name == "y":
|
||||
list.__setitem__(self, 1, value)
|
||||
else:
|
||||
list.__setattr__(self, name, value)
|
||||
|
||||
|
||||
class Spline:
|
||||
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
|
||||
class CubicSpline(Spline):
|
||||
|
||||
def __init__(self, x, a, b, c, d):
|
||||
Spline.__init__(self, x)
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
|
||||
def get_y(self, t):
|
||||
x = self.x
|
||||
return self.a + self.b * (t - x) + self.c * (t - x) ** 2 + self.d * \
|
||||
(t - x) ** 3
|
||||
|
||||
|
||||
class LinearSpline(Spline):
|
||||
|
||||
def __init__(self, x, y, m):
|
||||
Spline.__init__(self, x)
|
||||
self.y = y
|
||||
self.m = m
|
||||
|
||||
def get_y(self, t):
|
||||
return self.m * (t - self.x) + self.y
|
||||
|
||||
|
||||
class GUI(Animation):
|
||||
|
||||
B_DUPLICATE, B_WRITE, B_DELETE, B_LINEAR, B_CUBIC, B_SPLIT = range(6)
|
||||
S_NONE, S_LEFT, S_RIGHT = range(3)
|
||||
|
||||
def __init__(self, parent):
|
||||
Animation.__init__(self, parent, unfiltered=True)
|
||||
self.audio = self.get_audio()
|
||||
self.display = self.get_game().display
|
||||
self.display_surface = self.get_display_surface()
|
||||
self.time_filter = self.get_game().time_filter
|
||||
self.delegate = self.get_delegate()
|
||||
self.split = self.S_NONE
|
||||
self.success_indicator_active = True
|
||||
self.success_indicator_blink_count = 0
|
||||
self.load_configuration()
|
||||
self.font = Font(None, self.label_size)
|
||||
self.prompt = Prompt(self)
|
||||
self.set_temporary_file()
|
||||
self.set_background()
|
||||
self.set_success_indicator()
|
||||
self.set_plot_rect()
|
||||
self.set_marker_frame()
|
||||
self.set_buttons()
|
||||
self.active = False
|
||||
self.set_nodeset_index()
|
||||
self.set_y_range()
|
||||
self.set_markers()
|
||||
self.subscribe(self.respond_to_command)
|
||||
self.subscribe(self.respond_to_mouse_down, MOUSEBUTTONDOWN)
|
||||
self.subscribe(self.respond_to_key, KEYDOWN)
|
||||
self.register(self.show_success_indicator, interval=100)
|
||||
self.register(self.save_temporary_file, interval=10000)
|
||||
self.play(self.save_temporary_file)
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("interpolator-gui")
|
||||
self.label_size = config["label-size"]
|
||||
self.axis_label_count = config["axis-label-count"]
|
||||
self.margin = config["margin"]
|
||||
self.curve_color = config["curve-color"]
|
||||
self.marker_size = config["marker-size"]
|
||||
self.marker_color = config["marker-color"]
|
||||
self.label_precision = config["label-precision"]
|
||||
self.template_nodeset = config["template-nodeset"]
|
||||
self.template_nodeset_name = config["template-nodeset-name"]
|
||||
self.flat_y_range = config["flat-y-range"]
|
||||
|
||||
def set_temporary_file(self):
|
||||
self.temporary_file = open(join(gettempdir(), "pgfw-config"), "w")
|
||||
|
||||
def set_background(self):
|
||||
surface = Surface(self.display_surface.get_size())
|
||||
surface.fill((0, 0, 0))
|
||||
self.background = surface
|
||||
|
||||
def set_success_indicator(self):
|
||||
surface = Surface((10, 10))
|
||||
surface.fill((0, 255, 0))
|
||||
rect = surface.get_rect()
|
||||
rect.topleft = self.display_surface.get_rect().topleft
|
||||
self.success_indicator, self.success_indicator_rect = surface, rect
|
||||
|
||||
def set_plot_rect(self):
|
||||
margin = self.margin
|
||||
self.plot_rect = self.display_surface.get_rect().inflate(-margin,
|
||||
-margin)
|
||||
|
||||
def set_marker_frame(self):
|
||||
size = self.marker_size
|
||||
surface = Surface((size, size))
|
||||
transparent_color = (255, 0, 255)
|
||||
surface.fill(transparent_color)
|
||||
surface.set_colorkey(transparent_color)
|
||||
line_color = self.marker_color
|
||||
aaline(surface, line_color, (0, 0), (size - 1, size - 1))
|
||||
aaline(surface, line_color, (0, size - 1), (size - 1, 0))
|
||||
self.marker_frame = surface
|
||||
|
||||
def set_buttons(self):
|
||||
self.buttons = buttons = []
|
||||
text = "Duplicate", "Write", "Delete", "Linear", "Cubic", "Split: No"
|
||||
x = 0
|
||||
for instruction in text:
|
||||
buttons.append(Button(self, instruction, x))
|
||||
x += buttons[-1].location.w + 10
|
||||
|
||||
def set_nodeset_index(self, increment=None, index=None):
|
||||
parent = self.parent
|
||||
if index is None:
|
||||
if not increment:
|
||||
index = 0
|
||||
else:
|
||||
index = self.nodeset_index + increment
|
||||
limit = len(parent) - 1
|
||||
if index > limit:
|
||||
index = 0
|
||||
elif index < 0:
|
||||
index = limit
|
||||
self.nodeset_index = index
|
||||
self.set_nodeset_label()
|
||||
|
||||
def set_nodeset_label(self):
|
||||
surface = self.font.render(self.get_nodeset().name, True, (0, 0, 0),
|
||||
(255, 255, 255))
|
||||
rect = surface.get_rect()
|
||||
rect.bottomright = self.display_surface.get_rect().bottomright
|
||||
self.nodeset_label, self.nodeset_label_rect = surface, rect
|
||||
|
||||
def get_nodeset(self):
|
||||
if not len(self.parent):
|
||||
self.parent.add_nodeset(self.template_nodeset_name,
|
||||
self.template_nodeset)
|
||||
self.set_nodeset_index(0)
|
||||
return self.parent[self.nodeset_index]
|
||||
|
||||
def set_y_range(self):
|
||||
width = self.plot_rect.w
|
||||
nodeset = self.get_nodeset()
|
||||
self.y_range = y_range = [nodeset[0].y, nodeset[-1].y]
|
||||
x = 0
|
||||
while x < width:
|
||||
y = nodeset.get_y(self.get_function_coordinates(x)[0])
|
||||
if y < y_range[0]:
|
||||
y_range[0] = y
|
||||
elif y > y_range[1]:
|
||||
y_range[1] = y
|
||||
x += width * .01
|
||||
if y_range[1] - y_range[0] == 0:
|
||||
y_range[1] += self.flat_y_range
|
||||
if self.split:
|
||||
self.adjust_for_split(y_range, nodeset)
|
||||
self.set_axis_labels()
|
||||
|
||||
def get_function_coordinates(self, xp=0, yp=0):
|
||||
nodeset = self.get_nodeset()
|
||||
x_min, x_max, (y_min, y_max) = nodeset[0].x, nodeset[-1].x, self.y_range
|
||||
rect = self.plot_rect
|
||||
x = float(xp) / (rect.right - rect.left) * (x_max - x_min) + x_min
|
||||
y = float(yp) / (rect.bottom - rect.top) * (y_min - y_max) + y_max
|
||||
return x, y
|
||||
|
||||
def adjust_for_split(self, y_range, nodeset):
|
||||
middle = nodeset[0].y if self.split == self.S_LEFT else nodeset[-1].y
|
||||
below, above = middle - y_range[0], y_range[1] - middle
|
||||
if below > above:
|
||||
y_range[1] += below - above
|
||||
else:
|
||||
y_range[0] -= above - below
|
||||
|
||||
def set_axis_labels(self):
|
||||
self.axis_labels = labels = []
|
||||
nodeset, formatted, render, rect, yr = (self.get_nodeset(),
|
||||
self.get_formatted_measure,
|
||||
self.font.render,
|
||||
self.plot_rect, self.y_range)
|
||||
for ii, node in enumerate(nodeset[0::len(nodeset) - 1]):
|
||||
xs = render(formatted(node.x), True, (0, 0, 0), (255, 255, 255))
|
||||
xsr = xs.get_rect()
|
||||
xsr.top = rect.bottom
|
||||
if not ii:
|
||||
xsr.left = rect.left
|
||||
else:
|
||||
xsr.right = rect.right
|
||||
ys = render(formatted(yr[ii]), True, (0, 0, 0), (255, 255, 255))
|
||||
ysr = ys.get_rect()
|
||||
ysr.right = rect.left
|
||||
if not ii:
|
||||
ysr.bottom = rect.bottom
|
||||
else:
|
||||
ysr.top = rect.top
|
||||
labels.append(((xs, xsr), (ys, ysr)))
|
||||
|
||||
def get_formatted_measure(self, measure):
|
||||
return "%s" % float(("%." + str(self.label_precision) + "g") % measure)
|
||||
|
||||
def deactivate(self):
|
||||
self.active = False
|
||||
self.time_filter.open()
|
||||
# self.audio.muted = self.saved_mute_state
|
||||
self.display.set_mouse_visibility(self.saved_mouse_state)
|
||||
|
||||
def respond_to_command(self, event):
|
||||
compare = self.delegate.compare
|
||||
if compare(event, "toggle-interpolator"):
|
||||
self.toggle()
|
||||
elif self.active:
|
||||
if compare(event, "reset-game"):
|
||||
self.deactivate()
|
||||
elif compare(event, "quit"):
|
||||
self.get_game().end(event)
|
||||
|
||||
def toggle(self):
|
||||
if self.active:
|
||||
self.deactivate()
|
||||
self.get_game().delegate.post("refresh-nodesets")
|
||||
else:
|
||||
self.activate()
|
||||
|
||||
def activate(self):
|
||||
self.active = True
|
||||
self.time_filter.close()
|
||||
# self.saved_mute_state = self.audio.muted
|
||||
# self.audio.mute()
|
||||
self.draw()
|
||||
self.saved_mouse_state = self.display.set_mouse_visibility(True)
|
||||
|
||||
def respond_to_mouse_down(self, event):
|
||||
redraw = False
|
||||
if self.active and not self.prompt.active:
|
||||
nodeset_rect = self.nodeset_label_rect
|
||||
plot_rect = self.plot_rect
|
||||
if event.button == 1:
|
||||
pos = event.pos
|
||||
if nodeset_rect.collidepoint(pos):
|
||||
self.set_nodeset_index(1)
|
||||
redraw = True
|
||||
elif self.axis_labels[0][0][1].collidepoint(pos):
|
||||
text = "{0} {1}".format(*map(self.get_formatted_measure,
|
||||
self.get_nodeset()[0]))
|
||||
self.prompt.activate(text, self.resize_nodeset, 0)
|
||||
elif self.axis_labels[1][0][1].collidepoint(pos):
|
||||
text = "{0} {1}".format(*map(self.get_formatted_measure,
|
||||
self.get_nodeset()[-1]))
|
||||
self.prompt.activate(text, self.resize_nodeset, -1)
|
||||
else:
|
||||
bi = self.collide_buttons(pos)
|
||||
if bi is not None:
|
||||
if bi == self.B_WRITE:
|
||||
self.get_configuration().write()
|
||||
self.play(self.show_success_indicator)
|
||||
elif bi in (self.B_LINEAR, self.B_CUBIC):
|
||||
nodeset = self.get_nodeset()
|
||||
if bi == self.B_LINEAR:
|
||||
nodeset.set_interpolation_method(Nodeset.LINEAR)
|
||||
else:
|
||||
nodeset.set_interpolation_method(Nodeset.CUBIC)
|
||||
self.store_in_configuration()
|
||||
redraw = True
|
||||
elif bi == self.B_DUPLICATE:
|
||||
self.prompt.activate("", self.add_nodeset)
|
||||
elif bi == self.B_DELETE and len(self.parent) > 1:
|
||||
self.parent.remove(self.get_nodeset())
|
||||
self.set_nodeset_index(1)
|
||||
self.store_in_configuration()
|
||||
redraw = True
|
||||
elif bi == self.B_SPLIT:
|
||||
self.toggle_split()
|
||||
redraw = True
|
||||
elif plot_rect.collidepoint(pos) and \
|
||||
not self.collide_markers(pos):
|
||||
xp, yp = pos[0] - plot_rect.left, pos[1] - plot_rect.top
|
||||
self.get_nodeset().add_node(
|
||||
self.get_function_coordinates(xp, yp))
|
||||
self.store_in_configuration()
|
||||
redraw = True
|
||||
elif event.button == 3:
|
||||
pos = event.pos
|
||||
if nodeset_rect.collidepoint(pos):
|
||||
self.set_nodeset_index(-1)
|
||||
redraw = True
|
||||
elif plot_rect.collidepoint(pos):
|
||||
marker = self.collide_markers(pos)
|
||||
if marker:
|
||||
self.get_nodeset().remove(marker.node)
|
||||
self.store_in_configuration()
|
||||
redraw = True
|
||||
elif self.active and self.prompt.active and \
|
||||
not self.prompt.rect.collidepoint(event.pos):
|
||||
self.prompt.deactivate()
|
||||
redraw = True
|
||||
if redraw:
|
||||
self.set_y_range()
|
||||
self.set_markers()
|
||||
self.draw()
|
||||
|
||||
def resize_nodeset(self, text, index):
|
||||
result = match("^\s*(-{,1}\d*\.{,1}\d*)\s+(-{,1}\d*\.{,1}\d*)\s*$",
|
||||
text)
|
||||
if result:
|
||||
try:
|
||||
nodeset = self.get_nodeset()
|
||||
x, y = map(float, result.group(1, 2))
|
||||
if (index == -1 and x > nodeset[0].x) or \
|
||||
(index == 0 and x < nodeset[-1].x):
|
||||
nodeset[index].y = y
|
||||
if index == -1:
|
||||
nodeset.resize(nodeset[0].x, x - nodeset[0].x)
|
||||
else:
|
||||
nodeset.resize(x, nodeset[-1].x - x)
|
||||
self.store_in_configuration()
|
||||
self.set_y_range()
|
||||
self.set_axis_labels()
|
||||
self.set_markers()
|
||||
self.draw()
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def collide_buttons(self, pos):
|
||||
for ii, button in enumerate(self.buttons):
|
||||
if button.location.collidepoint(pos):
|
||||
return ii
|
||||
|
||||
def store_in_configuration(self):
|
||||
config = self.get_configuration()
|
||||
section = "interpolate"
|
||||
config.clear_section(section)
|
||||
for nodeset in self.parent:
|
||||
code = "L" if nodeset.interpolation_method == Nodeset.LINEAR else \
|
||||
"C"
|
||||
for ii, node in enumerate(nodeset):
|
||||
if ii > 0:
|
||||
code += ","
|
||||
code += " {0} {1}".format(*map(self.get_formatted_measure,
|
||||
node))
|
||||
if not config.has_section(section):
|
||||
config.add_section(section)
|
||||
config.set(section, nodeset.name, code)
|
||||
|
||||
def toggle_split(self):
|
||||
self.split += 1
|
||||
if self.split > self.S_RIGHT:
|
||||
self.split = self.S_NONE
|
||||
self.buttons[self.B_SPLIT].set_frame(["Split: No", "Split: L",
|
||||
"Split: R"][self.split])
|
||||
|
||||
def add_nodeset(self, name):
|
||||
nodeset = self.get_nodeset()
|
||||
self.set_nodeset_index(index=self.parent.add_nodeset(\
|
||||
name, nodeset, nodeset.interpolation_method))
|
||||
self.store_in_configuration()
|
||||
self.draw()
|
||||
return True
|
||||
|
||||
def collide_markers(self, pos):
|
||||
for marker in self.markers:
|
||||
if marker.location.collidepoint(pos):
|
||||
return marker
|
||||
|
||||
def set_markers(self):
|
||||
self.markers = markers = []
|
||||
for node in self.get_nodeset()[1:-1]:
|
||||
markers.append(Marker(self, node))
|
||||
markers[-1].location.center = self.get_plot_coordinates(*node)
|
||||
|
||||
def get_plot_coordinates(self, x=0, y=0):
|
||||
nodeset = self.get_nodeset()
|
||||
x_min, x_max, (y_min, y_max) = nodeset[0].x, nodeset[-1].x, self.y_range
|
||||
x_ratio = float(x - x_min) / (x_max - x_min)
|
||||
rect = self.plot_rect
|
||||
xp = x_ratio * (rect.right - rect.left) + rect.left
|
||||
y_ratio = float(y - y_min) / (y_max - y_min)
|
||||
yp = rect.bottom - y_ratio * (rect.bottom - rect.top)
|
||||
return xp, yp
|
||||
|
||||
def draw(self):
|
||||
display_surface = self.display_surface
|
||||
display_surface.blit(self.background, (0, 0))
|
||||
display_surface.blit(self.nodeset_label, self.nodeset_label_rect)
|
||||
self.draw_axes()
|
||||
self.draw_function()
|
||||
self.draw_markers()
|
||||
self.draw_buttons()
|
||||
|
||||
def draw_axes(self):
|
||||
display_surface = self.display_surface
|
||||
for xl, yl in self.axis_labels:
|
||||
display_surface.blit(*xl)
|
||||
display_surface.blit(*yl)
|
||||
|
||||
def draw_function(self):
|
||||
rect = self.plot_rect
|
||||
surface = self.display_surface
|
||||
nodeset = self.get_nodeset()
|
||||
step = 1
|
||||
for x in range(rect.left, rect.right + step, step):
|
||||
ii = x - rect.left
|
||||
fx = nodeset.get_y(self.get_function_coordinates(ii)[0])
|
||||
y = self.get_plot_coordinates(y=fx)[1]
|
||||
if ii > 0:
|
||||
aaline(surface, self.curve_color, (x - step, last_y), (x, y))
|
||||
last_y = y
|
||||
|
||||
def draw_markers(self):
|
||||
for marker in self.markers:
|
||||
marker.update()
|
||||
|
||||
def draw_buttons(self):
|
||||
for button in self.buttons:
|
||||
button.update()
|
||||
|
||||
def respond_to_key(self, event):
|
||||
if self.prompt.active:
|
||||
prompt = self.prompt
|
||||
if event.key == K_RETURN:
|
||||
if prompt.callback[0](prompt.text, *prompt.callback[1]):
|
||||
prompt.deactivate()
|
||||
elif event.key == K_BACKSPACE:
|
||||
prompt.text = prompt.text[:-1]
|
||||
prompt.update()
|
||||
prompt.draw_text()
|
||||
elif (event.unicode.isalnum() or event.unicode.isspace() or \
|
||||
event.unicode in (".", "-", "_")) and len(prompt.text) < \
|
||||
prompt.character_limit:
|
||||
prompt.text += event.unicode
|
||||
prompt.update()
|
||||
prompt.draw_text()
|
||||
|
||||
def show_success_indicator(self):
|
||||
self.draw()
|
||||
if self.success_indicator_blink_count > 1:
|
||||
self.success_indicator_blink_count = 0
|
||||
self.halt(self.show_success_indicator)
|
||||
else:
|
||||
if self.success_indicator_active:
|
||||
self.display_surface.blit(self.success_indicator,
|
||||
self.success_indicator_rect)
|
||||
if self.success_indicator_active:
|
||||
self.success_indicator_blink_count += 1
|
||||
self.success_indicator_active = not self.success_indicator_active
|
||||
|
||||
def save_temporary_file(self):
|
||||
fp = self.temporary_file
|
||||
fp.seek(0)
|
||||
fp.truncate()
|
||||
self.get_configuration().write(fp)
|
||||
|
||||
def rearrange(self):
|
||||
self.set_background()
|
||||
self.set_success_indicator()
|
||||
self.set_plot_rect()
|
||||
self.set_markers()
|
||||
self.set_nodeset_label()
|
||||
self.set_axis_labels()
|
||||
self.set_buttons()
|
||||
self.prompt.reset()
|
||||
|
||||
class Marker(Sprite):
|
||||
|
||||
def __init__(self, parent, node):
|
||||
Sprite.__init__(self, parent)
|
||||
self.add_frame(parent.marker_frame)
|
||||
self.node = node
|
||||
|
||||
|
||||
class Button(Sprite):
|
||||
|
||||
def __init__(self, parent, text, left):
|
||||
Sprite.__init__(self, parent)
|
||||
self.set_frame(text)
|
||||
self.location.bottomleft = left, \
|
||||
self.get_display_surface().get_rect().bottom
|
||||
|
||||
def set_frame(self, text):
|
||||
self.clear_frames()
|
||||
self.add_frame(self.parent.font.render(text, True, (0, 0, 0),
|
||||
(255, 255, 255)))
|
||||
|
||||
|
||||
class Prompt(Sprite):
|
||||
|
||||
def __init__(self, parent):
|
||||
Sprite.__init__(self, parent)
|
||||
self.load_configuration()
|
||||
self.font = Font(None, self.text_size)
|
||||
self.reset()
|
||||
self.deactivate()
|
||||
|
||||
def deactivate(self):
|
||||
self.active = False
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("interpolator-gui")
|
||||
self.size = config["prompt-size"]
|
||||
self.border_color = config["prompt-border-color"]
|
||||
self.border_width = config["prompt-border-width"]
|
||||
self.character_limit = config["prompt-character-limit"]
|
||||
self.text_size = config["prompt-text-size"]
|
||||
|
||||
def reset(self):
|
||||
self.set_frame()
|
||||
self.place()
|
||||
|
||||
def set_frame(self):
|
||||
self.clear_frames()
|
||||
surface = Surface(self.size)
|
||||
self.add_frame(surface)
|
||||
surface.fill(self.border_color)
|
||||
width = self.border_width * 2
|
||||
surface.fill((0, 0, 0), surface.get_rect().inflate(-width, -width))
|
||||
|
||||
def place(self):
|
||||
self.location.center = self.display_surface.get_rect().center
|
||||
|
||||
def activate(self, text, callback, *args):
|
||||
self.active = True
|
||||
self.text = str(text)
|
||||
self.callback = callback, args
|
||||
self.update()
|
||||
self.draw_text()
|
||||
|
||||
def draw_text(self):
|
||||
surface = self.font.render(self.text, True, (255, 255, 255), (0, 0, 0))
|
||||
rect = surface.get_rect()
|
||||
rect.center = self.location.center
|
||||
self.display_surface.blit(surface, rect)
|
117
pgfw/Mainloop.py
117
pgfw/Mainloop.py
|
@ -1,117 +0,0 @@
|
|||
from pygame import display
|
||||
from pygame.font import Font
|
||||
from pygame.time import get_ticks, wait
|
||||
|
||||
from .GameChild import GameChild
|
||||
|
||||
class Mainloop(GameChild):
|
||||
"""
|
||||
Maintain and display framerate. Call the parent's (the main game object) frame method once per frame. If frame skipping is enabled and the frame rate
|
||||
is below the requested framerate, the frame method will be called multiple times to try to keep pace (although this rarely makes sense, since calling
|
||||
the frame method multiple times would most likely cause further slow down). It is missing some important game loop features. For example, game logic
|
||||
is not separated from draw logic, and frame rate is expected to remain constant based on the definition in the config file. The time of each frame is
|
||||
tracked by the TimeFilter class.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
GameChild.__init__(self, parent)
|
||||
self.overflow = 0
|
||||
self.frame_count = 1
|
||||
self.actual_frame_duration = 0
|
||||
self.frames_this_second = 0
|
||||
self.last_framerate_display = 0
|
||||
self.frame_times = []
|
||||
self.load_configuration()
|
||||
self.init_framerate_display()
|
||||
self.last_ticks = get_ticks()
|
||||
self.stopping = False
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("display")
|
||||
self.target_frame_duration = config["frame-duration"]
|
||||
self.wait_duration = config["wait-duration"]
|
||||
self.skip_frames = config["skip-frames"]
|
||||
self.show_framerate = config["show-framerate"]
|
||||
self.framerate_text_size = config["framerate-text-size"]
|
||||
self.framerate_text_color = config["framerate-text-color"]
|
||||
self.framerate_text_background = config["framerate-text-background"]
|
||||
self.framerate_display_flag = config["framerate-display-flag"]
|
||||
|
||||
def init_framerate_display(self):
|
||||
if self.framerate_display_active():
|
||||
screen = self.get_screen()
|
||||
self.last_framerate_count = 0
|
||||
self.framerate_topright = self.get_configuration("display", "framerate-position")
|
||||
if self.framerate_topright[0] == -1:
|
||||
self.framerate_topright[0] = screen.get_rect().right
|
||||
self.display_surface = screen
|
||||
self.font = Font(None, self.framerate_text_size)
|
||||
self.font.set_bold(True)
|
||||
self.render_framerate()
|
||||
|
||||
def framerate_display_active(self):
|
||||
return self.check_command_line(self.framerate_display_flag) or self.show_framerate
|
||||
|
||||
def render_framerate(self):
|
||||
average_frame_time = sum(self.frame_times) / len(self.frame_times) if self.frame_times else 0
|
||||
self.frame_times = []
|
||||
text = self.font.render(f"{self.last_framerate_count} ({average_frame_time: .1f})", False, self.framerate_text_color, self.framerate_text_background)
|
||||
rect = text.get_rect()
|
||||
rect.topright = self.framerate_topright
|
||||
self.framerate_text = text
|
||||
self.framerate_text_rect = rect
|
||||
|
||||
def run(self):
|
||||
while not self.stopping:
|
||||
self.advance_frame()
|
||||
self.update_frame_duration()
|
||||
self.update_overflow()
|
||||
self.stopping = False
|
||||
|
||||
def advance_frame(self):
|
||||
refresh = False
|
||||
while self.frame_count > 0:
|
||||
refresh = True
|
||||
self.parent.frame()
|
||||
if self.framerate_display_active():
|
||||
self.update_framerate()
|
||||
self.frame_count -= 1
|
||||
if not self.skip_frames:
|
||||
break
|
||||
if refresh:
|
||||
display.update()
|
||||
|
||||
def update_frame_duration(self):
|
||||
last_ticks = self.last_ticks
|
||||
actual_frame_duration = get_ticks() - last_ticks
|
||||
last_ticks = get_ticks()
|
||||
self.frame_times.append(actual_frame_duration)
|
||||
while actual_frame_duration < self.target_frame_duration:
|
||||
wait(self.wait_duration)
|
||||
actual_frame_duration += get_ticks() - last_ticks
|
||||
last_ticks = get_ticks()
|
||||
self.actual_frame_duration = actual_frame_duration
|
||||
self.last_ticks = last_ticks
|
||||
|
||||
def update_overflow(self):
|
||||
self.frame_count = 1
|
||||
target_frame_duration = self.target_frame_duration
|
||||
overflow = self.overflow
|
||||
overflow += self.actual_frame_duration - target_frame_duration
|
||||
while overflow > target_frame_duration:
|
||||
self.frame_count += 1
|
||||
overflow -= target_frame_duration
|
||||
overflow = self.overflow
|
||||
|
||||
def update_framerate(self):
|
||||
count = self.frames_this_second + 1
|
||||
if get_ticks() - self.last_framerate_display > 1000:
|
||||
self.last_framerate_count = count
|
||||
self.render_framerate()
|
||||
self.last_framerate_display = get_ticks()
|
||||
count = 0
|
||||
self.display_surface.blit(self.framerate_text, self.framerate_text_rect)
|
||||
self.frames_this_second = count
|
||||
|
||||
def stop(self):
|
||||
self.stopping = True
|
156
pgfw/Note.py
156
pgfw/Note.py
|
@ -1,156 +0,0 @@
|
|||
# adapted from Pythoven
|
||||
# https://github.com/phiresky/pythoven
|
||||
|
||||
from random import randint
|
||||
from math import sin, log, pi
|
||||
from array import array
|
||||
|
||||
from pygame import version
|
||||
from pygame.mixer import Sound, get_init
|
||||
|
||||
class Samples(Sound):
|
||||
|
||||
def __init__(self):
|
||||
self.set_amplitude()
|
||||
if version.vernum < (1, 9, 2):
|
||||
Sound.__init__(self, self.build())
|
||||
else:
|
||||
Sound.__init__(self, buffer=self.build())
|
||||
|
||||
def set_amplitude(self):
|
||||
self.amplitude = (1 << (self.get_sample_width() * 8 - 1)) - 1
|
||||
|
||||
def get_sample_width(self):
|
||||
return abs(int(get_init()[1] / 8))
|
||||
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
def get_empty_array(self, length):
|
||||
return array(self.get_array_typecode(), [0] * length)
|
||||
|
||||
def get_array_typecode(self):
|
||||
return [None, "b", "h"][self.get_sample_width()]
|
||||
|
||||
|
||||
class Note(Samples):
|
||||
|
||||
base_frequency = 440.0
|
||||
base_octave = 4
|
||||
base_name = "A"
|
||||
names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
|
||||
SQUARE, TRIANGLE, SAW, SINE, DIRTY = range(5)
|
||||
|
||||
def __init__(self, name=None, octave=4, frequency=None, shape=SQUARE, volume=1.0):
|
||||
names = self.names
|
||||
self.shape = shape
|
||||
if frequency is None:
|
||||
self.name = name
|
||||
self.octave = octave
|
||||
self.set_frequency()
|
||||
elif name is None:
|
||||
self.frequency = float(frequency)
|
||||
self.set_name_and_octave()
|
||||
Samples.__init__(self)
|
||||
self.set_volume(volume)
|
||||
|
||||
def set_frequency(self):
|
||||
name, octave = self.name, self.octave
|
||||
names = self.names
|
||||
octave_length = len(names)
|
||||
offset = (octave - self.base_octave) * octave_length + \
|
||||
names.index(name) - names.index(self.base_name)
|
||||
self.frequency = self.base_frequency * 2 ** (offset / float(octave_length))
|
||||
|
||||
def set_name_and_octave(self):
|
||||
names = self.names
|
||||
octave_length = len(names)
|
||||
offset = int(round(log(self.frequency / self.base_frequency, 2) * \
|
||||
octave_length)) + names.index(self.base_name)
|
||||
self.octave = self.base_octave + offset / octave_length
|
||||
self.name = names[offset % octave_length]
|
||||
|
||||
def __repr__(self):
|
||||
return "%s%i %.2f" % (self.name, self.octave, self.frequency)
|
||||
|
||||
def build(self):
|
||||
period = int(round(get_init()[0] / self.frequency))
|
||||
samples = self.get_empty_array(period)
|
||||
shape = self.shape
|
||||
if shape == self.TRIANGLE:
|
||||
self.store_triangle_wave(samples, period)
|
||||
elif shape == self.SAW:
|
||||
self.store_saw_wave(samples, period)
|
||||
elif shape == self.SINE:
|
||||
self.store_sine_wave(samples, period)
|
||||
elif shape == self.DIRTY:
|
||||
self.store_dirty_wave(samples)
|
||||
else:
|
||||
self.store_square_wave(samples, period)
|
||||
return samples
|
||||
|
||||
def store_triangle_wave(self, samples, period):
|
||||
amplitude = self.amplitude
|
||||
if period > 1:
|
||||
coefficient = 4 * amplitude / float(period - 1)
|
||||
for time in range(int(round(period / 2.0))):
|
||||
y = int((coefficient * time) - amplitude)
|
||||
samples[time] = y
|
||||
samples[-time - 1] = y
|
||||
|
||||
def store_saw_wave(self, samples, period):
|
||||
amplitude = self.amplitude
|
||||
if period > 1:
|
||||
for time in range(period):
|
||||
samples[time] = int(2 * amplitude / float(period - 1) * time - \
|
||||
amplitude)
|
||||
|
||||
def store_sine_wave(self, samples, period):
|
||||
amplitude = self.amplitude
|
||||
for time in range(period):
|
||||
samples[time] = int(round(sin(time / (period / pi / 2)) * \
|
||||
amplitude))
|
||||
|
||||
def store_dirty_wave(self, samples):
|
||||
amplitude = self.amplitude
|
||||
for time in range(len(samples)):
|
||||
samples[time] = randint(-amplitude, amplitude)
|
||||
|
||||
def store_square_wave(self, samples, period):
|
||||
amplitude = self.amplitude
|
||||
for time in range(period):
|
||||
if time < period / 2:
|
||||
samples[time] = amplitude
|
||||
else:
|
||||
samples[time] = -amplitude
|
||||
|
||||
def play(self, maxtime=0, fadeout=None, panning=None, fade_in=0, position=None):
|
||||
channel = Samples.play(self, -1, maxtime, fade_in)
|
||||
if fadeout:
|
||||
self.fadeout(fadeout)
|
||||
if position is not None:
|
||||
panning = self.get_panning(position)
|
||||
if channel and panning:
|
||||
channel.set_volume(*panning)
|
||||
return channel
|
||||
|
||||
def get_panning(self, position):
|
||||
return 1 - max(0, ((position - .5) * 2)), 1 + min(0, ((position - .5) * 2))
|
||||
|
||||
|
||||
class Chord:
|
||||
|
||||
def __init__(self, *args):
|
||||
self.notes = args
|
||||
|
||||
def play(self, maxtime=0, fadeout=[None], panning=None, fade_in=[0], position=None):
|
||||
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)], position)
|
||||
|
||||
def stop(self):
|
||||
for note in self.notes:
|
||||
note.stop()
|
|
@ -1,27 +0,0 @@
|
|||
import cProfile
|
||||
from time import strftime
|
||||
from os import mkdir
|
||||
from os.path import join, exists
|
||||
|
||||
from .GameChild import GameChild
|
||||
|
||||
class Profile(cProfile.Profile, GameChild):
|
||||
|
||||
def __init__(self, parent):
|
||||
GameChild.__init__(self, parent)
|
||||
cProfile.Profile.__init__(self)
|
||||
if self.requested():
|
||||
self.enable()
|
||||
|
||||
def requested(self):
|
||||
return self.check_command_line("p")
|
||||
|
||||
def end(self):
|
||||
if self.requested():
|
||||
root = "stat/"
|
||||
if not exists(root):
|
||||
mkdir(root)
|
||||
self.disable()
|
||||
self.create_stats()
|
||||
self.print_stats(sort="cumtime")
|
||||
self.dump_stats(join(root, strftime("%Y%m%d-%H%M_%S.stat")))
|
|
@ -5,37 +5,32 @@ from time import strftime
|
|||
|
||||
from pygame import image
|
||||
|
||||
from .GameChild import *
|
||||
from .Input import *
|
||||
from GameChild import *
|
||||
from Input import *
|
||||
|
||||
class ScreenGrabber(GameChild):
|
||||
|
||||
def __init__(self, game):
|
||||
GameChild.__init__(self, game)
|
||||
self.delegate = self.get_delegate()
|
||||
self.load_configuration()
|
||||
self.subscribe(self.save_display)
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("screen-captures")
|
||||
self.save_path = config["path"]
|
||||
self.file_name_format = config["file-name-format"]
|
||||
self.file_extension = config["file-extension"]
|
||||
self.subscribe_to(self.get_user_event_id(), self.save_display)
|
||||
|
||||
def save_display(self, event):
|
||||
if self.delegate.compare(event, "capture-screen"):
|
||||
directory = self.save_path
|
||||
if self.is_command(event, "capture-screen"):
|
||||
directory = self.get_configuration().get("screen-captures", "path",
|
||||
"path")
|
||||
try:
|
||||
if not exists(directory):
|
||||
makedirs(directory)
|
||||
name = self.build_name()
|
||||
path = join(directory, name)
|
||||
capture = image.save(self.get_screen(), path)
|
||||
self.print_debug("Saved screen capture to %s" % (path))
|
||||
self.print_debug("Saved screen capture to {0}".format(path))
|
||||
except:
|
||||
self.print_debug("Couldn't save screen capture to %s, %s" %\
|
||||
(directory, exc_info()[1]))
|
||||
self.print_debug("Couldn't save screen capture to {0}, {1}".\
|
||||
format(directory, exc_info()[1]))
|
||||
|
||||
def build_name(self):
|
||||
return "{0}.{1}".format(strftime(self.file_name_format),
|
||||
self.file_extension)
|
||||
config = self.get_configuration().get_section("screen-captures")
|
||||
prefix = config["file-name-format"]
|
||||
extension = config["file-extension"]
|
||||
return "{0}.{1}".format(strftime(prefix), extension)
|
||||
|
|
110
pgfw/Setup.py
110
pgfw/Setup.py
|
@ -1,82 +1,70 @@
|
|||
from os import walk, remove
|
||||
from os.path import sep, join, exists, normpath, basename
|
||||
from re import findall, sub
|
||||
from os.path import sep, join, exists, normpath
|
||||
from re import findall
|
||||
from distutils.core import setup
|
||||
from distutils.command.install import install
|
||||
from pprint import pprint
|
||||
from fileinput import FileInput
|
||||
from re import sub, match
|
||||
from fnmatch import fnmatch
|
||||
|
||||
from .Configuration import *
|
||||
from configuration.Configuration import *
|
||||
|
||||
class Setup:
|
||||
|
||||
config = Configuration()
|
||||
manifest_path = "MANIFEST"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def remove_old_mainfest(self):
|
||||
path = self.manifest_path
|
||||
path = "MANIFEST"
|
||||
if exists(path):
|
||||
remove(path)
|
||||
|
||||
def build_package_list(self):
|
||||
packages = []
|
||||
config = self.config.get_section("setup")
|
||||
locations = [config["package-root"]] + config["additional-packages"]
|
||||
for location in locations:
|
||||
if exists(location):
|
||||
for root, dirs, files in walk(location, followlinks=True):
|
||||
if exists(join(root, "__init__.py")):
|
||||
packages.append(root.replace(sep, "."))
|
||||
package_root = self.config.get("setup", "package-root", "path")
|
||||
if exists(package_root):
|
||||
for root, dirs, files in walk(package_root, followlinks=True):
|
||||
packages.append(root.replace(sep, "."))
|
||||
return packages
|
||||
|
||||
def build_data_map(self):
|
||||
include = []
|
||||
config = self.config.get_section("setup")
|
||||
exclude = list(map(normpath, config["data-exclude"]))
|
||||
config = self.config
|
||||
exclude = map(normpath, config.get("setup", "data-exclude", "list"))
|
||||
for root, dirs, files in walk("."):
|
||||
dirs = self.remove_excluded(dirs, root, exclude)
|
||||
files = [join(root, f) for f in self.remove_excluded(files, root,
|
||||
exclude)]
|
||||
if files:
|
||||
include.append((normpath(join(config["installation-path"],
|
||||
root)), files))
|
||||
include.append((normpath(join(config.get("setup",
|
||||
"installation-path",
|
||||
"path"), root)),
|
||||
files))
|
||||
return include
|
||||
|
||||
def remove_excluded(self, paths, root, exclude):
|
||||
removal = []
|
||||
for path in paths:
|
||||
if self.contains_path(join(root, path), exclude):
|
||||
if normpath(join(root, path)) in exclude:
|
||||
removal.append(path)
|
||||
for path in removal:
|
||||
if path in paths:
|
||||
paths.remove(path)
|
||||
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"])
|
||||
return sub("[^\w-]", config["special-char-placeholder"], title)
|
||||
return self.config.get("setup", "title").replace(" ", "-")
|
||||
|
||||
def build_description(self):
|
||||
description = ""
|
||||
path = self.config.get("setup", "description-file")
|
||||
path = self.config.get("setup", "description-file", "path")
|
||||
if exists(path):
|
||||
description = "\n%s\n%s\n%s" % (file(path).read(),
|
||||
"Changelog\n=========",
|
||||
self.translate_changelog())
|
||||
description = "\n{0}\n{1}\n{2}".format(file(path).read(),
|
||||
"Changelog\n=========",
|
||||
self.translate_changelog())
|
||||
return description
|
||||
|
||||
def translate_changelog(self):
|
||||
translation = ""
|
||||
path = self.config.get("setup", "changelog")
|
||||
path = self.config.get("setup", "changelog", "path")
|
||||
if exists(path):
|
||||
lines = file(path).readlines()
|
||||
package_name = lines[0].split()[0]
|
||||
|
@ -92,48 +80,44 @@ class Setup:
|
|||
translation += " " + line + "\n"
|
||||
return translation
|
||||
|
||||
def setup(self, windows=[], options={}, name=None):
|
||||
print("running setup...")
|
||||
self.remove_old_mainfest()
|
||||
config = self.config.get_section("setup")
|
||||
scripts = []
|
||||
if config["init-script"]:
|
||||
scripts.append(config["init-script"])
|
||||
if name is None:
|
||||
name = self.translate_title()
|
||||
setup(cmdclass={"install": insert_resource_path},
|
||||
name=name,
|
||||
def setup(self):
|
||||
config = self.config
|
||||
section = config.get_section("setup")
|
||||
setup(cmdclass={"install": insert_resources_path},
|
||||
name=self.translate_title(),
|
||||
packages=self.build_package_list(),
|
||||
scripts=scripts,
|
||||
scripts=[config.get("setup", "init-script", "path")],
|
||||
data_files=self.build_data_map(),
|
||||
requires=config["requirements"],
|
||||
version=config["version"],
|
||||
description=config["summary"],
|
||||
classifiers=config["classifiers"],
|
||||
requires=config.get("setup", "requirements", "list"),
|
||||
version=section["version"],
|
||||
description=section["summary"],
|
||||
classifiers=config.get("setup", "classifiers", "list"),
|
||||
long_description=self.build_description(),
|
||||
license=config["license"],
|
||||
platforms=config["platforms"],
|
||||
author=config["contact-name"],
|
||||
author_email=config["contact-email"],
|
||||
url=config["url"],
|
||||
windows=windows,
|
||||
options=options)
|
||||
license=section["license"],
|
||||
platforms=config.get("setup", "platforms", "list"),
|
||||
author=section["contact-name"],
|
||||
author_email=section["contact-email"],
|
||||
url=section["url"])
|
||||
|
||||
|
||||
class insert_resource_path(install):
|
||||
class insert_resources_path(install):
|
||||
|
||||
def run(self):
|
||||
install.run(self)
|
||||
self.edit_game_object_file()
|
||||
|
||||
def edit_game_object_file(self):
|
||||
config = Configuration().get_section("setup")
|
||||
config = Configuration()
|
||||
section = "setup"
|
||||
for path in self.get_outputs():
|
||||
if path.endswith(config["main-object"]):
|
||||
if path.endswith(config.get(section, "main-object", "path")):
|
||||
for line in FileInput(path, inplace=True):
|
||||
pattern = "^ *{0} *=.*".\
|
||||
format(config["resource-path-identifier"])
|
||||
format(config.get(section,
|
||||
"resources-path-identifier"))
|
||||
if match(pattern, line):
|
||||
line = sub("=.*$", "= \"{0}\"".\
|
||||
format(config["installation-path"]), line)
|
||||
print(line.strip("\n"))
|
||||
format(config.get(section,
|
||||
"installation-path",
|
||||
"path"), line)
|
||||
print line.strip("\n")
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
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)
|
|
@ -1,85 +0,0 @@
|
|||
import sys
|
||||
from os import makedirs, walk, sep, remove
|
||||
from os.path import join, dirname, basename, exists
|
||||
from shutil import rmtree, copy, rmtree
|
||||
from itertools import chain
|
||||
from zipfile import ZipFile
|
||||
|
||||
import py2exe
|
||||
|
||||
from .Setup import Setup
|
||||
|
||||
class SetupWin(Setup):
|
||||
|
||||
def __init__(self):
|
||||
Setup.__init__(self)
|
||||
if sys.version_info[0] < 3:
|
||||
self.replace_isSystemDLL()
|
||||
|
||||
def replace_isSystemDLL(self):
|
||||
origIsSystemDLL = py2exe.build_exe.isSystemDLL
|
||||
def isSystemDLL(pathname):
|
||||
if basename(pathname).lower() in ("libogg-0.dll", "sdl_ttf.dll"):
|
||||
return 0
|
||||
return origIsSystemDLL(pathname)
|
||||
py2exe.build_exe.isSystemDLL = isSystemDLL
|
||||
|
||||
def setup(self):
|
||||
if sys.version_info[0] < 3 or sys.version_info[1] >= 5:
|
||||
self.numpy_dll_paths_fix()
|
||||
config = self.config.get_section("setup")
|
||||
windows = [{}]
|
||||
if config["init-script"]:
|
||||
windows[0]["script"] = config["init-script"]
|
||||
if config["windows-icon-path"]:
|
||||
windows[0]["icon-resources"] = [(1, config["windows-icon-path"])]
|
||||
Setup.setup(self, windows,
|
||||
{"py2exe": {"packages": self.build_package_list(),
|
||||
"dist_dir": config["windows-dist-path"]}})
|
||||
rmtree("build")
|
||||
self.copy_data_files()
|
||||
self.create_archive()
|
||||
|
||||
# https://stackoverflow.com/questions/36191770/py2exe-errno-2-no-such-file-or-directory-numpy-atlas-dll
|
||||
def numpy_dll_paths_fix(self):
|
||||
import numpy
|
||||
import sys
|
||||
paths = set()
|
||||
np_path = numpy.__path__[0]
|
||||
for dirpath, _, filenames in walk(np_path):
|
||||
for item in filenames:
|
||||
if item.endswith('.dll'):
|
||||
paths.add(dirpath)
|
||||
sys.path.append(*list(paths))
|
||||
|
||||
def copy_data_files(self):
|
||||
root = self.config.get("setup", "windows-dist-path")
|
||||
for path in chain(*list(zip(*self.build_data_map()))[1]):
|
||||
dest = join(root, dirname(path))
|
||||
if not exists(dest):
|
||||
makedirs(dest)
|
||||
copy(path, dest)
|
||||
self.include_readme(root)
|
||||
|
||||
def include_readme(self, root):
|
||||
name = "README"
|
||||
if exists(name):
|
||||
readme = open(name, "r")
|
||||
reformatted = open(join(root, name + ".txt"), "w")
|
||||
for line in open(name, "r"):
|
||||
reformatted.write(line.rstrip() + "\r\n")
|
||||
|
||||
def create_archive(self):
|
||||
config = self.config.get_section("setup")
|
||||
title = self.translate_title() + "-" + config["version"] + "-win"
|
||||
archive_name = title + ".zip"
|
||||
archive = ZipFile(archive_name, "w")
|
||||
destination = config["windows-dist-path"]
|
||||
for root, dirs, names in walk(destination):
|
||||
for name in names:
|
||||
path = join(root, name)
|
||||
archive.write(path, path.replace(destination, title + sep))
|
||||
archive.close()
|
||||
copy(archive_name, "dist")
|
||||
remove(archive_name)
|
||||
rmtree(destination)
|
607
pgfw/Sprite.py
607
pgfw/Sprite.py
|
@ -1,607 +0,0 @@
|
|||
import collections, pygame
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
from glob import glob
|
||||
|
||||
from pygame import Color, Rect, Surface, PixelArray, mask
|
||||
from pygame.image import load
|
||||
from pygame.transform import flip
|
||||
from pygame.locals import *
|
||||
|
||||
from .Animation import Animation
|
||||
from .Vector import Vector, EVector
|
||||
from .extension import get_hue_shifted_surface, get_step, load_frames, get_blinds_rects
|
||||
|
||||
class Sprite(Animation):
|
||||
|
||||
def __init__(self, parent, framerate=None, draw_children_on_frame=True):
|
||||
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._step = EVector()
|
||||
self.children = collections.OrderedDict()
|
||||
self.draw_children_on_frame = draw_children_on_frame
|
||||
self.set_frameset(0)
|
||||
self.locations.append(Location(self))
|
||||
self.motion_overflow = Vector()
|
||||
self.display_surface = self.get_display_surface()
|
||||
self.wipe_blinds = []
|
||||
self.current_wipe_index = 0
|
||||
self.areas = None
|
||||
self.register(self.hide, self.unhide, self.toggle_hidden, self.wipe_out, self.set_frameset)
|
||||
|
||||
def reset(self):
|
||||
self.halt(self.wipe_out)
|
||||
self.halt(self.set_frameset)
|
||||
self.current_wipe_index = 0
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in ("location", "rect"):
|
||||
return self.locations[0]
|
||||
if hasattr(Animation, "__getattr__"):
|
||||
return Animation.__getattr__(self, name)
|
||||
raise AttributeError(name)
|
||||
|
||||
def add_frameset(self, order=[], framerate=None, name=None, switch=False):
|
||||
if framerate is None:
|
||||
framerate = self.accounts[self.default_method].interval
|
||||
frameset = Frameset(self, order, framerate, name)
|
||||
self.framesets.append(frameset)
|
||||
if switch:
|
||||
self.set_frameset(len(self.framesets) - 1)
|
||||
return frameset
|
||||
|
||||
def set_frameset(self, identifier):
|
||||
if isinstance(identifier, str):
|
||||
for ii, frameset in enumerate(self.framesets):
|
||||
if frameset.name == identifier:
|
||||
identifier = ii
|
||||
break
|
||||
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()
|
||||
return self.get_current_frameset()
|
||||
|
||||
def register_interval(self):
|
||||
self.register(self.shift_frame,
|
||||
interval=self.get_current_frameset().framerate)
|
||||
|
||||
def get_current_frameset(self):
|
||||
return self.framesets[self.frameset_index]
|
||||
|
||||
def update_location_size(self):
|
||||
size = self.get_current_frameset().rect.size
|
||||
for location in self.locations:
|
||||
location.size = size
|
||||
location.fader.init_surface()
|
||||
|
||||
def set_framerate(self, framerate):
|
||||
self.get_current_frameset().set_framerate(framerate)
|
||||
self.register_interval()
|
||||
|
||||
def load_from_path(self, path, transparency=False, ppa=True, key=None, query=None, omit=False):
|
||||
for frame in load_frames(self.get_resource(path), transparency, ppa, key, query):
|
||||
self.add_frame(frame, omit)
|
||||
|
||||
def fill_colorkey(img, key=None):
|
||||
if not key:
|
||||
key = (255, 0, 255)
|
||||
img = img.convert_alpha()
|
||||
frame = Surface(img.get_size())
|
||||
frame.fill(key)
|
||||
frame.set_colorkey(key)
|
||||
frame.blit(img, (0, 0))
|
||||
return frame
|
||||
|
||||
def add_frame(self, frame, omit=False):
|
||||
self.frames.append(frame)
|
||||
frame.set_alpha(self.alpha)
|
||||
if not omit:
|
||||
frameset = self.get_current_frameset()
|
||||
frameset.add_index(self.frames.index(frame))
|
||||
self.update_location_size()
|
||||
self.wipe_blinds = get_blinds_rects(*self.location.size)
|
||||
self.wipe_blinds.reverse()
|
||||
if frameset.length() > 1:
|
||||
self.play()
|
||||
|
||||
def add_frames(self, frames):
|
||||
for frame in frames:
|
||||
self.add_frame(frame)
|
||||
|
||||
def shift_frame(self):
|
||||
self.get_current_frameset().shift()
|
||||
|
||||
def get_current_frame(self):
|
||||
return self.frames[self.get_current_frameset().get_current_id()]
|
||||
|
||||
def move(self, dx=0, dy=0, rotated=False):
|
||||
for location in self.locations:
|
||||
if not rotated:
|
||||
location.move_ip(dx, dy)
|
||||
else:
|
||||
location.move_ip(dy, -dx)
|
||||
|
||||
def reset_motion_overflow(self):
|
||||
for location in self.locations:
|
||||
location.reset_motion_overflow()
|
||||
|
||||
def collide(self, other):
|
||||
if not isinstance(other, Rect):
|
||||
if hasattr(other, "rect"):
|
||||
other = other.rect
|
||||
else:
|
||||
other = Rect(other[0], other[1], 1, 1)
|
||||
for location in self.locations:
|
||||
if location.colliderect(other):
|
||||
return location
|
||||
|
||||
def collide_mask(self, other):
|
||||
location = self.collide(other)
|
||||
if location:
|
||||
current_mask = mask.from_surface(self.get_current_frame())
|
||||
other_mask = mask.from_surface(other.get_current_frame())
|
||||
offset = other.location.left - location.left, \
|
||||
other.location.top - location.top
|
||||
return current_mask.overlap(other_mask, offset) is not None
|
||||
|
||||
def mirror(self):
|
||||
frames = self.frames
|
||||
for ii, frame in enumerate(frames):
|
||||
frames[ii] = flip(frame, True, False)
|
||||
self.mirrored = not self.mirrored
|
||||
|
||||
def clear_frames(self):
|
||||
self.frames = []
|
||||
for frameset in self.framesets:
|
||||
frameset.order = []
|
||||
frameset.reset()
|
||||
frameset.measure_rect()
|
||||
|
||||
def add_location(self, topleft=None, offset=(0, 0), count=1, base=0):
|
||||
if topleft is not None:
|
||||
for ii in range(count):
|
||||
self.locations.append(Location(
|
||||
self, Rect(topleft, self.locations[0].size)))
|
||||
else:
|
||||
base = self.locations[base]
|
||||
current_offset = list(offset)
|
||||
for ii in range(count):
|
||||
self.locations.append(Location(self,
|
||||
base.move(*current_offset)))
|
||||
current_offset[0] += offset[0]
|
||||
current_offset[1] += offset[1]
|
||||
return self.locations[-1]
|
||||
|
||||
def fade(self, length=0, out=None, index=None):
|
||||
if index is None:
|
||||
for location in self.locations:
|
||||
fader = location.fader
|
||||
fader.reset()
|
||||
fader.start(length, out)
|
||||
else:
|
||||
fader = self.locations[index].fader
|
||||
fader.reset()
|
||||
fader.start(length, out)
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
self.alpha = alpha
|
||||
for frame in self.frames:
|
||||
frame.set_alpha(alpha)
|
||||
for location in self.locations:
|
||||
location.fader.set_alpha()
|
||||
|
||||
def hide(self):
|
||||
for location in self.locations:
|
||||
location.hide()
|
||||
for child in self.children.values():
|
||||
child.hide()
|
||||
|
||||
def unhide(self):
|
||||
for location in self.locations:
|
||||
location.unhide()
|
||||
for child in self.children.values():
|
||||
child.unhide()
|
||||
|
||||
def toggle_hidden(self):
|
||||
for location in self.locations:
|
||||
location.toggle_hidden()
|
||||
for child in self.children.values():
|
||||
child.toggle_hidden()
|
||||
|
||||
def is_hidden(self):
|
||||
return all(location.is_hidden() for location in self.locations)
|
||||
|
||||
def remove_locations(self, location=None):
|
||||
if location:
|
||||
self.locations.remove(location)
|
||||
else:
|
||||
self.locations = self.locations[:1]
|
||||
|
||||
def reverse(self, frameset=None):
|
||||
if frameset:
|
||||
frameset.reverse()
|
||||
else:
|
||||
for frameset in self.framesets:
|
||||
frameset.reverse()
|
||||
|
||||
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
|
||||
self._step.set_step(dx, dy, magnitude, angle)
|
||||
|
||||
def cancel_step(self):
|
||||
self._step.set_step(0, 0)
|
||||
|
||||
def is_stepping(self):
|
||||
return bool(self._step)
|
||||
|
||||
def set_child(self, name, child):
|
||||
self.children[name] = child
|
||||
|
||||
def get_child(self, name):
|
||||
return self.children[name]
|
||||
|
||||
def has_child(self, name):
|
||||
return name in self.children
|
||||
|
||||
def remove_child(self, name):
|
||||
if self.has_child(name):
|
||||
self.children.pop(name)
|
||||
|
||||
def wipe_out(self):
|
||||
"""
|
||||
Increase index of wipe animation, causing blinds frames to grow. To launch the animation use
|
||||
sprite.play(sprite.wipe_out)
|
||||
"""
|
||||
for child in self.children.values():
|
||||
if not child.is_playing(child.wipe_out):
|
||||
child.play(child.wipe_out)
|
||||
self.current_wipe_index += 1
|
||||
if self.current_wipe_index == len(self.wipe_blinds):
|
||||
self.current_wipe_index = 0
|
||||
self.halt(self.wipe_out)
|
||||
self.hide()
|
||||
|
||||
def get_current_blinds(self):
|
||||
return self.wipe_blinds[self.current_wipe_index]
|
||||
|
||||
def set_areas(self, areas):
|
||||
self.areas = areas
|
||||
|
||||
def update(self, areas=None, substitute=None, flags=0):
|
||||
Animation.update(self)
|
||||
if self.is_stepping():
|
||||
self.move(self._step.dx, self._step.dy)
|
||||
if self.get_current_frameset().length():
|
||||
if self.is_playing(self.wipe_out):
|
||||
areas = self.get_current_blinds()
|
||||
elif not areas and self.areas:
|
||||
areas = self.areas
|
||||
self.draw(areas, substitute, flags)
|
||||
for child in self.children.values():
|
||||
if self.draw_children_on_frame:
|
||||
child.display_surface = self.get_current_frame()
|
||||
else:
|
||||
child.display_surface = self.get_display_surface()
|
||||
save_child_topleft = child.location.topleft
|
||||
child.move(*self.location.topleft)
|
||||
child.update()
|
||||
if not self.draw_children_on_frame:
|
||||
child.location.topleft = save_child_topleft
|
||||
|
||||
def draw(self, areas=None, substitute=None, flags=0):
|
||||
for location in self.locations:
|
||||
location.fader.draw(areas, substitute, flags)
|
||||
|
||||
def rotate(self):
|
||||
"""
|
||||
Rotate the sprite's frame surfaces and location rects by 90 degrees clockwise
|
||||
"""
|
||||
for ii, frame in enumerate(self.frames):
|
||||
self.frames[ii] = pygame.transform.rotate(frame, 90)
|
||||
for ii, location in enumerate(self.locations):
|
||||
rotated = pygame.Rect(0, 0, 0, 0)
|
||||
rotated.x = location.y
|
||||
rotated.y = self.get_display_surface().get_height() - location.x + location.w
|
||||
rotated.w = location.h
|
||||
rotated.h = location.w
|
||||
self.locations[ii] = Location(self, rect=rotated)
|
||||
for child in self.children.values():
|
||||
child.rotate()
|
||||
|
||||
class Location(Rect):
|
||||
|
||||
def __init__(self, sprite, rect=(0, 0, 0, 0)):
|
||||
self.sprite = sprite
|
||||
Rect.__init__(self, rect)
|
||||
self.motion_overflow = Vector()
|
||||
self.fader = Fader(self)
|
||||
self.unhide()
|
||||
|
||||
def move_ip(self, dx, dy):
|
||||
if isinstance(dx, float) or isinstance(dy, float):
|
||||
excess = self.update_motion_overflow(dx, dy)
|
||||
Rect.move_ip(self, int(dx) + excess[0], int(dy) + excess[1])
|
||||
else:
|
||||
Rect.move_ip(self, dx, dy)
|
||||
|
||||
def update_motion_overflow(self, dx, dy):
|
||||
overflow = self.motion_overflow
|
||||
overflow.move(dx - int(dx), dy - int(dy))
|
||||
excess = [int(value) for value in overflow]
|
||||
# excess = map(int, overflow)
|
||||
overflow[0] -= int(overflow[0])
|
||||
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()
|
||||
|
||||
def apply_motion_overflow(self, coordinates=None):
|
||||
if coordinates is None:
|
||||
coordinates = self.topleft
|
||||
return self.motion_overflow + coordinates
|
||||
|
||||
def hide(self):
|
||||
self.hidden = True
|
||||
|
||||
def unhide(self):
|
||||
self.hidden = False
|
||||
|
||||
def toggle_hidden(self):
|
||||
self.hidden = not self.hidden
|
||||
|
||||
def is_hidden(self):
|
||||
return self.hidden
|
||||
|
||||
|
||||
class Fader(Surface):
|
||||
|
||||
def __init__(self, location):
|
||||
self.location = location
|
||||
self.time_filter = location.sprite.get_game().time_filter
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.init_surface()
|
||||
self.fade_remaining = None
|
||||
|
||||
def init_surface(self):
|
||||
Surface.__init__(self, self.location.size)
|
||||
if self.location.sprite.get_current_frameset().length():
|
||||
background = Surface(self.get_size())
|
||||
sprite = self.location.sprite
|
||||
key = sprite.get_current_frame().get_colorkey() or (255, 0, 255)
|
||||
self.set_colorkey(key)
|
||||
background.fill(key)
|
||||
self.background = background
|
||||
self.set_alpha()
|
||||
|
||||
def set_alpha(self, alpha=None):
|
||||
if alpha is None:
|
||||
alpha = self.location.sprite.alpha
|
||||
Surface.set_alpha(self, alpha)
|
||||
|
||||
def start(self, length, out=None):
|
||||
if self.fade_remaining <= 0:
|
||||
alpha = self.get_alpha()
|
||||
maximum = self.location.sprite.alpha
|
||||
if out is None:
|
||||
out = alpha == maximum
|
||||
if out and alpha > 0 or not out and alpha < maximum:
|
||||
self.fade_length = self.fade_remaining = length
|
||||
self.start_time = self.time_filter.get_ticks()
|
||||
self.fading_out = out
|
||||
|
||||
def draw(self, areas=None, substitute=None, flags=0):
|
||||
sprite = self.location.sprite
|
||||
if substitute is None:
|
||||
frame = sprite.get_current_frame()
|
||||
else:
|
||||
frame = substitute
|
||||
if self.fade_remaining is not None and self.fade_remaining >= 0:
|
||||
self.update_alpha()
|
||||
self.clear()
|
||||
frame.set_alpha(255)
|
||||
self.blit(frame, (0, 0))
|
||||
frame.set_alpha(sprite.alpha)
|
||||
if not self.location.is_hidden():
|
||||
if frame.get_colorkey() is None:
|
||||
ratio = self.get_alpha() / 255.0
|
||||
pixels = PixelArray(frame.copy())
|
||||
color = Color(0, 0, 0)
|
||||
for x in range(len(pixels)):
|
||||
for y in range(len(pixels[0])):
|
||||
h, s, l, a = Color(*frame.unmap_rgb(pixels[x][y])).hsla
|
||||
if a:
|
||||
color.hsla = h, s, l, int(a * ratio)
|
||||
pixels[x][y] = color
|
||||
self.blit_to_display(pixels.make_surface(), areas, flags)
|
||||
del pixels
|
||||
else:
|
||||
self.blit_to_display(self, areas, flags)
|
||||
elif self.fade_remaining is None or self.get_alpha() >= sprite.alpha:
|
||||
if self.fade_remaining is not None and self.fade_remaining >= 0:
|
||||
self.update_alpha()
|
||||
if not self.location.is_hidden():
|
||||
self.blit_to_display(frame, areas, flags)
|
||||
|
||||
def blit_to_display(self, frame, areas=None, flags=0):
|
||||
if not isinstance(areas, list):
|
||||
areas = [areas]
|
||||
for area in areas:
|
||||
if area:
|
||||
dest = area.left + self.location.left, \
|
||||
area.top + self.location.top
|
||||
else:
|
||||
dest = self.location
|
||||
self.location.sprite.display_surface.blit(frame, dest, area, flags)
|
||||
|
||||
def update_alpha(self):
|
||||
remaining = self.fade_remaining = self.fade_length - \
|
||||
(self.time_filter.get_ticks() - self.start_time)
|
||||
ratio = self.fade_length and float(remaining) / self.fade_length
|
||||
if not self.fading_out:
|
||||
ratio = 1 - ratio
|
||||
maximum = self.location.sprite.alpha
|
||||
alpha = int(ratio * maximum)
|
||||
if alpha > maximum:
|
||||
alpha = maximum
|
||||
elif alpha < 0:
|
||||
alpha = 0
|
||||
self.set_alpha(alpha)
|
||||
|
||||
def clear(self):
|
||||
self.blit(self.background, (0, 0))
|
||||
|
||||
|
||||
class Frameset:
|
||||
|
||||
def __init__(self, sprite, order=[], framerate=None, name=None):
|
||||
self.sprite = sprite
|
||||
self.name = name
|
||||
self.reversed = False
|
||||
self.order = []
|
||||
self.rect = Rect(0, 0, 0, 0)
|
||||
self.add_index(order)
|
||||
self.set_framerate(framerate)
|
||||
self.reset()
|
||||
|
||||
def add_index(self, order):
|
||||
if isinstance(order, int):
|
||||
order = [order]
|
||||
self.order += order
|
||||
self.measure_rect()
|
||||
|
||||
def set_framerate(self, framerate):
|
||||
self.framerate = framerate
|
||||
|
||||
def reset(self):
|
||||
self.current_index = 0
|
||||
self.sprite.accounts[self.sprite.shift_frame].reset_interval()
|
||||
|
||||
def get_current_id(self):
|
||||
return self.order[self.current_index]
|
||||
|
||||
def measure_rect(self):
|
||||
max_width, max_height = 0, 0
|
||||
frames = self.sprite.frames
|
||||
for index in self.order:
|
||||
frame = frames[index]
|
||||
width, height = frame.get_size()
|
||||
max_width = max(width, max_width)
|
||||
max_height = max(height, max_height)
|
||||
self.rect.size = max_width, max_height
|
||||
|
||||
def shift(self):
|
||||
if len(self.order) > 1:
|
||||
self.increment_index()
|
||||
|
||||
def increment_index(self, increment=None):
|
||||
if increment is None:
|
||||
increment = 1 if not self.reversed else -1
|
||||
index = self.current_index + increment
|
||||
while index < 0:
|
||||
index += self.length()
|
||||
while index >= self.length():
|
||||
index -= self.length()
|
||||
self.current_index = index
|
||||
|
||||
def length(self):
|
||||
return len(self.order)
|
||||
|
||||
def reverse(self):
|
||||
self.reversed = not self.reversed
|
||||
|
||||
def __str__(self):
|
||||
if self.name is None:
|
||||
return ""
|
||||
else:
|
||||
return self.name
|
||||
|
||||
class BlinkingSprite(Sprite):
|
||||
|
||||
def __init__(self, parent, blink_rate, framerate=None):
|
||||
Sprite.__init__(self, parent, framerate)
|
||||
self.register(self.blink, interval=blink_rate)
|
||||
self.register(self.start_blinking, self.stop_blinking)
|
||||
self.play(self.blink)
|
||||
|
||||
def reset(self):
|
||||
self.unhide()
|
||||
|
||||
def blink(self):
|
||||
self.toggle_hidden()
|
||||
|
||||
def start_blinking(self):
|
||||
self.play(self.blink)
|
||||
|
||||
def stop_blinking(self):
|
||||
self.halt(self.blink)
|
||||
self.unhide()
|
||||
|
||||
|
||||
class RainbowSprite(Sprite):
|
||||
|
||||
def __init__(self, parent, image=None, hue_shift=8, framerate=None):
|
||||
Sprite.__init__(self, parent, framerate)
|
||||
self.hue_shift = hue_shift
|
||||
if image is not None:
|
||||
self.set_frames(image)
|
||||
|
||||
def set_frames(self, image):
|
||||
for hue in range(0, 360, self.hue_shift):
|
||||
self.add_frame(get_hue_shifted_surface(image, hue))
|
||||
|
||||
|
||||
class ShadowedSprite(Sprite):
|
||||
|
||||
def __init__(self, parent, offset=Vector(3, 3), opacity=1, color=(0, 0, 0), framerate=None):
|
||||
Sprite.__init__(self, parent, framerate)
|
||||
if not isinstance(offset, Vector):
|
||||
offset = Vector(*offset)
|
||||
if not isinstance(color, Color):
|
||||
color = Color(*color)
|
||||
self.shadow_color = color
|
||||
self.shadow_offset = offset
|
||||
self.shadow_opacity = opacity
|
||||
self.shadow_color = color
|
||||
self.shadow = Sprite(self)
|
||||
|
||||
def add_frame(self, frame, omit=False):
|
||||
Sprite.add_frame(self, frame, omit)
|
||||
fr = frame.get_rect()
|
||||
shadow = Surface(fr.size, SRCALPHA)
|
||||
pixels = PixelArray(frame)
|
||||
shadow_pixels = PixelArray(shadow)
|
||||
r, g, b = self.shadow_color.r, self.shadow_color.g, self.shadow_color.b
|
||||
for y in range(len(pixels)):
|
||||
for x in range(len(pixels[0])):
|
||||
frame_pixel_color = frame.unmap_rgb(pixels[y][x])
|
||||
if frame_pixel_color == frame.get_colorkey():
|
||||
shadow_pixel_color = 0, 0, 0, 0
|
||||
else:
|
||||
shadow_pixel_color = r, g, b, frame_pixel_color.a * self.shadow_opacity
|
||||
shadow_pixels[y][x] = shadow_pixel_color
|
||||
self.shadow.add_frame(shadow)
|
||||
|
||||
def clear_frames(self):
|
||||
self.shadow.clear_frames()
|
||||
Sprite.clear_frames(self)
|
||||
|
||||
def update(self):
|
||||
self.shadow.location.topleft = Vector(*self.location.topleft).get_moved(
|
||||
*self.shadow_offset)
|
||||
self.shadow.update()
|
||||
Sprite.update(self)
|
|
@ -1,39 +0,0 @@
|
|||
from pygame.time import get_ticks
|
||||
|
||||
from .GameChild import GameChild
|
||||
|
||||
class TimeFilter(GameChild):
|
||||
|
||||
def __init__(self, parent):
|
||||
GameChild.__init__(self, parent)
|
||||
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
|
||||
|
||||
def open(self):
|
||||
self.closed = False
|
||||
|
||||
def get_ticks(self):
|
||||
return self.ticks
|
||||
|
||||
def get_unfiltered_ticks(self):
|
||||
return self.unfiltered_ticks
|
||||
|
||||
def get_last_ticks(self):
|
||||
return self.last_ticks
|
||||
|
||||
def get_last_frame_duration(self):
|
||||
return self.last_frame_duration
|
||||
|
||||
def update(self):
|
||||
ticks = get_ticks()
|
||||
self.last_frame_duration = duration = ticks - self.last_ticks
|
||||
if not self.closed:
|
||||
self.ticks += duration
|
||||
self.unfiltered_ticks += duration
|
||||
self.last_ticks = ticks
|
148
pgfw/Vector.py
148
pgfw/Vector.py
|
@ -1,148 +0,0 @@
|
|||
from math import pi, degrees
|
||||
|
||||
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]
|
||||
elif name == "y":
|
||||
return self[1]
|
||||
else:
|
||||
raise AttributeError
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == "x":
|
||||
self[0] = value
|
||||
elif name == "y":
|
||||
self[1] = value
|
||||
else:
|
||||
list.__setattr__(self, name, value)
|
||||
|
||||
def __add__(self, other):
|
||||
return Vector(self.x + other[0], self.y + other[1])
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.x += other[0]
|
||||
self.y += other[1]
|
||||
return self
|
||||
|
||||
def __sub__(self, other):
|
||||
return Vector(self.x - other[0], self.y - other[1])
|
||||
|
||||
def __rsub__(self, other):
|
||||
return Vector(other[0] - self.x, other[1] - self.y)
|
||||
|
||||
def __isub__(self, other):
|
||||
self.x -= other[0]
|
||||
self.y -= other[1]
|
||||
return self
|
||||
|
||||
def __mul__(self, other):
|
||||
return Vector(self.x * other, self.y * other)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __imul__(self, other):
|
||||
self.x *= other
|
||||
self.y *= other
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
for sv, ov in zip(self, other):
|
||||
if sv != ov:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
for sv, ov in zip(self, other):
|
||||
if sv == ov:
|
||||
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)
|
||||
|
||||
def place(self, x=None, y=None):
|
||||
if x is not None:
|
||||
self.x = x
|
||||
if y is not None:
|
||||
self.y = y
|
||||
|
||||
def move(self, dx=0, dy=0):
|
||||
if dx:
|
||||
self.x += dx
|
||||
if dy:
|
||||
self.y += dy
|
||||
|
||||
def get_moved(self, dx=0, dy=0):
|
||||
return Vector(self.x + dx, self.y + dy)
|
||||
|
||||
def place_at_origin(self):
|
||||
self.place(0, 0)
|
||||
|
||||
def vol(self):
|
||||
return self.x * self.y
|
||||
|
||||
def copy(self):
|
||||
return Vector(self.x, self.y)
|
||||
|
||||
|
||||
class EVector(Vector):
|
||||
|
||||
def __init__(self, x=0, y=0, dx=0, dy=0, magnitude=None, angle=0):
|
||||
Vector.__init__(self, x, y)
|
||||
self.angle = None
|
||||
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:
|
||||
from .extension import get_delta
|
||||
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:
|
||||
from .extension import get_angle, get_distance
|
||||
end = self.x + dx, self.y + dy
|
||||
self.angle = get_angle(self, end, True)
|
||||
self.magnitude = get_distance(self, end)
|
||||
|
||||
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 __setattr__(self, name, value):
|
||||
list.__setattr__(self, name, value)
|
||||
if name == "magnitude" and self.angle is not None:
|
||||
from .extension import get_delta
|
||||
self.dx, self.dy = get_delta(self.angle, value, False)
|
||||
|
||||
def move(self):
|
||||
self += self.dx, self.dy
|
|
@ -1,113 +0,0 @@
|
|||
from os import makedirs
|
||||
from os.path import exists, join
|
||||
from tempfile import TemporaryFile
|
||||
from time import strftime
|
||||
|
||||
from pygame.image import tostring, frombuffer, save
|
||||
from pygame.time import get_ticks
|
||||
|
||||
from .GameChild import GameChild
|
||||
|
||||
class VideoRecorder(GameChild):
|
||||
|
||||
def __init__(self, parent):
|
||||
GameChild.__init__(self, parent)
|
||||
self.set_requested()
|
||||
if self.requested:
|
||||
self.display_surface = self.get_display_surface()
|
||||
self.delegate = self.get_delegate()
|
||||
self.load_configuration()
|
||||
self.reset()
|
||||
self.subscribe(self.respond)
|
||||
|
||||
def set_requested(self):
|
||||
self.requested = self.get_configuration("video-recordings")["enable"] \
|
||||
or self.check_command_line("-enable-video")
|
||||
|
||||
def load_configuration(self):
|
||||
config = self.get_configuration("video-recordings")
|
||||
self.root = config["path"]
|
||||
self.directory_name_format = config["directory-name-format"]
|
||||
self.file_extension = config["file-extension"]
|
||||
self.frame_format = config["frame-format"]
|
||||
self.framerate = config["framerate"]
|
||||
|
||||
def reset(self):
|
||||
self.recording = False
|
||||
self.frame_length = None
|
||||
self.frames = None
|
||||
self.last_frame = 0
|
||||
|
||||
def respond(self, event):
|
||||
compare = self.delegate.compare
|
||||
if compare(event, "record-video"):
|
||||
self.toggle_record()
|
||||
elif compare(event, "reset-game"):
|
||||
self.reset()
|
||||
|
||||
def is_audio_recording_enabled(self):
|
||||
return self.get_configuration("video-recordings", "record-audio") or\
|
||||
self.check_command_line("-enable-sound-recording")
|
||||
|
||||
def toggle_record(self):
|
||||
recording = not self.recording
|
||||
if recording:
|
||||
self.frame_length = len(self.get_string())
|
||||
temp_dir = self.get_configuration("video-recordings", "temp-directory")
|
||||
if temp_dir == "":
|
||||
temp_dir = None
|
||||
self.frames = TemporaryFile(dir=temp_dir)
|
||||
print("writing video frames to {}".format(self.frames.name))
|
||||
if self.is_audio_recording_enabled():
|
||||
import pyaudio
|
||||
import wave
|
||||
self.audio_frames = []
|
||||
self.audio_recorder = pyaudio.PyAudio()
|
||||
def audio_callback(in_data, frame_count, time_info, status):
|
||||
self.audio_frames.append(in_data)
|
||||
return (in_data, pyaudio.paContinue)
|
||||
for ii in range(self.audio_recorder.get_device_count()):
|
||||
print("device {}: {}".format(ii, self.audio_recorder.get_device_info_by_index(ii)))
|
||||
self.audio_stream = self.audio_recorder.open(format=pyaudio.paInt32, channels=2, rate=44100,
|
||||
frames_per_buffer=1024,
|
||||
input=True, stream_callback=audio_callback)
|
||||
print("writing audio data to {}".format(self.audio_stream))
|
||||
else:
|
||||
self.write_frames()
|
||||
self.recording = recording
|
||||
|
||||
def get_string(self):
|
||||
return tostring(self.display_surface, self.frame_format)
|
||||
|
||||
def write_frames(self):
|
||||
root = join(self.root, strftime(self.directory_name_format))
|
||||
if not exists(root):
|
||||
makedirs(root)
|
||||
if self.is_audio_recording_enabled():
|
||||
import pyaudio
|
||||
import wave
|
||||
self.audio_stream.stop_stream()
|
||||
self.audio_stream.close()
|
||||
self.audio_recorder.terminate()
|
||||
wf = wave.open(join(root, "audio.wav"), "wb")
|
||||
wf.setnchannels(2)
|
||||
wf.setsampwidth(self.audio_recorder.get_sample_size(pyaudio.paInt32))
|
||||
wf.setframerate(44100)
|
||||
wf.writeframes(b"".join(self.audio_frames))
|
||||
wf.close()
|
||||
print("saved audio in {}".format(root))
|
||||
size = self.display_surface.get_size()
|
||||
frames = self.frames
|
||||
frames.seek(0)
|
||||
for ii, frame in enumerate(
|
||||
iter(lambda: frames.read(self.frame_length), b'')):
|
||||
path = join(root, "{:0{digits}}.png".format(
|
||||
ii, digits=self.get_configuration("video-recordings", "filename-digits")))
|
||||
save(frombuffer(frame, size, self.frame_format), path)
|
||||
print("saved video frames in {}".format(root))
|
||||
|
||||
def update(self):
|
||||
ticks = get_ticks()
|
||||
if self.recording and ticks - self.last_frame >= self.framerate:
|
||||
self.frames.write(self.get_string())
|
||||
self.last_frame = ticks
|
|
@ -1,12 +0,0 @@
|
|||
# __init__.py
|
||||
#
|
||||
# Import all classes from module files, making them accessible through `pgfw.CLASSNAME`
|
||||
|
||||
# Game imports most (all?) of PGFW, so those class become accessible as well (?)
|
||||
from .Game import *
|
||||
|
||||
# Not sure why but pgfw.GameChild causes a module error without importing Animation directly
|
||||
from .Animation import *
|
||||
|
||||
# Import all classes defined in Sprite
|
||||
from .Sprite import *
|
|
@ -0,0 +1,245 @@
|
|||
from os import sep, getcwd
|
||||
from os.path import join, exists, basename, dirname, expanduser
|
||||
from sys import argv
|
||||
from pprint import pformat
|
||||
|
||||
from ConfigParser import RawConfigParser
|
||||
|
||||
class Configuration(RawConfigParser):
|
||||
|
||||
default_project_file_rel_path = "config"
|
||||
default_resources_paths = [".", "resources"]
|
||||
list_member_sep = ','
|
||||
|
||||
def __init__(self, project_file_rel_path=None, resources_path=None,
|
||||
type_declarations=None):
|
||||
RawConfigParser.__init__(self)
|
||||
self.project_file_rel_path = project_file_rel_path
|
||||
self.resources_path = resources_path
|
||||
self.set_type_declarations(type_declarations)
|
||||
self.set_defaults()
|
||||
self.read_project_config_file()
|
||||
self.modify_defaults()
|
||||
self.print_debug(self)
|
||||
|
||||
def set_type_declarations(self, type_declarations):
|
||||
if type_declarations is None:
|
||||
type_declarations = TypeDeclarations()
|
||||
self.type_declarations = type_declarations
|
||||
|
||||
# def set(self, section, option, value):
|
||||
# value = self.cast_value(section, option, value)
|
||||
# RawConfigParser.set(self, section, option, value)
|
||||
|
||||
def set_defaults(self):
|
||||
add_section = self.add_section
|
||||
set_option = self.set
|
||||
section = "setup"
|
||||
add_section(section)
|
||||
set_option(section, "package-root", basename(getcwd()))
|
||||
set_option(section, "title", "")
|
||||
set_option(section, "classifiers", "")
|
||||
set_option(section, "resources-search-path", "./, resources/")
|
||||
set_option(section, "installation-dir", "/usr/local/share/games/")
|
||||
set_option(section, "changelog", "changelog")
|
||||
set_option(section, "description-file", "")
|
||||
set_option(section, "init-script", "")
|
||||
set_option(section, "version", "")
|
||||
set_option(section, "summary", "")
|
||||
set_option(section, "license", "")
|
||||
set_option(section, "platforms", "")
|
||||
set_option(section, "contact-name", "")
|
||||
set_option(section, "contact-email", "")
|
||||
set_option(section, "url", "")
|
||||
set_option(section, "requirements", "")
|
||||
set_option(section, "main-object", "pgfw/Game.py")
|
||||
set_option(section, "resources-path-identifier", "resources_path")
|
||||
section = "display"
|
||||
add_section(section)
|
||||
set_option(section, "dimensions", "480, 320")
|
||||
set_option(section, "frame-duration", "33")
|
||||
set_option(section, "wait-duration", "2")
|
||||
set_option(section, "caption", "")
|
||||
set_option(section, "centered", "yes")
|
||||
section = "screen-captures"
|
||||
add_section(section)
|
||||
set_option(section, "rel-path", "caps")
|
||||
set_option(section, "file-name-format", "%Y-%m-%d_%H:%M:%S")
|
||||
set_option(section, "file-extension", "png")
|
||||
section = "keys"
|
||||
add_section(section)
|
||||
set_option(section, "up", "K_UP, K_w")
|
||||
set_option(section, "right", "K_RIGHT, K_d")
|
||||
set_option(section, "down", "K_DOWN, K_s")
|
||||
set_option(section, "left", "K_LEFT, K_a")
|
||||
set_option(section, "confirm", "K_RETURN")
|
||||
set_option(section, "capture-screen", "K_F9")
|
||||
section = "event"
|
||||
add_section(section)
|
||||
set_option(section, "user-event-id", "USEREVENT")
|
||||
set_option(section, "command-event-name", "command")
|
||||
|
||||
def read(self, filenames):
|
||||
files_read = RawConfigParser.read(self, filenames)
|
||||
for section in self.sections():
|
||||
for option, value in self.items(section):
|
||||
self.set(section, option, value)
|
||||
return files_read
|
||||
|
||||
def read_project_config_file(self):
|
||||
path = self.locate_project_config_file()
|
||||
if path:
|
||||
self.read(path)
|
||||
else:
|
||||
self.print_debug("No configuration file found")
|
||||
|
||||
def locate_project_config_file(self):
|
||||
rel_path = self.project_file_rel_path
|
||||
if not rel_path:
|
||||
rel_path = self.default_project_file_rel_path
|
||||
if exists(rel_path) and not self.is_shared_mode():
|
||||
return rel_path
|
||||
if self.resources_path:
|
||||
installed_path = join(self.resources_path, rel_path)
|
||||
if exists(installed_path):
|
||||
return installed_path
|
||||
|
||||
def is_shared_mode(self):
|
||||
return "-s" in argv
|
||||
|
||||
def print_debug(self, statement):
|
||||
if self.is_debug_mode():
|
||||
print statement
|
||||
|
||||
def is_debug_mode(self):
|
||||
return "-d" in argv
|
||||
|
||||
def modify_defaults(self):
|
||||
self.set_installation_path()
|
||||
self.set_resources_search_path()
|
||||
self.set_screen_captures_path()
|
||||
self.set_data_exclusion_list()
|
||||
self.set_requirements()
|
||||
|
||||
def set_installation_path(self):
|
||||
self.set("setup", "installation-path",
|
||||
join(self.get("setup", "installation-dir", "path"),
|
||||
self.get("setup", "package-root", "path")))
|
||||
|
||||
def set_resources_search_path(self):
|
||||
section, option = "setup", "resources-search-path"
|
||||
search_path = self.get(section, option, "list")
|
||||
if self.resources_path:
|
||||
search_path.append(self.resources_path)
|
||||
else:
|
||||
search_path.append(self.get("setup", "installation-path", "path"))
|
||||
self.set(section, option, search_path)
|
||||
|
||||
def get(self, section, option, cast=None):
|
||||
value = RawConfigParser.get(self, section, option)
|
||||
if value is None:
|
||||
value = self.get_substitute(section, option)
|
||||
if cast:
|
||||
value = self.cast_value(value, cast)
|
||||
return value
|
||||
|
||||
def cast_value(self, value, cast):
|
||||
list_sep = self.list_member_sep
|
||||
if cast == "bool":
|
||||
return True if value == "yes" else False
|
||||
elif cast == "int":
|
||||
return int(value)
|
||||
elif cast == "float":
|
||||
return float(value)
|
||||
elif cast == "path":
|
||||
return self.translate_path(value)
|
||||
elif cast == "list":
|
||||
if value == "":
|
||||
return []
|
||||
else:
|
||||
return map(str.strip, value.split(list_sep))
|
||||
elif cast == "int-list":
|
||||
return map(int, value.split(list_sep))
|
||||
return value
|
||||
|
||||
def translate_path(self, path):
|
||||
new = ""
|
||||
if path and path[0] == sep:
|
||||
new += sep
|
||||
return expanduser("{0}{1}".format(new, join(*path.split(sep))))
|
||||
|
||||
def get_substitute(self, section, option):
|
||||
if section == "display":
|
||||
if option == "caption":
|
||||
return self.get("setup", "title")
|
||||
|
||||
def set_screen_captures_path(self):
|
||||
section, option = "screen-captures", "path"
|
||||
if not self.has_option(section, option):
|
||||
self.set(section, option, join(self.build_home_path(),
|
||||
self.get(section, "rel-path",
|
||||
"path")))
|
||||
|
||||
def build_home_path(self):
|
||||
return join("~", "." + self.get("setup", "package-root", "path"))
|
||||
|
||||
def set_data_exclusion_list(self):
|
||||
section, option = "setup", "data-exclude"
|
||||
exclude = []
|
||||
if self.has_option(section, option):
|
||||
exclude = self.get(section, option, "list")
|
||||
exclude += [".git", ".gitignore", "README", "build/", "dist/",
|
||||
"setup.py", "MANIFEST",
|
||||
self.get("setup", "package-root", "path"),
|
||||
self.get("setup", "changelog", "path")]
|
||||
self.set(section, option, exclude)
|
||||
|
||||
def set_requirements(self):
|
||||
section, option = "setup", "requirements"
|
||||
requirements = []
|
||||
if self.has_option(section, option):
|
||||
requirements = self.get(section, option, "list")
|
||||
if "pygame" not in requirements:
|
||||
requirements.append("pygame")
|
||||
self.set(section, option, requirements)
|
||||
|
||||
def get_section(self, section, cast=None):
|
||||
assignments = {}
|
||||
for option in self.options(section):
|
||||
assignments[option] = self.get(section, option, cast)
|
||||
return assignments
|
||||
|
||||
def __repr__(self):
|
||||
config = {}
|
||||
for section in self.sections():
|
||||
config[section] = self.get_section(section)
|
||||
return pformat(config, 2, 1)
|
||||
|
||||
|
||||
class TypeDeclarations(dict):
|
||||
|
||||
def __init__(self):
|
||||
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [],
|
||||
"list": [], "int-list": []})
|
||||
add = self.add
|
||||
add("int", "display", "frame-duration")
|
||||
add("int", "display", "wait-duration")
|
||||
add("bool", "display", "centered")
|
||||
add("int-list", "display", "dimensions")
|
||||
add("path", "screen-captures", "path")
|
||||
add("list", "setup", "classifiers")
|
||||
add("list", "setup", "resources-search-path")
|
||||
add("path", "setup", "installation-dir")
|
||||
add("path", "setup", "package-root")
|
||||
add("list", "setup", "data-exclude")
|
||||
add("path", "setup", "changelog")
|
||||
add("list", "setup", "requirements")
|
||||
add("path", "setup", "description-file")
|
||||
add("path", "setup", "main-object")
|
||||
add("list", "keys", "up")
|
||||
add("list", "keys", "right")
|
||||
add("list", "keys", "down")
|
||||
add("list", "keys", "left")
|
||||
|
||||
def add(self, type, section, option):
|
||||
self[type].append((section, option))
|
|
@ -1,10 +0,0 @@
|
|||
CONFIRM_QUIT_MESSAGE = '''
|
||||
............................................................
|
||||
..###..###..#....#.####.#.####..#...#....###..#...#.#.#####.
|
||||
.#....#...#.##...#.#....#.#...#.##.##...#...#.#...#.#...#...
|
||||
.#....#...#.#.#..#.####.#.#...#.#.#.#...#...#.#...#.#...#...
|
||||
.#....#...#.#..#.#.#....#.####..#...#...#.#.#.#...#.#...#...
|
||||
.#....#...#.#...##.#....#.#...#.#...#...#..#..#...#.#...#...
|
||||
..###..###..#....#.#....#.#...#.#...#....##.#..###..#...#...
|
||||
............................................................
|
||||
'''
|
|
@ -1,570 +0,0 @@
|
|||
import itertools, random, os, glob
|
||||
from math import sin, cos, atan2, radians, sqrt, pi
|
||||
|
||||
import pygame
|
||||
from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw
|
||||
from pygame.mixer import get_num_channels, Channel
|
||||
from pygame.locals import *
|
||||
|
||||
from .Vector import Vector
|
||||
|
||||
def clamp(n, min_n, max_n):
|
||||
if n < min_n:
|
||||
return min_n
|
||||
elif n > max_n:
|
||||
return max_n
|
||||
else:
|
||||
return n
|
||||
|
||||
def get_step(start, end, speed):
|
||||
x0, y0 = start
|
||||
x1, y1 = end
|
||||
angle = atan2(x1 - x0, y1 - y0)
|
||||
return Vector(speed * sin(angle), speed * cos(angle))
|
||||
|
||||
def get_step_relative(start, end, step):
|
||||
return get_step(start, end, get_distance(start, end) * step)
|
||||
|
||||
def get_segments(start, end, count):
|
||||
rel_step = get_step_relative(start, end, 1 / float(count))
|
||||
segs = [[Vector(start[0], start[1])]]
|
||||
for ii in range(count):
|
||||
seg_end = Vector(segs[-1][0].x + rel_step.x, segs[-1][0].y + rel_step.y)
|
||||
segs[-1].append(seg_end)
|
||||
if ii < count - 1:
|
||||
segs.append([seg_end])
|
||||
return segs
|
||||
|
||||
def get_points_on_line(start, end, count):
|
||||
rel_step = get_step_relative(start, end, 1 / float(count - 1))
|
||||
points = [Vector(start[0], start[1])]
|
||||
for ii in range(count - 2):
|
||||
points.append(Vector(points[-1][0] + rel_step[0], points[-1][1] + rel_step[1]))
|
||||
points.append(Vector(end[0], end[1]))
|
||||
return points
|
||||
|
||||
def get_angle(start, end, transpose=False):
|
||||
"""counterclockwise, 0 is down"""
|
||||
angle = atan2(end[0] - start[0], end[1] - start[1])
|
||||
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, translate_angle)
|
||||
return Vector(x0 + dx, y0 + dy)
|
||||
|
||||
def get_delta(angle, magnitude, translate_angle=True):
|
||||
if translate_angle:
|
||||
angle = radians(angle)
|
||||
return Vector(sin(angle) * magnitude, -cos(angle) * magnitude)
|
||||
|
||||
def reflect_angle(angle, wall):
|
||||
return wall - angle
|
||||
|
||||
def rotate_2d(point, center, angle, translate_angle=True):
|
||||
if translate_angle:
|
||||
angle = radians(angle)
|
||||
x, y = point
|
||||
cx, cy = center
|
||||
return cos(angle) * (x - cx) - sin(angle) * (y - cy) + cx, \
|
||||
sin(angle) * (x - cx) + cos(angle) * (y - cy) + cy
|
||||
|
||||
def get_points_on_circle(center, radius, count, offset=0):
|
||||
angle_step = 360.0 / count
|
||||
points = []
|
||||
current_angle = 0
|
||||
for _ in range(count):
|
||||
points.append(get_point_on_circle(center, radius,
|
||||
current_angle + offset))
|
||||
current_angle += angle_step
|
||||
return points
|
||||
|
||||
def get_point_on_circle(center, radius, angle, translate_angle=True):
|
||||
if translate_angle:
|
||||
angle = radians(angle)
|
||||
return Vector(center[0] + sin(angle) * radius,
|
||||
center[1] - cos(angle) * radius)
|
||||
|
||||
def get_range_steps(start, end, count, omit=[]):
|
||||
'''
|
||||
Iterator that yields evenly spaced steps from `start` to `end` as floats based on step `count`. Indicies in the
|
||||
omit parameter will be skipped.
|
||||
'''
|
||||
# normalize array indicies (for example -1 becomes count - 1)
|
||||
for ii in range(len(omit)):
|
||||
omit[ii] = omit[ii] % count
|
||||
for ii in range(count):
|
||||
if ii in omit:
|
||||
continue
|
||||
else:
|
||||
yield start + (end - start) * ii / float(count - 1)
|
||||
|
||||
def get_percent_way(iterable):
|
||||
for ii in range(len(iterable)):
|
||||
yield iterable[ii], float(ii) / (len(iterable) - 1)
|
||||
|
||||
def mirrored(iterable, full=False, tail=True):
|
||||
for ii, item in enumerate(itertools.chain(iterable, reversed(iterable))):
|
||||
if not full and ii == len(iterable):
|
||||
continue
|
||||
elif not tail and ii == len(iterable) * 2 - 1:
|
||||
continue
|
||||
else:
|
||||
yield item
|
||||
|
||||
def get_distance(p0, p1):
|
||||
"""
|
||||
Return the distance between two points
|
||||
"""
|
||||
return sqrt((p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2)
|
||||
|
||||
def place_in_rect(rect, incoming, contain=True, *args):
|
||||
while True:
|
||||
incoming.center = random.randint(0, rect.w), random.randint(0, rect.h)
|
||||
if not contain or rect.contains(incoming):
|
||||
collides = False
|
||||
for inner in args:
|
||||
if inner.colliderect(incoming):
|
||||
collides = True
|
||||
break
|
||||
if not collides:
|
||||
break
|
||||
|
||||
def constrain_dimensions_2d(vector, container):
|
||||
dw = vector[0] - container[0]
|
||||
dh = vector[1] - container[1]
|
||||
if dw > 0 or dh > 0:
|
||||
if dw > dh:
|
||||
size = Vector(container[0], int(round(container[0] / vector[0] * vector[1])))
|
||||
else:
|
||||
size = Vector(int(round(container[1] / vector[1] * vector[0])), container[1])
|
||||
else:
|
||||
size = Vector(vector[0], vector[1])
|
||||
return size
|
||||
|
||||
# from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c
|
||||
def get_intersection(p0, p1, p2, p3):
|
||||
x0, y0 = p0
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
x3, y3 = p3
|
||||
a0 = y1 - y0
|
||||
b0 = x0 - x1
|
||||
c0 = x1 * y0 - x0 * y1
|
||||
r2 = a0 * x2 + b0 * y2 + c0
|
||||
r3 = a0 * x3 + b0 * y3 + c0
|
||||
if r2 != 0 and r3 != 0 and r2 * r3 > 0:
|
||||
return None
|
||||
a1 = y3 - y2
|
||||
b1 = x2 - x3
|
||||
c1 = x3 * y2 - x2 * y3
|
||||
r0 = a1 * x0 + b1 * y0 + c1
|
||||
r1 = a1 * x1 + b1 * y1 + c1
|
||||
if r0 != 0 and r1 != 0 and r0 * r1 > 0:
|
||||
return None
|
||||
denominator = a0 * b1 - a1 * b0
|
||||
if denominator == 0:
|
||||
return (x0 + x1 + x2 + x3) / 4, (y0 + y1 + y2 + y3) / 4
|
||||
if denominator < 0:
|
||||
offset = -denominator / 2
|
||||
else:
|
||||
offset = denominator / 2
|
||||
numerator = b0 * c1 - b1 * c0
|
||||
x = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
|
||||
numerator = a1 * c0 - a0 * c1
|
||||
y = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
|
||||
return x, y
|
||||
|
||||
def collide_line_with_rect(rect, p0, p1):
|
||||
for line in ((rect.topleft, rect.topright),
|
||||
(rect.topright, rect.bottomright),
|
||||
(rect.bottomright, rect.bottomleft),
|
||||
(rect.bottomleft, rect.topleft)):
|
||||
if get_intersection(p0, p1, *line):
|
||||
return True
|
||||
|
||||
def get_random_number_in_range(start, end):
|
||||
return random.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 fill_borders(surface, color=Color(0, 0, 0), thickness=1, rect=None, flags=0, include=(True, True, True, True)):
|
||||
'''
|
||||
Draw borders on a surface at rect position. A subset of the four borders can be drawn by passing the include
|
||||
iterable with bools for each position (top, right, bottom, left)
|
||||
'''
|
||||
if rect is None:
|
||||
rect = surface.get_rect()
|
||||
# handle horizontal sides differently based on whether top and bottom are being drawn
|
||||
horizontal_h = rect.h - thickness * 2
|
||||
horizontal_y = rect.top + thickness
|
||||
if not include[0]:
|
||||
horizontal_y = rect.top
|
||||
horizontal_h += thickness
|
||||
if not include[2]:
|
||||
horizontal_h += thickness
|
||||
if include[0]:
|
||||
surface.fill(color, (rect.left, rect.top, rect.w, thickness), flags)
|
||||
if include[1]:
|
||||
surface.fill(color, (rect.right - thickness, horizontal_y, thickness, horizontal_h), flags)
|
||||
if include[2]:
|
||||
surface.fill(color, (rect.left, rect.bottom - thickness, rect.w, thickness), flags)
|
||||
if include[3]:
|
||||
surface.fill(color, (rect.left, horizontal_y, thickness, horizontal_h), flags)
|
||||
|
||||
def get_boxed_surface(surface, background=None, border=None, border_width=1, padding=0):
|
||||
if padding:
|
||||
if isinstance(padding, int):
|
||||
padding = [padding] * 2
|
||||
padding = [x * 2 for x in padding]
|
||||
rect = surface.get_rect()
|
||||
padded_surface = Surface(rect.inflate(padding).size, SRCALPHA)
|
||||
if background is not None:
|
||||
padded_surface.fill(background)
|
||||
rect.center = padded_surface.get_rect().center
|
||||
padded_surface.blit(surface, rect)
|
||||
surface = padded_surface
|
||||
if border is not None:
|
||||
if isinstance(border_width, int):
|
||||
border_width = [border_width] * 2
|
||||
border_width = [x * 2 for x in border_width]
|
||||
rect = surface.get_rect()
|
||||
bordered_surface = Surface(rect.inflate(border_width).size, SRCALPHA)
|
||||
bordered_surface.fill(border)
|
||||
rect.center = bordered_surface.get_rect().center
|
||||
bordered_surface.fill((255, 255, 255, 255), rect)
|
||||
bordered_surface.blit(surface, rect, None, BLEND_RGBA_MIN)
|
||||
surface = bordered_surface
|
||||
return surface
|
||||
|
||||
def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background=None, border=None,
|
||||
border_width=1, padding=0, width=None, height=None):
|
||||
if font is not None:
|
||||
surface = font.render(text, antialias, color, background)
|
||||
if width is not None or height is not None:
|
||||
if width is None:
|
||||
width = surface.get_width()
|
||||
if height is None:
|
||||
height = surface.get_height()
|
||||
container = Surface((width, height), SRCALPHA)
|
||||
if background is not None:
|
||||
container.fill(background)
|
||||
text_rect = surface.get_rect()
|
||||
text_rect.center = container.get_rect().center
|
||||
container.blit(surface, text_rect)
|
||||
surface = container
|
||||
else:
|
||||
surface = pygame.Surface((width, height), SRCALPHA)
|
||||
if background is not None:
|
||||
surface.fill(background)
|
||||
return get_boxed_surface(surface, background, border, border_width, padding)
|
||||
|
||||
def get_wrapped_text_surface(font, text, width, antialias=True, color=(0, 0, 0),
|
||||
background=None, border=None, border_width=1,
|
||||
padding=0, align="left"):
|
||||
lines = []
|
||||
height = 0
|
||||
for chunk in text.split("\n"):
|
||||
line_text = ""
|
||||
ii = 0
|
||||
finished = False
|
||||
if chunk.startswith("\*") and chunk.endswith("\*"):
|
||||
chunk = chunk.replace("\*", "*")
|
||||
elif chunk.startswith("*") and chunk.endswith("*"):
|
||||
chunk = chunk[1:-1]
|
||||
font.set_italic(True)
|
||||
words = chunk.split(" ")
|
||||
while not finished:
|
||||
line_width = font.size(line_text + " " + words[ii])[0]
|
||||
if line_width > width or ii == len(words) - 1:
|
||||
if ii == len(words) - 1 and line_width <= width:
|
||||
if line_text != "":
|
||||
line_text += " "
|
||||
line_text += words[ii]
|
||||
finished = True
|
||||
line = font.render(line_text, antialias, color)
|
||||
height += line.get_height()
|
||||
lines.append(line)
|
||||
line_text = ""
|
||||
else:
|
||||
if ii > 0:
|
||||
line_text += " "
|
||||
line_text += words[ii]
|
||||
ii += 1
|
||||
font.set_italic(False)
|
||||
top = 0
|
||||
surface = Surface((width, height), pygame.SRCALPHA)
|
||||
# if background:
|
||||
# surface.fill(background)
|
||||
rect = surface.get_rect()
|
||||
for line in lines:
|
||||
line_rect = line.get_rect()
|
||||
line_rect.top = top
|
||||
if align == "center":
|
||||
line_rect.centerx = rect.centerx
|
||||
surface.blit(line, line_rect)
|
||||
top += line_rect.h
|
||||
return get_boxed_surface(surface, background, border, border_width, padding)
|
||||
|
||||
def replace_color(surface, color, replacement):
|
||||
'''
|
||||
Replace a color on a surface without creating a copy
|
||||
'''
|
||||
pixels = PixelArray(surface)
|
||||
pixels.replace(color, replacement)
|
||||
del pixels
|
||||
|
||||
def get_color_swapped_surface(surface, color, replacement):
|
||||
'''
|
||||
Get a copy of given Surface with given color replaced by given replacement
|
||||
'''
|
||||
swapped = surface.copy()
|
||||
replace_color(swapped, color, replacement)
|
||||
return swapped
|
||||
|
||||
def get_busy_channel_count():
|
||||
'''
|
||||
Return count of audio channels currently playing audio
|
||||
'''
|
||||
count = 0
|
||||
for index in range(get_num_channels()):
|
||||
count += Channel(index).get_busy()
|
||||
return count
|
||||
|
||||
def get_hue_shifted_surface(base, offset):
|
||||
surface = base.copy()
|
||||
pixels = PixelArray(surface)
|
||||
for x in range(surface.get_width()):
|
||||
for y in range(surface.get_height()):
|
||||
rgb = surface.unmap_rgb(pixels[x][y])
|
||||
color = Color(*rgb)
|
||||
h, s, l, a = color.hsla
|
||||
if a and surface.get_colorkey() != rgb:
|
||||
color.hsla = (int(h) + offset) % 360, int(s), int(l), int(a)
|
||||
pixels[x][y] = color
|
||||
del pixels
|
||||
return surface
|
||||
|
||||
def get_inverted_color(color):
|
||||
return Color(255 - color[0], 255 - color[1], 255 - color[2])
|
||||
|
||||
def get_inverted_surface(base):
|
||||
surface = base.copy()
|
||||
pixels = PixelArray(surface)
|
||||
for x in range(surface.get_width()):
|
||||
for y in range(surface.get_height()):
|
||||
color = Color(*surface.unmap_rgb(pixels[x][y]))
|
||||
if color.hsla[3]:
|
||||
pixels[x][y] = get_inverted_color(color)
|
||||
del pixels
|
||||
return surface
|
||||
|
||||
def fill_tile(surface, tile, rect=None, flags=0, offset=Vector(0, 0)):
|
||||
'''
|
||||
Fill surface with tile surface. If rect is set, only fill in that section. If offset is set, start filling
|
||||
with tile shifted by offset (-x, -y)
|
||||
'''
|
||||
w, h = tile.get_size()
|
||||
surface.set_clip(rect)
|
||||
for x in range(-offset.x, surface.get_width(), w):
|
||||
for y in range(-offset.y, surface.get_height(), h):
|
||||
surface.blit(tile, (x, y), None, flags)
|
||||
surface.set_clip(None)
|
||||
|
||||
def load_frames(path, transparency=False, ppa=True, key=None, query=None):
|
||||
if os.path.isfile(path):
|
||||
paths = [path]
|
||||
else:
|
||||
if query:
|
||||
paths = sorted(glob.glob(os.path.join(path, query)))
|
||||
else:
|
||||
paths = [os.path.join(path, name) for name in sorted(os.listdir(path))]
|
||||
frames = []
|
||||
for path in paths:
|
||||
img = pygame.image.load(path)
|
||||
if transparency:
|
||||
if ppa:
|
||||
frame = img.convert_alpha()
|
||||
else:
|
||||
frame = fill_colorkey(img, key)
|
||||
else:
|
||||
frame = img.convert()
|
||||
frames.append(frame)
|
||||
return frames
|
||||
|
||||
def fill_colorkey(img, key=None):
|
||||
if key is None:
|
||||
key = 255, 0, 255
|
||||
img = img.convert_alpha()
|
||||
frame = Surface(img.get_size())
|
||||
frame.fill(key)
|
||||
frame.set_colorkey(key)
|
||||
frame.blit(img, (0, 0))
|
||||
return frame
|
||||
|
||||
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_blinds_rects(w, h, step=.05, count=4):
|
||||
blinds = []
|
||||
blind_h = int(round(h / float(count)))
|
||||
for ii in range(1, count + 1):
|
||||
blinds.append(Rect(0, blind_h * ii, w, 0))
|
||||
inflate_h = int(round(blind_h * step))
|
||||
if inflate_h < 1:
|
||||
inflate_h = 1
|
||||
rects = []
|
||||
while blinds[0].h < blind_h:
|
||||
rects.append([])
|
||||
for blind in blinds:
|
||||
bottom = blind.bottom
|
||||
blind.inflate_ip(0, inflate_h)
|
||||
blind.bottom = bottom
|
||||
rects[-1].append(blind.copy())
|
||||
return rects
|
||||
|
||||
def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)):
|
||||
frames = []
|
||||
rects = []
|
||||
h = int(round(surface.get_height() / float(count)))
|
||||
for ii in range(1, count + 1):
|
||||
rects.append(Rect(0, h * ii, surface.get_width(), 0))
|
||||
bar_h = int(round(h * step))
|
||||
if bar_h < 1:
|
||||
bar_h = 1
|
||||
while rects[0].h < h:
|
||||
frame = surface.copy()
|
||||
for rect in rects:
|
||||
bottom = rect.bottom
|
||||
rect.inflate_ip(0, bar_h)
|
||||
rect.bottom = bottom
|
||||
frame.fill(fill, rect)
|
||||
frames.append(frame)
|
||||
return frames
|
||||
|
||||
def get_hsla_color(hue, saturation=100, lightness=50, alpha=100):
|
||||
'''
|
||||
Get a pygame Color object from HSLA value
|
||||
'''
|
||||
color = Color(0, 0, 0, 0)
|
||||
color.hsla = hue % 360, clamp(saturation, 0, 100), clamp(lightness, 0, 100), clamp(alpha, 0, 100)
|
||||
return color
|
||||
|
||||
def get_random_hsla_color(hue_range=(0, 359), saturation_range=(0, 100), lightness_range=(0, 100), alpha_range=(100, 100)):
|
||||
'''
|
||||
Get a random color with constrained hue, saturation, lightness and alpha
|
||||
'''
|
||||
return get_hsla_color(
|
||||
random.randint(*hue_range), random.randint(*saturation_range), random.randint(*lightness_range),
|
||||
random.randint(*alpha_range))
|
||||
|
||||
def get_hsva_color(hue, saturation=100, value=100, alpha=100):
|
||||
'''
|
||||
Get a pygame Color object from HSVA value
|
||||
'''
|
||||
color = Color(0, 0, 0, 0)
|
||||
color.hsva = hue % 360, saturation, value, alpha
|
||||
return color
|
||||
|
||||
def get_lightened_color(color, lightness):
|
||||
'''
|
||||
Return a copy of the provided color with specified lightness
|
||||
'''
|
||||
h, s, _, a = color.hsla
|
||||
return get_hsla_color(h, s, clamp(lightness, 0, 100), a)
|
||||
|
||||
def get_glow_frames(radius, segments, colors=[(0, 0, 0), (255, 255, 255)], minsize=4, transparency=True):
|
||||
frames = []
|
||||
radius = int(round(radius))
|
||||
sizes = [int(round(minsize + float(ii) / (segments - 1) * (radius - minsize))) for ii in range(segments)]
|
||||
if transparency:
|
||||
alpha_step = 255.0 / segments
|
||||
alpha = alpha_step
|
||||
else:
|
||||
alpha = 255
|
||||
for color_offset in range(len(colors)):
|
||||
frame = Surface([radius * 2] * 2, SRCALPHA if transparency else None)
|
||||
if transparency:
|
||||
alpha = alpha_step
|
||||
for segment_ii, segment_radius in enumerate(reversed(sizes)):
|
||||
color = Color(*(colors[(color_offset + segment_ii) % len(colors)] + (int(round(alpha)),)))
|
||||
gfxdraw.filled_circle(frame, radius, radius, int(round(segment_radius)), color)
|
||||
if transparency:
|
||||
alpha += alpha_step
|
||||
frames.append(frame)
|
||||
return frames
|
||||
|
||||
# http://www.pygame.org/wiki/BezierCurve
|
||||
def compute_bezier_points(vertices, numPoints=60):
|
||||
points = []
|
||||
b0x = vertices[0][0]
|
||||
b0y = vertices[0][1]
|
||||
b1x = vertices[1][0]
|
||||
b1y = vertices[1][1]
|
||||
b2x = vertices[2][0]
|
||||
b2y = vertices[2][1]
|
||||
b3x = vertices[3][0]
|
||||
b3y = vertices[3][1]
|
||||
ax = -b0x + 3 * b1x + -3 * b2x + b3x
|
||||
ay = -b0y + 3 * b1y + -3 * b2y + b3y
|
||||
bx = 3 * b0x + -6 * b1x + 3 * b2x
|
||||
by = 3 * b0y + -6 * b1y + 3 * b2y
|
||||
cx = -3 * b0x + 3 * b1x
|
||||
cy = -3 * b0y + 3 * b1y
|
||||
dx = b0x
|
||||
dy = b0y
|
||||
numSteps = numPoints - 1
|
||||
h = 1.0 / numSteps
|
||||
pointX = dx
|
||||
pointY = dy
|
||||
firstFDX = ax * h ** 3 + bx * h ** 2 + cx * h
|
||||
firstFDY = ay * h ** 3 + by * h ** 2 + cy * h
|
||||
secondFDX = 6 * ax * h ** 3 + 2 * bx * h ** 2
|
||||
secondFDY = 6 * ay * h ** 3 + 2 * by * h ** 2
|
||||
thirdFDX = 6 * ax * h ** 3
|
||||
thirdFDY = 6 * ay * h ** 3
|
||||
points.append(Vector(pointX, pointY))
|
||||
for i in range(numSteps):
|
||||
pointX += firstFDX
|
||||
pointY += firstFDY
|
||||
firstFDX += secondFDX
|
||||
firstFDY += secondFDY
|
||||
secondFDX += thirdFDX
|
||||
secondFDY += thirdFDY
|
||||
points.append(Vector(pointX, pointY))
|
||||
return points
|
||||
|
||||
def get_marching_ants_color(position, time=0, colors=((0, 0, 0), (255, 255, 255))):
|
||||
return Color(*(colors[0], colors[1])[((position // 2) % 2 + (time % 2)) % 2])
|
||||
|
||||
def diagonal_to_rect(start, end):
|
||||
sx, sy = start
|
||||
ex, ey = end
|
||||
return Rect(min(sx, ex), min(sy, ey), abs(sx - ex), abs(ex - ey))
|
||||
|
||||
def scale_2x_multiple(surface, times):
|
||||
'''
|
||||
Run the scale2x scale on a surface times number of times
|
||||
'''
|
||||
for _ in range(times):
|
||||
surface = pygame.transform.scale2x(surface)
|
||||
return surface
|
|
@ -1,14 +0,0 @@
|
|||
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)
|
|
@ -21,4 +21,4 @@ class SampleGame(Game):
|
|||
|
||||
if __name__ == '__main__':
|
||||
# the play method begins the project's animation
|
||||
SampleGame().run()
|
||||
SampleGame().play()
|
||||
|
|
14
setup.py
14
setup.py
|
@ -1,14 +0,0 @@
|
|||
from distutils.core import setup
|
||||
|
||||
setup(name="Pygame Framework",
|
||||
version="0.1",
|
||||
description="Classes to facilitate the creation of pygame projects",
|
||||
author="Frank DeMarco",
|
||||
author_email="frank.s.demarco@gmail.com",
|
||||
url="http://usethematrixze.us",
|
||||
packages=["pgfw"],
|
||||
classifiers=["Development Status :: 2 - Pre-Alpha",
|
||||
"Environment :: Plugins",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: Public Domain",
|
||||
"Programming Language :: Python :: 2.7"])
|
Loading…
Reference in New Issue