encompass-cs-docs/content/pong/ball/revisiting_spawners.md

9.6 KiB

title date weight
Revisiting Spawners 2019-05-28T21:22:44-07:00 1000

At this point, our LoadContent method in PongFEGame.cs should look like this:

    protected override void LoadContent()
    {
        SpriteBatch = new SpriteBatch(GraphicsDevice);

        WhitePixel = new Texture2D(GraphicsDevice, 1, 1);
        WhitePixel.SetData(new Color[] { Color.White });

        PaddleTexture = new RenderTarget2D(GraphicsDevice, 20, 80);
        GraphicsDevice.SetRenderTarget(PaddleTexture);
        SpriteBatch.Begin();
        SpriteBatch.Draw(WhitePixel, new Rectangle(0, 0, 20, 80), Color.White);
        SpriteBatch.End();

        BallTexture = new RenderTarget2D(GraphicsDevice, 16, 16);
        GraphicsDevice.SetRenderTarget(BallTexture);
        SpriteBatch.Begin();
        SpriteBatch.Draw(WhitePixel, new Rectangle(0, 0, 16, 16), Color.White);
        SpriteBatch.End();
        GraphicsDevice.SetRenderTarget(null);

        WorldBuilder.AddEngine(new InputEngine());
        WorldBuilder.AddEngine(new PaddleMovementEngine());
        WorldBuilder.AddEngine(new VelocityEngine());
        WorldBuilder.AddEngine(new MotionEngine());
        WorldBuilder.AddEngine(new CollisionEngine());
        WorldBuilder.AddEngine(new BounceEngine());
        WorldBuilder.AddEngine(new UpdatePositionEngine());
        WorldBuilder.AddEngine(new UpdateVelocityEngine());

        WorldBuilder.AddEngine(new BallSpawner(BallTexture));
        WorldBuilder.AddEngine(new BoundarySpawner());

        WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch));

        var paddle = WorldBuilder.CreateEntity();
        WorldBuilder.SetComponent(paddle, new PlayerInputComponent(PongFE.Components.PlayerIndex.One));
        WorldBuilder.SetComponent(paddle, new PaddleMoveSpeedComponent(400));
        WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5)));
        WorldBuilder.SetComponent(paddle, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, 20, 80)));
        WorldBuilder.SetComponent(paddle, new CanCauseBounceComponent());
        WorldBuilder.SetComponent(paddle, new Texture2DComponent(PaddleTexture, 0));

        WorldBuilder.SendMessage(
            new BallSpawnMessage(
                new MoonTools.Structs.Position2D(640, 360),
                new System.Numerics.Vector2(-200, -50)
            )
        );

        // top boundary
        WorldBuilder.SendMessage(
            new BoundarySpawnMessage(
                new MoonTools.Structs.Position2D(0, -6),
                1280,
                6
            )
        );

        // right boundary
        WorldBuilder.SendMessage(
            new BoundarySpawnMessage(
                new MoonTools.Structs.Position2D(1280, 0),
                6,
                720
            )
        );

        // bottom boundary
        WorldBuilder.SendMessage(
            new BoundarySpawnMessage(
                new MoonTools.Structs.Position2D(0, 720),
                1280,
                6
            )
        );

        World = WorldBuilder.Build();
    }

That paddle entity is looking out of place. Why don't we make a Spawner for it?

PongFE/Messages/PaddleSpawnMessage.cs:

using Encompass;
using MoonTools.Structs;

namespace PongFE.Messages
{
    public struct PaddleSpawnMessage : IMessage
    {
        public Position2D Position { get; }
        public PlayerIndex PlayerIndex { get; }
        public int Width { get; }
        public int Height { get; }

        public PaddleSpawnMessage(Position2D position, PlayerIndex playerIndex, int width, int height)
        {
            Position = position;
            PlayerIndex = playerIndex;
            Width = width;
            Height = height;
        }
    }
}

Actually while we're here, let's consolidate our Enums.

Create a file, Enums.cs:

namespace PongFE.Enums
{
    public enum PlayerIndex
    {
        One,
        Two
    }

    public enum HitOrientation
    {
        Horizontal,
        Vertical
    }

    public enum PaddleMoveDirection
    {
        Up,
        Down
    }
}

Remove these references in the other files and make sure anything that uses these enums includes the line

using PongFE.Enums;

Now for the paddle spawner.

In PongFE/Engines/Spawners/PaddleSpawner.cs:

using Encompass;
using Microsoft.Xna.Framework.Graphics;
using PongFE.Components;
using PongFE.Messages;

