159 lines
4.5 KiB
Markdown
159 lines
4.5 KiB
Markdown
---
|
|
title: "Moving"
|
|
date: 2019-05-23T18:10:17-07:00
|
|
weight: 20
|
|
---
|
|
|
|
We already have MotionMessages and a MotionEngine. So it seems logical to re-use these structures for our ball.
|
|
|
|
What is actually going to be sending out the MotionMessages?
|
|
|
|
What is the main characteristic of the ball in Pong? That's right - it is continuously moving. In other words, it has velocity.
|
|
|
|
Let's make a VelocityComponent. In **PongFE/Components/VelocityComponent.cs**:
|
|
|
|
```cs
|
|
using System.Numerics;
|
|
using Encompass;
|
|
|
|
namespace PongFE.Components
|
|
{
|
|
public struct VelocityComponent : IComponent
|
|
{
|
|
public Vector2 Velocity { get; }
|
|
|
|
public VelocityComponent(Vector2 velocity)
|
|
{
|
|
Velocity = velocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
Now, the XNA API actually provides us with a Vector2 struct. Why am I using System.Numerics here? The answer is that it is more up-to-date and optimized than the XNA specification. As long as we can convert the values when we need to hand the Vector2 off to something, it won't be an issue.
|
|
|
|
Let's also create a VelocityEngine.
|
|
|
|
What does our VelocityEngine actually do? Basically, if something has both a PositionComponent and VelocityComponent, we want the PositionComponent to update based on the VelocityComponent every frame.
|
|
|
|
It turns out Encompass provides a structure for this pattern. Let's use it now.
|
|
|
|
Create **PongFE/Engines/VelocityEngine.cs**:
|
|
|
|
```cs
|
|
using Encompass;
|
|
using PongFE.Components;
|
|
using PongFE.Messages;
|
|
|
|
namespace PongFE.Engines
|
|
{
|
|
[Sends(typeof(MotionMessage))]
|
|
[QueryWith(typeof(PositionComponent), typeof(VelocityComponent))]
|
|
public class VelocityEngine : Engine
|
|
{
|
|
public override void Update(double dt)
|
|
{
|
|
foreach (var entity in TrackedEntities)
|
|
{
|
|
ref readonly var velocityComponent = ref GetComponent<VelocityComponent>(entity);
|
|
SendMessage(new MotionMessage(entity, velocityComponent.Velocity * (float)dt));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
**QueryWith** is a class attribute that allows us to specify a set of components that will cause the Engine to track an entity. In this case, our QueryWith attribute will cause entities that have both a PositionComponent and a VelocityComponent to be tracked. QueryWith also implicitly creates Reads for the relevant Components.
|
|
|
|
{{% notice note %}}
|
|
There is also a **QueryWithout** that will exclude entities from tracking if they have the specified component(s). This can come in handy if you, say, want to temporarily pause motion or something.
|
|
{{% /notice %}}
|
|
|
|
Let's add our new Engine to the WorldBuilder:
|
|
|
|
```cs
|
|
WorldBuilder.AddEngine(new VelocityEngine());
|
|
```
|
|
|
|
And add our new VelocityComponent in the BallSpawner.
|
|
|
|
```cs
|
|
AddComponent(ball, new VelocityComponent(new System.Numerics.Vector2(50, -50)));
|
|
```
|
|
|
|
Actually... let's get rid of that magic value by adding velocity to the BallSpawnMessage.
|
|
|
|
**PongFE/Messages/BallSpawnMessage.cs**
|
|
|
|
```cs
|
|
using System.Numerics;
|
|
using Encompass;
|
|
using MoonTools.Structs;
|
|
|
|
namespace PongFE.Messages
|
|
{
|
|
public struct BallSpawnMessage : IMessage
|
|
{
|
|
public Position2D Position { get; }
|
|
public Vector2 Velocity { get; }
|
|
|
|
public BallSpawnMessage(Position2D position, Vector2 velocity)
|
|
{
|
|
Position = position;
|
|
Velocity = velocity;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**PongFE/Engines/Spawners/BallSpawner.cs**
|
|
|
|
```cs
|
|
using Encompass;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using PongFE.Components;
|
|
using PongFE.Messages;
|
|
|
|
namespace PongFE.Spawners
|
|
{
|
|
public class BallSpawner : Spawner<BallSpawnMessage>
|
|
{
|
|
private Texture2D BallTexture { get; }
|
|
|
|
public BallSpawner(Texture2D ballTexture)
|
|
{
|
|
BallTexture = ballTexture;
|
|
}
|
|
|
|
protected override void Spawn(in BallSpawnMessage message)
|
|
{
|
|
var ball = CreateEntity();
|
|
AddComponent(ball, new PositionComponent(message.Position));
|
|
AddComponent(ball, new VelocityComponent(message.Velocity));
|
|
AddComponent(ball, new Texture2DComponent(BallTexture, 0));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**PongFEGame.cs**
|
|
|
|
```cs
|
|
WorldBuilder.SendMessage(
|
|
new BallSpawnMessage(
|
|
new MoonTools.Structs.Position2D(640, 360),
|
|
new System.Numerics.Vector2(50, -50)
|
|
)
|
|
);
|
|
```
|
|
|
|
Let's run the game again.
|
|
|
|
<video width="75%" autoplay="autoplay" muted="muted" loop="loop" style="display: block; margin: 0 auto;">
|
|
<source src="/images/moving_ball.webm" type="video/webm">
|
|
</video>
|
|
|
|
Still pretty boring but we're getting somewhere.
|