From d3622b34f0c20b514a03d8008e07be132333ca54 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <evan@moonside.games>
Date: Sun, 12 Jul 2020 15:49:08 -0700
Subject: [PATCH] moving

---
 content/pong/ball/moving.md | 153 ++++++++++++++++++++++++------------
 1 file changed, 101 insertions(+), 52 deletions(-)

diff --git a/content/pong/ball/moving.md b/content/pong/ball/moving.md
index 44abda0..780dc17 100644
--- a/content/pong/ball/moving.md
+++ b/content/pong/ball/moving.md
@@ -10,94 +10,143 @@ 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 **game/components/velocity.ts**:
+Let's make a VelocityComponent. In **PongFE/Components/VelocityComponent.cs**:
 
-```ts
-import { Component } from "encompass-ecs";
+```cs
+using System.Numerics;
+using Encompass;
 
-export class VelocityComponent extends Component {
-    public x: number;
-    public y: number;
+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, called a Detector. Let's use it now.
+It turns out Encompass provides a structure for this pattern. Let's use it now.
 
- In **game/engines/velocity.ts**:
+Create **PongFE/Engines/VelocityEngine.cs**:
 
-```ts
-import { Detector, Emits, Entity } from "encompass-ecs";
-import { PositionComponent } from "game/components/position";
-import { VelocityComponent } from "game/components/velocity";
-import { MotionMessage } from "game/messages/component/motion";
+```cs
+using Encompass;
+using PongFE.Components;
+using PongFE.Messages;
 
-@Emits(MotionMessage)
-@Detects(PositionComponent, VelocityComponent)
-export class VelocityEngine extends Detector {
-    protected detect(entity: Entity) {
-        const position_component = entity.get_component(PositionComponent);
-        const velocity_component = entity.get_component(VelocityComponent);
-
-        const motion_message = this.emit_component_message(MotionMessage, position_component);
-        motion_message.x = velocity_component.x;
-        motion_message.y = velocity_component.y;
+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));
+            }
+        }
     }
 }
+
 ```
 
-A Detector, like a Spawner, is an engine with one required method: *detect*.
+**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.
 
-When an Entity has all of the components specified in **@Detects**, it begins to track the Entity. Each frame, it calls its *detect* method on that Entity.
-
-So, our VelocityEngine will track everything with a PositionComponent and VelocityComponent and create a MotionMessage every frame.
+{{% 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:
 
-```ts
-world_builder.add_engine(VelocityEngine);
+```cs
+WorldBuilder.AddEngine(new VelocityEngine());
 ```
 
 And add our new VelocityComponent in the BallSpawner.
 
-```ts
-const velocity_component = ball_entity.add_component(VelocityComponent);
-velocity_component.x = 50;
-velocity_component.y = -50;
+```cs
+AddComponent(ball, new VelocityComponent(new System.Numerics.Vector2(50, -50)));
 ```
 
-Actually lets get rid of that magic value by adding velocity to the BallSpawnMessage.
+Actually... let's get rid of that magic value by adding velocity to the BallSpawnMessage.
 
-**game/messages/ball_spawn.ts**
+**PongFE/Messages/BallSpawnMessage.cs**
 
-```ts
-import { Message } from "encompass-ecs";
+```cs
+using System.Numerics;
+using Encompass;
+using MoonTools.Structs;
 
-export class BallSpawnMessage extends Message {
-    public x: number;
-    public y: number;
-    public size: number;
-    public x_velocity: number;
-    public y_velocity: number;
+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;
+        }
+    }
 }
 ```
 
-**game/engines/spawners/ball.ts**
+**PongFE/Engines/Spawners/BallSpawner.cs**
 
-```ts
-const velocity_component = ball_entity.add_component(VelocityComponent);
-velocity_component.x = message.x_velocity;
-velocity_component.y = message.y_velocity;
+```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(BallSpawnMessage message)
+        {
+            var ball = CreateEntity();
+            AddComponent(ball, new PositionComponent(message.Position));
+            AddComponent(ball, new VelocityComponent(message.Velocity));
+            AddComponent(ball, new Texture2DComponent(BallTexture, 0));
+        }
+    }
+}
 ```
 
-**game/game.ts**
+**PongFEGame.cs**
 
-```ts
-ball_spawn_message.x_velocity = 50;
-ball_spawn_message.y_velocity = -50;
+```cs
+    WorldBuilder.SendMessage(
+        new BallSpawnMessage(
+            new MoonTools.Structs.Position2D(640, 360),
+            new System.Numerics.Vector2(50, -50)
+        )
+    );
 ```
 
 Let's run the game again.