summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-04-29 16:45:55 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-04-29 16:45:55 -0400
commitc00668c58b26325203cb6815bc3dedf1e7d7ac5e (patch)
treef03d9b420779f365a51285c9a2b675a5e7e965c5 /src
parent36cceabfc5ddd22d9ae0d6c4dee9d4041bf2e348 (diff)
downloadtherapy-c00668c58b26325203cb6815bc3dedf1e7d7ac5e.tar.gz
therapy-c00668c58b26325203cb6815bc3dedf1e7d7ac5e.tar.bz2
therapy-c00668c58b26325203cb6815bc3dedf1e7d7ac5e.zip
Added map object collision
Collision checking in PonderingSystem was rewritten to work as follows: horizontal movement is step first, then vertical. In each step, the closest environmental boundary to the body is found on the axis of movement in the space traversed by the body. Then, if any map objects fall in the region between the body's old position and the environmental boundary (or body new position if no boundary was found), process collision with those bodies in increasing distance order, stopping if a collision stops movement short of where the next collision would take place. After this, process collision with all of the environmental boundaries at the axis distance found earlier, as long as movement hasn't stopped short.

This is not the most optimal implementation, and there is a lot of code repetition, but it is a start and it works.

All map objects currently function as walls.

This fixes the bug where you could, with pixel-perfect precision, jump into the corner of a wall tile.

