def init_batch(self, batch, parent): tex = self.textures['piece'] vertices = [] tex_coords = [] da = self.wobble_angle / self.height pos = self.pos step = Vec2(0, self.PIECE_HEIGHT).rotate(self.base_angle) radius = Vec2(self.RADIUS, 0).rotate(self.base_angle) for v in self.tree_vertices(): vertices += [v.x, v.y] for i in range(self.height + 1): tex_coords += [ tex.tex_coords[0], (i + 1) * self.TEX_PERIOD, tex.tex_coords[3], (i + 1) * self.TEX_PERIOD ] vertices = vertices[:2] + vertices + vertices[-2:] tex_coords = tex_coords[:2] + tex_coords + tex_coords[-2:] parent_group = self.get_parent_group(parent) group = pyglet.sprite.SpriteGroup(self.textures['piece'], GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, parent=parent_group) self.vertex_list = batch.add((self.height + 2) * 2, GL_QUAD_STRIP, group, ('v2f/stream', vertices), ('t2f/static', tex_coords)) self.foliage = [] for i in range(self.height): prob = self.height - i if random.random() * prob < 1: l = random.choice(['leaf1-l', 'leaf2-l']) right = pyglet.sprite.Sprite(self.graphics[l], x=self.pos.x, y=self.PIECE_HEIGHT * i + self.pos.y, batch=batch, group=parent_group) else: right = None if random.random() * prob < 1: l = random.choice(['leaf1-r', 'leaf2-r']) left = pyglet.sprite.Sprite(self.graphics[l], x=self.pos.x, y=self.PIECE_HEIGHT * i + self.pos.y, batch=batch, group=parent_group) else: left = None self.foliage.append((left, None, right)) top = pyglet.sprite.Sprite(self.graphics['top'], batch=batch, group=parent_group) self.foliage.append((None, top, None))
def throw_projectile(self, target, kls): if not self.can_attack(): return start = self.pos + Vec2(0, 80) v = target - start if not v: return v += Vec2(0, (0.02 * v.x)**2) # aim above v = v.normalized() * 30 self.level.spawn(kls(v, self), x=start.x, y=start.y)
def update(self): from bamboo.actors.particles import Smoke if random.randint(0, 10) == 0: v = Vec2(random.random() * 2 - 1, random.random() * 2) s = Smoke(v) s.scale = 0.1 self.level.spawn(s, x=self.pos.x + random.random() * 20 - 10, y=self.pos.y + 40)
def move_to(self, pos): hw = self.width * self.scale / 2.0 hh = self.height * self.scale / 2.0 x = max(hw, min(self.level.width - hw, pos.x)) y = max(hh, pos.y) # NB: floating point camera centers can cause fringes on sprites # integer camera centers seem jumpy self.center = Vec2(x, y)
def __init__(self, x=60, height=9, angle=0): Climbable.__init__(self) self.height = height self.pos = Vec2(x, 0) self.base_angle = angle self.wobble_angle = 0 self.wind_phase = 0 self.batch = None
def create_puff_of_smoke(rect, level): import random c = rect.center() for i in range(10): x = random.gauss(c.x, rect.w/3) y = random.gauss(c.y, rect.h/3) v = (Vec2(x, y) - c) * 0.05 s = Smoke(v) level.spawn(s, x=x, y=y)
def height_for_y(self, y): """Estimate the height in this tree for a coordinate of y. This only works for small wobble angles.""" da = self.wobble_angle / self.height rotation = Matrix2.rotation(da) pos = self.pos step = Vec2(0, self.PIECE_HEIGHT).rotate(self.base_angle) radius = Vec2(self.RADIUS, 0).rotate(self.base_angle) for i in range(self.height + 1): if step.y <= 0: raise ValueError("Tree does not reach a height of %f." % y) next = pos + step if next.y >= y: return i + float(y - pos.y) / step.y pos += step step = rotation * step raise ValueError("Tree does not reach a height of %f." % y)
def load_object(self, use): name = use.get('{%s}href' % XLINK_NS).replace('#', '') transform = use.get('transform') mo = re.match(r'translate\(([\d.-]+),([\d.-]+)\)', transform) if not mo: raise LevelParseError("Cannot parse transform attribute '%s'" % transform) pos = Vec2(float(mo.group(1)), self.height - float(mo.group(2))) return ActorSpawn(name, pos)
def distance_from(self, p): """Estimate the distance from x, y to this tree. This only works for small wobbly angles.""" da = self.wobble_angle / self.height rotation = Matrix2.rotation(da) pos = self.pos step = Vec2(0, self.PIECE_HEIGHT).rotate(self.base_angle) radius = Vec2(self.RADIUS, 0).rotate(self.base_angle) if pos.y > p.y: return (p - pos).mag() for i in range(self.height + 1): if pos.y > p.y: return abs(pos.x - p.x) pos += step step = rotation * step return (p - pos).mag()
def hit(self, point, force, damage=10): for s in range(4): off = Vec2(random.random() * 20 - 10, random.random() * 10 - 5) self.level.spawn(BloodSpray(v=force + off), x=point.x, y=point.y) if not self.is_climbing(): self.apply_impulse(force / self.MASS) self.health -= damage if self.health <= 0: self.create_corpse() self.on_death() self.die()
def update(self): f = self.get_net_force() accel = f / self.MASS g = self.ground_level() if self.pos.y < g: self.pos -= self.ground_normal().component_of( Vec2(0, self.pos.y - g)) self.v = (self.v + accel) * (1 - self.LINEAR_DAMPING) self.pos += self.v
def compute_wobble(self): """Generator for the vertex list. Iterate to give a sequence of Vec2 objects""" da = self.wobble_angle / self.height pos = self.pos rotation = Matrix2.rotation(da) step = Vec2(0, self.PIECE_HEIGHT) radius = Vec2(self.RADIUS, 0) angle = 0 steprotation = -da * 180 / math.pi actor_segments = {} for a in self.actors: h = int(a.climbing_height) actor_segments.setdefault(h, []).append(a) vertices = [] for i in range(self.height + 1): vertices.append(pos - radius) vertices.append(pos + radius) for side, f in enumerate(self.foliage[i]): if f is None: continue p = pos + (side - 1) * radius f.x = p.x f.y = p.y f.rotation = angle for a in actor_segments.get(i, []): h = a.climbing_height - i apos = pos + h * step a.v = apos - a.pos a.pos = apos a.rotation = angle pos += step step = rotation * step angle += steprotation radius = (rotation * radius) * self.THINNING return vertices
def strategy_treesniping(self): if not self.character.is_climbing(): self.pick_strategy() return alt = self.character.pos.y - self.character.ground_level() if alt < 400: self.character.climb_up() elif self.character.can_attack(): self.character.throw_projectile(self.target.pos + Vec2(0, 80), Shuriken) self.set_strategy('treefight')
def __init__(self, v=Vec2(0, 0), dir=None): super(Smoke, self).__init__() if dir is None: self.dir = random.choice(['l', 'r']) else: self.dir = dir self.v = v self.scale = 0.3 + random.random() * 0.4 self.time = 0 self.lifetime = 30 + int(random.random() * 30) self.spin = 30 if self.dir == 'l' else -30
def collide(self): for i, a in enumerate(self.characters): for b in self.characters[i + 1:]: intersection = a.bounds().intersection(b.bounds()) if intersection: d = min(intersection.w, intersection.h) # amount of intersection ab = (b.pos - a.pos) if not ab: ab = Vec2(0, 1) v = ab.normalized() # direction AB a.pos -= v b.pos += v
def run_left(self): if self.is_climbing(): self.apply_force(Vec2(-10, 0)) self.looking = 'l' self.climb_rate = 0 else: self.dir = 'l' if self.is_on_ground(): self.apply_force(self.run_speed() * self.ground_normal().perpendicular()) else: self.apply_force(-self.AIR_ACCEL) self.crouching = False
def jump(self): if self.is_on_ground(): self.crouching = False self.apply_impulse(self.JUMP_IMPULSE) self.on_jump() elif self.is_climbing(): self.climbing.remove_actor(self) if self.looking: self.dir = self.looking if self.looking == 'r': self.apply_impulse(self.TREE_JUMP_IMPULSE) elif self.looking == 'l': ix, iy = self.TREE_JUMP_IMPULSE self.apply_impulse(Vec2(-ix, iy)) self.on_tree_jump()
def attack(self): if not self.can_attack(): return self.attack_timer = self.ATTACK_RATE + 6 off = 0 if self.is_climbing(): if self.dir == 'r': off = -30 else: off = +30 if self.dir != self.looking: off *= 1.2 c = self.pos + Vec2(off, 60) elif self.crouching: c = self.pos + Vec2(off, 72) else: c = self.pos + Vec2(off, 100) dir = self.looking or self.dir if dir == 'r': attack_region = Rect.from_corners(c - Vec2(0, 15), c + Vec2(180, 25)) force = Vec2(50, 0) + self.v else: attack_region = Rect.from_corners(c - Vec2(0, 15), c + Vec2(-180, 25)) force = Vec2(-50, 0) + self.v victims = [ a for a in self.level.characters_colliding(attack_region) if a != self ] if not victims: return damage = 10.0 / len(victims) force = force / len(victims) for a in victims: point = attack_region.intersection(a.bounds()).center() a.hit(point, force, damage)
def update(self): if self.death_timer < 200: super(Corpse, self).update() self.death_timer += 1 if self.death_timer < 15: rot = 1 if self.dir == 'l' else -1 self.rotation = min( 50, self.rotation + 2 + 0.5 * rot * self.death_timer) elif self.death_timer == 15: self.rotation = 0 self.play_animation('dead', directional=True) elif self.death_timer == 350: self.level.kill(self) elif self.death_timer > 200: self.pos += Vec2(0, -0.5)
def tree_vertices(self): da = self.wobble_angle / self.height pos = self.pos rotation = Matrix2.rotation(da) step = Vec2(0, self.PIECE_HEIGHT) radius = Vec2(self.RADIUS, 0) angle = 0 actor_segments = {} for a in self.actors: h = int(a.climbing_height) actor_segments.setdefault(h, []).append(a) vertices = [] for i in range(self.height + 1): vertices.append(pos - radius) vertices.append(pos + radius) pos += step step = rotation * step angle = angle + da radius = (rotation * radius) * self.THINNING return vertices
class LeadingCamera(RegionTrackingCamera): """A region tracking camera, which doesn't let the tracked object go out of frame, but tries to scroll the camera away in the direction of its movement""" last_track_point = None lead = Vec2(0, 0) def track(self, p): if self.last_track_point is not None: v = p - self.last_track_point if v.mag() > 100: self.lead *= 0.9 else: self.lead = (self.lead + 0.5 * v) * 0.95 self.center = p + self.lead self.last_track_point = p super(LeadingCamera, self).track(p)
def from_region(self, p): """The shortest vector to p from the focus region""" r = self.get_region() if p.x < r.l: x = p.x - r.l elif p.x > r.r: x = p.x - r.r else: x = 0 if p.y < r.b: y = p.y - r.b elif p.y > r.t: y = p.y - r.t else: y = 0 return Vec2(x, y)
def parse(self): self.start_contour() self.closed = False # until proven guilty state = None pos = Vec2(0, 0) x = None # hold x coordinate while we wait for the y for tok in self.tokens(): # read until we have a coordinate pair try: v = float(tok) except ValueError: if tok in 'Zz': self.closed = True self.end_contour() # M/m actually means move with pen up, which # would end the contour too, except that we need # closed contours state = tok continue if x is None: if state == 'v': pos += Vec2(0, v) self.add_vertex(pos) elif state == 'V': pos = Vec2(pos.x, v) self.add_vertex(pos) elif state == 'h': pos += Vec2(v, 0) self.add_vertex(pos) elif state == 'H': pos = Vec2(v, pos.y) self.add_vertex(pos) else: x = v continue # we have a coordinate pair, work out what to do with it in the current state v = Vec2(x, v) x = None if state in 'lm': pos += v self.add_vertex(pos) elif state in 'LM': pos = v self.add_vertex(pos) else: raise LevelParseError( "Coordinate pair in state %s is unsupported." % state) self.end_contour() return self.polygon
def update(self): """Run physics, update everything in the world""" from bamboo.actors.characters import Character self.ground.update() for c in self.controllers: c.update() # self.collide() for a in self.actors: if isinstance(a, Character): if a.pos.x < 0: a.pos = Vec2(0, a.pos.y) elif a.pos > self.width: # TODO: fire level completion event pass a.update()
class Actor(ResourceTracker): initial_animation = None current = None next = None # sprite to change to at next frame sprite = None controller = None collision_mask = 0x00 level = None rotation = 0 scale = 1.0 opacity = 255 dir = 'r' def _get_pos(self): return self._pos def _set_pos(self, pos): self._pos = pos if self.level: # TODO: tell level we've moved, so level can optimise self._ground_level = self.level.ground.height_at(pos.x) self._ground_normal = self.level.ground.normal_at(pos.x) _pos = Vec2(0, 0) pos = property(_get_pos, _set_pos) def add_death_listener(self, callback): try: self.death_listeners.add(callback) except AttributeError: self.death_listeners = set([callback]) def remove_death_listener(self, callback): try: self.death_listeners.remove(callback) except AttributeError, KeyError: pass
def spawn(self, actor, x, y=None, controller=None): if not actor._resources_loaded: actor.load_resources() if y is None: y = self.ground.height_at(x) actor.pos = Vec2(x, y) actor.level = self if controller is not None: actor.controller = controller self.controllers.append(controller) from bamboo.actors.samurai import Character from bamboo.actors.trees import Climbable if isinstance(actor, Character): self.characters.append(actor) elif isinstance(actor, Climbable): if actor.is_climbable(): self.climbables.append(actor) self.actors.append(actor) actor.on_spawn()
class Smoke(Actor): layer = 6 GRAVITY = Vec2(0, 0.5) def __init__(self, v=Vec2(0, 0), dir=None): super(Smoke, self).__init__() if dir is None: self.dir = random.choice(['l', 'r']) else: self.dir = dir self.v = v self.scale = 0.3 + random.random() * 0.4 self.time = 0 self.lifetime = 30 + int(random.random() * 30) self.spin = 30 if self.dir == 'l' else -30 def on_spawn(self): self.play_animation('smoke', directional=True) @classmethod def on_class_load(cls): cls.load_directional_sprite('smoke', 'smoke.png', anchor_x='center', anchor_y='center') def update(self): self.time += 1 if self.time >= self.lifetime: self.die() self.v = (self.v + Smoke.GRAVITY) * 0.9 self.pos += self.v self.rotation += self.spin self.scale += 0.02 self.opacity = 255 - (self.time / float(self.lifetime)) * 255
def __init__(self, pos=Vec2(0, 0)): self.pos = Vec2(0, 0) self.v = Vec2(0, 0) self.f = self.get_weight() self.runforce = 0
def delete(self): """Remove from batch""" if self.sprite: self.sprite.delete() self.sprite = None def update(self): """Subclasses can implement this method if necessary to implement game logic""" def on_spawn(self): """Subclasses can implement this method to initialise the actor""" if self.initial_animation: self.play_animation(self.initial_animation) GRAVITY = Vec2(0, -2.3) class PhysicalObject(Actor): """A PhysicalObject is an actor bound by simple platform physics""" MASS = 15 FRICTION = 0.6 LINEAR_DAMPING = 0.0 def __init__(self, pos=Vec2(0, 0)): self.pos = Vec2(0, 0) self.v = Vec2(0, 0) self.f = self.get_weight() self.runforce = 0 def apply_force(self, vec):
def spawn_p2(self): self.pc2.dir = 'l' self.pc2.v = Vec2(0, 0) self.level.spawn(self.pc2, x=self.level.width - 60, controller=self.player2)