update for new version of encompass
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
66fe9ecefb
commit
be028dd21c
|
@ -6,13 +6,11 @@ weight: 5
|
|||
|
||||
A Component is a structure of related data.
|
||||
|
||||
To define a Component, declare a struct which implements the **IComponent** interface.
|
||||
|
||||
```cs
|
||||
using Encompass;
|
||||
using System.Numerics;
|
||||
|
||||
public struct VelocityComponent : IComponent {
|
||||
public struct VelocityComponent {
|
||||
|
||||
public Vector2 Velocity { get; }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
To define a message, declare a struct which implements the IMessage interface.
|
||||
To define a message, declare a struct.
|
||||
|
||||
```cs
|
||||
using Encompass;
|
||||
|
||||
public struct MotionMessage : IMessage {
|
||||
public struct MotionMessage {
|
||||
public Vector2 Motion { get; }
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
{{% /notice %}}
|
||||
|
||||
There are two kinds of renderers: GeneralRenderers and OrderedRenderers.
|
||||
|
||||
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:
|
||||
If you were using FNA, a Renderer might look like this:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
|
@ -25,12 +21,12 @@ using MyGame.Messages;
|
|||
|
||||
namespace MyGame.Renderers
|
||||
{
|
||||
public class GridRenderer : GeneralRenderer
|
||||
public class GridRenderer : Renderer
|
||||
{
|
||||
private int gridSize;
|
||||
private PrimitiveDrawer primitiveDrawer;
|
||||
|
||||
public GridRenderer(PrimitiveDrawer primitiveDrawer)
|
||||
public Renderer(PrimitiveDrawer primitiveDrawer)
|
||||
{
|
||||
this.primitiveDrawer = primitiveDrawer;
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
|
|
@ -27,7 +27,7 @@ public class MyGame : Game
|
|||
var worldBuilder = new WorldBuilder();
|
||||
|
||||
worldBuilder.AddEngine(new MotionEngine());
|
||||
worldBuilder.AddOrderedRenderer(new TextureRenderer());
|
||||
worldBuilder.AddRenderer(new TextureRenderer());
|
||||
|
||||
var entity = worldBuilder.CreateEntity();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct BoundarySpawnMessage : IMessage
|
||||
public struct BoundarySpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public int Width { get; }
|
||||
|
|
|
@ -15,7 +15,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanCauseBounceComponent : IComponent { }
|
||||
public struct CanCauseBounceComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -26,7 +26,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanBeBouncedComponent : IComponent { }
|
||||
public struct CanBeBouncedComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -38,7 +38,7 @@ using PongFE.Components;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct BounceMessage : IMessage, IHasEntity
|
||||
public struct BounceMessage : IHasEntity
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
public HitOrientation HitOrientation { get; }
|
||||
|
|
|
@ -62,7 +62,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct UpdateVelocityMessage : IMessage, IHasEntity
|
||||
public struct UpdateVelocityMessage : IHasEntity
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
public Vector2 Velocity { get; }
|
||||
|
|
|
@ -20,7 +20,7 @@ using MoonTools.Bonk;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CollisionComponent : IComponent
|
||||
public struct CollisionComponent
|
||||
{
|
||||
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.
|
||||
|
||||
|
@ -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 %}}
|
||||
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
|
||||
}
|
||||
|
||||
public struct CollisionMessage : IMessage
|
||||
public struct CollisionMessage
|
||||
{
|
||||
public Entity EntityA { get; }
|
||||
public Entity EntityB { get; }
|
||||
|
@ -193,7 +193,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct UpdatePositionMessage : IMessage, IHasEntity
|
||||
public struct UpdatePositionMessage : IHasEntity
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
public Position2D Position { get; }
|
||||
|
|
|
@ -18,7 +18,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct VelocityComponent : IComponent
|
||||
public struct VelocityComponent
|
||||
{
|
||||
public Vector2 Velocity { get; }
|
||||
|
||||
|
@ -94,7 +94,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct BallSpawnMessage : IMessage
|
||||
public struct BallSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public Vector2 Velocity { get; }
|
||||
|
|
|
@ -97,7 +97,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct PaddleSpawnMessage : IMessage
|
||||
public struct PaddleSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
@ -189,17 +189,15 @@ using Microsoft.Xna.Framework.Graphics;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct Texture2DComponent : IComponent, IDrawableComponent
|
||||
public struct Texture2DComponent
|
||||
{
|
||||
public Texture2D Texture { 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;
|
||||
Scale = scale;
|
||||
Layer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -208,22 +206,17 @@ namespace PongFE.Components
|
|||
And in **PongFE/Renderers/Texture2DRenderer.cs**:
|
||||
|
||||
```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
|
||||
);
|
||||
}
|
||||
_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...
|
||||
|
@ -287,7 +280,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct BallSpawnMessage : IMessage
|
||||
public struct BallSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public Vector2 Velocity { get; }
|
||||
|
|
|
@ -40,7 +40,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct BallSpawnMessage : IMessage
|
||||
public struct BallSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
In a 2D game, Encompass needs to know which order that things should draw in.
|
||||
|
||||
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*.
|
||||
We'll need two things to get a paddle drawing on screen: A *Component* and an *Renderer*.
|
||||
|
||||
Let's start with the Component.
|
||||
|
|
|
@ -86,7 +86,7 @@ Now we want to attach our Texture2DRenderer to the World.
|
|||
|
||||
```cs
|
||||
...
|
||||
WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch));
|
||||
WorldBuilder.AddRenderer(new Texture2DRenderer(SpriteBatch));
|
||||
|
||||
var paddle = WorldBuilder.CreateEntity();
|
||||
...
|
||||
|
@ -103,9 +103,7 @@ Our game's Draw method should look like this:
|
|||
{
|
||||
GraphicsDevice.Clear(Color.CornflowerBlue);
|
||||
|
||||
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
|
||||
World.Draw();
|
||||
SpriteBatch.End();
|
||||
|
||||
base.Draw(gameTime);
|
||||
}
|
||||
|
@ -161,7 +159,7 @@ namespace PongFE
|
|||
SpriteBatch.End();
|
||||
GraphicsDevice.SetRenderTarget(null);
|
||||
|
||||
WorldBuilder.AddOrderedRenderer(new Texture2DRenderer(SpriteBatch));
|
||||
WorldBuilder.AddRenderer(new Texture2DRenderer(SpriteBatch));
|
||||
|
||||
var paddle = WorldBuilder.CreateEntity();
|
||||
WorldBuilder.SetComponent(paddle, new PositionComponent(new MoonTools.Structs.Position2D(5, 5)));
|
||||
|
@ -186,9 +184,7 @@ namespace PongFE
|
|||
{
|
||||
GraphicsDevice.Clear(Color.CornflowerBlue);
|
||||
|
||||
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
|
||||
World.Draw();
|
||||
SpriteBatch.End();
|
||||
|
||||
base.Draw(gameTime);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct PositionComponent : IComponent
|
||||
public struct PositionComponent
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
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**
|
||||
|
||||
```cs
|
||||
using Encompass;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct Texture2DComponent : IComponent, IDrawableComponent
|
||||
public struct Texture2DComponent
|
||||
{
|
||||
public Texture2D Texture { get; }
|
||||
public int Layer { get; }
|
||||
|
||||
public Texture2DComponent(Texture2D texture, int layer)
|
||||
public Texture2DComponent(Texture2D 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.
|
||||
|
||||
*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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
That's it for our component. We need one more bit of information before we can write our Renderer.
|
||||
|
|
|
@ -16,9 +16,9 @@ using PongFE.Components;
|
|||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
{{% notice note %}}
|
||||
|
@ -52,7 +50,7 @@ using PongFE.Components;
|
|||
|
||||
namespace PongFE.Renderers
|
||||
{
|
||||
public class Texture2DRenderer : OrderedRenderer<Texture2DComponent>
|
||||
public class Texture2DRenderer : Renderer
|
||||
{
|
||||
private readonly SpriteBatch _spriteBatch;
|
||||
|
||||
|
@ -61,21 +59,28 @@ namespace PongFE.Renderers
|
|||
_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(
|
||||
textureComponent.Texture,
|
||||
positionComponent.Position,
|
||||
null,
|
||||
Color.White,
|
||||
0,
|
||||
Vector2.Zero,
|
||||
Vector2.One,
|
||||
SpriteEffects.None,
|
||||
0
|
||||
);
|
||||
foreach (ref readonly var entity in ReadEntities<Texture2DComponent>())
|
||||
{
|
||||
ref readonly var positionComponent = ref GetComponent<PositionComponent>(entity);
|
||||
|
||||
_spriteBatch.Draw(
|
||||
textureComponent.Texture,
|
||||
positionComponent.Position,
|
||||
null,
|
||||
Color.White,
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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 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.
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace PongFE.Messages
|
|||
Down
|
||||
}
|
||||
|
||||
public struct PaddleMoveMessage : IMessage, IHasEntity
|
||||
public struct PaddleMoveMessage : IHasEntity
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
public PaddleMoveDirection PaddleMoveDirection { get; }
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace PongFE.Components
|
|||
Two
|
||||
}
|
||||
|
||||
public struct PlayerInputComponent : IComponent
|
||||
public struct PlayerInputComponent
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct PaddleMoveSpeedComponent : IComponent
|
||||
public struct PaddleMoveSpeedComponent
|
||||
{
|
||||
public float Speed { get; }
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct MotionMessage : IMessage, IHasEntity
|
||||
public struct MotionMessage : IHasEntity
|
||||
{
|
||||
public Entity Entity { 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 %}}
|
||||
Don't **ever** have a Message that refers to another Message. That is very bad.
|
||||
|
|
|
@ -32,7 +32,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct PaddleSpawnMessage : IMessage
|
||||
public struct PaddleSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
@ -68,7 +68,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct ComputerControlComponent : IComponent
|
||||
public struct ComputerControlComponent
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
||||
|
@ -104,7 +104,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanBeTrackedComponent : IComponent { }
|
||||
public struct CanBeTrackedComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanCauseAngledBounceComponent : IComponent { }
|
||||
public struct CanCauseAngledBounceComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -31,7 +31,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct AngledBounceMessage : IMessage
|
||||
public struct AngledBounceMessage
|
||||
{
|
||||
public Entity Bounced { get; }
|
||||
public Entity Bouncer { get; }
|
||||
|
@ -79,7 +79,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct ScaleComponent : IComponent
|
||||
public struct ScaleComponent
|
||||
{
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
@ -220,7 +220,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct SpawnBallAfterDestroyComponent : IComponent
|
||||
public struct SpawnBallAfterDestroyComponent
|
||||
{
|
||||
public float Speed { get; }
|
||||
public float Seconds { get; }
|
||||
|
|
|
@ -28,7 +28,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct GameStateComponent : IComponent
|
||||
public struct GameStateComponent
|
||||
{
|
||||
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.
|
||||
|
||||
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**:
|
||||
|
||||
|
@ -172,7 +172,7 @@ using SpriteFontPlus;
|
|||
|
||||
namespace PongFE.Renderers
|
||||
{
|
||||
public class TitleRenderer : GeneralRenderer
|
||||
public class TitleRenderer : Renderer
|
||||
{
|
||||
private SpriteBatch SpriteBatch { 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));
|
||||
|
||||
...
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ using SpriteFontPlus;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct UITextComponent : IComponent
|
||||
public struct UITextComponent
|
||||
{
|
||||
public DynamicSpriteFont Font { get; }
|
||||
public string Text { get; }
|
||||
|
@ -41,7 +41,7 @@ using SpriteFontPlus;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct UITextSpawnMessage : IMessage
|
||||
public struct UITextSpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public string Text { get; }
|
||||
|
@ -93,7 +93,7 @@ using SpriteFontPlus;
|
|||
|
||||
namespace PongFE.Renderers
|
||||
{
|
||||
public class UITextRenderer : GeneralRenderer
|
||||
public class UITextRenderer : Renderer
|
||||
{
|
||||
private SpriteBatch SpriteBatch { get; }
|
||||
|
||||
|
@ -104,6 +104,8 @@ namespace PongFE.Renderers
|
|||
|
||||
public override void Render()
|
||||
{
|
||||
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Nonpremultiplied);
|
||||
|
||||
foreach (ref readonly var entity in ReadEntities<UITextComponent>())
|
||||
{
|
||||
ref readonly var uiTextComponent = ref GetComponent<UITextComponent>(entity);
|
||||
|
@ -116,6 +118,8 @@ namespace PongFE.Renderers
|
|||
Color.White
|
||||
);
|
||||
}
|
||||
|
||||
SpriteBatch.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +137,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct GameWinMessage : IMessage
|
||||
public struct GameWinMessage
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
||||
|
@ -153,7 +157,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct ChangeGameStateMessage : IMessage
|
||||
public struct ChangeGameStateMessage
|
||||
{
|
||||
public GameState GameState { get; }
|
||||
|
||||
|
@ -244,7 +248,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct BallParametersComponent : IComponent
|
||||
public struct BallParametersComponent
|
||||
{
|
||||
public int Speed { 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.
|
||||
|
|
|
@ -6,7 +6,7 @@ weight: 50
|
|||
|
||||
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**:
|
||||
|
||||
|
@ -19,7 +19,7 @@ using PongFE.Components;
|
|||
|
||||
namespace PongFE.Renderers
|
||||
{
|
||||
public class CenterLineRenderer : GeneralRenderer
|
||||
public class CenterLineRenderer : Renderer
|
||||
{
|
||||
public SpriteBatch SpriteBatch { 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...
|
||||
|
||||
```ts
|
||||
WorldBuilder.AddRenderer(new CenterLineRendereR());
|
||||
WorldBuilder.AddRenderer(new CenterLineRenderer());
|
||||
```
|
||||
|
||||
![center dashed line](/images/center_line.png)
|
||||
|
|
|
@ -27,7 +27,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct PlayAreaComponent : IComponent
|
||||
public struct PlayAreaComponent
|
||||
{
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
|
|
@ -4,9 +4,7 @@ date: 2019-06-04T17:21:52-07:00
|
|||
weight: 40
|
||||
---
|
||||
|
||||
Remember Renderers? Haven't thought about those in a while!
|
||||
|
||||
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.
|
||||
Remember Renderers? Haven't thought about those in a while! All we need to draw new elements to the screen are Renderers.
|
||||
|
||||
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
|
||||
{
|
||||
public struct GoalBoundarySpawnMessage : IMessage
|
||||
public struct GoalBoundarySpawnMessage
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
public Position2D Position { get; }
|
||||
|
@ -137,7 +135,7 @@ using SpriteFontPlus;
|
|||
|
||||
namespace PongFE.Renderers
|
||||
{
|
||||
public class ScoreRenderer : GeneralRenderer
|
||||
public class ScoreRenderer : Renderer
|
||||
{
|
||||
public SpriteBatch SpriteBatch { 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));
|
||||
|
||||
...
|
||||
```
|
||||
|
|
|
@ -13,7 +13,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanDestroyComponent : IComponent { }
|
||||
public struct CanDestroyComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -24,7 +24,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct CanBeDestroyedComponent : IComponent { }
|
||||
public struct CanBeDestroyedComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -35,7 +35,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct SpawnBallAfterDestroyComponent : IComponent
|
||||
public struct SpawnBallAfterDestroyComponent
|
||||
{
|
||||
public float Seconds { get; }
|
||||
|
||||
|
@ -54,7 +54,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct DestroyMessage : IMessage
|
||||
public struct DestroyMessage
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
|
||||
|
@ -145,7 +145,7 @@ using MoonTools.Structs;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct GoalBoundarySpawnMessage : IMessage
|
||||
public struct GoalBoundarySpawnMessage
|
||||
{
|
||||
public Position2D Position { get; }
|
||||
public int Width { get; }
|
||||
|
|
|
@ -17,7 +17,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct ScoreComponent : IComponent
|
||||
public struct ScoreComponent
|
||||
{
|
||||
public int Score { get; }
|
||||
|
||||
|
@ -46,7 +46,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct IncreaseScoreAfterDestroyComponent : IComponent { }
|
||||
public struct IncreaseScoreAfterDestroyComponent { }
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -59,7 +59,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct DestroyMessage : IMessage
|
||||
public struct DestroyMessage
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
public Entity DestroyedBy { get; }
|
||||
|
@ -97,7 +97,7 @@ using Encompass;
|
|||
|
||||
namespace PongFE.Messages
|
||||
{
|
||||
public struct ScoreMessage : IMessage
|
||||
public struct ScoreMessage
|
||||
{
|
||||
public Entity Entity { get; }
|
||||
|
||||
|
@ -220,7 +220,7 @@ using PongFE.Enums;
|
|||
|
||||
namespace PongFE.Components
|
||||
{
|
||||
public struct PlayerComponent : IComponent
|
||||
public struct PlayerComponent
|
||||
{
|
||||
public PlayerIndex PlayerIndex { get; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue