Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

unusable. client/server migration.

  • Loading branch information...
commit eace8925c6dab8231b67aff2d8e2eae5422311d5 1 parent b440e83
@bitcraft authored
View
11 README
@@ -2,13 +2,20 @@ this is a ongoing project for some sort of multiplayer adventure game.
a world of warning, this repository is generally not stable, and may not run. there is a stable version under the downloads section.
-python/pygame adventure game library
+server:
+ network support coming soon with twisted integration
native support for tiled maps
supports keyboard and joystick movement
save game and persistence for all objects
+
+client:
scrolling map for large areas
animated sprites
- network support coming soon with twisted integration
+
+goals are:
+ generic server and client
+ resources from the server can be pushed to the client
+ framework for multiplayer games
started during pyweek sept '11, has slowly morphed into something else.
View
203 buildworld.py
@@ -1,203 +0,0 @@
-"""
-Game world for "mh"
-
-this will create a pickle that can be read by the library
-"""
-
-from lib2d.area import AbstractArea, Area, createAreaFromTMX
-from lib2d.avatar import Avatar, Animation, StaticAnimation
-from lib2d.objects import AvatarObject
-from lib2d import res, tmxloader
-from lib.rpg import Hero, NPC
-
-from collections import defaultdict
-
-
-
-# build the initial environment
-uni = AbstractArea()
-uni.name = 'MH'
-uni.setGUID(0)
-
-# build our avatars and heros
-avatar = Avatar()
-ani = Animation("warrior-male-stand.png", "stand", 1, 4)
-avatar.add(ani)
-ani = Animation("warrior-male-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-ani = Animation("warrior-male-attack.png", "attack", 4, 4, 60)
-avatar.add(ani)
-avatar.play("walk")
-npc = Hero()
-npc.setName("Rat")
-npc.setAvatar(avatar)
-npc.setGUID(1)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = Animation("townfolk-male-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-npc = NPC()
-npc.setName("Mayor")
-npc.setAvatar(avatar)
-npc.setGUID(2)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = Animation("ranger-male-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-npc = NPC()
-npc.setName("Bolt")
-npc.setAvatar(avatar)
-npc.setGUID(3)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = Animation("healer-male-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-npc = NPC()
-npc.setName("Ax")
-npc.setAvatar(avatar)
-npc.setGUID(4)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = Animation("healer-female-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-npc = NPC()
-npc.setName("Tooth")
-npc.setAvatar(avatar)
-npc.setGUID(5)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = Animation("magician-male-walk.png", "walk", [0,1,2,1], 4)
-avatar.add(ani)
-npc = NPC()
-npc.setName("Nail")
-npc.setAvatar(avatar)
-npc.setGUID(6)
-uni.add(npc)
-
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "barrel", (9,1), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = True
-item.setName("Barrel")
-item.setAvatar(avatar)
-item.setGUID(513)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "sign", (11,1), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Sign")
-item.setAvatar(avatar)
-item.setGUID(514)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "rock", (8,9), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = True
-item.setName("Rock")
-item.setAvatar(avatar)
-item.setGUID(515)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Stump")
-item.setAvatar(avatar)
-item.setGUID(516)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Stump")
-item.setAvatar(avatar)
-item.setGUID(517)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Stump")
-item.setAvatar(avatar)
-item.setGUID(518)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Stump")
-item.setAvatar(avatar)
-item.setGUID(519)
-uni.add(item)
-
-avatar = Avatar()
-ani = StaticAnimation("16x16-forest-town.png", "stump", (8,7), (16,16))
-avatar.add(ani)
-item = AvatarObject()
-item.pushable = False
-item.setName("Stump")
-item.setAvatar(avatar)
-item.setGUID(520)
-uni.add(item)
-
-
-
-# build the areas to explore
-
-village = createAreaFromTMX(uni, "village.tmx")
-village.setName("Village")
-village.setGUID(1001)
-
-
-home = createAreaFromTMX(uni, "building0.tmx")
-home.setName("Building0")
-home.setGUID(1002)
-
-
-# finialize exits by adding the needed references
-
-allAreas = [ i for i in uni.getChildren() if isinstance(i, Area) ]
-allExits = defaultdict(list)
-
-# make table of all exits
-for area in allAreas:
- for guid in area.exits.keys():
- allExits[guid].append(area)
- print guid, area
-
-# set the exits properly
-for guid, areaList in allExits.items():
- if len(areaList) == 2:
- areaList[0].exits[guid] = (areaList[0].exits[guid][0], areaList[1].guid)
- areaList[1].exits[guid] = (areaList[1].exits[guid][0], areaList[0].guid)
-
- print areaList[0], areaList[0].exits
- print areaList[1], areaList[1].exits
-
-
-uni.save("mh")
View
8 docs/NOTES
@@ -1,8 +0,0 @@
-# resize the "700+ icon set to usable sizes"
-
-# remove the border of each image
-mogrify -shave 14x14
-
-# make the tilemap, thing...
-montage -geometry 64x64 -tile 25x32 *.png iconset.png
-
View
6 docs/TODO
@@ -132,3 +132,9 @@ the use of signals may be lost now, i will see where that will go once i get the
achievements should be built into the game. this means that all state changes should be run through a achievement checker and then applied to the player's 'account'.
if all state changes, and actions tracked, then that will cover 90% of achievements.
+
+Quadtree:
+ + Fast collision detection
+ + Great for static geometry
+ - Slow add/remove of nodes
+
View
8 lib/cutscene.py
@@ -1,7 +1,7 @@
-from lib2d.gamestate import GameState
-from lib2d.buttons import *
-from lib2d.statedriver import driver as sd
-from lib2d import res
+from lib2d.client.gamestate import GameState
+from lib2d.client.buttons import *
+from lib2d.client.statedriver import driver as sd
+from lib2d.common import res
import pygame
from dialog import TextDialog
View
9 lib/dialog.py
@@ -1,7 +1,8 @@
-from lib2d.buttons import *
-from lib2d.gamestate import GameState
-from lib2d.statedriver import driver as sd
-from lib2d import res, gui
+from lib2d.client.buttons import *
+from lib2d.client.gamestate import GameState
+from lib2d.client.statedriver import driver as sd
+from lib2d.client import gui
+from lib2d.common import res
from pygame.locals import *
from pygame.surface import Surface
View
4 lib/renderer.py
@@ -1,5 +1,5 @@
-from lib2d.tilemap import BufferedTilemapRenderer
-from lib2d.objects import AvatarObject
+from lib2d.client.tilemap import BufferedTilemapRenderer
+from lib2d.common.objects import AvatarObject
from pygame.rect import Rect
View
4 lib/rpg.py
@@ -1,5 +1,5 @@
-from lib2d.objects import AvatarObject
-from lib2d.buttons import *
+from lib2d.common.objects import AvatarObject
+from lib2d.client.buttons import *
from conditions import *
from math import cos, sin, degrees
View
52 lib/titlescreen.py
@@ -1,17 +1,18 @@
-from lib2d.gamestate import GameState
-from lib2d.cmenu import cMenu
-from lib2d.tilemap import BufferedTilemapRenderer
-from lib2d.statedriver import driver as sd
-from lib2d.banner import TextBanner
-from lib2d.subpixelsurface import SubPixelSurface
-from lib2d.objects import loadObject
-from lib2d import res
+from lib2d.client.gamestate import GameState
+from lib2d.client.cmenu import cMenu
+from lib2d.client.tilemap import BufferedTilemapRenderer
+from lib2d.client.statedriver import driver as sd
+from lib2d.client.banner import TextBanner
+from lib2d.client.subpixelsurface import SubPixelSurface
+from lib2d.common.objects import loadObject
+from lib2d.common import res
from pygame import Rect, Surface
import pygame
from worldstate import WorldState
from cutscene import Cutscene
+import world
from collections import deque
from random import randint, random, uniform
@@ -32,10 +33,12 @@ def __init__(self, inQueue, outQueue):
Thread.__init__(self)
self.inQueue = inQueue
self.outQueue = outQueue
+ self.running = True
+ self.done = False
def run(self):
- while 1:
+ while self.running:
try:
surface = self.inQueue.get(0)
@@ -46,6 +49,8 @@ def run(self):
self.outQueue.put(subpix)
self.inQueue.task_done()
+ if not self.running: self.done = True
+
class TitleScreen(GameState):
"""
@@ -62,7 +67,7 @@ def activate(self):
res.fadeoutMusic()
self.maps = []
- self.change_delay = 8000 # seconds until map moves to next point
+ self.change_delay = 2000 # seconds until map moves to next point
self.map_fadeout = 60.0 # must be a float
self.last_update = 0
self.surfaceQueue = queue()
@@ -75,11 +80,13 @@ def activate(self):
self.menu = cMenu(Rect((42,20), sd.get_size()),
20, 5, 'vertical', 100,
[('New Game', self.new_game),
- ('Battle Test', self.continue_game),
+ ('Load Game', self.load_game),
('Introduction', self.show_intro),
('Quit Game', self.quit_game)],
font="northwoodhigh.ttf", font_size=24)
+ self.game = None
+
self.menu.ready()
self.change_map()
@@ -157,12 +164,22 @@ def draw(self, surface):
def new_game(self):
- uni = loadObject("mh")
- village = uni.getChildByGUID(1001)
+ res.fadeoutMusic(1000)
+ self.game = world.build()
+ village = self.game.getChildByGUID(5001)
sd.start_restart(WorldState(village))
- def continue_game(self):
- sd.start_restart(BattleState(None, None))
+
+ def load_game(self):
+ res.fadeoutMusic(1000)
+ try:
+ path = os.path.join("resources", "saves", "save")
+ self.game = loadObject(path)
+ except IOError:
+ return self.new_game()
+
+ level = self.game.getChildByGUID(5001)
+ sd.start(WorldState(level))
def show_intro(self):
@@ -171,5 +188,8 @@ def show_intro(self):
def quit_game(self):
- sd.done()
+ self.thread.running = False
+ while not self.thread.done:
+ pass
+ sd.done()
View
66 lib/worldstate.py
@@ -1,12 +1,18 @@
+"""
+Example GameState for a top-down adventure game.
+This is the client portion of the game.
+"""
+
from renderer import AreaCamera
from rpg import GRAB, LIFT
-from lib2d.gamestate import GameState
-from lib2d.statedriver import driver as sd
-from lib2d.buttons import *
-from lib2d.vec import Vec2d
-from lib2d.signals import *
-from lib2d import tmxloader, res, gui
+from lib2d.client.gamestate import GameState
+from lib2d.client.statedriver import driver as sd
+from lib2d.client.buttons import *
+from lib2d.client import gui
+from lib2d.common.vec import Vec2d
+from lib2d.common import res
+from pytmx import tmxloader
import math, pygame
@@ -27,8 +33,6 @@ def play(self, name, volume=1.0):
sound.stop()
sound.play()
-
-
SoundMan = SoundManager()
@@ -255,54 +259,8 @@ def _update(self, time):
self.area.movePosition(self.hero, (x, y, 0), True, caller=self)
- @receiver(timeSignal)
- def update(self, sender, **kwargs):
- time = kwargs['time']
- print kwargs
- sender.update(time)
-
-
def handle_commandlist(self, cmdlist):
self.controller.process(cmdlist)
-"""
-Below are functions to handle signals sent from the area
-"""
-
-@receiver(emitSound)
-def playSound(sender, **kwargs):
- SoundMan.play(kwargs['filename'])
-
-
-@receiver(bodyAbsMove)
-def bodyMove(sender, **kwargs):
- area = sender
- body = kwargs['body']
- position = kwargs['position']
- state = kwargs['caller']
-
- if state == None:
- return
-
- if body == state.hero:
- body.avatar.play("walk")
- state.camera.center(position)
-
-
-@receiver(bodyWarp)
-def bodyWarp(sender, **kwargs):
- area = sender
- body = kwargs['body']
- destination = kwargs['destination']
- state = kwargs['caller']
-
- if state == None:
- return
-
- if body == state.hero:
- sd.push(WorldState(destination))
- sd.done()
-
-
View
105 lib2d/client/ani.py
@@ -1,105 +0,0 @@
-from objects import GameObject
-import res
-
-"""
-TODO: make some sort of manager class for animations
- would make resource sharing less expensive
- in many cases, a cache doesn't make sense, but here...it does
-"""
-
-def padimage(image):
- """
- Do a little processing to the input image to make it prettier when rotated.
- Pad the image with transparent pixels so the edges get antialised when
- rotated and scaled.
- """
-
- import pygame
-
-
-
- # replacement surface that is slightly bigger then the original
- new = Surface(image.get_rect().inflate(2, 2).size, pygame.SRCALPHA)
- color = image.get_at((0,0))
-
- # set the color's alpha channel to be clear
- color[3] = 0
- new.fill(color)
- new.blit(image, (1,1))
-
- return new
-
-
-# TODO: make this more like a list object. slices, etc.
-class Animation(GameObject):
- """
- Animation is a collection of frames with a few control variables.
- Animations can store multiple directions and are picklable.
-
- each set of animation added will count as a seperate direction.
- 1 animation = no rotations
- 2 animations = left and right (or up and down)
- 4 animations = left, right, up, down
- 6 animations = up, down, nw, ne, sw, se (for hex maps)
- and so on.
-
- The animation loader expects the image to be in a specific format.
-
- TODO: implement some sort of timing, rather than relying on frames
- """
-
- def __init__(self, filename, size, name=None, directions=1):
- GameObject.__init__(self)
-
- self.filename = filename
- self.size = size
-
- if name == None:
- self.name = filename
-
- self.directions = directions # number of directions for animation
- self.frames = ([],) * directions
-
-
- def load(self):
- """
- load the images for use with pygame
- """
-
- from pygame.image import load
-
-
- tw, th = self.size
- image = load(self.filename)
- iw, ih = image.get_size()
- self.directions = ih/th
- self.frames = ([],) * self.directions
- for y in range(0, ih/th, th):
- for x in range(0, iw/tw, tw):
- frame = AnimationFrame(image.subsurface((x, y, tw, th)))
- self.add_frame(frame, y)
-
- def __len__(self):
- return self._len
-
-
- def get_frame(self, number, direction=0):
- """
- return the frame by number with the correct image for the direction
- """
-
- if direction < 0:
- direction = pi + (pi - abs(direction))
-
- d = int(ceil(direction / pi2 * (self.directions - 1)))
-
- return self.frames[d][number]
-
-
- def add_frame(self, frame, direction=0):
- frames[direction].append(frame)
- self._len = len(self.frames[0])
-
-
- def __repr__(self):
- return "<Animation %s: \"%s\">" % (id(self), self.name)
View
4 lib2d/client/game.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
-from lib2d.statedriver import driver as sd
-from lib2d import gfx
+from lib2d.client.statedriver import driver as sd
+from lib2d.client import gfx
import pygame
View
6 lib2d/client/gamestate.py
@@ -5,10 +5,10 @@ class GameState(object):
def __init__(self):
"""
- Called when objest is instanced.
+ Called when object is instanced.
Not a good idea to load large objects here since it is possible
- that the state is simply instanced and placed in a queue. It will
+ that the state is simply instanced and placed in a queue. It would
be wasteful.
Ideally, any initialization will be handled in activate() since
@@ -61,7 +61,7 @@ def draw(self, surface):
def handle_event(self, event):
"""
- Called when there is an pygame event to process
+ Called when there is a lib2d or pygame event to process
Better to use handle_command() or handle_commandlist()
for player input
View
31 lib2d/client/gfx.py
@@ -1,3 +1,25 @@
+"""
+Copyright 2010, 2011 Leif Theden
+
+
+This file is part of lib2d.
+
+lib2d is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+lib2d is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with lib2d. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from lib2d.common.vec import Vec2d
+
from pygame.transform import scale, scale2x
from pygame.display import flip
import pygame, os.path, pprint
@@ -7,6 +29,7 @@
"""
a few utilities for making retro looking games by scaling the screen
and providing a few functions for handling screen changes
+
"""
DEBUG = False
@@ -24,7 +47,7 @@ def debug(text):
update_display = None
double_buffer = False
hwsurface = False
-surface_flags = 0
+surface_flags = pygame.FULLSCREEN
@@ -40,7 +63,7 @@ def init():
global screen_dim
# determine if we can use hardware accelerated surfaces or not
- pygame.display.set_caption("RPG World Test")
+ pygame.display.set_caption("robots!")
# is it redundant to have a pygame buffer, and one for pixalization? maybe...
@@ -56,7 +79,7 @@ def set_screen(dim, scale=1, transform=None):
global pixelize, pix_scale, buffer_dim, screen, update_display, screen_surface, screen_dim
- screen_dim = dim
+ screen_dim = Vec2d(dim)
if transform == "scale2x" or transform == "scale":
set_scale(scale, transform)
@@ -66,7 +89,7 @@ def set_screen(dim, scale=1, transform=None):
pixel_buffer = None
pix_scale = 1
buffer_dim = None
- update_display = flip
+ update_display = pygame.display.update
screen_surface = pygame.display.set_mode(screen_dim, surface_flags)
screen = screen_surface
View
2  lib2d/client/gui.py
@@ -1,4 +1,4 @@
-import lib2d.res
+import lib2d.common.res
import pygame
from math import ceil
from itertools import product
View
49 lib2d/client/playerinput.py
@@ -1,31 +1,38 @@
-from pygame.locals import *
-from buttons import *
-import pygame
-
-
"""
-this provides an abstraction between pygame's input's and
-input handling. events will be translated into a format
-that the game will handle. this provides an way to deal
-with multiple inputs and reconfiguring keys at runtime.
+this provides an abstraction between pygame's inputs and input handling.
+events will be translated into a format that the game will handle. this
+provides an way to deal with multiple inputs and reconfiguring keys at runtime.
provides a couple nice features:
- input can be reconfigured during runtime, without chaning game code
+ inputs can be reconfigured during runtime, without changing game code
inputs can be changed during runtime: want a joystick? no problem
input commands keep track of buttons being held as well
-"""
+When processing the pygame event queue, be sure to check events against the
+processEvent() method.
+"""
+from pygame.locals import *
+from buttons import *
+import pygame
-get_pressed = pygame.key.get_pressed
-class PlayerInput:
- def getCommand(self, event):
+class PlayerInput(object):
+ def processEvent(self, event):
+ """
+ Determine state by checking a pygame event
+ """
raise NotImplementedError
- def getHeld(self):
- pass
+
+ def getState(self):
+ """
+ Return a list of buttons that have been pressed or held since
+ last time input was checked
+ """
+ raise NotImplementedError
+
class KeyboardPlayerInput(PlayerInput):
@@ -55,15 +62,15 @@ def __init__(self, keymap=None):
self.held = []
- def getHeld(self):
+ def getState(self):
"""
return a list of keys that are being held down
"""
- return [ (self.__class__, key, BUTTONHELD) for key in self.held ]
+ return [ (self.__class__, key, ) for key in self.held ]
- def getCommand(self, event):
+ def processEvent(self, event):
try:
key = self.keymap[event.key]
@@ -103,6 +110,8 @@ def getCommand(self, event):
return self.__class__, key, state
+
+
class JoystickPlayerInput(PlayerInput):
default_p1 = {
@@ -124,7 +133,7 @@ def __init__(self, keymap=None):
if keymap == None:
self.keymap = JoystickPlayerInput.default_p1
- def getCommand(self, event):
+ def processEvent(self, event):
try:
if event.joy != self.jsNumber: return
except AttributeError:
View
126 lib2d/client/protocol.py
@@ -1,126 +0,0 @@
-from twisted.internet.protocol import Protocol
-from twisted.internet import reactor
-from twisted.protocols.policies import TimeoutMixin
-
-
-SUPPORTED_PROTOCOL = 1
-
-class Lib2dServerProtocol(object, Protocol, TimeoutMixin):
- """
- Lib2d server protocol
- """
-
-
- excess = ""
- packet = None
-
- state = STATE_UNAUTHENTICATED
-
- buf = ""
- parser = None
- handler = None
-
- player = None
- username = None
- motd = "Lib2d Server"
-
- _health = 20
- _latency = 0
-
- def __init__(self):
-
- self.handlers = {
- 0: self.ping,
- 1: self.login,
- 2: self.chat,
- 3: self.position,
- 4: self.orientation,
- 255: self.quit,
- }
-
- self.setTimeout(30)
-
- def ping(self, container):
- now = timestamp_from_clock(reactor)
- then = container.pid
- self.latency = now - then
-
- def login(self, container):
- if container.protocol < SUPPORTED_PROTOCOL:
- self.error("This server doesn't support your ancient client.")
- elif container.protocol > SUPPORTED_PROTOCOL:
- self.error("This server doesn't support your newfangled client.")
- else:
- reactor.callLater(0, self.authenticated)
-
- def chat(self, container):
- pass
-
- def position(self, container):
- pass
-
- def orientation(self, container):
- pass
-
- def quit(self, container):
- pass
-
- def challenged(self):
- """
- Called when the client has started authentication with the server.
- """
-
- self.state = STATE_CHALLENGED
-
- def authenticated(self):
- """
- Called when the client has successfully authenticated with the server.
- """
-
- self.state = STATE_AUTHENTICATED
-
- self._ping_loop.start(30)
-
- def error(self, message):
- """
- Error out.
-
- This method sends ``message`` to the client as a descriptive error
- message, then closes the connection.
- """
-
- self.transport.write(make_error_packet(message))
- self.transport.loseConnection()
-
-
- # Following methods are for Twisted
-
- def dataReceived(self, data):
- self.buf += data
- packets, self.buf = parse_packets(self.buf)
-
- if packets:
- self.resetTimeout()
-
- for header, payload in packets:
- if header in self.handlers:
- self.handlers[header](payload)
- else:
- log.err("Didn't handle parseable packet %d!" % header)
- log.err(payload)
-
- def connectionLost(self, reason):
- if self._ping_loop.running:
- self._ping_loop.stop()
-
- def timeoutConnection(self):
- self.error("Connection timed out")
-
- def write_packet(self, header, **payload):
- """
- Send a packet to the client.
- """
-
- self.transport.write(make_packet(header, **payload))
-
-
View
81 lib2d/client/statedriver.py
@@ -1,8 +1,26 @@
-import gfx
-from signals import *
-from playerinput import KeyboardPlayerInput
+"""
+Copyright 2010, 2011 Leif Theden
+
+
+This file is part of lib2d.
+
+lib2d is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+lib2d is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with lib2d. If not, see <http://www.gnu.org/licenses/>.
+"""
+import gfx
import pygame
+from playerinput import KeyboardPlayerInput
from collections import deque
from itertools import cycle, islice
from pygame.locals import *
@@ -14,6 +32,8 @@
from dealing with input too often and slowing down rendering.
"""
+target_fps = 30
+
inputs = []
inputs.append(KeyboardPlayerInput())
@@ -180,15 +200,6 @@ def roundrobin(*iterables):
nexts = cycle(islice(nexts, pending))
- def tick(self, time):
- """
- send a tick to the game world
- send the amount of time to simulate
- """
-
- timeSignal.send(sender=self, time=time)
-
-
def run(self):
"""
run the state driver.
@@ -206,7 +217,11 @@ def run(self):
# set an event to flush out commands
event_flush = pygame.USEREVENT
- pygame.time.set_timer(pygame.USEREVENT, 30)
+ pygame.time.set_timer(event_flush, 20)
+
+ # set an event to update the game state
+ #update_state = pygame.USEREVENT + 1
+ #pygame.time.set_timer(update_state, 30)
# make sure our custom events will be triggered
pygame.event.set_allowed([event_flush])
@@ -218,13 +233,13 @@ def run(self):
currentState = current_state()
while currentState:
- time = clock.tick(20)
+ clock.tick(target_fps)
event = event_poll()
while event:
# check each input for something interesting
- for cmd in [ c.getCommand(event) for c in inputs ]:
+ for cmd in [ c.processEvent(event) for c in inputs ]:
rawcmds += 1
if (cmd != None) and (cmd[:2] not in checkedcmds):
checkedcmds.append(cmd[:2])
@@ -238,7 +253,7 @@ def run(self):
# do we flush input now?
elif event.type == pygame.USEREVENT:
currentState.handle_commandlist(cmdlist)
- [ currentState.handle_commandlist(i.getHeld())
+ [ currentState.handle_commandlist(i.getState())
for i in inputs ]
rawcmds = 0
checkedcmds = []
@@ -260,21 +275,45 @@ def run(self):
currentState = originalState
if currentState:
- time = time / 4.0
+ time = target_fps / 10.0
+
+ currentState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
+
+ currentState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
+
+ originalState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
+
+ currentState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
+
+ currentState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
+
+ currentState.update(time)
+ currentState = current_state()
+ if not currentState == originalState: continue
- self.tick(time)
+ originalState.update(time)
currentState = current_state()
if not currentState == originalState: continue
- self.tick(time)
+ currentState.update(time)
currentState = current_state()
if not currentState == originalState: continue
- self.tick(time)
+ currentState.update(time)
currentState = current_state()
if not currentState == originalState: continue
- self.tick(time)
+ currentState.update(time)
currentState = current_state()
if not currentState == originalState: continue
View
52 lib2d/client/tilemap.py
@@ -6,6 +6,7 @@
import pygame
from itertools import product, chain, ifilter
+from pytmx import tmxloader
# this image will be used when a tile cannot be loaded
@@ -37,8 +38,6 @@ class BufferedTilemapRenderer(object):
"""
def __init__(self, tmx, rect, **kwargs):
- import tmxloader
-
self.default_image = generateDefaultImage((tmx.tilewidth,
tmx.tileheight))
self.tmx = tmx
@@ -86,6 +85,7 @@ def setSize(self, size):
rects.append(rect)
self.layerQuadtree = quadtree.FastQuadTree(rects, 4)
+ self.idle = False
self.blank = True
self.queue = None
@@ -100,8 +100,11 @@ def center(self, (x, y)):
x, y = int(x), int(y)
if (self.oldX == x) and (self.oldY == y):
+ self.idle = True
return
+ self.idle = False
+
# calc the new postion in tiles and offset
left, self.xoffset = divmod(x-self.halfWidth, self.tmx.tilewidth)
top, self.yoffset = divmod(y-self.halfHeight, self.tmx.tileheight)
@@ -196,26 +199,21 @@ def optSurfaces(self, surface=None, depth=None, flags=0):
if surface:
for i, t in enumerate(self.tilemap.images):
- if not t == 0: self.tilemap.images[i] = t.convert(surface)
+ if t: self.tilemap.images[i] = t.convert(surface)
self.buffer = self.buffer.convert(surface)
elif depth or flags:
for i, t in enumerate(self.tilemap.images):
- if not t == 0:
+ if t:
self.tilemap.images[i] = t.convert(depth, flags)
self.buffer = self.buffer.convert(depth, flags)
def queueEdgeTiles(self, (x, y)):
"""
- add the tiles on the edge that need to be redrawn to the queue also,
- for colorkey layers, we will fill the new edge with the colorkey color
- here
-
- uses a standard python list for the queue
+ add the tiles on the edge that need to be redrawn to the queue.
+ uses a iterator for the queue
override if you want a different type of queue
-
- for internal use only
"""
if self.queue == None:
@@ -251,12 +249,12 @@ def queueEdgeTiles(self, (x, y)):
self.queue = chain(p, self.queue)
- def update(self, time):
+ def update(self, time=None):
"""
the drawing operations and management of the buffer is handled here.
if you notice that the tiles are being drawn while the screen
is scrolling, you will need to adjust the number of tiles that are
- bilt per update.
+ bilt per update or increase update frequency.
"""
if self.queue:
@@ -275,7 +273,7 @@ def update(self, time):
break
image = getTile((x, y, l))
- if not image == 0:
+ if image:
bufblit(image, (x * tw - ltw, y * th - tth))
@@ -285,14 +283,13 @@ def draw(self, surface, surfaces=[]):
surfaces may optionally be passed that will be blited onto the surface.
this must be a list of tuples containing a layer number, image, and
- rect in screen coordinates. surfaces will be drawn in oder passed,
+ rect in screen coordinates. surfaces will be drawn in order passed,
and will be correctly drawn with tiles from a higher layer overlap
the surface.
passing a list here will correctly draw the surfaces to create the
illusion of depth.
- TODO: make sure the soccrect rects are returned for dirty updates
"""
if self.blank:
@@ -327,25 +324,20 @@ def draw(self, surface, surfaces=[]):
dirtyRect = dirtyRect.move(ox, oy)
for r in self.layerQuadtree.hit(dirtyRect):
x, y, tw, th = r
- if dirtyRect.bottom < y+th:
- # create illusion of depth by sorting images and
- # tiles that are on the same layer. if the image is
- # lower than the tile, don't reblit the tile
- tile = getTile((x/tw + left, y/th + top, layer))
- if not tile == 0:
- surblit(tile, (x-ox, y-oy))
-
- for l in range(layer+1,len(self.tmx.visibleTileLayers)):
+ for l in range(layer+1, len(self.tmx.visibleTileLayers)):
# there is a collision between a tile and a image, so
# we simply reblit the affected tiles over the sprite
tile = getTile((x/tw + left, y/th + top, l))
- if not tile == 0:
+ if tile:
surblit(tile, (x-ox, y-oy))
# restore clipping area
surface.set_clip(origClip)
- return self.rect
+ if self.idle:
+ return [ i[0] for i in dirty ]
+ else:
+ return [ self.rect ]
def flushQueue(self):
@@ -361,10 +353,9 @@ def flushQueue(self):
tth = self.view.top * th
getTile = self.getTileImage
- images = [ (i, getTile(i)) for i in self.queue ]
+ images = ifilter(lambda x: x[1], ((i, getTile(i)) for i in self.queue) )
- [ blit(image, (x*tw-ltw, y * th-tth))
- for ((x,y,l), image) in ifilter(lambda x: x[1], images) ]
+ [ blit(image, (x*tw-ltw, y * th-tth)) for ((x,y,l), image) in images ]
self.queue = None
@@ -455,3 +446,4 @@ def center(self, (x,y)):
def draw(self, surface, origin):
pass
+
View
720 lib2d/common/area.py
@@ -1,720 +0,0 @@
-import res
-from objects import GameObject
-from quadtree import QuadTree, FrozenRect
-from pygame import Rect
-from bbox import BBox
-from pathfinding import astar
-from lib2d.signals import *
-import math
-
-cardinalDirs = {"north": math.pi*1.5, "east": 0.0, "south": math.pi/2, "west": math.pi}
-
-
-class CollisionError(Exception):
- pass
-
-
-
-
-
-class ExitTile(FrozenRect):
- def __init__(self, rect, exit):
- FrozenRect.__init__(self, rect)
- self._value = exit
-
- def __repr__(self):
- return "<ExitTile ({}, {}, {}, {}): {}>".format(
- self._left,
- self._top,
- self._width,
- self._height,
- self._value)
-
-
-
-class AbstractArea(GameObject):
- pass
-
-
-class Sound(object):
- """
- Class that manages how sounds are played and emitted from the area
- """
-
- def __init__(self, filename, ttl):
- self.filename = filename
- self.ttl = ttl
- self._done = 0
- self.timer = 0
-
- def update(self, time):
- if self.timer >= self.ttl:
- self._done = 1
- else:
- self.timer += time
-
- @property
- def done(self):
- return self._done
-
-
-
-class AreaData(GameObject):
- """
- Contains data that is needed to store objects in a 2d area.
-
- """
-
-
- def __init__(self):
- AbstractArea.__init__(self)
- self.bodies = {} # position and size of bodies in 3d space
- self.orientations = {} # records where the body is facing
- self.joins = [] # records simple joins between bodies
- self._oldPositions = {} # used in collision handling
-
-
- def defaultPosition(self):
- return BBox(0,0,0,1,1,1)
-
-
- def defaultSize(self):
- # TODO: this cannot be hardcoded!
- return (10, 8)
-
-
- def load(self):
- """Load the data from a TMX file that is required for this map
-
- This must be done when using the object in the game!
- """
-
- import tmxloader
-
- self.tmxdata = tmxloader.load_pygame(
- self.mappath, force_colorkey=(128,128,0))
-
- # quadtree for handling collisions with exit tiles
- rects = []
- for guid, param in self.exits.items():
- try:
- x, y, l = param[0]
- except:
- continue
-
- rects.append(ExitTile((x,y,
- self.tmxdata.tilewidth, self.tmxdata.tileheight), guid))
-
- self.exitQT = QuadTree(rects)
-
-
- def join(self, body1, body2):
- """
- joins two bodies together.
- if one body moves, the other one will move with it, if able
- """
-
- self.joins.append((body1, body2))
-
-
- def unjoin(self, body1, body2):
- """
- breaks a join between two bodies
- """
-
- try:
- self.joins.remove((body1, body2))
- return True
- except:
- return False
-
-
- def getBBox(self, body):
- """ Return a bbox that represents this body in world """
- return self.bodies[body]
-
-
- def setBBox(self, body, bbox):
- """ Attempt to set a bodies bbox. Returns True if able. """
-
- if not isinstance(bbox, BBox):
- bbox = BBox(bbox)
-
- if self.testCollide(bbox):
- return False
- else:
- self.bodies[body] = bbox
- self._oldPositions[body] = bbox
- return True
-
-
- def testCollide(self, bbox):
- return False
-
-
- def getSize(self, body):
- """ Return 3d size of the body """
-
- return self.bodies[body].size
-
-
- def getPositions(self):
- return [ (o, b.origin) for (o, b) in self.bodies.items() ]
-
-
- def getOrientation(self, body):
- """ Return the angle body is facing in radians """
-
- return self.orientations[body]
-
-
- def setOrientation(self, body, angle):
- """ Set the angle the body is facing. Expects radians. """
-
- if isinstance(angle, str):
- try:
- angle = cardinalDirs[angle]
- except:
- raise
- self.orientations[body] = angle
-
-
- def add(self, body):
- GameObject.add(self, body)
- self.bodies[body] = self.defaultPosition()
- self._oldPositions[body] = self.bodies[body]
- self.orientations[body] = 0.0
-
-
- def remove(self, body):
- GameObject.remove(self, body)
- del self.bodies[body]
- del self._oldPositions[body]
- del self.orientations[body]
-
- to_remove = []
- for j in self.joins:
- if body in j:
- to_remove.append(j)
-
- for j in to_remove:
- self.joins.remove(j)
-
-
- def setPosition(self, body, (x, y, z)):
- """
- Set the position on an object. Does no checking.
- """
-
- self._oldPositions[body] = self.bodies[body]
- self.bodies[body] = bbox
-
-
- def getOldPosition(self, body):
- return self._oldPositions[body]
-
-
- def getPosition(self, body):
- return self.bodies[body].origin
-
-
-class Area(AbstractArea):
- """3D environment for things to live in.
- Includes basic pathfinding, collision detection, among other things.
-
- uses a quadtree for fast collision detection with level geometry.
- collisions are checked whenever a body is moved. the return value of
- the two movement functions will let you know if the body was able to be
- moved or not.
-
- bodies can exits in layers, just like maps. since the y values can
- vary, when testing for collisions the y value will be truncated and tested
- against the quadtree that is closest. if there is no quadtree, no
- collision testing will be done.
-
- there are a few hacks to be aware of:
- bodies move in 3d space, but level geometry is 2d space
- when using pygame rects, the y value maps to the z value in the area
-
- a word on the coordinate system:
- coordinates are 'right handed'
- x axis moves toward viewer
- y axis move left right
- z axis is height
-
- Expects to load a specially formatted TMX map created with Tiled.
- Layers:
- Control Tiles
- Upper Partial Tiles
- Lower Partial Tiles
- Lower Full Tiles
-
- The control layer is where objects and boundries are placed. It will not
- be rendered. Your map must not have any spaces that are open. Each space
- must have a tile in it. Blank spaces will not be rendered properly and
- will leave annoying trails on the map.
-
- The control layer must be created with the utility included with lib2d. It
- contains metadata that lib2d can use to layout and position objects
- correctly.
-
- NOTE: some of the code is specific for maps from the tmxloader
- """
-
-
- def defaultPosition(self):
- return BBox(0,0,0,1,1,1)
-
- def defaultSize(self):
- # TODO: this cannot be hardcoded!
- return (10, 8)
-
-
- def __init__(self):
- AbstractArea.__init__(self)
- self.exits = {}
- self.geometry = {} # geometry (for collisions) of each layer
- self.bodies = {} # position and size of bodies in 3d space
- self.orientations = {} # records where the body is facing
- self.extent = None # absolute boundries of the area
- self.joins = [] # records simple joins between bodies
- self._oldPositions = {} # used in collision handling
- self.messages = []
- self.time = 0
- self.tmxdata = None
- self.mappath = None
- self.sounds = []
-
-
- def pathfind(self, obj, destination):
- """Pathfinding for the world. Destinations are 'snapped' to tiles.
- """
-
- def NodeFactory((x, y, l)):
- try:
- if self.tmxdata.getTileGID(x, y, l) == 0:
- node = Node((x, y))
- else:
- return None
- except:
- return None
- else:
- return node
-
-
- start = self.worldToTile(self.bodies[obj].bottomcenter)
- finish = self._worldToTile((destination[0], destination[1], 0))
-
- path = astar.search(start, finish, factory)
-
- print path
-
-
- def tileToWorld(self, (x, y, z)):
- xx = int(x) * self.tmxdata.tileheight
- yy = int(y) * self.tmxdata.tilewidth
- return xx, yy, z
-
- def worldToTile(self, (x, y, z)):
- xx = int(y) / self.tmxdata.tileheight
- yy = int(x) / self.tmxdata.tilewidth
- return xx, yy, z
-
- def pixelToWorld(self, (x, y, z)):
- return (y, x, z)
-
- def _worldToTile(self, (x, y)):
- return int(y)/self.tmxdata.tileheight, int(x)/self.tmxdata.tilewidth
-
- def emitSound(self, filename, pos):
- self.sounds = [ s for s in self.sounds if not s.done ]
- if filename not in [ s.filename for s in self.sounds ]:
- emitSound.send(sender=self, filename=filename, position=pos)
- self.sounds.append(Sound(filename, 300))
-
-
- def load(self):
- """Load the data from a TMX file that is required for this map
-
- This must be done when using the object in the game!
- """
-
- import tmxloader
-
- self.tmxdata = tmxloader.load_pygame(
- self.mappath, force_colorkey=(128,128,0))
-
- # quadtree for handling collisions with exit tiles
- rects = []
- for guid, param in self.exits.items():
- try:
- x, y, l = param[0]
- except:
- continue
-
- rects.append(ExitTile((x,y,
- self.tmxdata.tilewidth, self.tmxdata.tileheight), guid))
-
- self.exitQT = QuadTree(rects)
-
-
- def update(self, time):
- self.time += time
- [ sound.update(time) for sound in self.sounds ]
- [ o.update(time) for o in self.bodies ]
-
-
- def join(self, body1, body2):
- """
- joins two bodies together.
- if one body moves, the other one will move with it, if able
- """
-
- self.joins.append((body1, body2))
-
-
- def unjoin(self, body1, body2):
- """
- breaks a join between two bodies
- """
-
- try:
- self.joins.remove((body1, body2))
- return True
- except:
- return False
-
-
- def setExtent(self, rect):
- """
- set the limits that things can move inside the area
- """
-
- self.extent = Rect(rect)
-
-
- def setLayerGeometry(self, layer, rects):
- """
- set the layer's geometry. expects a list of rects.
- """
-
- import quadtree
-
- self.geometry[layer] = quadtree.FastQuadTree(rects)
- self.geoRect = rects
-
-
- def testCollideGeometry(self, bbox):
- """
- test if a bbox collides with the layer geometry
-
- the geometry layer will be calculated from the z value
- """
-
- # TODO: calc layer value
- layer = 4
-
- try:
- rect = self.toRect(bbox)
- hit = self.geometry[layer].hit(rect)
- con = self.extent.contains(rect)
-
- return bool(hit) or not bool(con)
-
- except KeyError:
- msg = "Area Layer {} does not have a collision layer"
- print msg.format(layer)
- return False
-
- raise Exception, msg.format(layer)
-
-
- def testCollideObjects(self, bbox):
- values = []
- keys = []
-
- for body, b in self.bodies.items():
- values.append(b)
- keys.append(body)
-
- return [ keys[i] for i in bbox.collidelistall(values) ]
-
-
- def testCollideGeometryAll(self):
- # return list of all collisions between bodies and level geometry
- pass
-
-
- def getBBox(self, body):
- """ Return a bbox that represents this body in world """
- return self.bodies[body]
-
-
- def setBBox(self, body, bbox):
- """ Attempt to set a bodies bbox. Returns True if able. """
-
- if not isinstance(bbox, BBox):
- bbox = BBox(bbox)
-
- if self.testCollide(bbox):
- return False
- else:
- self.bodies[body] = bbox
- self._oldPositions[body] = bbox
- return True
-
-
- def testCollide(self, bbox):
- return False
-
-
- def getSize(self, body):
- """ Return 3d size of the body """
-
- return self.bodies[body].size
-
-
- def getPositions(self):
- return [ (o, b.origin) for (o, b) in self.bodies.items() ]
-
-
- def getOrientation(self, body):
- """ Return the angle body is facing in radians """
-
- return self.orientations[body]
-
-
- def setOrientation(self, body, angle):
- """ Set the angle the body is facing. Expects radians. """
-
- if isinstance(angle, str):
- try:
- angle = cardinalDirs[angle]
- except:
- raise
- self.orientations[body] = angle
-
-
- def add(self, body):
- AbstractArea.add(self, body)
- self.bodies[body] = self.defaultPosition()
- self._oldPositions[body] = self.bodies[body]
- self.orientations[body] = 0.0
-
-
- def remove(self, body):
- AbstractArea.remove(self, body)
- del self.bodies[body]
- del self._oldPositions[body]
- del self.orientations[body]
-
- to_remove = []
- for j in self.joins:
- if body in j:
- to_remove.append(j)
-
- for j in to_remove:
- self.joins.remove(j)
-
-
- def setPosition(self, body, (x, y, z)):
- """ Attempt to move body in 3d space. Returns true if able. """
-
- size = self.bodies[body].size
- bbox = BBox((x, y, z), size)
- collide = self.testCollideGeometry(bbox)
-
- # body collides with something else, cannot set new position
- if collide:
- return False
-
- # body is within areas extent and doesn't collide, so set it
- elif self.extent.contains(self.toRect(bbox)):
- self._oldPositions[body] = self.bodies[body]
- self.bodies[body] = bbox
- return True
-
- # body is outside bounds of area, can't move it
- else:
- return False
-
-
- def toRect(self, bbox):
- """
- Make a rect that represents the body's 'bottom plane'.
- """
-
- return Rect((bbox.x, bbox.y, bbox.depth, bbox.width))
-
-
- def getOldPosition(self, body):
- return self._oldPositions[body]
-
-
- def _sendBodyMove(self, body, caller, force=None):
- position = self.getPosition(body)
- bodyAbsMove.send(sender=self, body=body, position=position, caller=caller, force=force)
-
-
- def movePosition(self, body, (x, y, z), push=False, caller=None, suppress_warp=False, clip=True):
- """Attempt to move a body in 3d space.
-
- Args:
- body: (body): body to move
- (x, y, z): difference of position to move
-
- Kwargs:
- push: if True, then any colliding objects will be moved as well
- caller: part of callback for object that created request to move
-
- Returns:
- None
-
- Raises:
- CollisionError
-
-
- You should catch the exception if body cannot move.
- This function will emit a bodyRelMove event if successful.
- """
-
- movable = 0
- bbox = self.bodies[body].move(x, y, z)
-
- # collides with level geometry, cannot move
- if self.testCollideGeometry(bbox) and clip:
- return False
-
- # test for collisions with other bodies
- collide = self.testCollideObjects(bbox)
- try:
- collide.remove(body)
- except:
- pass
-
- # find things we are joined to
- joins = [ i[1] for i in self.joins if i[0] == body ]
-
- # if joined, then add it to collisions and treat it is if being pushed
- if joins:
- collide.extend(joins)
- push = True
-
- # handle collisions with bodies
- if collide:
-
- print "[collide] body", collide
-
- # are we pushing something?
- if push and all([ other.pushable for other in collide ]):
-
- # we are able to move
- originalPosition = self._oldPositions[body]
- self._oldPositions[body] = self.bodies[body]
- self.bodies[body] = bbox
-
- # recursively push other bodies
- for other in collide:
- if not self.movePosition(other, (x, y, z), True):
- # we collided, so just go back to old position
- self.bodies[body] = self._oldPositions[body]
- self._oldPositions[body] = originalPosition
- return False
-
- else:
- if clip: return False
-
- self._oldPositions[body] = self.bodies[body]
- self.bodies[body] = bbox
-
- self._sendBodyMove(body, caller=caller)
-
- try:
- # emit sounds from bodies walking on them
- tilePos = self.worldToTile(bbox.bottomcenter)
- prop = self.tmxdata.getTileProperties(tilePos)
- if not prop == None:
- name = prop.get('walkSound', None)
- if not name == None:
- self.emitSound(name, bbox.bottomcenter)
- except:
- print "TILE ERROR"
-
-
- try:
- # test for collisions with exits
- exits = self.exitQT.hit(self.toRect(bbox))
- except AttributeError:
- exits = []
-
-
- if exits and not suppress_warp:
- # warp the player
- exit = exits.pop()
-
- # get the position and guid of the exit tile of the other map
- fromExit, guid = self.exits[exit.value]
- if not guid == None:
- # used to correctly align sprites
- fromTileBBox = BBox(fromExit, (16,16,1))
- tx, ty, tz = fromTileBBox.origin
-
- # get the GUID of the map we are warping to
- dest = self.getRoot().getChildByGUID(guid)
- toExit, otherExit = dest.exits[exit.value]
-
- bx, by, bz = bbox.origin
- ox, oy, oz = self._oldPositions[body].origin
- bz = 0
-
- # determine wich direction we are traveling through the exit
- angle = math.atan2(oy-by, ox-bx)
-
- # get the position of the tile in out new area
- newx, newy, newz = toExit
-
- # create a a bbox to position the object in the new area
- dx = 16 / 2 - bbox.depth / 2
- dy = 16 / 2 - bbox.width / 2
- dz = 0
- bbox = BBox((newx+dx, newy+dy, newz+dz), bbox.size)
- face = self.getOrientation(body)
- dest.add(body)
- dest.setBBox(body, bbox)
- dest.setOrientation(body, face)
-
-
- # when changing the destination, we do a bunch of moves first
- # to push objects out of the way from the door...if possible
- dx = round(math.cos(angle))
- dy = round(math.sin(angle))
- dz = 0
- print dx, dy, angle
- dest.movePosition(body, (dx*20, dy*20, dz*20), False, suppress_warp=True, clip=False)
- for x in range(40):
- dest.movePosition(body, (-dx, -dy, -dz), True, suppress_warp=True, clip=False)
-
- # send a signal that this body is warping
- bodyWarp.send(sender=self, body=body, destination=dest, caller=caller)
-
- return True
-
-
- def warpBody(self):
- """
- move a body to another area using an exit tile.
- objects on or around the tile will be push out of the way
- if objects cannot be pushed, then the warp will fail
- """
-
-
- def getPosition(self, body):
- return self.bodies[body].origin
-
-
- def stick(self, body):
- self.setPosition(body, self._oldPositions[body])
-
-
- def unstick(self, body):
- pass
View
180 lib2d/common/avatar.py
@@ -1,3 +1,24 @@
+"""
+Copyright 2010, 2011 Leif Theden
+
+
+This file is part of lib2d.
+
+lib2d is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+lib2d is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with lib2d. If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
from objects import GameObject
import res
@@ -7,10 +28,14 @@
from itertools import product
from random import randint
+from pygame.transform import flip
+
+import time
pi2 = pi * 2
+
class Avatar(GameObject):
"""
Avatar is a sprite-like class that supports multiple animations, animation
@@ -26,14 +51,16 @@ def __init__(self):
self.curFrame = None # current frame number
self.curAnimation = None
self.animations = {}
+ self.axis = (0,0)
self.loop_frame = 0
self.looped = 0
self.loop = -1
- self.timer = 0
+ self.timer = 0.0
+ self.flip = 0
self._prevAngle = None
self._prevFrame = None
self._is_paused = False
-
+ self._rect = None
def _updateCache(self):
angle = self.getOrientation()
@@ -43,28 +70,34 @@ def _updateCache(self):
if not angle == self._prevAngle:
self.curImage = self.curAnimation.getImage(self.curFrame, angle)
+ self._rect = self.curImage.get_rect().move(self.axis)
+ if self.flip: self.curImage = flip(self.curImage, 1, 0)
elif not self.curFrame == self._prevFrame:
self.curImage = self.curAnimation.getImage(self.curFrame, angle)
+ self._rect = self.curImage.get_rect().move(self.axis)
+ if self.flip: self.curImage = flip(self.curImage, 1, 0)
elif self.curImage == None:
self.curImage = self.curAnimation.getImage(self.curFrame, angle)
-
+ self._rect = self.curImage.get_rect().move(self.axis)
+ if self.flip: self.curImage = flip(self.curImage, 1, 0)
+
def get_rect(self):
self._updateCache()
- return self.curImage.get_rect()
+ return self._rect
def get_size(self):
self._updateCache()
- return self.curImage.get_size()
+ return self._rect.get_size()
@property
def rect(self):
self._updateCache()
- return self.curImage.get_rect()
+ return self._rect
@property
@@ -72,6 +105,7 @@ def image(self):
self._updateCache()
return self.curImage
+
@property
def visible(self):
return self._is_visible
@@ -97,27 +131,36 @@ def paused(self, value):
self._is_paused = bool(value)
+ def unload(self):
+ self.curImage = None
+
+
def update(self, time):
"""
call this as often as possible with a time. the units in the
animation files must match the units provided here. ie: milliseconds.
"""
- if self._is_paused: return
if not self.curAnimation:
- self.play(self.default)
+ self.reset()
+ if self._is_paused: return
self.timer += time
ttl = self.curAnimation.getTTL(self.curFrame)
- if ttl < 0:
- self.paused = 1
- return
-
while (self.timer >= ttl):
+ if ttl < 0:
+ self.paused = 1
+ return
+
self.timer -= ttl
self.advanceFrame()
- ttl = self.curAnimation.getTTL(self.curFrame)
+
+ # the animation may have been lost after the advance
+ if self.curAnimation:
+ ttl = self.curAnimation.getTTL(self.curFrame)
+ else:
+ break
def advanceFrame(self):
@@ -139,7 +182,7 @@ def advanceFrame(self):
# loop, but count the loops
elif self.loop > 0:
- if self.looped > self.loop:
+ if self.looped >= self.loop:
self.stop()
else:
self.setFrame(self.loop_frame)
@@ -157,10 +200,14 @@ def stop(self):
"""
pauses the current animation and runs the callback if needed.
"""
- if self.callback:
- self.callback[0](*self.callback[1])
self.reset()
+ self.doCallback()
+
+
+ def doCallback(self):
+ if self.callback:
+ self.callback[0](*self.callback[1])
def reset(self):
@@ -168,6 +215,8 @@ def reset(self):
sets defaults of the avatar.
"""
+ if len(self.animations) == 0:
+ return
self.play(self.default)
@@ -176,13 +225,14 @@ def setFrame(self, frame):
set the frame of the animation
frame should be 0-indexed number of frame to show
"""
-
self._prevFrame = frame
self.curFrame = frame
+
+
def isPlaying(self, name):
if isinstance(name, Animation):
- if name == self.curanimation: return True
+ if name == self.curAnimation: return True
else:
if self.getAnimation(name) == self.curAnimation: return True
return False
@@ -191,9 +241,6 @@ def isPlaying(self, name):
def play(self, name=None, start_frame=0, loop=-1, loop_frame=0, \
callback=None, arg=[]):
- # play animation. if currently playing animation is same as 'name'
- # then simply return.
-
if isinstance(name, Animation):
if name == self.curAnimation: return
self.curAnimation = name
@@ -215,14 +262,35 @@ def play(self, name=None, start_frame=0, loop=-1, loop_frame=0, \
def add(self, other):
- if isinstance(other, Animation):
+ if isinstance(other, Animation) or isinstance(other, StaticAnimation):
self.animations[other.name] = other
if self.default == None:
self.setDefault(other)
-
GameObject.add(self, other)
+ def remove(self, other):
+ playing = False
+ if isinstance(other, Animation) or isinstance(other, StaticAnimation):
+ if self.isPlaying(other):
+ playing = True
+ del self.animations[other.name]
+
+ GameObject.remove(self, other)
+
+ # handle when there are no animations left
+ if len(self.animations) == 0:
+ self.callback = None
+ self.curAnimation = None
+ self.curImage = None
+ self.curFrame = None
+ self.default = None
+ self._is_paused = True
+ elif playing:
+ self.setDefault(self.animations.keys()[0])
+ self.reset()
+
+
def getAnimation(self, name):
"""
return the animation for this name.
@@ -230,7 +298,7 @@ def getAnimation(self, name):
try:
return self.animations[name]
- except KeyError:
+ except:
raise
@@ -239,9 +307,8 @@ def setDefault(self, name):
set the defualt animation to play if the avatar has nothing else to do
"""
- if isinstance(name, Animation):
+ if isinstance(name, Animation) or isinstance(name, StaticAnimation):
self.default = name
- return
else:
try:
self.default = self.getAnimation(name)
@@ -271,7 +338,9 @@ class Animation(GameObject):
TODO: implement some sort of timing, rather than relying on frames
"""
- def __init__(self, filename, name, frames, directions=1, timing=None):
+ def __init__(self, filename, name, frames, directions=1, timing=None,
+ order=None):
+
GameObject.__init__(self)
self.filename = filename
@@ -292,24 +361,35 @@ def __init__(self, filename, name, frames, directions=1, timing=None):
self.images = []
- def load(self):
+ def returnNew(self):
+ return self
+
+
+ def load(self, force=False):
"""
load the images for use with pygame
"""
- image = res.loadImage(self.filename, 0, 1)
+ if not self.images == [] and not force: