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(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 + { + 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.