The top of the hitbox for the spike tile was lowered by one pixel. This fixes a problem where if the player is halfway on a floor tile and halfway over a spike tile, the floor tile would not stop the spike tile from being processed, and the player would die.
Diffstat (limited to 'src')
-rw-r--r--src/collision.cpp97
-rw-r--r--src/collision.h81
-rw-r--r--src/components/mappable.h4
-rw-r--r--src/components/ponderable.h16
-rw-r--r--src/systems/mapping.cpp2
-rw-r--r--src/systems/pondering.cpp882
-rw-r--r--src/systems/pondering.h23
-rw-r--r--src/systems/realizing.cpp4
8 files changed, 714 insertions, 395 deletions
diff --git a/src/collision.cpp b/src/collision.cpp deleted file mode 100644 index b747a90..0000000 --- a/src/collision.cpp +++ /dev/null
@@ -1,97 +0,0 @@
1#include "collision.h"
2
3bool Collision::operator<(const Collision& other) const
4{
5 // Most important is the type of collision
6 if (type_ != other.type_)
7 {
8 return (static_cast<int>(type_) > static_cast<int>(other.type_));
9 }
10
11 // Next, categorize the collisions arbitrarily based on direction
12 if (dir_ != other.dir_)
13 {
14 return (static_cast<int>(dir_) < static_cast<int>(other.dir_));
15 }
16
17 // We want to process closer collisions first
18 if (axis_ != other.axis_)
19 {
20 switch (dir_)
21 {
22 case Direction::left:
23 case Direction::up:
24 {
25 return (axis_ < other.axis_);
26 }
27
28 case Direction::right:
29 case Direction::down:
30 {
31 return (axis_ > other.axis_);
32 }
33 }
34 }
35
36 // Order the remaining attributes arbitrarily
37 return std::tie(collider_, lower_, upper_) <
38 std::tie(other.collider_, other.lower_, other.upper_);
39}
40
41bool Collision::isColliding(
42 double x,
43 double y,
44 int w,
45 int h) const
46{
47 int right = x + w;
48 int bottom = y + h;
49
50 switch (dir_)
51 {
52 case Direction::left:
53 case Direction::right:
54 {
55 if (!((bottom > lower_) && (y < upper_)))
56 {
57 return false;
58 }
59
60 break;
61 }
62
63 case Direction::up:
64 case Direction::down:
65 {
66 if (!((right > lower_) && (x < upper_)))
67 {
68 return false;
69 }
70
71 break;
72 }
73 }
74
75 switch (dir_)
76 {
77 case Direction::left:
78 {
79 return (axis_ >= x);
80 }
81
82 case Direction::right:
83 {
84 return (axis_ <= right);
85 }
86
87 case Direction::up:
88 {
89 return (axis_ >= y);
90 }
91
92 case Direction::down:
93 {
94 return (axis_ <= bottom);
95 }
96 }
97}
diff --git a/src/collision.h b/src/collision.h deleted file mode 100644 index e5371f8..0000000 --- a/src/collision.h +++ /dev/null
@@ -1,81 +0,0 @@
1#ifndef COLLISION_H_53D84877
2#define COLLISION_H_53D84877
3
4#include "entity_manager.h"
5#include "direction.h"
6
7class Collision {
8public:
9
10 using id_type = EntityManager::id_type;
11
12 // Types are defined in descending priority order
13 enum class Type {
14 wall,
15 platform,
16 adjacency,
17 warp,
18 danger
19 };
20
21 Collision(
22 id_type collider,
23 Direction dir,
24 Type type,
25 int axis,
26 double lower,
27 double upper) :
28 collider_(collider),
29 dir_(dir),
30 type_(type),
31 axis_(axis),
32 lower_(lower),
33 upper_(upper)
34 {
35 }
36
37 inline id_type getCollider() const
38 {
39 return collider_;
40 }
41
42 inline Direction getDirection() const
43 {
44 return dir_;
45 }
46
47 inline Type getType() const
48 {
49 return type_;
50 }
51
52 inline int getAxis() const
53 {
54 return axis_;
55 }
56
57 inline double getLower() const
58 {
59 return lower_;
60 }
61
62 inline double getUpper() const
63 {
64 return upper_;
65 }
66
67 bool operator<(const Collision& other) const;
68
69 bool isColliding(double x, double y, int w, int h) const;
70
71private:
72
73 id_type collider_;
74 Direction dir_;
75 Type type_;
76 int axis_;
77 double lower_;
78 double upper_;
79};
80
81#endif /* end of include guard: COLLISION_H_53D84877 */
diff --git a/src/components/mappable.h b/src/components/mappable.h index 6f3d38e..e92074e 100644 --- a/src/components/mappable.h +++ b/src/components/mappable.h
@@ -7,7 +7,7 @@
7#include <list> 7#include <list>
8#include "component.h" 8#include "component.h"
9#include "renderer/texture.h" 9#include "renderer/texture.h"
10#include "collision.h" 10#include "components/ponderable.h"
11#include "entity_manager.h" 11#include "entity_manager.h"
12 12
13class MappableComponent : public Component { 13class MappableComponent : public Component {
@@ -46,7 +46,7 @@ public:
46 class Boundary { 46 class Boundary {
47 public: 47 public:
48 48
49 using Type = Collision::Type; 49 using Type = PonderableComponent::Collision;
50 50
51 Boundary( 51 Boundary(
52 double axis, 52 double axis,
diff --git a/src/components/ponderable.h b/src/components/ponderable.h index fd7e775..5354f87 100644 --- a/src/components/ponderable.h +++ b/src/components/ponderable.h
@@ -21,6 +21,17 @@ public:
21 }; 21 };
22 22
23 /** 23 /**
24 * List of different types of collidable surfaces.
25 */
26 enum class Collision {
27 wall,
28 platform,
29 adjacency,
30 warp,
31 danger
32 };
33
34 /**
24 * Constructor for initializing the body type, which is a constant. 35 * Constructor for initializing the body type, which is a constant.
25 */ 36 */
26 PonderableComponent(Type type) : type(type) 37 PonderableComponent(Type type) : type(type)
@@ -67,6 +78,11 @@ public:
67 bool collidable = true; 78 bool collidable = true;
68 79
69 /** 80 /**
81 * The effect that colliding with this body has.
82 */
83 Collision colliderType = Collision::wall;
84
85 /**
70 * If this flag is disabled, the entity will be ignored by the pondering 86 * If this flag is disabled, the entity will be ignored by the pondering
71 * system. 87 * system.
72 * 88 *
diff --git a/src/systems/mapping.cpp b/src/systems/mapping.cpp index af67aed..d78c8fe 100644 --- a/src/systems/mapping.cpp +++ b/src/systems/mapping.cpp
@@ -175,7 +175,7 @@ void MappingSystem::generateBoundaries(id_type mapEntity)
175 175
176 addBoundary( 176 addBoundary(
177 mappable.downBoundaries, 177 mappable.downBoundaries,
178 y * TILE_HEIGHT, 178 y * TILE_HEIGHT + 1,
179 x * TILE_WIDTH, 179 x * TILE_WIDTH,
180 (x+1) * TILE_WIDTH, 180 (x+1) * TILE_WIDTH,
181 MappableComponent::Boundary::Type::danger); 181 MappableComponent::Boundary::Type::danger);
diff --git a/src/systems/pondering.cpp b/src/systems/pondering.cpp index 4ae6176..04b45a1 100644 --- a/src/systems/pondering.cpp +++ b/src/systems/pondering.cpp
@@ -11,7 +11,6 @@
11#include "systems/playing.h" 11#include "systems/playing.h"
12#include "systems/realizing.h" 12#include "systems/realizing.h"
13#include "consts.h" 13#include "consts.h"
14#include "collision.h"
15 14
16void PonderingSystem::tick(double dt) 15void PonderingSystem::tick(double dt)
17{ 16{
@@ -56,304 +55,566 @@ void PonderingSystem::tick(double dt)
56 const double oldRight = oldX + transformable.w; 55 const double oldRight = oldX + transformable.w;
57 const double oldBottom = oldY + transformable.h; 56 const double oldBottom = oldY + transformable.h;
58 57
59 double newX = oldX + ponderable.velX * dt; 58 CollisionResult result;
60 double newY = oldY + ponderable.velY * dt; 59 result.newX = oldX + ponderable.velX * dt;
60 result.newY = oldY + ponderable.velY * dt;
61 61
62 bool oldGrounded = ponderable.grounded; 62 bool oldGrounded = ponderable.grounded;
63 ponderable.grounded = false; 63 ponderable.grounded = false;
64 64
65 std::priority_queue<Collision> collisions; 65 // Find horizontal collisions.
66 66 if (result.newX < oldX)
67 // Find collisions
68 if (newX < oldX)
69 { 67 {
70 for (auto it = mappable.leftBoundaries.lower_bound(oldX); 68 bool boundaryCollision = false;
71 (it != std::end(mappable.leftBoundaries)) && (it->first >= newX); 69 auto it = mappable.leftBoundaries.lower_bound(oldX);
72 it++) 70
71 // Find the axis distance of the closest environmental boundary.
72 for (;
73 (it != std::end(mappable.leftBoundaries)) &&
74 (it->first >= result.newX);
75 it++)
73 { 76 {
74 if ((oldBottom > it->second.lower) 77 // Check that the boundary is in range for the other axis.
75 && (oldY < it->second.upper)) 78 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
76 { 79 {
77 // We have a collision! 80 // We have a collision!
78 collisions.emplace( 81 boundaryCollision = true;
79 mapEntity, 82
80 Direction::left, 83 break;
81 it->second.type,
82 it->first,
83 it->second.lower,
84 it->second.upper);
85 } 84 }
86 } 85 }
87 } else if (newX > oldX) 86
88 { 87 // Find a list of potential colliders, sorted so that the closest is
89 for (auto it = mappable.rightBoundaries.lower_bound(oldRight); 88 // first.
90 (it != std::end(mappable.rightBoundaries)) 89 std::vector<id_type> colliders;
91 && (it->first <= (newX + transformable.w)); 90
92 it++) 91 for (id_type collider : entities)
93 { 92 {
94 if ((oldBottom > it->second.lower) 93 // Can't collide with self.
95 && (oldY < it->second.upper)) 94 if (collider == entity)
96 { 95 {
97 // We have a collision! 96 continue;
98 collisions.emplace(
99 mapEntity,
100 Direction::right,
101 it->second.type,
102 it->first,
103 it->second.lower,
104 it->second.upper);
105 } 97 }
106 }
107 }
108 98
109 if (newY < oldY) 99 auto& colliderPonder = game_.getEntityManager().
110 { 100 getComponent<PonderableComponent>(collider);
111 for (auto it = mappable.upBoundaries.lower_bound(oldY); 101
112 (it != std::end(mappable.upBoundaries)) && (it->first >= newY); 102 // Only check objects that are active.
113 it++) 103 if (!colliderPonder.active)
114 {
115 if ((oldRight > it->second.lower)
116 && (oldX < it->second.upper))
117 { 104 {
118 // We have a collision! 105 continue;
119 collisions.emplace(
120 mapEntity,
121 Direction::up,
122 it->second.type,
123 it->first,
124 it->second.lower,
125 it->second.upper);
126 } 106 }
127 } 107
128 } else if (newY > oldY) 108 auto& colliderTrans = game_.getEntityManager().
129 { 109 getComponent<TransformableComponent>(collider);
130 for (auto it = mappable.downBoundaries.lower_bound(oldBottom); 110
131 (it != std::end(mappable.downBoundaries)) 111 // Check if the entity would move into the potential collider,
132 && (it->first <= (newY + transformable.h)); 112 if ((colliderTrans.x + colliderTrans.w > result.newX) &&
133 it++) 113 // that it wasn't already colliding,
134 { 114 (colliderTrans.x + colliderTrans.w <= oldX) &&
135 if ((oldRight > it->second.lower) 115 // that the position on the other axis is in range,
136 && (oldX < it->second.upper)) 116 (colliderTrans.y + colliderTrans.h > oldY) &&
117 (colliderTrans.y < oldBottom) &&
118 // and that the collider is not farther away than the environmental
119 // boundary.
120 (!boundaryCollision ||
121 (colliderTrans.x + colliderTrans.w >= it->first)))
137 { 122 {
138 // We have a collision! 123 colliders.push_back(collider);
139 collisions.emplace(
140 mapEntity,
141 Direction::down,
142 it->second.type,
143 it->first,
144 it->second.lower,
145 it->second.upper);
146 } 124 }
147 } 125 }
148 }
149 126
150 // Process collisions in order of priority 127 std::sort(
151 bool adjacentlyWarping = false; 128 std::begin(colliders),
152 Direction adjWarpDir; 129 std::end(colliders),
153 size_t adjWarpMapId; 130 [&] (id_type left, id_type right) {
131 auto& leftTrans = game_.getEntityManager().
132 getComponent<TransformableComponent>(left);
154 133
155 while (!collisions.empty()) 134 auto& rightTrans = game_.getEntityManager().
156 { 135 getComponent<TransformableComponent>(right);
157 Collision collision = collisions.top();
158 collisions.pop();
159
160 // Make sure that they are still colliding
161 if (!collision.isColliding(
162 newX,
163 newY,
164 transformable.w,
165 transformable.h))
166 {
167 continue;
168 }
169 136
170 bool touchedWall = false; 137 return (rightTrans.x < leftTrans.x);
171 bool stopProcessing = false; 138 });
172 139
173 switch (collision.getType()) 140 for (id_type collider : colliders)
174 { 141 {
175 case Collision::Type::wall: 142 auto& colliderTrans = game_.getEntityManager().
143 getComponent<TransformableComponent>(collider);
144
145 // Check if the entity would still move into the potential collider.
146 if (colliderTrans.x + colliderTrans.w <= result.newX)
176 { 147 {
177 touchedWall = true; 148 break;
149 }
178 150
151 auto& colliderPonder = game_.getEntityManager().
152 getComponent<PonderableComponent>(collider);
153
154 processCollision(
155 entity,
156 collider,
157 Direction::left,
158 colliderPonder.colliderType,
159 colliderTrans.x + colliderTrans.w,
160 colliderTrans.y,
161 colliderTrans.y + colliderTrans.h,
162 result);
163
164 if (result.stopProcessing)
165 {
179 break; 166 break;
180 } 167 }
168 }
181 169
182 case Collision::Type::platform: 170 // If movement hasn't been stopped by an intermediary object, and
171 // collision checking hasn't been stopped, process the environmental
172 // boundaries closest to the entity.
173 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
174 {
175 double boundaryAxis = it->first;
176
177 for (;
178 (it != std::end(mappable.leftBoundaries)) &&
179 (it->first == boundaryAxis);
180 it++)
183 { 181 {
184 if (game_.getEntityManager(). 182 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
185 hasComponent<OrientableComponent>(entity))
186 { 183 {
187 auto& orientable = game_.getEntityManager(). 184 processCollision(
188 getComponent<OrientableComponent>(entity); 185 entity,
189 186 mapEntity,
190 if (orientable.getDropState() != 187 Direction::left,
191 OrientableComponent::DropState::none) 188 it->second.type,
189 it->first,
190 it->second.lower,
191 it->second.upper,
192 result);
193
194 if (result.stopProcessing)
192 { 195 {
193 orientable.setDropState(OrientableComponent::DropState::active); 196 break;
194 } else {
195 touchedWall = true;
196 } 197 }
197 } else {
198 touchedWall = true;
199 } 198 }
199 }
200 }
201 } else if (result.newX > oldX)
202 {
203 bool boundaryCollision = false;
204 auto it = mappable.rightBoundaries.lower_bound(oldRight);
205
206 // Find the axis distance of the closest environmental boundary.
207 for (;
208 (it != std::end(mappable.rightBoundaries))
209 && (it->first <= (result.newX + transformable.w));
210 it++)
211 {
212 // Check that the boundary is in range for the other axis.
213 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
214 {
215 // We have a collision!
216 boundaryCollision = true;
200 217
201 break; 218 break;
202 } 219 }
220 }
221
222 // Find a list of potential colliders, sorted so that the closest is
223 // first.
224 std::vector<id_type> colliders;
203 225
204 case Collision::Type::adjacency: 226 for (id_type collider : entities)
227 {
228 // Can't collide with self.
229 if (collider == entity)
205 { 230 {
206 auto& mappable = game_.getEntityManager(). 231 continue;
207 getComponent<MappableComponent>(collision.getCollider()); 232 }
208 233
209 auto& adj = [&] () -> const MappableComponent::Adjacent& { 234 auto& colliderPonder = game_.getEntityManager().
210 switch (collision.getDirection()) 235 getComponent<PonderableComponent>(collider);
211 {
212 case Direction::left: return mappable.leftAdjacent;
213 case Direction::right: return mappable.rightAdjacent;
214 case Direction::up: return mappable.upAdjacent;
215 case Direction::down: return mappable.downAdjacent;
216 }
217 }();
218 236
219 switch (adj.type) 237 // Only check objects that are active.
220 { 238 if (!colliderPonder.active)
221 case MappableComponent::Adjacent::Type::wall: 239 {
222 { 240 continue;
223 touchedWall = true; 241 }
224 242
225 break; 243 auto& colliderTrans = game_.getEntityManager().
226 } 244 getComponent<TransformableComponent>(collider);
245
246 // Check if the entity would move into the potential collider,
247 if ((colliderTrans.x < result.newX + transformable.w) &&
248 // that it wasn't already colliding,
249 (colliderTrans.x >= oldRight) &&
250 // that the position on the other axis is in range,
251 (colliderTrans.y + colliderTrans.h > oldY) &&
252 (colliderTrans.y < oldBottom) &&
253 // and that the collider is not farther away than the environmental
254 // boundary.
255 (!boundaryCollision || (colliderTrans.x <= it->first)))
256 {
257 colliders.push_back(collider);
258 }
259 }
227 260
228 case MappableComponent::Adjacent::Type::wrap: 261 std::sort(
229 { 262 std::begin(colliders),
230 switch (collision.getDirection()) 263 std::end(colliders),
231 { 264 [&] (id_type left, id_type right) {
232 case Direction::left: 265 auto& leftTrans = game_.getEntityManager().
233 { 266 getComponent<TransformableComponent>(left);
234 newX = GAME_WIDTH + WALL_GAP - transformable.w;
235 267
236 break; 268 auto& rightTrans = game_.getEntityManager().
237 } 269 getComponent<TransformableComponent>(right);
238 270
239 case Direction::right: 271 return (leftTrans.x < rightTrans.x);
240 { 272 });
241 newX = -WALL_GAP;
242 273
243 break; 274 for (id_type collider : colliders)
244 } 275 {
276 auto& colliderTrans = game_.getEntityManager().
277 getComponent<TransformableComponent>(collider);
245 278
246 case Direction::up: 279 // Check if the entity would still move into the potential collider.
247 { 280 if (colliderTrans.x >= result.newX + transformable.w)
248 newY = MAP_HEIGHT * TILE_HEIGHT + WALL_GAP - transformable.h; 281 {
282 break;
283 }
249 284
250 break; 285 auto& colliderPonder = game_.getEntityManager().
251 } 286 getComponent<PonderableComponent>(collider);
252 287
253 case Direction::down: 288 processCollision(
254 { 289 entity,
255 newY = -WALL_GAP; 290 collider,
291 Direction::right,
292 colliderPonder.colliderType,
293 colliderTrans.x,
294 colliderTrans.y,
295 colliderTrans.y + colliderTrans.h,
296 result);
297
298 if (result.stopProcessing)
299 {
300 break;
301 }
302 }
256 303
257 break; 304 // If movement hasn't been stopped by an intermediary object, and
258 } 305 // collision checking hasn't been stopped, process the environmental
259 } 306 // boundaries closest to the entity.
260 } 307 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
308 {
309 double boundaryAxis = it->first;
261 310
262 case MappableComponent::Adjacent::Type::warp: 311 for (;
312 (it != std::end(mappable.rightBoundaries)) &&
313 (it->first == boundaryAxis);
314 it++)
315 {
316 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
317 {
318 processCollision(
319 entity,
320 mapEntity,
321 Direction::right,
322 it->second.type,
323 it->first,
324 it->second.lower,
325 it->second.upper,
326 result);
327
328 if (result.stopProcessing)
263 { 329 {
264 if (game_.getEntityManager().
265 hasComponent<PlayableComponent>(entity))
266 {
267 adjacentlyWarping = true;
268 adjWarpDir = collision.getDirection();
269 adjWarpMapId = adj.mapId;
270 }
271
272 break; 330 break;
273 } 331 }
332 }
333 }
334 }
335 }
274 336
275 case MappableComponent::Adjacent::Type::reverse: 337 // Find vertical collisions
276 { 338 result.touchedWall = false;
277 // TODO: not yet implemented.
278 339
279 break; 340 if ((!result.stopProcessing) && (result.newY < oldY))
280 } 341 {
281 } 342 bool boundaryCollision = false;
343 auto it = mappable.upBoundaries.lower_bound(oldY);
344
345 // Find the axis distance of the closest environmental boundary.
346 for (;
347 (it != std::end(mappable.upBoundaries)) &&
348 (it->first >= result.newY);
349 it++)
350 {
351 // Check that the boundary is in range for the other axis.
352 if ((result.newX + transformable.h > it->second.lower) &&
353 (result.newX < it->second.upper))
354 {
355 // We have a collision!
356 boundaryCollision = true;
282 357
283 break; 358 break;
284 } 359 }
360 }
285 361
286 case Collision::Type::danger: 362 // Find a list of potential colliders, sorted so that the closest is
363 // first.
364 std::vector<id_type> colliders;
365
366 for (id_type collider : entities)
367 {
368 // Can't collide with self.
369 if (collider == entity)
287 { 370 {
288 if (game_.getEntityManager(). 371 continue;
289 hasComponent<PlayableComponent>(entity)) 372 }
290 {
291 game_.getSystemManager().getSystem<PlayingSystem>().die(entity);
292 373
293 adjacentlyWarping = false; 374 auto& colliderPonder = game_.getEntityManager().
294 } 375 getComponent<PonderableComponent>(collider);
295 376
296 stopProcessing = true; 377 // Only check objects that are active.
378 if (!colliderPonder.active)
379 {
380 continue;
381 }
297 382
298 break; 383 auto& colliderTrans = game_.getEntityManager().
384 getComponent<TransformableComponent>(collider);
385
386 // Check if the entity would move into the potential collider,
387 if ((colliderTrans.y + colliderTrans.h > result.newY) &&
388 // that it wasn't already colliding,
389 (colliderTrans.y + colliderTrans.h <= oldY) &&
390 // that the position on the other axis is in range,
391 (colliderTrans.x + colliderTrans.w > result.newX) &&
392 (colliderTrans.x < result.newX + transformable.w) &&
393 // and that the collider is not farther away than the environmental
394 // boundary.
395 (!boundaryCollision ||
396 (colliderTrans.y + colliderTrans.h >= it->first)))
397 {
398 colliders.push_back(collider);
299 } 399 }
400 }
401
402 std::sort(
403 std::begin(colliders),
404 std::end(colliders),
405 [&] (id_type left, id_type right) {
406 auto& leftTrans = game_.getEntityManager().
407 getComponent<TransformableComponent>(left);
408
409 auto& rightTrans = game_.getEntityManager().
410 getComponent<TransformableComponent>(right);
300 411
301 default: 412 return (rightTrans.y < leftTrans.y);
413 });
414
415 for (id_type collider : colliders)
416 {
417 auto& colliderTrans = game_.getEntityManager().
418 getComponent<TransformableComponent>(collider);
419
420 // Check if the entity would still move into the potential collider.
421 if (colliderTrans.y + colliderTrans.h <= result.newY)
302 { 422 {
303 // Not yet implemented. 423 break;
424 }
425
426 auto& colliderPonder = game_.getEntityManager().
427 getComponent<PonderableComponent>(collider);
304 428
429 processCollision(
430 entity,
431 collider,
432 Direction::up,
433 colliderPonder.colliderType,
434 colliderTrans.y + colliderTrans.h,
435 colliderTrans.x,
436 colliderTrans.x + colliderTrans.w,
437 result);
438
439 if (result.stopProcessing)
440 {
305 break; 441 break;
306 } 442 }
307 } 443 }
308 444
309 if (stopProcessing) 445 // If movement hasn't been stopped by an intermediary object, and
446 // collision checking hasn't been stopped, process the environmental
447 // boundaries closest to the entity.
448 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
310 { 449 {
311 break; 450 double boundaryAxis = it->first;
451
452 for (;
453 (it != std::end(mappable.upBoundaries)) &&
454 (it->first == boundaryAxis);
455 it++)
456 {
457 if ((result.newX + transformable.w > it->second.lower) &&
458 (result.newX < it->second.upper))
459 {
460 processCollision(
461 entity,
462 mapEntity,
463 Direction::up,
464 it->second.type,
465 it->first,
466 it->second.lower,
467 it->second.upper,
468 result);
469
470 if (result.stopProcessing)
471 {
472 break;
473 }
474 }
475 }
312 } 476 }
477 } else if ((!result.stopProcessing) && (result.newY > oldY))
478 {
479 bool boundaryCollision = false;
480 auto it = mappable.downBoundaries.lower_bound(oldBottom);
481
482 // Find the axis distance of the closest environmental boundary.
483 for (;
484 (it != std::end(mappable.downBoundaries))
485 && (it->first <= (result.newY + transformable.h));
486 it++)
487 {
488 // Check that the boundary is in range for the other axis.
489 if ((result.newX + transformable.w > it->second.lower) &&
490 (result.newX < it->second.upper))
491 {
492 // We have a collision!
493 boundaryCollision = true;
313 494
314 if (touchedWall) 495 break;
496 }
497 }
498
499 // Find a list of potential colliders, sorted so that the closest is
500 // first.
501 std::vector<id_type> colliders;
502
503 for (id_type collider : entities)
315 { 504 {
316 switch (collision.getDirection()) 505 // Can't collide with self.
506 if (collider == entity)
317 { 507 {
318 case Direction::left: 508 continue;
319 { 509 }
320 newX = collision.getAxis();
321 ponderable.velX = 0.0;
322 510
323 break; 511 auto& colliderPonder = game_.getEntityManager().
324 } 512 getComponent<PonderableComponent>(collider);
325 513
326 case Direction::right: 514 // Only check objects that are active.
327 { 515 if (!colliderPonder.active)
328 newX = collision.getAxis() - transformable.w; 516 {
329 ponderable.velX = 0.0; 517 continue;
518 }
330 519
331 break; 520 auto& colliderTrans = game_.getEntityManager().
332 } 521 getComponent<TransformableComponent>(collider);
522
523 // Check if the entity would move into the potential collider,
524 if ((colliderTrans.y < result.newY + transformable.h) &&
525 // that it wasn't already colliding,
526 (colliderTrans.y >= oldBottom) &&
527 // that the position on the other axis is in range,
528 (colliderTrans.x + colliderTrans.w > result.newX) &&
529 (colliderTrans.x < result.newX + transformable.w) &&
530 // and that the collider is not farther away than the environmental
531 // boundary.
532 (!boundaryCollision || (colliderTrans.y <= it->first)))
533 {
534 colliders.push_back(collider);
535 }
536 }
333 537
334 case Direction::up: 538 std::sort(
335 { 539 std::begin(colliders),
336 newY = collision.getAxis(); 540 std::end(colliders),
337 ponderable.velY = 0.0; 541 [&] (id_type left, id_type right) {
542 auto& leftTrans = game_.getEntityManager().
543 getComponent<TransformableComponent>(left);
338 544
339 break; 545 auto& rightTrans = game_.getEntityManager().
340 } 546 getComponent<TransformableComponent>(right);
341 547
342 case Direction::down: 548 return (leftTrans.y < rightTrans.y);
343 { 549 });
344 newY = collision.getAxis() - transformable.h; 550
345 ponderable.velY = 0.0; 551 for (id_type collider : colliders)
346 ponderable.grounded = true; 552 {
553 auto& colliderTrans = game_.getEntityManager().
554 getComponent<TransformableComponent>(collider);
555
556 // Check if the entity would still move into the potential collider.
557 if (colliderTrans.y >= result.newY + transformable.h)
558 {
559 break;
560 }
561
562 auto& colliderPonder = game_.getEntityManager().
563 getComponent<PonderableComponent>(collider);
564
565 processCollision(
566 entity,
567 collider,
568 Direction::down,
569 colliderPonder.colliderType,
570 colliderTrans.y,
571 colliderTrans.x,
572 colliderTrans.x + colliderTrans.w,
573 result);
574
575 if (result.stopProcessing)
576 {
577 break;
578 }
579 }
580
581 // If movement hasn't been stopped by an intermediary object, and
582 // collision checking hasn't been stopped, process the environmental
583 // boundaries closest to the entity.
584 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
585 {
586 double boundaryAxis = it->first;
347 587
348 break; 588 for (;
589 (it != std::end(mappable.downBoundaries)) &&
590 (it->first == boundaryAxis);
591 it++)
592 {
593 if ((result.newX + transformable.w > it->second.lower) &&
594 (result.newX < it->second.upper))
595 {
596 processCollision(
597 entity,
598 mapEntity,
599 Direction::down,
600 it->second.type,
601 it->first,
602 it->second.lower,
603 it->second.upper,
604 result);
605
606 if (result.stopProcessing)
607 {
608 break;
609 }
349 } 610 }
350 } 611 }
351 } 612 }
352 } 613 }
353 614
354 // Move 615 // Move
355 transformable.x = newX; 616 transformable.x = result.newX;
356 transformable.y = newY; 617 transformable.y = result.newY;
357 618
358 // Perform cleanup for orientable entites 619 // Perform cleanup for orientable entites
359 if (game_.getEntityManager().hasComponent<OrientableComponent>(entity)) 620 if (game_.getEntityManager().hasComponent<OrientableComponent>(entity))
@@ -381,12 +642,12 @@ void PonderingSystem::tick(double dt)
381 } 642 }
382 643
383 // Move to an adjacent map, if necessary 644 // Move to an adjacent map, if necessary
384 if (adjacentlyWarping) 645 if (result.adjacentlyWarping)
385 { 646 {
386 double warpX = newX; 647 double warpX = result.newX;
387 double warpY = newY; 648 double warpY = result.newY;
388 649
389 switch (adjWarpDir) 650 switch (result.adjWarpDir)
390 { 651 {
391 case Direction::left: 652 case Direction::left:
392 { 653 {
@@ -420,7 +681,7 @@ void PonderingSystem::tick(double dt)
420 game_.getSystemManager().getSystem<PlayingSystem>(). 681 game_.getSystemManager().getSystem<PlayingSystem>().
421 changeMap( 682 changeMap(
422 entity, 683 entity,
423 adjWarpMapId, 684 result.adjWarpMapId,
424 warpX, 685 warpX,
425 warpY); 686 warpY);
426 } 687 }
@@ -453,3 +714,196 @@ void PonderingSystem::initPrototype(id_type prototype)
453 ponderable.frozen = false; 714 ponderable.frozen = false;
454 ponderable.collidable = true; 715 ponderable.collidable = true;
455} 716}
717
718void PonderingSystem::processCollision(
719 id_type entity,
720 id_type collider,
721 Direction dir,
722 PonderableComponent::Collision type,
723 double axis,
724 double lower,
725 double upper,
726 CollisionResult& result)
727{
728 auto& ponderable = game_.getEntityManager().
729 getComponent<PonderableComponent>(entity);
730
731 auto& transformable = game_.getEntityManager().
732 getComponent<TransformableComponent>(entity);
733
734 switch (type)
735 {
736 case PonderableComponent::Collision::wall:
737 {
738 result.touchedWall = true;
739
740 break;
741 }
742
743 case PonderableComponent::Collision::platform:
744 {
745 if (game_.getEntityManager().
746 hasComponent<OrientableComponent>(entity))
747 {
748 auto& orientable = game_.getEntityManager().
749 getComponent<OrientableComponent>(entity);
750
751 if (orientable.getDropState() !=
752 OrientableComponent::DropState::none)
753 {
754 orientable.setDropState(OrientableComponent::DropState::active);
755 } else {
756 result.touchedWall = true;
757 }
758 } else {
759 result.touchedWall = true;
760 }
761
762 break;
763 }
764
765 case PonderableComponent::Collision::adjacency:
766 {
767 auto& mappable = game_.getEntityManager().
768 getComponent<MappableComponent>(collider);
769
770 auto& adj = [&] () -> const MappableComponent::Adjacent& {
771 switch (dir)
772 {
773 case Direction::left: return mappable.leftAdjacent;
774 case Direction::right: return mappable.rightAdjacent;
775 case Direction::up: return mappable.upAdjacent;
776 case Direction::down: return mappable.downAdjacent;
777 }
778 }();
779
780 switch (adj.type)
781 {
782 case MappableComponent::Adjacent::Type::wall:
783 {
784 result.touchedWall = true;
785
786 break;
787 }
788
789 case MappableComponent::Adjacent::Type::wrap:
790 {
791 switch (dir)
792 {
793 case Direction::left:
794 {
795 result.newX = GAME_WIDTH + WALL_GAP - transformable.w;
796
797 break;
798 }
799
800 case Direction::right:
801 {
802 result.newX = -WALL_GAP;
803
804 break;
805 }
806
807 case Direction::up:
808 {
809 result.newY =
810 MAP_HEIGHT * TILE_HEIGHT + WALL_GAP - transformable.h;
811
812 break;
813 }
814
815 case Direction::down:
816 {
817 result.newY = -WALL_GAP;
818
819 break;
820 }
821 }
822 }
823
824 case MappableComponent::Adjacent::Type::warp:
825 {
826 if (game_.getEntityManager().
827 hasComponent<PlayableComponent>(entity))
828 {
829 result.adjacentlyWarping = true;
830 result.adjWarpDir = dir;
831 result.adjWarpMapId = adj.mapId;
832 }
833
834 break;
835 }
836
837 case MappableComponent::Adjacent::Type::reverse:
838 {
839 // TODO: not yet implemented.
840
841 break;
842 }
843 }
844
845 break;
846 }
847
848 case PonderableComponent::Collision::danger:
849 {
850 if (game_.getEntityManager().
851 hasComponent<PlayableComponent>(entity))
852 {
853 game_.getSystemManager().getSystem<PlayingSystem>().die(entity);
854
855 result.adjacentlyWarping = false;
856 }
857
858 result.stopProcessing = true;
859
860 break;
861 }
862
863 default:
864 {
865 // Not yet implemented.
866
867 break;
868 }
869 }
870
871 if (!result.stopProcessing && result.touchedWall)
872 {
873 switch (dir)
874 {
875 case Direction::left:
876 {
877 result.newX = axis;
878 ponderable.velX = 0.0;
879
880 break;
881 }
882
883 case Direction::right:
884 {
885 result.newX = axis - transformable.w;
886 ponderable.velX = 0.0;
887
888 break;
889 }
890
891 case Direction::up:
892 {
893 result.newY = axis;
894 ponderable.velY = 0.0;
895
896 break;
897 }
898
899 case Direction::down:
900 {
901 result.newY = axis - transformable.h;
902 ponderable.velY = 0.0;
903 ponderable.grounded = true;
904
905 break;
906 }
907 }
908 }
909}
diff --git a/src/systems/pondering.h b/src/systems/pondering.h index 58e6496..aa430db 100644 --- a/src/systems/pondering.h +++ b/src/systems/pondering.h
@@ -18,6 +18,29 @@ public:
18 18
19 void initPrototype(id_type prototype); 19 void initPrototype(id_type prototype);
20 20
21private:
22
23 struct CollisionResult
24 {
25 double newX;
26 double newY;
27 bool stopProcessing = false;
28 bool touchedWall = false;
29 bool adjacentlyWarping = false;
30 Direction adjWarpDir;
31 size_t adjWarpMapId;
32 };
33
34 void processCollision(
35 id_type entity,
36 id_type collider,
37 Direction dir,
38 PonderableComponent::Collision type,
39 double axis,
40 double lower,
41 double upper,
42 CollisionResult& result);
43
21}; 44};
22 45
23#endif /* end of include guard: PONDERING_H_F2530E0E */ 46#endif /* end of include guard: PONDERING_H_F2530E0E */
diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp index c86dd5e..3656acb 100644 --- a/src/systems/realizing.cpp +++ b/src/systems/realizing.cpp
@@ -207,6 +207,10 @@ EntityManager::id_type RealizingSystem::initSingleton(
207 207
208 animatable.origAnimation = "static"; 208 animatable.origAnimation = "static";
209 209
210 // Create a physics body.
211 game_.getSystemManager().getSystem<PonderingSystem>().
212 initializeBody(mapObject, PonderableComponent::Type::vacuumed);
213
210 mappable.objects.push_back(mapObject); 214 mappable.objects.push_back(mapObject);
211 } else if (!xmlStrcmp( 215 } else if (!xmlStrcmp(
212 mapNode->name, 216 mapNode->name,