X-Git-Url: https://git.yukkurigames.com/?p=python-bulletml.git;a=blobdiff_plain;f=bulletml%2Fimpl.py;h=5d7cb54281eb340f8ee3c7fd5379baeb4b2ebc39;hp=f3d7e12ca4d9120372f208b6db9c74e74a02e7b7;hb=76079fba9b913a620bb4fee71504d427e0f9df10;hpb=3ee9895b163809deadc02fc1a34bce265cc89d3a diff --git a/bulletml/impl.py b/bulletml/impl.py index f3d7e12..5d7cb54 100644 --- a/bulletml/impl.py +++ b/bulletml/impl.py @@ -2,19 +2,26 @@ from __future__ import division -import math +from math import atan2, sin, cos, pi as PI from bulletml import parser -# TODO(jfw): This is very non-Pythonic, it's pretty much just the -# BulletML reference ActionImpl translated to Python. - -PI_2 = math.pi * 2 +PI_2 = PI * 2 __all__ = ["Action", "Bullet"] class Action(object): - """Running action implementation.""" + """Running action implementation. + + To implement new actions, + + - Add a new element/class pair to parser.ActionDef.CONSTRUCTORS. + It should support FromXML, __getstate__, and __setstate__. + - Subclass impl.Action and override the 'handle' method to handle + your custom action type. + - Pass the impl.Bullet constructor your Action subclass when + creating your root Bullet. + """ def __init__(self, owner, parent, actions, params, rank, repeat=1): self.actions = actions @@ -61,9 +68,10 @@ class Action(object): self.previous_fire_direction = other.previous_fire_direction self.previous_fire_speed = other.previous_fire_speed - def step(self, owner, rank, created, sin=math.sin, cos=math.cos): + def step(self, owner, created): """Advance by one frame.""" s_params = self.params + rank = owner.rank if self.speed_frames > 0: self.speed_frames -= 1 @@ -112,20 +120,22 @@ class Action(object): if isinstance(action, parser.Repeat): repeat, (actions, params) = action(s_params, rank) - child = Action(owner, self, actions, params, rank, repeat) + child = self.__class__( + owner, self, actions, params, rank, repeat) owner.replace(self, child) - child.step(owner, rank, created, sin, cos) + child.step(owner, created) break elif isinstance(action, (parser.ActionDef, parser.ActionRef)): actions, params = action(s_params, rank) - child = Action(owner, self, actions, params, rank) + child = self.__class__(owner, self, actions, params, rank) owner.replace(self, child) - child.step(owner, rank, created, sin, cos) + child.step(owner, created) break elif isinstance(action, (parser.FireDef, parser.FireRef)): - direction, speed, actions, offset = action(s_params, rank) + direction, speed, offset, tags, appearance, actions = action( + s_params, rank) if direction: direction, type = direction if type == "aim" or type is None: @@ -165,14 +175,23 @@ class Action(object): x += off_x y += off_y - bullet = Bullet( - x, y, direction, speed, owner.target, actions, self, rank) + if appearance is None: + appearance = owner.appearance + bullet = owner.__class__( + x=x, y=y, direction=direction, speed=speed, + target=owner.target, actions=actions, rank=rank, + appearance=appearance, tags=tags, Action=self.__class__) created.append(bullet) elif isinstance(action, parser.ChangeSpeed): frames, (speed, type) = action(s_params, rank) self.speed_frames = frames - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.speed = speed + elif type == "relative": + owner.speed += speed + elif type == "sequence": self.speed = speed elif type == "relative": self.speed = speed / frames @@ -188,21 +207,28 @@ class Action(object): else: if type == "absolute": direction -= owner.direction - elif type == "relative": - direction = direction - else: + elif type != "relative": # aim or default self.aiming = True direction += owner.aim - owner.direction - self.direction = ( - (direction + math.pi) % PI_2 - math.pi) / frames + # Normalize to [-pi, pi). + direction = (direction + PI) % PI_2 - PI + if frames <= 0: + owner.direction += direction + else: + self.direction = direction / frames elif isinstance(action, parser.Accel): frames, horizontal, vertical = action(s_params, rank) self.accel_frames = frames if horizontal: mx, type = horizontal - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.mx = mx + elif type == "relative": + owner.mx += mx + elif type == "sequence": self.mx = mx elif type == "absolute": self.mx = (mx - owner.mx) / frames @@ -210,7 +236,12 @@ class Action(object): self.mx = mx / frames if vertical: my, type = vertical - if type == "sequence": + if frames <= 0: + if type == "absolute": + owner.my = my + elif type == "relative": + owner.my += my + elif type == "sequence": self.my = my elif type == "absolute": self.my = (my - owner.my) / frames @@ -234,6 +265,16 @@ class Action(object): owner.vanish() break + elif isinstance(action, parser.Appearance): + owner.appearance = action.appearance + + else: + self.handle(action, owner, created) + + def handle(self, action, owner, created): + """Override in subclasses for new action types.""" + raise NotImplementedError(action.__class__.__name__) + class Bullet(object): """Simple bullet implementation. @@ -251,13 +292,14 @@ class Bullet(object): Contructor Arguments: x, y, direction, speed, target, rank - same as the attributes actions - internal action list - parent - parent of actions, None for manually-created bullets + Action - custom Action constructor """ def __init__(self, x=0, y=0, direction=0, speed=0, target=None, - actions=(), parent=None, rank=0.5): + actions=(), rank=0.5, tags=(), appearance=None, + Action=Action): self.x = self.px = x self.y = self.py = y self.mx = 0 @@ -267,25 +309,40 @@ class Bullet(object): self.vanished = False self.target = target self.rank = rank - self.tags = set() + self.tags = set(tags) + self.appearance = appearance # New bullets reset the parent hierarchy. self._actions = [Action(self, None, action, params, rank) for action, params in actions] + @classmethod + def FromDocument(cls, doc, x=0, y=0, direction=0, speed=0, target=None, + params=(), rank=0.5, Action=Action): + """Construct a new Bullet from a loaded BulletML document.""" + actions = [a(params, rank) for a in doc.actions] + return cls(x=x, y=y, direction=direction, speed=speed, + target=target, actions=actions, rank=rank, Action=Action) + def __repr__(self): return ("%s(%r, %r, accel=%r, direction=%r, speed=%r, " - "actions=%r, target=%r, vanished=%r)") % ( + "actions=%r, target=%r, appearance=vanished=%r)") % ( type(self).__name__, self.x, self.y, (self.mx, self.my), self.direction, self.speed, self._actions, self.target, - self.vanished) + self.appearance, self.vanished) @property def aim(self): - """Angle to the target, in radians.""" - if self.target is None: - return self.direction + """Angle to the target, in radians. + + If the target does not exist or cannot be found, return 0. + """ + try: + target_x = self.target.x + target_y = self.target.y + except AttributeError: + return 0 else: - return math.atan2(self.target.x - self.x, self.y - self.target.y) + return atan2(target_x - self.x, target_y - self.y) @property def finished(self): @@ -324,7 +381,7 @@ class Bullet(object): else: self._actions[idx] = new - def step(self, sin=math.sin, cos=math.cos): + def step(self): """Advance by one frame. This updates the position and velocity, and may also set the @@ -335,11 +392,11 @@ class Bullet(object): created = [] for action in self._actions: - action.step(self, self.rank, created, sin, cos) + action.step(self, created) self.px = self.x self.py = self.y self.x += self.mx + sin(self.direction) * self.speed - self.y += self.my - cos(self.direction) * self.speed + self.y += -self.my + cos(self.direction) * self.speed return created