|
| 1 | +Map Colliders |
| 2 | +============= |
| 3 | + |
| 4 | +Overview |
| 5 | +-------- |
| 6 | + |
| 7 | +If a scene has an actor moving, we may update its position from frame to frame |
| 8 | +using the discrete approximation:: |
| 9 | + |
| 10 | + velocity = velocity + acceleration * dt |
| 11 | + position = position + velocity * dt |
| 12 | + |
| 13 | +That's fine when the actor does not find an obstacle, like a wall. |
| 14 | + |
| 15 | +If there is an obstacle, we usually want to |
| 16 | + - stop the position change so the actor touches the obstacle but does |
| 17 | + not overlap it |
| 18 | + - do something sensible with the velocity (stop ?, bounce ?) |
| 19 | + - maybe do some action based on which obstacle (spikes ?, glass ?) |
| 20 | + |
| 21 | +To do this we can represent the actor and the obstacles as rects with sides |
| 22 | +parallel to the axis, and do the calculations. |
| 23 | + |
| 24 | +This is what 'map colliders' do:: |
| 25 | + - with the actor velocity, rect before, tentative rect after |
| 26 | + - with a 'map layer' that groups a number of obstacles |
| 27 | + |
| 28 | +it will |
| 29 | + - Properly update velocity and position |
| 30 | + - Call appropriate methods when it detects actor bumped into an obstacle |
| 31 | + - Tell if any collision in the x and / or y axis happened |
| 32 | + |
| 33 | +While some mapcolliders are meant to be used in tiled maps (RectMapCollider, |
| 34 | +RectMapWithPropsCollider), others (TmxObjectMapCollider) can be used with no |
| 35 | +tiles at all. |
| 36 | + |
| 37 | + |
| 38 | +Properly update velocity and position |
| 39 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 40 | + |
| 41 | +the position and velocity update can be written as:: |
| 42 | + |
| 43 | + vx, vy = actor.velocity |
| 44 | + |
| 45 | + # using the player controls, gravity and other acceleration influences |
| 46 | + # update the velocity |
| 47 | + vx = (keyboard[key.RIGHT] - keyboard[key.LEFT]) * actor.MOVE_SPEED |
| 48 | + vy += GRAVITY * dt |
| 49 | + if actor.on_ground and keyboard[key.SPACE]: |
| 50 | + vy = actor.JUMP_SPEED |
| 51 | + |
| 52 | + # with the updated velocity calculate the (tentative) displacement |
| 53 | + dx = vx * dt |
| 54 | + dy = vy * dt |
| 55 | + |
| 56 | + # get the player's current bounding rectangle |
| 57 | + last = actor.get_rect() |
| 58 | + |
| 59 | + # build the tentative displaced rect |
| 60 | + new = last.copy() |
| 61 | + new.x += dx |
| 62 | + new.y += dy |
| 63 | + |
| 64 | + # account for hitting obstacles, it will adjust new and vx, vy |
| 65 | + actor.velocity = mapcollider.collide_map(maplayer, last, new, vx, vy) |
| 66 | + |
| 67 | + # update on_ground status |
| 68 | + actor.on_ground = (new.y == last.y) |
| 69 | + |
| 70 | + # update player position; player position is anchored at the center of the image rect |
| 71 | + actor.position = new.center |
| 72 | + |
| 73 | +How velocity changes in a collision is handled by method mapcollider.on_bump_handler; |
| 74 | +it can be set to custom code or to one of the stock handlers: |
| 75 | + |
| 76 | + - on_bump_bounce(): Bounces when a wall is touched. |
| 77 | + - on_bump_stick(): Stops all movement when any wall is touched. (sticky bomb...) |
| 78 | + - on_bump_slide(): Blocks movement only in the axis that touched a wall. (player...) |
| 79 | + |
| 80 | +For convenience, a stock handler can be specified at instantiation time with |
| 81 | +the 'velocity_on_bump' parameter. |
| 82 | + |
| 83 | + |
| 84 | +Call appropriate methods when it detects actor bumped into an obstacle |
| 85 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 86 | + |
| 87 | +When mapcollider.collide_map detects that actor collides with obj 'someobj' from |
| 88 | +side 'someside', it will call mapcollider.collide_<someside>(someobj). |
| 89 | + |
| 90 | +There 'someside' is one of 'left', 'right', 'top' and 'bottom'. |
| 91 | + |
| 92 | +This provides an opportunity to do things based on which object the actor touched, |
| 93 | +like 'spike' ? -> init spikes animation and kill actor. |
| 94 | + |
| 95 | +Notice that each mapcollider.collide_* can receive multiple calls in the same |
| 96 | +mapcollider.collide_map call. |
| 97 | + |
| 98 | + |
| 99 | +Tell if any collision in the x and / or y axis happened |
| 100 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 101 | + |
| 102 | +After collide_map returns, the flags mapcollider.bumped_x and mapcollider.bumped_y |
| 103 | +tells if there's have been any collision along the respective axis. |
| 104 | + |
| 105 | +This can be handy to flip actor's animation animation direction, by example from |
| 106 | +'walk_left' to 'walk_right' |
| 107 | + |
| 108 | + |
| 109 | +Variants |
| 110 | +--------- |
| 111 | + |
| 112 | +Currently they are three map colliders variants, |
| 113 | + |
| 114 | +RectMapCollider |
| 115 | +^^^^^^^^^^^^^^^ |
| 116 | + |
| 117 | +Obstacles are rectangular tiles grouped into a RectMapLayer; all non empty cells |
| 118 | +are deemed solid. |
| 119 | + |
| 120 | +:class:`implementation <cocos.mapcolliders.RectMapCollider>` |
| 121 | + |
| 122 | +RectMapWithPropsCollider |
| 123 | +^^^^^^^^^^^^^^^^^^^^^^^^ |
| 124 | + |
| 125 | +Obstacles are rectangular tiles grouped into a RectMapLayer, cells use properties |
| 126 | +"left", "right", "top" and "bottom" to signal which side(s) block movements. |
| 127 | + |
| 128 | +A solid block would probably have all four sides set; a platform can set |
| 129 | +only the top so the player might jump up from underneath and pass through. |
| 130 | + |
| 131 | +For convenience these properties would typically be set on the tiles |
| 132 | +themselves, rather than on individual cells. Of course for the cell which |
| 133 | +is the entrance to a secret area you could override a wall's properties to |
| 134 | +set the side to False and allow ingress. |
| 135 | + |
| 136 | +See :ref:`cell_properties_label` for information about properties. |
| 137 | + |
| 138 | +:class:`implementation <cocos.mapcolliders.RectMapWithPropsCollider>` |
| 139 | + |
| 140 | + |
| 141 | +TmxObjectMapCollider |
| 142 | +^^^^^^^^^^^^^^^^^^^^ |
| 143 | + |
| 144 | +Obstacles are TmxObjects grouped into a TmxObjectLayer, each object is meant |
| 145 | +to block movement. |
| 146 | + |
| 147 | +A common use case is to do moving platforms in a platform game. |
| 148 | + |
| 149 | +:class:`implementation <cocos.mapcolliders.TmxObjectMapCollider>` |
| 150 | + |
| 151 | + |
| 152 | +How to use |
| 153 | +----------------------- |
| 154 | + |
| 155 | +There are basically two ways to include this functionality into an |
| 156 | +actor class |
| 157 | + |
| 158 | + - as a component, essentially passing (mapcollider, maplayer) in |
| 159 | + the actor's __init__ |
| 160 | + - mixin style, by using RectMapCollider or a subclass as a secondary |
| 161 | + base class for actor. |
| 162 | + |
| 163 | +Component way is more decoupled, Mixin style is more powerful because |
| 164 | +the collision code will have access to the entire actor trough his 'self'. |
| 165 | + |
| 166 | +To have a working instance the behavior of velocity in a collision must be |
| 167 | +defined, and that's the job of method `on_bump_handler` |
| 168 | + |
| 169 | + - if one of the stock on_bump_<variant> suits the requirements, suffices |
| 170 | + `mapcollider.on_bump_handler = mapcollider.on_bump_<desired variant>` |
| 171 | + or passing a selector at instantiation time |
| 172 | + `mapcollider = MapCollider(<desired variant>)` |
| 173 | + |
| 174 | + - for custom behavior define on_bump_handler in a subclass and instantiate it. |
0 commit comments