namespace PongFE.Spawners
{
    public class PaddleSpawner : Spawner<PaddleSpawnMessage>
    {
        private Texture2D PaddleTexture { get; }

        public PaddleSpawner(Texture2D paddleTexture)
        {
            PaddleTexture = paddleTexture;
        }

        protected override void Spawn(in PaddleSpawnMessage message)
        {
            var paddle = CreateEntity();
            AddComponent(paddle, new Texture2DComponent(PaddleTexture, 0));
        }
    }
}

Actually, I'm starting to get a bad feeling here again. What if we wanted to change the size of the paddle? We have an implicit dependency between the paddle texture and the collision size, so why not make it explicit instead? All we'd have to do is scale a white pixel to our desired size to draw a filled rectangle.

In PongFE/Components/Texture2DComponent.cs:

using System.Numerics;
using Encompass;
using Microsoft.Xna.Framework.Graphics;

namespace PongFE.Components
{
    public struct Texture2DComponent : IComponent, IDrawableComponent
    {
        public Texture2D Texture { get; }
        public Vector2 Scale { get; }
        public int Layer { get; }

        public Texture2DComponent(Texture2D texture, int layer, Vector2 scale)
        {
            Texture = texture;
            Scale = scale;
            Layer = layer;
        }
    }
}

And in PongFE/Renderers/Texture2DRenderer.cs:

public override void Render(Entity entity, in Texture2DComponent textureComponent)
{
    ref readonly var positionComponent = ref GetComponent<PositionComponent>(entity);

    _spriteBatch.Draw(
        textureComponent.Texture,
        positionComponent.Position.ToXNAVector(),
        null,
        Color.White,
        0,
        Vector2.Zero,
        textureComponent.Scale.ToXNAVector(),
        SpriteEffects.None,
        0
    );
}

Now go back to the paddle spawner...

using Encompass;
using Microsoft.Xna.Framework.Graphics;
using PongFE.Components;
using PongFE.Messages;

namespace PongFE.Spawners
{
    public class PaddleSpawner : Spawner<PaddleSpawnMessage>
    {
        private Texture2D WhitePixel { get; }

        public PaddleSpawner(Texture2D whitePixel)
        {
            WhitePixel = whitePixel;
        }

        protected override void Spawn(in PaddleSpawnMessage message)
        {
            var paddle = CreateEntity();
            AddComponent(paddle, new PlayerInputComponent(message.PlayerIndex));
            AddComponent(paddle, new PaddleMoveSpeedComponent(400));
            AddComponent(paddle, new PositionComponent(message.Position));
            AddComponent(paddle, new CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, message.Width, message.Height)));
            AddComponent(paddle, new CanCauseBounceComponent());
            AddComponent(paddle, new Texture2DComponent(WhitePixel, 0, new System.Numerics.Vector2(message.Width, message.Height)));
        }
    }
}

And in PongFEGame.cs:

WorldBuilder.AddEngine(new PaddleSpawner(WhitePixel));

...

WorldBuilder.SendMessage(
    new PaddleSpawnMessage(
        new MoonTools.Structs.Position2D(5, 5),
        Enums.PlayerIndex.One,
        20,
        80
    )
);

You can get rid of the PaddleTexture references too. Why don't we do this for the ball as well?

PongFE/Messages/BallSpawnMessage.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 int Width { get; }
        public int Height { get; }

        public BallSpawnMessage(Position2D position, Vector2 velocity, int width, int height)
        {
            Position = position;
            Velocity = velocity;
            Width = width;
            Height = height;
        }
    }
}

PongFE/Engines/Spawners/BallSpawner.cs:

using Encompass;
using Microsoft.Xna.Framework.Graphics;
using PongFE.Components;
using PongFE.Messages;

namespace PongFE.Spawners
{
    public class BallSpawner : Spawner<BallSpawnMessage>
    {
        private Texture2D WhitePixel { get; }

        public BallSpawner(Texture2D whitePixel)
        {
            WhitePixel = whitePixel;
        }

        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 CollisionComponent(new MoonTools.Bonk.Rectangle(0, 0, 16, 16)));
            AddComponent(ball, new Texture2DComponent(WhitePixel, 0, new System.Numerics.Vector2(message.Width, message.Height)));
            AddComponent(ball, new CanBeBouncedComponent());
            AddComponent(ball, new BounceResponseComponent());
        }
    }
}

PongFEGame.cs

WorldBuilder.AddEngine(new BallSpawner(WhitePixel));

You can get rid of the BallTexture now too. We reduced the number of textures we're using and ensured that the visual display of our game entities will match their collision detection. It's always a good idea to analyze your current structures and see if they can be improved!