source: nagslang/game_object.py@ 591:8dcf5176ffd8

Last change on this file since 591:8dcf5176ffd8 was 591:8dcf5176ffd8, checked in by Jeremy Thurgood <firxen@…>, 8 years ago

Door sound.

File size: 25.5 KB
Line 
1import pymunk
2import pymunk.pygame_util
3
4import math
5
6from nagslang import environment
7from nagslang import puzzle
8from nagslang import render
9from nagslang.mutators import FLIP_H, ImageOverlay, rotator
10from nagslang.constants import (
11 COLLISION_TYPE_DOOR, COLLISION_TYPE_FURNITURE, COLLISION_TYPE_PROJECTILE,
12 COLLISION_TYPE_SWITCH, COLLISION_TYPE_SHEEP, COLLISION_TYPE_SHEEP_PEN,
13 COLLISION_TYPE_WEREWOLF_ATTACK, SWITCH_PUSHERS, ZORDER_FLOOR, ZORDER_LOW,
14 ZORDER_HIGH)
15from nagslang.resources import resources
16from nagslang.events import DoorEvent
17from nagslang.sound import sound
18
19
20class Result(object):
21 '''
22 Return from an update() function, to add new objects to the world, and/or
23 remove old objects.
24 '''
25 def __init__(self, add=(), remove=()):
26 self.add = add
27 self.remove = remove
28
29 def merge(self, result):
30 if result is not None:
31 self.add += result.add
32 self.remove += result.remove
33 return self
34
35
36def get_editable_game_objects():
37 classes = []
38 for cls_name, cls in globals().iteritems():
39 if isinstance(cls, type) and hasattr(cls, 'requires'):
40 classes.append((cls_name, cls))
41 return classes
42
43
44class Physicser(object):
45 def __init__(self, space):
46 self._space = space
47
48 def get_space(self):
49 return self._space
50
51 def set_space(self, new_space):
52 self._space = new_space
53
54 def set_game_object(self, game_object):
55 self.game_object = game_object
56
57 def get_shape(self):
58 raise NotImplementedError()
59
60 def add_to_space(self):
61 shape = self.get_shape()
62 self.get_space().add(shape)
63 if not shape.body.is_static:
64 self.get_space().add(shape.body)
65
66 def remove_from_space(self):
67 shape = self.get_shape()
68 self.get_space().remove(shape)
69 if not shape.body.is_static:
70 self.get_space().remove(shape.body)
71
72 def get_render_position(self, surface):
73 pos = self.get_shape().body.position
74 return pymunk.pygame_util.to_pygame(pos, surface)
75
76 def get_angle(self):
77 return self.get_shape().body.angle
78
79 def get_velocity(self):
80 return self.get_shape().body.velocity
81
82 def _get_position(self):
83 return self.get_shape().body.position
84
85 def _set_position(self, position):
86 self.get_shape().body.position = position
87
88 position = property(_get_position, _set_position)
89
90 def apply_impulse(self, j, r=(0, 0)):
91 return self.get_shape().body.apply_impulse(j, r)
92
93
94class SingleShapePhysicser(Physicser):
95 def __init__(self, space, shape):
96 super(SingleShapePhysicser, self).__init__(space)
97 self._shape = shape
98 shape.physicser = self
99
100 def get_shape(self):
101 return self._shape
102
103
104class MultiShapePhysicser(Physicser):
105 def __init__(self, space, shape, *extra_shapes):
106 super(MultiShapePhysicser, self).__init__(space)
107 self._shape = shape
108 self._extra_shapes = extra_shapes
109 shape.physicser = self
110
111 def get_shape(self):
112 return self._shape
113
114 def add_to_space(self):
115 shape = self.get_shape()
116 self.get_space().add(shape)
117 if not shape.body.is_static:
118 self.get_space().add(shape.body)
119 for s in self._extra_shapes:
120 self.get_space().add(s)
121
122 def remove_from_space(self):
123 shape = self.get_shape()
124 self.get_space().remove(shape)
125 if not shape.body.is_static:
126 self.get_space().remove(shape.body)
127 for s in self._extra_shapes:
128 self.get_space().remove(s)
129
130
131def damping_velocity_func(body, gravity, damping, dt):
132 """Apply custom damping to this body's velocity.
133 """
134 damping = getattr(body, 'damping', damping)
135 return pymunk.Body.update_velocity(body, gravity, damping, dt)
136
137
138def make_body(mass, moment, position, damping=None):
139 body = pymunk.Body(mass, moment)
140 body.position = tuple(position)
141 if damping is not None:
142 body.damping = damping
143 body.velocity_func = damping_velocity_func
144 return body
145
146
147class GameObject(object):
148 """A representation of a thing in the game world.
149
150 This has a rendery thing, physicsy things and maybe some other things.
151 """
152
153 zorder = ZORDER_LOW
154 is_moving = False # `True` if a movement animation should play.
155
156 def __init__(self, physicser, renderer, puzzler=None, overlay=None,
157 interactible=None):
158 self.lifetime = 0
159 self.physicser = physicser
160 if physicser is not None:
161 physicser.set_game_object(self)
162 self.physicser.add_to_space()
163 self.renderer = renderer
164 renderer.set_game_object(self)
165 self.puzzler = puzzler
166 if puzzler is not None:
167 puzzler.set_game_object(self)
168 self.overlay = overlay
169 if overlay is not None:
170 self.overlay.set_game_object(self)
171 self.interactible = interactible
172 if interactible is not None:
173 self.interactible.set_game_object(self)
174 self._timers = {}
175 self._active_timers = {}
176
177 def add_timer(self, name, secs):
178 self._timers[name] = secs
179
180 def start_timer(self, name, secs=None):
181 if secs is None:
182 secs = self._timers[name]
183 self._active_timers[name] = secs
184
185 def check_timer(self, name):
186 return name in self._active_timers
187
188 def set_stored_state_dict(self, stored_state):
189 """Override this to set up whatever state storage you want.
190
191 The `stored_state` dict passed in contains whatever saved state we
192 might have for this object. If the return value of this method
193 evaluates to `True`, the contents of the `stored_state` dict will be
194 saved, otherwise it will be discarded.
195 """
196 pass
197
198 def get_space(self):
199 return self.physicser.get_space()
200
201 def get_shape(self):
202 return self.physicser.get_shape()
203
204 def get_render_position(self, surface):
205 return self.physicser.get_render_position(surface)
206
207 def get_render_angle(self):
208 return self.physicser.get_angle()
209
210 def get_facing_direction(self):
211 """Used by rendererd that care what direction an object is facing.
212 """
213 return None
214
215 def render(self, surface):
216 return self.renderer.render(surface)
217
218 def update(self, dt):
219 self.lifetime += dt
220 for timer in self._active_timers.keys():
221 self._active_timers[timer] -= dt
222 if self._active_timers[timer] <= 0:
223 self._active_timers.pop(timer)
224 self.renderer.update(dt)
225
226 def hit(self, weapon):
227 '''Was hit with a weapon (such as a bullet)'''
228 pass
229
230 def collide_with_protagonist(self, protagonist):
231 """Called as a `pre_solve` collision callback with the protagonist.
232
233 You can return `False` to ignore the collision, anything else
234 (including `None`) to process the collision as normal.
235 """
236 return True
237
238 def collide_with_furniture(self, furniture):
239 return True
240
241 def collide_with_claw_attack(self, claw_attack):
242 return True
243
244 def environmental_movement(self, vec):
245 self.physicser.apply_impulse(vec)
246
247 @classmethod
248 def requires(cls):
249 """Hints for the level editor"""
250 return [("name", "string")]
251
252 @classmethod
253 def movable(cls):
254 # Are we movable
255 hints = cls.requires()
256 for x in hints:
257 if 'position' in x:
258 return True
259 return False
260
261
262class FloorSwitch(GameObject):
263 zorder = ZORDER_FLOOR
264
265 def __init__(self, space, position):
266 body = make_body(None, None, position)
267 self.shape = pymunk.Circle(body, 30)
268 self.shape.collision_type = COLLISION_TYPE_SWITCH
269 self.shape.sensor = True
270 super(FloorSwitch, self).__init__(
271 SingleShapePhysicser(space, self.shape),
272 render.ImageStateRenderer({
273 True: resources.get_image('objects', 'sensor_on.png'),
274 False: resources.get_image('objects', 'sensor_off.png'),
275 }),
276 puzzle.CollidePuzzler(*SWITCH_PUSHERS),
277 )
278
279 @classmethod
280 def requires(cls):
281 return [("name", "string"), ("position", "coordinates")]
282
283
284class Note(GameObject):
285 zorder = ZORDER_FLOOR
286
287 def __init__(self, space, position, message):
288 body = make_body(None, None, position)
289 self.shape = pymunk.Circle(body, 30)
290 self.shape.sensor = True
291 super(Note, self).__init__(
292 SingleShapePhysicser(space, self.shape),
293 render.ImageRenderer(resources.get_image('objects', 'note.png')),
294 puzzle.CollidePuzzler(),
295 render.TextOverlay(message),
296 )
297
298 @classmethod
299 def requires(cls):
300 return [("name", "string"), ("position", "coordinates"),
301 ("message", "text")]
302
303
304class EphemeralNote(GameObject):
305 def __init__(self, message, timeout, **kwargs):
306 kwargs.setdefault('bg_colour', (255, 180, 180, 192))
307 super(EphemeralNote, self).__init__(
308 None,
309 render.NullRenderer(),
310 puzzle.YesPuzzler(),
311 render.TextOverlay(message, **kwargs),
312 )
313 self.add_timer('timeout', timeout)
314 self.start_timer('timeout')
315
316 def update(self, dt):
317 super(EphemeralNote, self).update(dt)
318 if not self.check_timer('timeout'):
319 return Result(remove=[self])
320
321
322class FloorLight(GameObject):
323 zorder = ZORDER_FLOOR
324
325 def __init__(self, space, position, state_source):
326 body = make_body(None, None, position)
327 self.shape = pymunk.Circle(body, 10)
328 self.shape.collision_type = COLLISION_TYPE_SWITCH
329 self.shape.sensor = True
330 super(FloorLight, self).__init__(
331 SingleShapePhysicser(space, self.shape),
332 render.ImageStateRenderer({
333 True: resources.get_image('objects', 'light_on.png'),
334 False: resources.get_image('objects', 'light_off.png'),
335 }),
336 puzzle.StateProxyPuzzler(state_source),
337 )
338
339 @classmethod
340 def requires(cls):
341 return [("name", "string"), ("position", "coordinates"),
342 ("state_source", "puzzler")]
343
344
345class Box(GameObject):
346 def __init__(self, space, position):
347 body = make_body(10, 10000, position, damping=0.5)
348 self.shape = pymunk.Poly(
349 body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
350 self.shape.friction = 0.5
351 self.shape.collision_type = COLLISION_TYPE_FURNITURE
352 super(Box, self).__init__(
353 SingleShapePhysicser(space, self.shape),
354 render.ImageRenderer(resources.get_image('objects', 'crate.png')),
355 )
356
357 @classmethod
358 def requires(cls):
359 return [("name", "string"), ("position", "coordinates"),
360 ("state_source", "puzzler")]
361
362
363class SokoBox(GameObject):
364 def __init__(self, space, position):
365 body = make_body(1, pymunk.inf, position, 0.1)
366 self.shape = pymunk.Poly(
367 body, [(-40, -40), (40, -40), (40, 40), (-40, 40)])
368 self.shape.friction = 2.0
369 self.shape.collision_type = COLLISION_TYPE_FURNITURE
370 super(SokoBox, self).__init__(
371 SingleShapePhysicser(space, self.shape),
372 render.ImageRenderer(
373 resources.get_image('objects', 'sokobox.png')),
374 )
375
376 @classmethod
377 def requires(cls):
378 return [("name", "string"), ("position", "coordinates"),
379 ("state_source", "puzzler")]
380
381
382class BaseDoor(GameObject):
383 zorder = ZORDER_FLOOR
384 is_open = True
385
386 def __init__(self, space, position, destination, dest_pos, angle,
387 renderer, condition):
388 body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
389 self.shape = pymunk.Circle(body, 30)
390 self.shape.collision_type = COLLISION_TYPE_DOOR
391 self.shape.body.angle = float(angle) / 180 * math.pi
392 self.shape.sensor = True
393 self.destination = destination
394 self.dest_pos = tuple(dest_pos)
395 super(BaseDoor, self).__init__(
396 SingleShapePhysicser(space, self.shape),
397 renderer,
398 puzzle.ParentAttrPuzzler('is_open'),
399 interactible=environment.Interactible(
400 environment.Action(self._post_door_event, condition)),
401 )
402
403 def _post_door_event(self, protagonist):
404 self.door_opened()
405 DoorEvent.post(self.destination, self.dest_pos)
406
407 def door_opened(self):
408 sound.play_sound('robotstep2.ogg')
409
410
411class Door(BaseDoor):
412 def __init__(self, space, position, destination, dest_pos, angle):
413 super(Door, self).__init__(
414 space, position, destination, dest_pos, angle,
415 render.ImageRenderer(resources.get_image('objects', 'door.png')),
416 environment.YesCondition(),
417 )
418
419 @classmethod
420 def requires(cls):
421 return [("name", "string"), ("position", "coordinates"),
422 ("destination", "level name"), ("dest_pos", "coordinate"),
423 ("angle", "degrees")]
424
425
426class RestartGameDoor(Door):
427 def _post_door_event(self, protagonist):
428 protagonist.world.reset()
429 super(RestartGameDoor, self)._post_door_event(protagonist)
430
431
432class ContinueGameDoor(Door):
433 def _post_door_event(self, protagonist):
434 world = protagonist.world
435 if world.level[0]:
436 DoorEvent.post(world.level[0], world.level[1])
437 else:
438 # New game?
439 super(ContinueGameDoor, self)._post_door_event(protagonist)
440
441
442def make_overlay_image(image_name, angle):
443 transforms = ()
444 if angle != 0:
445 transforms = (rotator(-angle),)
446 return resources.get_image('objects', image_name, transforms=transforms)
447
448
449class PuzzleDoor(BaseDoor):
450 def __init__(self, space, position, destination, dest_pos, angle,
451 key_state):
452 self._key_state = key_state
453 overlay = ImageOverlay(make_overlay_image('lock.png', angle))
454 super(PuzzleDoor, self).__init__(
455 space, position, destination, dest_pos, angle,
456 render.ImageStateRenderer({
457 True: resources.get_image('objects', 'door.png'),
458 False: resources.get_image(
459 'objects', 'door.png', transforms=(overlay,)),
460 }),
461 environment.FunctionCondition(lambda p: self.is_open),
462 )
463
464 @property
465 def is_open(self):
466 if self._stored_state['is_open']:
467 return True
468 return self.puzzler.glue.get_state_of(self._key_state)
469
470 def door_opened(self):
471 self._stored_state['is_open'] = True
472 super(PuzzleDoor, self).door_opened()
473
474 def set_stored_state_dict(self, stored_state):
475 self._stored_state = stored_state
476 self._stored_state.setdefault('is_open', False)
477 return True
478
479 @classmethod
480 def requires(cls):
481 return [("name", "string"), ("position", "coordinates"),
482 ("destination", "level name"), ("dest_pos", "coordinate"),
483 ("angle", "degrees"),
484 ("key_state", "puzzler")]
485
486
487class KeyedDoor(BaseDoor):
488 def __init__(self, space, position, destination, dest_pos, angle,
489 key_item=None):
490 self._key_item = key_item
491 overlay = ImageOverlay(
492 make_overlay_image('%s.png' % (key_item,), angle))
493 super(KeyedDoor, self).__init__(
494 space, position, destination, dest_pos, angle,
495 render.ImageRenderer(resources.get_image(
496 'objects', 'door.png', transforms=(overlay,))),
497 environment.ItemRequiredCondition(key_item),
498 )
499
500 @classmethod
501 def requires(cls):
502 return [("name", "string"), ("position", "coordinates"),
503 ("destination", "level name"), ("dest_pos", "coordinate"),
504 ("angle", "degrees"), ("key_item", "item name")]
505
506
507class Hatch(GameObject):
508 zorder = ZORDER_FLOOR
509
510 def __init__(self, space, end1, end2, key_state=None):
511 body = make_body(None, None, (0, 0))
512 self.shape = pymunk.Segment(body, tuple(end1), tuple(end2), 7)
513 self.shape.collision_type = COLLISION_TYPE_DOOR
514 if key_state is None:
515 puzzler = puzzle.YesPuzzler()
516 else:
517 puzzler = puzzle.StateProxyPuzzler(key_state)
518 super(Hatch, self).__init__(
519 SingleShapePhysicser(space, self.shape),
520 render.HatchRenderer(),
521 puzzler,
522 )
523
524 def collide_with_protagonist(self, protagonist):
525 if self.puzzler.get_state():
526 # Reject the collision, we can walk through.
527 return False
528 return True
529
530 collide_with_furniture = collide_with_protagonist
531
532 @classmethod
533 def requires(cls):
534 return [("name", "string"), ("end1", "coordinates"),
535 ("end2", "coordinates"), ("key_state", "puzzler")]
536
537 # The level knows that bulkheads are magical
538 @classmethod
539 def movable(cls):
540 return True
541
542
543class ToggleSwitch(GameObject):
544 zorder = ZORDER_LOW
545
546 def __init__(self, space, position):
547 body = make_body(None, None, position)
548 self.shape = pymunk.Circle(body, 20)
549 self.shape.sensor = True
550 super(ToggleSwitch, self).__init__(
551 SingleShapePhysicser(space, self.shape),
552 render.ImageStateRenderer({
553 True: resources.get_image('objects', 'lever.png'),
554 False: resources.get_image(
555 'objects', 'lever.png', transforms=(FLIP_H,)),
556 }),
557 puzzle.ParentAttrPuzzler('toggle_on'),
558 interactible=environment.Interactible(
559 environment.Action(self._toggle)),
560 )
561
562 @property
563 def toggle_on(self):
564 return self._stored_state['toggle_on']
565
566 def _toggle(self, protagonist):
567 self._stored_state['toggle_on'] = not self.toggle_on
568
569 def set_stored_state_dict(self, stored_state):
570 self._stored_state = stored_state
571 # We start in the "off" position.
572 self._stored_state.setdefault('toggle_on', False)
573 return True
574
575 @classmethod
576 def requires(cls):
577 return [("name", "string"), ("position", "coordinates")]
578
579
580class Bullet(GameObject):
581 def __init__(self, space, position, impulse, damage, bullet_type,
582 source_collision_type):
583 body = make_body(1, pymunk.inf, position)
584 body.angle = impulse.angle
585 self.last_position = position
586 self.shape = pymunk.Circle(body, 2)
587 self.shape.sensor = True
588 self.shape.collision_type = COLLISION_TYPE_PROJECTILE
589 self.damage = damage
590 self.type = bullet_type
591 self.source_collision_type = source_collision_type
592 super(Bullet, self).__init__(
593 SingleShapePhysicser(space, self.shape),
594 render.ImageRenderer(resources.get_image(
595 'objects', '%s.png' % self.type)),
596 )
597 self.physicser.apply_impulse(impulse)
598
599 def update(self, dt):
600 super(Bullet, self).update(dt)
601 position = (self.physicser.position.x, self.physicser.position.y)
602 r = self.get_space().segment_query(self.last_position, position)
603 self.last_position = position
604 for collision in r:
605 shape = collision.shape
606 if (shape.collision_type == self.source_collision_type
607 or shape == self.physicser.get_shape()
608 or shape.sensor):
609 continue
610 if hasattr(shape, 'physicser'):
611 shape.physicser.game_object.hit(self)
612 self.physicser.remove_from_space()
613 return Result(remove=[self])
614
615
616class ClawAttack(GameObject):
617 def __init__(self, space, pos, vector, damage):
618 body = make_body(1, pymunk.inf,
619 (pos[0] + (vector.length * math.cos(vector.angle)),
620 pos[1] + (vector.length * math.sin(vector.angle))))
621 body.angle = vector.angle
622 self.shape = pymunk.Circle(body, 30)
623 self.shape.sensor = True
624 self.shape.collision_type = COLLISION_TYPE_WEREWOLF_ATTACK
625 self.damage = damage
626 super(ClawAttack, self).__init__(
627 SingleShapePhysicser(space, self.shape),
628 render.ImageRenderer(resources.get_image(
629 'objects', 'werewolf_SW_claw_attack.png',
630 transforms=(FLIP_H,))),
631 )
632
633 def update(self, dt):
634 super(ClawAttack, self).update(dt)
635 if self.lifetime > 0.1:
636 self.physicser.remove_from_space()
637 return Result(remove=[self])
638
639
640class HostileTerrain(GameObject):
641 zorder = ZORDER_FLOOR
642 damage = None
643 tiles = []
644 tile_alpha = 255
645 tile_frame_ticks = 3
646 # How often to hit the player
647 rate = 5
648
649 def __init__(self, space, position, outline):
650 body = make_body(10, pymunk.inf, position)
651 # Adjust shape relative to position
652 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
653 p in outline]
654 self.shape = pymunk.Poly(body, shape_outline)
655 self._ticks = 0
656 self.shape.collision_type = COLLISION_TYPE_SWITCH
657 self.shape.sensor = True
658 renderer = self._fix_image(outline)
659 super(HostileTerrain, self).__init__(
660 SingleShapePhysicser(space, self.shape),
661 renderer)
662
663 def _fix_image(self, outline):
664 if len(self.tiles) > 1:
665 tile_images = [resources.get_image('tiles', x)
666 for x in self.tiles]
667 renderer = render.TimedTiledRenderer(outline, tile_images,
668 self.tile_frame_ticks,
669 self.tile_alpha)
670 else:
671 tile_image = resources.get_image('tiles', self.tiles[0])
672 renderer = render.TiledRenderer(outline, tile_image,
673 self.tile_alpha)
674 return renderer
675
676 def update_image(self, new_outline):
677 self.renderer = self._fix_image(new_outline)
678
679 def collide_with_protagonist(self, protagonist):
680 # We're called every frame we're colliding, so
681 # There are timing issues with stepping on and
682 # off terrian, but as long as the rate is reasonably
683 # low, they shouldn't impact gameplay
684 if self._ticks == 0:
685 self.apply_effect(protagonist)
686 self._ticks += 1
687 if self._ticks > self.rate:
688 self._ticks = 0
689
690 def apply_effect(self, protagonist):
691 protagonist.lose_health(self.damage)
692
693 @classmethod
694 def requires(cls):
695 return [("name", "string"), ("position", "coordinates"),
696 ("outline", "polygon (convex)")]
697
698
699class AcidFloor(HostileTerrain):
700 damage = 1
701 tiles = ['acid.png', 'acid2.png', 'acid3.png']
702 tile_alpha = 200
703 tile_frame_ticks = 10
704
705
706class ForceWolfFloor(HostileTerrain):
707 tiles = ['moonlight.png']
708 rate = 0
709 tile_alpha = 150
710 zorder = ZORDER_HIGH
711
712 def apply_effect(self, protagonist):
713 protagonist.force_wolf_form()
714
715
716class GravityWell(GameObject):
717 zorder = ZORDER_FLOOR
718 # How often to hit the player
719 rate = 5
720
721 def __init__(self, space, position, radius, force):
722 body = make_body(None, None, position)
723 # Adjust shape relative to position
724 self._radius = radius
725 self.shape = pymunk.Circle(body, radius)
726 self.centre = pymunk.Circle(body, 10)
727 self.centre.friction = pymunk.inf
728 self._ticks = 0
729 self.force = force
730 self.shape.collision_type = COLLISION_TYPE_SWITCH
731 self.shape.sensor = True
732 super(GravityWell, self).__init__(
733 MultiShapePhysicser(space, self.shape, self.centre),
734 render.ImageRenderer(resources.get_image(
735 'objects', 'gravity_well.png')),
736 )
737
738 def collide_with_protagonist(self, protagonist):
739 # We're called every frame we're colliding, so
740 # There are timing issues with stepping on and
741 # off terrian, but as long as the rate is reasonably
742 # low, they shouldn't impact gameplay
743 self.apply_effect(protagonist)
744
745 def collide_with_furniture(self, furniture):
746 # We're called every frame we're colliding, so
747 # There are timing issues with stepping on and
748 # off terrian, but as long as the rate is reasonably
749 # low, they shouldn't impact gameplay
750 self.apply_effect(furniture)
751
752 def apply_effect(self, object_to_move):
753 movement = self.physicser.position - object_to_move.physicser.position
754 local_force = self.force * math.sqrt(
755 object_to_move.get_shape().body.mass)
756 movement.length = local_force
757 object_to_move.environmental_movement(movement)
758
759 @classmethod
760 def requires(cls):
761 return [("name", "string"), ("position", "coordinates"),
762 ("radius", "int"), ("force", "int")]
763
764
765class SheepPen(GameObject):
766 zorder = ZORDER_FLOOR
767
768 def __init__(self, space, position, outline, sheep_count):
769 body = make_body(None, None, position)
770 # Adjust shape relative to position
771 shape_outline = [(p[0] - position[0], p[1] - position[1]) for
772 p in outline]
773 self.shape = pymunk.Poly(body, shape_outline)
774 self.shape.collision_type = COLLISION_TYPE_SHEEP_PEN
775 self.shape.sensor = True
776 super(SheepPen, self).__init__(
777 SingleShapePhysicser(space, self.shape),
778 render.Renderer(),
779 puzzle.MultiCollidePuzzler(sheep_count, COLLISION_TYPE_SHEEP),
780 )
781
782 @classmethod
783 def requires(cls):
784 return [("name", "string"), ("position", "coordinates"),
785 ("outline", "polygon (convex)"), ("sheep_count", "int")]
Note: See TracBrowser for help on using the repository browser.