update for new version of encompass
continuous-integration/drone/push Build is passing Details

main
cosmonaut 2021-05-01 12:07:59 -07:00
parent 66fe9ecefb
commit be028dd21c
29 changed files with 118 additions and 180 deletions

View File

@ -6,13 +6,11 @@ weight: 5
A Component is a structure of related data. A Component is a structure of related data.
To define a Component, declare a struct which implements the **IComponent** interface.
```cs ```cs
using Encompass; using Encompass;
using System.Numerics; using System.Numerics;
public struct VelocityComponent : IComponent { public struct VelocityComponent {
public Vector2 Velocity { get; } public Vector2 Velocity { get; }

View File

@ -8,12 +8,12 @@ Similar to Components, Messages are collections of data.
Messages are used to transmit data between Engines so they can manipulate the game state accordingly. Messages are used to transmit data between Engines so they can manipulate the game state accordingly.
To define a message, declare a struct which implements the IMessage interface. To define a message, declare a struct.
```cs ```cs
using Encompass; using Encompass;
public struct MotionMessage : IMessage { public struct MotionMessage {
public Vector2 Motion { get; } public Vector2 Motion { get; }
} }
``` ```

View File

@ -10,11 +10,7 @@ A Renderer is responsible for reading the game state and telling the game engine
Remember: Encompass isn't a game engine and it doesn't have a rendering system. So Renderers aren't actually doing the rendering, it is just a way of structuring how we tell the game engine what to render. Remember: Encompass isn't a game engine and it doesn't have a rendering system. So Renderers aren't actually doing the rendering, it is just a way of structuring how we tell the game engine what to render.
{{% /notice %}} {{% /notice %}}
There are two kinds of renderers: GeneralRenderers and OrderedRenderers. If you were using FNA, a Renderer might look like this:
A GeneralRenderer is a Renderer which reads the game state in order to draw elements to the screen. It also requires a layer, which represents the order in which the Draw method will execute in relation to other Renderers.
If you were using FNA, a GeneralRenderer might look like this:
```cs ```cs
using System; using System;
@ -25,12 +21,12 @@ using MyGame.Messages;
namespace MyGame.Renderers namespace MyGame.Renderers
{ {
public class GridRenderer : GeneralRenderer public class GridRenderer : Renderer
{ {
private int gridSize; private int gridSize;
private PrimitiveDrawer primitiveDrawer; private PrimitiveDrawer primitiveDrawer;
public GridRenderer(PrimitiveDrawer primitiveDrawer) public Renderer(PrimitiveDrawer primitiveDrawer)
{ {
this.primitiveDrawer = primitiveDrawer; this.primitiveDrawer = primitiveDrawer;
this.gridSize = 16; this.gridSize = 16;
@ -58,51 +54,6 @@ namespace MyGame.Renderers
} }
``` ```
This GeneralRenderer will draw a rectangle at the position of the mouse on the screen if an `EditorModeComponent` exists in the world. This Renderer will draw a rectangle at the position of the mouse on the screen if an `EditorModeComponent` exists in the world.
GeneralRenderers are great for things like a heads-up display, where we always want a group of particular elements to be drawn at a specific layer regardless of the specifics of the game state. It's your job to figure out how to interpret your game's data into rendering. Many games will only have one big Renderer that just renders everything.
An OrderedRenderer provides a structure for the common pattern of wanting to draw an individual Component at a specific layer. OrderedRenderers must specify a component that implements IDrawableComponent.
If you were using FNA, an OrderedRenderer might look like this:
```cs
using Encompass;
using Microsoft.Xna.Framework.Graphics;
using MyGame.Components;
using MyGame.Extensions;
using System;
using System.Numerics;
namespace MyGame.Renderers
{
public class Texture2DRenderer : OrderedRenderer<Texture2DComponent>
{
private SpriteBatch spriteBatch;
public Texture2DRenderer(SpriteBatch spriteBatch)
{
this.spriteBatch = spriteBatch;
}
public override void Render(Entity entity, Texture2DComponent textureComponent)
{
var transformComponent = GetComponent<TransformComponent>(entity);
spriteBatch.Draw(
textureComponent.Texture,
transformComponent.Position,
null,
textureComponent.Color,
transformComponent.Rotation,
textureComponent.Origin,
transformComponent.Scale,
SpriteEffects.None,
0
);
}
}
}
```
For 2D games, you will need to use layers to be specific about the order in which elements are drawn to the screen. For a 3D game you will probably end up delegating most of the rendering to some kind of scene/camera system.

View File

@ -27,7 +27,7 @@ public class MyGame : Game
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new MotionEngine()); worldBuilder.AddEngine(new MotionEngine());
worldBuilder.AddOrderedRenderer(new TextureRenderer()); worldBuilder.AddRenderer(new TextureRenderer());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();

View File

@ -14,7 +14,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct BoundarySpawnMessage : IMessage public struct BoundarySpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public int Width { get; } public int Width { get; }

View File

@ -15,7 +15,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanCauseBounceComponent : IComponent { } public struct CanCauseBounceComponent { }
} }
``` ```
@ -26,7 +26,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanBeBouncedComponent : IComponent { } public struct CanBeBouncedComponent { }
} }
``` ```
@ -38,7 +38,7 @@ using PongFE.Components;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct BounceMessage : IMessage, IHasEntity public struct BounceMessage : IHasEntity
{ {
public Entity Entity { get; } public Entity Entity { get; }
public HitOrientation HitOrientation { get; } public HitOrientation HitOrientation { get; }

View File

@ -62,7 +62,7 @@ using Encompass;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct UpdateVelocityMessage : IMessage, IHasEntity public struct UpdateVelocityMessage : IHasEntity
{ {
public Entity Entity { get; } public Entity Entity { get; }
public Vector2 Velocity { get; } public Vector2 Velocity { get; }

View File

@ -20,7 +20,7 @@ using MoonTools.Bonk;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CollisionComponent : IComponent public struct CollisionComponent
{ {
public Rectangle Rectangle { get; } public Rectangle Rectangle { get; }
@ -72,7 +72,7 @@ namespace PongFE.Engines
} }
``` ```
First, at the very beginning of the processing, we insert all of our entities with a Position and Collision into the SpatialHash. First, at the very beginning of the processing, we insert all of our entities with a Position and Collision into the SpatialHash.
Next, in a separate block, let's consolidate our MotionMessages per Entity. Next, in a separate block, let's consolidate our MotionMessages per Entity.
@ -146,7 +146,7 @@ Finally, let's implement our sweep test.
} }
``` ```
Here we use Bonk's **SweepTest** functionality. A sweep test moves a rectangle along a vector, checking for collisions along the movement of the sweep. First we sweep in a horizontal direction, and then in a vertical direction, returning the positions where collisions occurred. This means that objects won't awkwardly stop in place when they touch something. Here we use Bonk's **SweepTest** functionality. A sweep test moves a rectangle along a vector, checking for collisions along the movement of the sweep. First we sweep in a horizontal direction, and then in a vertical direction, returning the positions where collisions occurred. This means that objects won't awkwardly stop in place when they touch something.
{{% notice note %}} {{% notice note %}}
In the future you might want to change how the sweep tests resolve a position, and this would certainly be possible using this system. You could have different components like **SlideOnCollisionComponent** or **StickOnCollisionComponent**, for example, that would affect the returned sweep position. In the future you might want to change how the sweep tests resolve a position, and this would certainly be possible using this system. You could have different components like **SlideOnCollisionComponent** or **StickOnCollisionComponent**, for example, that would affect the returned sweep position.
@ -167,7 +167,7 @@ namespace PongFE.Messages
Vertical Vertical
} }
public struct CollisionMessage : IMessage public struct CollisionMessage
{ {
public Entity EntityA { get; } public Entity EntityA { get; }
public Entity EntityB { get; } public Entity EntityB { get; }
@ -193,7 +193,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct UpdatePositionMessage : IMessage, IHasEntity public struct UpdatePositionMessage : IHasEntity
{ {
public Entity Entity { get; } public Entity Entity { get; }
public Position2D Position { get; } public Position2D Position { get; }

View File

@ -18,7 +18,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct VelocityComponent : IComponent public struct VelocityComponent
{ {
public Vector2 Velocity { get; } public Vector2 Velocity { get; }
@ -94,7 +94,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct BallSpawnMessage : IMessage public struct BallSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public Vector2 Velocity { get; } public Vector2 Velocity { get; }

View File

@ -97,7 +97,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct PaddleSpawnMessage : IMessage public struct PaddleSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }
@ -189,17 +189,15 @@ using Microsoft.Xna.Framework.Graphics;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct Texture2DComponent : IComponent, IDrawableComponent public struct Texture2DComponent
{ {
public Texture2D Texture { get; } public Texture2D Texture { get; }
public Vector2 Scale { get; } public Vector2 Scale { get; }
public int Layer { get; }
public Texture2DComponent(Texture2D texture, int layer, Vector2 scale) public Texture2DComponent(Texture2D texture, Vector2 scale)
{ {
Texture = texture; Texture = texture;
Scale = scale; Scale = scale;
Layer = layer;
} }
} }
} }
@ -208,22 +206,17 @@ namespace PongFE.Components
And in **PongFE/Renderers/Texture2DRenderer.cs**: And in **PongFE/Renderers/Texture2DRenderer.cs**:
```cs ```cs
public override void Render(Entity entity, in Texture2DComponent textureComponent) _spriteBatch.Draw(
{ textureComponent.Texture,
ref readonly var positionComponent = ref GetComponent<PositionComponent>(entity); positionComponent.Position.ToXNAVector(),
null,
_spriteBatch.Draw( Color.White,
textureComponent.Texture, 0,
positionComponent.Position.ToXNAVector(), Vector2.Zero,
null, textureComponent.Scale.ToXNAVector(),
Color.White, SpriteEffects.None,
0, 0
Vector2.Zero, );
textureComponent.Scale.ToXNAVector(),
SpriteEffects.None,
0
);
}
``` ```
Now go back to the paddle spawner... Now go back to the paddle spawner...
@ -287,7 +280,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct BallSpawnMessage : IMessage public struct BallSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public Vector2 Velocity { get; } public Vector2 Velocity { get; }

View File

@ -40,7 +40,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct BallSpawnMessage : IMessage public struct BallSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }

View File

@ -6,10 +6,6 @@ weight: 10
It's nice to see something on screen right away when we start making a game, so let's make that happen. It's nice to see something on screen right away when we start making a game, so let's make that happen.
In a 2D game, Encompass needs to know which order that things should draw in. We'll need two things to get a paddle drawing on screen: A *Component* and an *Renderer*.
Encompass draws things back to front using integer layers. A negative value means farther in the back. A positive value means farther in the front. So an object on layer 10 will draw on top of an object on layer -10.
We'll need two things to get a paddle drawing on screen: A *DrawComponent* and an *OrderedRenderer*.
Let's start with the Component. Let's start with the Component.

View File

@ -86,7 +86,7 @@ Now we want to attach our Texture2DRenderer to the World.
```cs ```cs
... ...
WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch)); WorldBuilder.AddRenderer(new Texture2DRenderer(SpriteBatch));
var paddle = WorldBuilder.CreateEntity(); var paddle = WorldBuilder.CreateEntity();
... ...
@ -103,9 +103,7 @@ Our game's Draw method should look like this:
{ {
GraphicsDevice.Clear(Color.CornflowerBlue); GraphicsDevice.Clear(Color.CornflowerBlue);
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
World.Draw(); World.Draw();
SpriteBatch.End();
base.Draw(gameTime); base.Draw(gameTime);
} }
@ -161,7 +159,7 @@ namespace PongFE
SpriteBatch.End(); SpriteBatch.End();
GraphicsDevice.SetRenderTarget(null); GraphicsDevice.SetRenderTarget(null);
WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch)); WorldBuilder.AddRenderer(new Texture2DRenderer(SpriteBatch));
var paddle = WorldBuilder.CreateEntity(); var paddle = WorldBuilder.CreateEntity();
WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5))); WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5)));
@ -186,9 +184,7 @@ namespace PongFE
{ {
GraphicsDevice.Clear(Color.CornflowerBlue); GraphicsDevice.Clear(Color.CornflowerBlue);
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
World.Draw(); World.Draw();
SpriteBatch.End();
base.Draw(gameTime); base.Draw(gameTime);
} }

