spawning
continuous-integration/drone/push Build is passing Details

main
Evan Hemsley 2020-07-12 14:46:45 -07:00
parent 1ca0448189
commit ecd1024d71
2 changed files with 119 additions and 58 deletions

View File

@ -4,89 +4,150 @@ date: 2019-05-23T18:09:07-07:00
weight: 10
---
In **game/game.ts**...
In **PongFEGame.cs**...
```ts
const ball_entity = world_builder.create_entity();
```cs
protected override void LoadContent()
{
...
const ball_position_component = ball_entity.add_component(PositionComponent);
ball_position_component.x = 640;
ball_position_component.y = 360;
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);
const ball_size = 16;
const ball_canvas = love.graphics.newCanvas(ball_size, ball_size);
love.graphics.setCanvas(ball_canvas);
love.graphics.setBlendMode("alpha");
love.graphics.setColor(1, 1, 1, 1);
love.graphics.rectangle("fill", 0, 0, ball_size, ball_size);
love.graphics.setCanvas();
...
const ball_canvas_component = ball_entity.add_component(CanvasComponent);
ball_canvas_component.canvas = ball_canvas;
ball_canvas_component.x_scale = 1;
ball_canvas_component.y_scale = 1;
var ball = WorldBuilder.CreateEntity();
WorldBuilder.SetComponent(ball, new PositionComponent(new MoonTools.Structs.Position2D(640, 360)));
WorldBuilder.SetComponent(ball, new Texture2DComponent(BallTexture, 0));
...
}
```
OK. We're both thinking it. Why is all this crap going straight in **game.ts**? And there's magic values everywhere! You are absolutely right. Encompass actually has a built-in abstraction Engine for creating new Entities called a Spawner. Let's use one.
OK. We're both thinking it. Why is all this crap going straight in **PongFEGame.cs**? And there's magic values everywhere! You are absolutely right. Encompass actually has a built-in abstraction Engine for creating new Entities called a Spawner for just this purpose. Let's create one.
Let's create a new folder: **game/engines/spawners**
First off, we need a spawn message. There's nothing particularly special about a spawn message, just create one that has the parameters you think you'll need to spawn a particular sort of entity.
And a new file: **game/engines/spawners/ball.ts**
Create a file: **PongFE/Messages/BallSpawnMessage.cs**
```ts
import { Reads, Spawner } from "encompass-ecs";
import { CanvasComponent } from "game/components/canvas";
import { PositionComponent } from "game/components/position";
import { BallSpawnMessage } from "game/messages/ball_spawn";
```cs
using Encompass;
using MoonTools.Structs;
@Reads(BallSpawnMessage)
export class BallSpawner extends Spawner {
public spawn(message: BallSpawnMessage) {
const ball_entity = this.create_entity();
namespace PongFE.Messages
{
public struct BallSpawnMessage : IMessage
{
public Position2D Position { get; }
const ball_position_component = ball_entity.add_component(PositionComponent);
ball_position_component.x = message.x;
ball_position_component.y = message.y;
const ball_canvas = love.graphics.newCanvas(message.size, message.size);
love.graphics.setCanvas(ball_canvas);
love.graphics.setBlendMode("alpha");
love.graphics.setColor(1, 1, 1, 1);
love.graphics.rectangle("fill", 0, 0, message.size, message.size);
love.graphics.setCanvas();
const ball_canvas_component = ball_entity.add_component(CanvasComponent);
ball_canvas_component.canvas = ball_canvas;
ball_canvas_component.x_scale = 1;
ball_canvas_component.y_scale = 1;
public BallSpawnMessage(Position2D position)
{
Position = position;
}
}
}
```
Spawners aren't very complicated. They have one required method: *spawn*.
The first Message type given to **@Reads** is assumed to be the spawn message type.
Let's create a new folder: **PongFEGame/Engines/Spawners**
When the Spawner reads a message of the spawn message type, it runs its spawn method once. Simple as that.
And a new file: **PongFE/Engines/Spawners/Ball.cs**
```cs
using Encompass;
using Microsoft.Xna.Framework.Graphics;
using PongFE.Components;
using PongFE.Messages;
namespace PongFE.Spawners
{
[Writes(typeof(PositionComponent))]
[Writes(typeof(Texture2DComponent))]
public class BallSpawner : Spawner<BallSpawnMessage>
{
private Texture2D BallTexture { get; }
public BallSpawner(Texture2D ballTexture)
{
BallTexture = ballTexture;
}
protected override void Spawn(BallSpawnMessage message)
{
var ball = CreateEntity();
SetComponent(ball, new PositionComponent(new MoonTools.Structs.Position2D(640, 360)));
SetComponent(ball, new Texture2DComponent(BallTexture, 0));
}
}
}
```
Spawners aren't very complicated. They have one required method: *Spawn*. Any time a message of the given type is sent, Spawn will run once for that message. When we create the BallSpawner, we give it a BallTexture so it can attach the texture to the ball entities it creates.
Now let's actually send out the BallSpawnMessage.
In **game/game.ts**...
In **PongFEGame.ts**, get rid of the ball entity code and do:
```ts
...
```cs
...
world_builder.add_engine(BallSpawner);
WorldBuilder.AddEngine(new BallSpawner(BallTexture));
...
...
const ball_spawn_message = world_builder.emit_message(BallSpawnMessage);
ball_spawn_message.x = 640;
ball_spawn_message.y = 360;
ball_spawn_message.size = 16;
WorldBuilder.SendMessage(new BallSpawnMessage(new MoonTools.Structs.Position2D(640, 360)));
...
...
```
Now, if we try to run the game...
```sh
[ERROR] FATAL UNHANDLED EXCEPTION: Encompass.Exceptions.EngineWriteConflictException: Multiple Engines write the same Component without declaring priority:
PositionComponent written by: MotionEngine, BallSpawner
To resolve the conflict, add priority arguments to the Writes declarations or use a DefaultWritePriority attribute.
```
Oh no!!! It's yelling at us! That's ok though. Encompass lets us know exactly what the problem is: we have two different Engines that both try to write PositionComponent. Think about why this is bad... if two engines write the same component to the same entity on the same frame, only one of those values is gonna get through, right? That could surprise us and make the game harder to understand. I hate surprises.
The Writes attribute allows you to specify a priority value, to allow one engine's component writes to take priority over another's. In this case, however, it is unnecessary, because we have access to a special variant of **SetComponent**, called **AddComponent**.
**AddComponent** is only valid when an entity has been created in the same context as it was called. This makes it ideal for spawning new entities. It means you don't have to worry about write priority, because the entity must have been newly created.
Let's revise the BallSpawner to use AddComponent.
```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(new MoonTools.Structs.Position2D(640, 360)));
AddComponent(ball, new Texture2DComponent(BallTexture, 0));
}
}
}
```
And run the game...
![boring ball](/images/paddle_with_ball.png)
Well, it draws, but it's a bit boring without any movement. Let's make it move around.
Well, it draws! but it's a bit boring without any movement. Let's make it move around.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 238 KiB