encompass-cs-docs/content/pong/ball/bouncing/design.md

59 lines
4.4 KiB
Markdown
Raw Normal View History

2019-05-29 04:05:36 +00:00
---
title: "Design"
date: 2019-05-28T17:27:59-07:00
weight: 400
---
2020-07-13 08:45:56 +00:00
Now we have a way to tell when objects are colliding, so let's put together our collision system.
2019-05-29 04:05:36 +00:00
First, let's think about the structure of a collision system, and how we want it to resolve collisions.
2020-07-13 08:45:56 +00:00
One thing we really don't want is for collision to resolve late - for example, a frame finishing with two solid objects lodged inside each other, and then fixing itself on the next frame. Even if it's only for a frame, it's enough for players to detect some awkwardness. It really does look bad.
Think about it... if you were an animator, and you sent an animation test to a director where two objects were overlapping each other for a frame, they would send it back. Games are largely an animation-based medium. So we should take this stuff seriously.
2019-05-29 04:05:36 +00:00
We also need objects to behave differently based on _what_ they collide with. For example, a ball colliding with the top boundary is going to react differently than if it collides with a goal boundary.
2020-07-13 08:45:56 +00:00
One possibility would be to give entities components that tell us what they are. For example, an entity that has BallComponent would react when it collides with an entity that has GoalComponent. I don't like this approach. The power of ECS is that it lets us attain high flexibility and reusability by defining entities by their _behavior_. If we are building our simulation based on object types, we might as well just do object-oriented programming. So we really should be asking what entities _do_, not what they _are_. A ball entity increases the score of one player and is destroyed when it collides with a goal.
I think a good way to achieve this goal is to break collision down into three overarching elements.
**Actors** do things to objects. For example, a **CanDamageComponent** would be an Actor that is capable of causing damage to other objects.
2019-05-29 04:05:36 +00:00
2020-07-13 08:45:56 +00:00
**Receivers** receive actions. For example, a **CanBeDamagedComponent** could be damaged by a **CanDamageComponent** Actor.
2019-05-29 04:05:36 +00:00
2020-07-13 08:45:56 +00:00
**Responses** do something when a **Receiver** is hit by an **Actor**. For example, a **DieWhenDamagedComponent** would cause a **CanBeDamagedComponent** Receiver object to die when hit by a **CanDamageComponent** Actor.
2019-05-29 04:05:36 +00:00
2020-07-13 08:45:56 +00:00
All collision related behavior can be described by breaking it down into these three elements.
Here's the breakdown:
2019-05-29 04:05:36 +00:00
- Tell things to move
2020-07-13 08:45:56 +00:00
- Calculate the position objects would potentially move to
- Sweep from starting positions to ending positions, checking for solid collisions along the way
- If collision with a solid is detected during the sweep, adjust movement
- Update game state appropriately in response to what collided with what
2019-05-29 04:05:36 +00:00
We already have MotionMessages. Let's keep those, but redirect them slightly. We can't read MotionMessages and then Emit them again later down the line: that would cause a cycle. So let's have a new UpdatePositionMessage.
2020-07-13 08:45:56 +00:00
Let's have the MotionEngine consolidate all the MotionMessages per-component, and send out an UpdatePositionMessage with that final delta after a sweep check.
2020-07-21 22:20:25 +00:00
Finally, a CollisionEngine figures out what two kinds of objects collided and emits a Message in response. We can then implement various collision resolution Engines that read each of those kinds of Messages. For example, a ScoreEngine would receive **ScoreMessages**, and perform behavior based on attached **Responses**, like an **IncreaseScoreResponseComponent**.
2019-05-29 04:05:36 +00:00
2020-07-13 08:45:56 +00:00
We can visualize the flow of data like so:
2019-06-11 19:25:21 +00:00
{{<mermaid align="center">}}
graph TD;
2020-07-13 08:45:56 +00:00
Engines(Various Engines) -->|MotionMessages| MotionEngine[MotionEngine]
MotionEngine -->|UpdatePositionMessages| UpdatePositionEngine
MotionEngine -->|CollisionMessages| CollisionEngine
CollisionEngine -->|ScoreMessages| ScoreEngine
CollisionEngine -->|DamageMessages| DamageEngine
2019-06-11 19:25:21 +00:00
{{< /mermaid >}}
2019-05-29 04:05:36 +00:00
2020-07-13 08:45:56 +00:00
Whew! That's kind of complicated! You might be wondering why we're doing all this work just to get some objects reacting to each other.
2019-05-29 04:05:36 +00:00
The thing is: this kind of thing is the backbone of many game designs. So of course it's a bit complex! The point is that there's no point obscuring the complexity of such a system or putting off thinking about it until later. If we build our project on a shoddy foundation we will surely have problems later. Let's get a robust system in there so we don't have to fundamentally reorganize our program at a later time, when it will be more frustrating and have more potential to break things.
Let's get started.