View File

@ -48,7 +48,7 @@ using MoonTools.Structs;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct PositionComponent : IComponent public struct PositionComponent
{ {
public Position2D Position { get; } public Position2D Position { get; }

View File

@ -6,25 +6,22 @@ weight: 5
In a 2D game using FNA, the main way you will be drawing elements to the screen is via Texture2D. In a 2D game using FNA, the main way you will be drawing elements to the screen is via Texture2D.
Let's set up a Texture2DComponent. To create a new Component type, we define a struct that implements the IComponent interface. Let's set up a Texture2DComponent. To create a new Component type, we define a struct.
Create a file: **PongFE/Components/Texture2DComponent.cs** Create a file: **PongFE/Components/Texture2DComponent.cs**
```cs ```cs
using Encompass;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct Texture2DComponent : IComponent, IDrawableComponent public struct Texture2DComponent
{ {
public Texture2D Texture { get; } public Texture2D Texture { get; }
public int Layer { get; }
public Texture2DComponent(Texture2D texture, int layer) public Texture2DComponent(Texture2D texture)
{ {
Texture = texture; Texture = texture;
Layer = layer;
} }
} }
} }
@ -32,7 +29,7 @@ namespace PongFE.Components
There's a lot of new information here, so let's break this down a bit. There's a lot of new information here, so let's break this down a bit.
*using* means that we are using functionality provided to us by another project. In this case, Encompass provides us with the IComponent and IDrawableComponent interfaces, and FNA provides us with the Texture2D class through the Microsoft.Xna.Framework.Graphics namespace. *using* means that we are using functionality provided to us by another project. In this case, FNA provides us with the Texture2D class through the Microsoft.Xna.Framework.Graphics namespace.
*namespace* is used for organization and to prevent naming collisions. Let's say that we have a Texture2DComponent here, but another library that we include also defines something called Texture2DComponent. Now we have an ambiguity! Using namespaces avoids this problem. *namespace* is used for organization and to prevent naming collisions. Let's say that we have a Texture2DComponent here, but another library that we include also defines something called Texture2DComponent. Now we have an ambiguity! Using namespaces avoids this problem.
@ -42,8 +39,6 @@ Why is the namespace Microsoft.Xna.Framework instead of just FNA? Remember that
*public* means you would like Texture2DComponent to be accessible in other files and projects. Most of your classes and structs will be public, though there are cases where you might want them to be *private* or *internal*, for example a utility class that you don't want to expose externally. *public* means you would like Texture2DComponent to be accessible in other files and projects. Most of your classes and structs will be public, though there are cases where you might want them to be *private* or *internal*, for example a utility class that you don't want to expose externally.
What is IDrawableComponent? An IDrawableComponent is an interface that lets Encompass know that the Component includes a *Layer* property, which is used for rendering. Any time you want a component to be drawn in a specific order before or after other components, you will need to declare that your component implements IDrawableComponent.
Finally, that method is called a *constructor*. When we create an instance of Texture2DComponent, we will assign it a Texture2D that has stuff drawn on it and tell it which layer to use. We'll get to all that in a minute. Finally, that method is called a *constructor*. When we create an instance of Texture2DComponent, we will assign it a Texture2D that has stuff drawn on it and tell it which layer to use. We'll get to all that in a minute.
That's it for our component. We need one more bit of information before we can write our Renderer. That's it for our component. We need one more bit of information before we can write our Renderer.

View File

@ -16,9 +16,9 @@ using PongFE.Components;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class Texture2DRenderer : OrderedRenderer<Texture2DComponent> public class Texture2DRenderer : Renderer
{ {
public override void Render(Entity entity, in Texture2DComponent textureComponent) public override void Render()
{ {
} }
@ -30,8 +30,6 @@ Before we go any further, let's talk about some of the new concepts introduced h
Notice that this is a *class* instead of a struct, like we have done before. There are many reasons why you might want to use classes vs structs in C# in general, but for our purposes, you can use the following rule of thumb: if the object contains data, it should be a struct. If it contains logic, it should be a class. Since Renderers draw things (logic), they are classes. Notice that this is a *class* instead of a struct, like we have done before. There are many reasons why you might want to use classes vs structs in C# in general, but for our purposes, you can use the following rule of thumb: if the object contains data, it should be a struct. If it contains logic, it should be a class. Since Renderers draw things (logic), they are classes.
An *OrderedRenderer* is defined by the Component type it tracks and a *Render* method. It uses the *Layer* property of the specified Component to draw things in the correct order. Each time *World.Draw* is called, the OrderedRenderer will run its *Render* method for each Entity that contains a Component of its specified tracking type.
The most efficient way for us to draw Texture2Ds is to use FNA's SpriteBatch system. If you actually care about how SpriteBatch works and why we need to structure our draws using it, read the Note section below. Otherwise feel free to just skip ahead. The most efficient way for us to draw Texture2Ds is to use FNA's SpriteBatch system. If you actually care about how SpriteBatch works and why we need to structure our draws using it, read the Note section below. Otherwise feel free to just skip ahead.
{{% notice note %}} {{% notice note %}}
@ -52,7 +50,7 @@ using PongFE.Components;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class Texture2DRenderer : OrderedRenderer<Texture2DComponent> public class Texture2DRenderer : Renderer
{ {
private readonly SpriteBatch _spriteBatch; private readonly SpriteBatch _spriteBatch;
@ -61,21 +59,28 @@ namespace PongFE.Renderers
_spriteBatch = spriteBatch; _spriteBatch = spriteBatch;
} }
public override void Render(Entity entity, in Texture2DComponent textureComponent) public override void Render()
{ {
ref readonly var positionComponent = ref GetComponent<PositionComponent>(entity); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
_spriteBatch.Draw( foreach (ref readonly var entity in ReadEntities<Texture2DComponent>())
textureComponent.Texture, {
positionComponent.Position, ref readonly var positionComponent = ref GetComponent<PositionComponent>(entity);
null,
Color.White, _spriteBatch.Draw(
0, textureComponent.Texture,
Vector2.Zero, positionComponent.Position,
Vector2.One, null,
SpriteEffects.None, Color.White,
0 0,
); Vector2.Zero,
Vector2.One,
SpriteEffects.None,
0
);
}
SpriteBatch.End();
} }
} }
} }
@ -83,9 +88,11 @@ namespace PongFE.Renderers
First of all, when we construct the Texture2DRenderer, we will pass in a SpriteBatch instance. Simple enough. First of all, when we construct the Texture2DRenderer, we will pass in a SpriteBatch instance. Simple enough.
Our *Render* method will run exactly once for each Texture2DComponent that lives in our world. It also gives us a reference to the Entity that each specific Texture2DComponent is attached to. The *in* keyword means that the textureComponent is accessed by reference, but cannot be modified. We shouldn't ever be changing the data of a component inside a Renderer, because that is not the Renderer's job. So the method requires us to use the *in* keyword here. Our *Render* method will run exactly once when the World draws.
Next, we want to retrieve our position data. We can do this with the Renderer's `GetComponent` method. First, we begin the SpriteBatch and tell it to draw in *Deferred* mode, which waits as long as possible before pushing data to the GPU.
Next, we want to iterate over all our entities that have texture components and retrieve their position data. We can do this with the Renderer's `GetComponent` method.
`GetComponent<PositionComponent>(entity)` means that we retrieve the PositionComponent that is attached to the given entity. Simple! The brackets mean that the method is what is called a *generic method*. That means we have to tell the method which type it needs to return, which in this case is PositionComponent. `GetComponent<PositionComponent>(entity)` means that we retrieve the PositionComponent that is attached to the given entity. Simple! The brackets mean that the method is what is called a *generic method*. That means we have to tell the method which type it needs to return, which in this case is PositionComponent.
@ -154,6 +161,6 @@ The seventh argument is a scaling value, which multiples the sprite's dimensions
The eighth argument is a `SpriteEffects` argument, which can be used to flip the sprite horizontally or vertically. This argument is basically useless and you will pretty much never need to pass anything except `SpriteEffects.None` here, because passing in negative scaling values can handle sprite flipping. This is one of those examples of the XNA design being a bit weird and crusty in certain places. The eighth argument is a `SpriteEffects` argument, which can be used to flip the sprite horizontally or vertically. This argument is basically useless and you will pretty much never need to pass anything except `SpriteEffects.None` here, because passing in negative scaling values can handle sprite flipping. This is one of those examples of the XNA design being a bit weird and crusty in certain places.
The ninth and final argument is a `layerDepth` integer. This is only used when the SpriteBatch uses an internal sorting technique. This is much less efficient than letting Encompass do the sorting, so we will ignore this value and just pass 0 for everything. The ninth and final argument is a `layerDepth` integer. This is only used when the SpriteBatch uses an internal sorting technique. This is much less efficient than drawing things in order yourself, so we will ignore this value and just pass 0 for everything.
That's it! Now we need to set up our World with its starting configuration so our Encompass elements can work in concert. That's it! Now we need to set up our World with its starting configuration so our Encompass elements can work in concert.

View File

@ -35,7 +35,7 @@ namespace PongFE.Messages
Down Down
} }
public struct PaddleMoveMessage : IMessage, IHasEntity public struct PaddleMoveMessage : IHasEntity
{ {
public Entity Entity { get; } public Entity Entity { get; }
public PaddleMoveDirection PaddleMoveDirection { get; } public PaddleMoveDirection PaddleMoveDirection { get; }

View File

@ -55,7 +55,7 @@ namespace PongFE.Components
Two Two
} }
public struct PlayerInputComponent : IComponent public struct PlayerInputComponent
{ {
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }

View File

@ -37,7 +37,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct PaddleMoveSpeedComponent : IComponent public struct PaddleMoveSpeedComponent
{ {
public float Speed { get; } public float Speed { get; }

View File

@ -39,7 +39,7 @@ using Encompass;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct MotionMessage : IMessage, IHasEntity public struct MotionMessage : IHasEntity
{ {
public Entity Entity { get; } public Entity Entity { get; }
public Vector2 Movement { get; } public Vector2 Movement { get; }
@ -53,7 +53,7 @@ namespace PongFE.Messages
} }
``` ```
Similar to a component, a message is a struct which implements the *IMessage* interface. Also, motion is something that refers to a specific object, right? So we want our message to have a reference to an entity. We can declare the *IHasEntity* interface on our message, which allows Encompass to perform certain lookup optimizations. We'll talk about that in a second. Similar to a component, a message is just a struct. Also, motion is something that refers to a specific object, right? So we want our message to have a reference to an entity. We can declare the *IHasEntity* interface on our message, which allows Encompass to perform certain lookup optimizations. We'll talk about that in a second.
{{% notice warning %}} {{% notice warning %}}
Don't **ever** have a Message that refers to another Message. That is very bad. Don't **ever** have a Message that refers to another Message. That is very bad.

View File

@ -32,7 +32,7 @@ using PongFE.Enums;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct PaddleSpawnMessage : IMessage public struct PaddleSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }
@ -68,7 +68,7 @@ using PongFE.Enums;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct ComputerControlComponent : IComponent public struct ComputerControlComponent
{ {
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }
@ -104,7 +104,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanBeTrackedComponent : IComponent { } public struct CanBeTrackedComponent { }
} }
``` ```

View File

@ -19,7 +19,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanCauseAngledBounceComponent : IComponent { } public struct CanCauseAngledBounceComponent { }
} }
``` ```
@ -31,7 +31,7 @@ using PongFE.Enums;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct AngledBounceMessage : IMessage public struct AngledBounceMessage
{ {
public Entity Bounced { get; } public Entity Bounced { get; }
public Entity Bouncer { get; } public Entity Bouncer { get; }
@ -79,7 +79,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct ScaleComponent : IComponent public struct ScaleComponent
{ {
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
@ -220,7 +220,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct SpawnBallAfterDestroyComponent : IComponent public struct SpawnBallAfterDestroyComponent
{ {
public float Speed { get; } public float Speed { get; }
public float Seconds { get; } public float Seconds { get; }

View File

@ -28,7 +28,7 @@ using PongFE.Enums;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct GameStateComponent : IComponent public struct GameStateComponent
{ {
public GameState GameState { get; } public GameState GameState { get; }
@ -158,7 +158,7 @@ namespace PongFE.Engines
Notice how we took most of the *StartGame* stuff from the *PongFEGame.LoadContent* method. Make sure to take those message sends out of that method. Notice how we took most of the *StartGame* stuff from the *PongFEGame.LoadContent* method. Make sure to take those message sends out of that method.
Now we need to draw the title screen. There's many different approaches. Generic UI text rendering elements would probably be a good idea. But I'm lazy, so let's just set up a GeneralRenderer. Now we need to draw the title screen. There's many different approaches. Generic UI text rendering elements would probably be a good idea. But I'm lazy, so let's just set up a very basic renderer.
In **PongFE/Renderers/TitleRenderer.cs**: In **PongFE/Renderers/TitleRenderer.cs**:
@ -172,7 +172,7 @@ using SpriteFontPlus;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class TitleRenderer : GeneralRenderer public class TitleRenderer : Renderer
{ {
private SpriteBatch SpriteBatch { get; } private SpriteBatch SpriteBatch { get; }
private DynamicSpriteFont TitleFont { get; } private DynamicSpriteFont TitleFont { get; }
@ -248,7 +248,7 @@ Let's set everything up in **PongFEGame**.
... ...
WorldBuilder.AddGeneralRenderer(new TitleRenderer(SpriteBatch, ScoreFont, InstructionFont), 0); WorldBuilder.AddRenderer(new TitleRenderer(SpriteBatch, ScoreFont, InstructionFont));
... ...
``` ```

View File

@ -18,7 +18,7 @@ using SpriteFontPlus;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct UITextComponent : IComponent public struct UITextComponent
{ {
public DynamicSpriteFont Font { get; } public DynamicSpriteFont Font { get; }
public string Text { get; } public string Text { get; }
@ -41,7 +41,7 @@ using SpriteFontPlus;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct UITextSpawnMessage : IMessage public struct UITextSpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public string Text { get; } public string Text { get; }
@ -93,7 +93,7 @@ using SpriteFontPlus;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class UITextRenderer : GeneralRenderer public class UITextRenderer : Renderer
{ {
private SpriteBatch SpriteBatch { get; } private SpriteBatch SpriteBatch { get; }
@ -104,6 +104,8 @@ namespace PongFE.Renderers
public override void Render() public override void Render()
{ {
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Nonpremultiplied);
foreach (ref readonly var entity in ReadEntities<UITextComponent>()) foreach (ref readonly var entity in ReadEntities<UITextComponent>())
{ {
ref readonly var uiTextComponent = ref GetComponent<UITextComponent>(entity); ref readonly var uiTextComponent = ref GetComponent<UITextComponent>(entity);
@ -116,6 +118,8 @@ namespace PongFE.Renderers
Color.White Color.White
); );
} }
SpriteBatch.End();
} }
} }
} }
@ -133,7 +137,7 @@ using PongFE.Enums;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct GameWinMessage : IMessage public struct GameWinMessage
{ {
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }
@ -153,7 +157,7 @@ using PongFE.Enums;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct ChangeGameStateMessage : IMessage public struct ChangeGameStateMessage
{ {
public GameState GameState { get; } public GameState GameState { get; }
@ -244,7 +248,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct BallParametersComponent : IComponent public struct BallParametersComponent
{ {
public int Speed { get; } public int Speed { get; }
public double Delay { get; } public double Delay { get; }
@ -547,7 +551,7 @@ WorldBuilder.AddEngine(new UITextSpawner());
... ...
WorldBuilder.AddGeneralRenderer(new UITextRenderer(SpriteBatch), 0); WorldBuilder.AddRenderer(new UITextRenderer(SpriteBatch));
``` ```
That's it... our re-implementation of Pong is complete! If you followed along to this point, give yourself a pat on the back. That's it... our re-implementation of Pong is complete! If you followed along to this point, give yourself a pat on the back.

View File

@ -6,7 +6,7 @@ weight: 50
Now we need to draw the center line. Now we need to draw the center line.
This will be a fairly basic GeneralRenderer - it doesn't need to react to anything. This will be a fairly basic renderer - it doesn't need to react to anything.
In **PongFE/Renderers/CenterLineRenderer.cs**: In **PongFE/Renderers/CenterLineRenderer.cs**:
@ -19,7 +19,7 @@ using PongFE.Components;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class CenterLineRenderer : GeneralRenderer public class CenterLineRenderer : Renderer
{ {
public SpriteBatch SpriteBatch { get; } public SpriteBatch SpriteBatch { get; }
public Texture2D WhitePixel { get; } public Texture2D WhitePixel { get; }
@ -85,7 +85,7 @@ The main magic to understand here is the matrix transformation - the gist of it
Add our CenterLineRenderer to the WorldBuilder... Add our CenterLineRenderer to the WorldBuilder...
```ts ```ts
WorldBuilder.AddRenderer(new CenterLineRendereR()); WorldBuilder.AddRenderer(new CenterLineRenderer());
``` ```
![center dashed line](/images/center_line.png) ![center dashed line](/images/center_line.png)

View File

@ -27,7 +27,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct PlayAreaComponent : IComponent public struct PlayAreaComponent
{ {
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }

View File

@ -4,9 +4,7 @@ date: 2019-06-04T17:21:52-07:00
weight: 40 weight: 40
--- ---
Remember Renderers? Haven't thought about those in a while! Remember Renderers? Haven't thought about those in a while! All we need to draw new elements to the screen are Renderers.
All we need to draw new elements to the screen are Renderers. Since displaying the score is a UI element, I think it would be best to do this with a GeneralRenderer.
But first, we're gonna need a font. I liked [this font](https://www.dafont.com/squared-display.font). But you can pick any font you like. It's your world and you can do whatever you like in it. But first, we're gonna need a font. I liked [this font](https://www.dafont.com/squared-display.font). But you can pick any font you like. It's your world and you can do whatever you like in it.
@ -54,7 +52,7 @@ using PongFE.Enums;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct GoalBoundarySpawnMessage : IMessage public struct GoalBoundarySpawnMessage
{ {
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }
public Position2D Position { get; } public Position2D Position { get; }
@ -137,7 +135,7 @@ using SpriteFontPlus;
namespace PongFE.Renderers namespace PongFE.Renderers
{ {
public class ScoreRenderer : GeneralRenderer public class ScoreRenderer : Renderer
{ {
public SpriteBatch SpriteBatch { get; } public SpriteBatch SpriteBatch { get; }
public DynamicSpriteFont Font { get; } public DynamicSpriteFont Font { get; }
@ -215,7 +213,7 @@ Let's add our ScoreRenderer to the WorldBuilder. First we need to set up the fon
... ...
WorldBuilder.AddGeneralRenderer(new ScoreRenderer(SpriteBatch, ScoreFont), 0); WorldBuilder.AddRenderer(new ScoreRenderer(SpriteBatch, ScoreFont));
... ...
``` ```

View File

@ -13,7 +13,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanDestroyComponent : IComponent { } public struct CanDestroyComponent { }
} }
``` ```
@ -24,7 +24,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct CanBeDestroyedComponent : IComponent { } public struct CanBeDestroyedComponent { }
} }
``` ```
@ -35,7 +35,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct SpawnBallAfterDestroyComponent : IComponent public struct SpawnBallAfterDestroyComponent
{ {
public float Seconds { get; } public float Seconds { get; }
@ -54,7 +54,7 @@ using Encompass;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct DestroyMessage : IMessage public struct DestroyMessage
{ {
public Entity Entity { get; } public Entity Entity { get; }
@ -145,7 +145,7 @@ using MoonTools.Structs;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct GoalBoundarySpawnMessage : IMessage public struct GoalBoundarySpawnMessage
{ {
public Position2D Position { get; } public Position2D Position { get; }
public int Width { get; } public int Width { get; }

View File

@ -17,7 +17,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct ScoreComponent : IComponent public struct ScoreComponent
{ {
public int Score { get; } public int Score { get; }
@ -46,7 +46,7 @@ using Encompass;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct IncreaseScoreAfterDestroyComponent : IComponent { } public struct IncreaseScoreAfterDestroyComponent { }
} }
``` ```
@ -59,7 +59,7 @@ using Encompass;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct DestroyMessage : IMessage public struct DestroyMessage
{ {
public Entity Entity { get; } public Entity Entity { get; }
public Entity DestroyedBy { get; } public Entity DestroyedBy { get; }
@ -97,7 +97,7 @@ using Encompass;
namespace PongFE.Messages namespace PongFE.Messages
{ {
public struct ScoreMessage : IMessage public struct ScoreMessage
{ {
public Entity Entity { get; } public Entity Entity { get; }
@ -220,7 +220,7 @@ using PongFE.Enums;
namespace PongFE.Components namespace PongFE.Components
{ {
public struct PlayerComponent : IComponent public struct PlayerComponent
{ {
public PlayerIndex PlayerIndex { get; } public PlayerIndex PlayerIndex { get; }