updates to the Concepts section

pull/1/head
Evan Hemsley 2020-07-10 17:06:40 -07:00
parent 089bbfc8b7
commit 9bc6934de0
12 changed files with 66 additions and 37 deletions

View File

@ -31,7 +31,12 @@ worldBuilder.SetComponent(entity, new VelocityComponent { velocity = Vector2.Zer
**SetComponent** can also be used from within an **Engine**. We will talk more about this later. **SetComponent** can also be used from within an **Engine**. We will talk more about this later.
Components are always structs, meaning they follow value-type semantics. (Insert link about that here). If you use them idiomatically, you don't have to worry about them being garbage-collected. Components are always structs, meaning they follow value-type semantics. If you are used to working with classes you might find this confusing.
One major point of difference is that value types are _copied_ rather that passed by reference by default.
You can read more about value types here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types
If you use them idiomatically, you don't have to worry about them creating garbage collection pressure, so this is a big win for performance when working in C#.
{{% notice warning %}} {{% notice warning %}}
Components should **never** reference other Components directly. This breaks the principle of loose coupling. You **will** regret it if you do this. Components should **never** reference other Components directly. This breaks the principle of loose coupling. You **will** regret it if you do this.

View File

@ -10,7 +10,7 @@ An Engine is the Encompass notion of an ECS System. Much like the engine on a tr
I never liked the term System. It is typically used to mean structures in game design and I found this confusing when discussing code implementation vs design. I never liked the term System. It is typically used to mean structures in game design and I found this confusing when discussing code implementation vs design.
{{% /notice %}} {{% /notice %}}
Engines may read any Entities and Components in the game world, read and send messages, and create or update or destroy Entities and Components. Engines may read any Entities and Components in the game world, read and send messages, and create, update or destroy Entities and Components.
An Engine which Reads a particular message is guaranteed to run *after* all Engines which Send that particular message. An Engine which Reads a particular message is guaranteed to run *after* all Engines which Send that particular message.
@ -28,7 +28,7 @@ public class PauseEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var pauseMessage in ReadMessages<PauseMessage>()) foreach (ref readonly var pauseMessage in ReadMessages<PauseMessage>())
{ {
SetComponent(pauseMessage.entity, new PauseComponent SetComponent(pauseMessage.entity, new PauseComponent
{ {
@ -36,8 +36,10 @@ public class PauseEngine : Engine
}); });
} }
foreach (var (pauseComponent, entity) in ReadComponentsIncludingEntity<PauseComponent>()) foreach (ref readonly var entity in ReadEntities<PauseComponent>())
{ {
ref readonly var pauseComponent = GetComponent<PauseComponent>(entity);
var timer = pauseComponent.timer; var timer = pauseComponent.timer;
timer -= dt; timer -= dt;
@ -55,6 +57,6 @@ public class PauseEngine : Engine
``` ```
This engine deals with a Component called a PauseComponent. If a PauseMessage is received, a new PauseComponent is attached to the Entity specified by the message. PauseComponent has a timer which counts down based on delta-time. When the timer ticks past zero, the PauseComponent is removed. This engine deals with a Component called a PauseComponent. When a PauseMessage is received, a new PauseComponent is attached to the Entity specified by the message. PauseComponent has a timer which counts down based on delta-time. When the timer ticks past zero, the PauseComponent is removed.
Notice that this Engine doesn't actually "do" the pausing, or even care if the Entity in question is capable of movement or not. In Engines that deals with movement, we can check if the Entities being moved have PauseComponents and modify how they are updated accordingly. This is the power of de-coupled logic. Notice that this Engine doesn't actually "do" the pausing, or even care if the Entity in question is capable of movement or not. In Engines that deals with movement, we can check if the Entities being moved have PauseComponents attached to them and modify how they are updated accordingly. This is the power of de-coupled logic.

View File

@ -4,12 +4,12 @@ date: 2019-05-22T12:55:22-07:00
weight: 10 weight: 10
--- ---
An Entity is a structure composed of a unique ID and a collection of Components. An Entity is a structure composed of a unique integer ID and a collection of Components.
Entities do not have any implicit properties or behaviors. They are granted these by their collection of Components. Entities do not have any implicit properties or behaviors. They are granted these by their collection of Components.
There is no limit to the amount of Components an Entity may have, but Entities may only have a single Component of a particular type. There is no limit to the amount of Components an Entity may have, but Entities may only have a single Component of a particular type.
Entities can also be destroyed, permanently removing them and their components from the World. Entities can also be destroyed, permanently removing them and all their components from the World.
Entities are created either by the WorldBuilder or by Engines. (We'll get into these soon.) Entities are created and have components added to them either by the WorldBuilder or by Engines. (We'll get into these soon.)

View File

@ -21,5 +21,5 @@ public struct MotionMessage : IMessage {
Messages are temporary and Encompass destroys all its references to them at the end of the frame. Messages are temporary and Encompass destroys all its references to them at the end of the frame.
{{% notice tip %}} {{% notice tip %}}
Again, because structs are value types, we can create as many of them as we want without worrying about creating pressure on the garbage collector. Neato! Again, because structs are value types, and Encompass stores them in a particular way, we can create as many of them as we want without worrying about creating pressure on the garbage collector. Neato!
{{% /notice %}} {{% /notice %}}

View File

@ -12,21 +12,18 @@ Remember: Encompass isn't a game engine and it doesn't have a rendering system.
There are two kinds of renderers: GeneralRenderers and OrderedRenderers. 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 it will draw to the screen. 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 GeneralRenderer might look like this:
(this example kind of sucks right now)
```cs ```cs
using System; using System;
using Encompass; using Encompass;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using SamuraiGunn2.Components; using MyGame.Components;
using SamuraiGunn2.Editor.Components; using MyGame.Messages;
using SamuraiGunn2.Helpers;
namespace SamuraiGunn2.Editor.Renderers namespace MyGame.Renderers
{ {
public class GridRenderer : GeneralRenderer public class GridRenderer : GeneralRenderer
{ {
@ -61,6 +58,8 @@ namespace SamuraiGunn2.Editor.Renderers
} }
``` ```
This GeneralRenderer 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. 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. 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.
@ -70,12 +69,12 @@ If you were using FNA, an OrderedRenderer might look like this:
```cs ```cs
using Encompass; using Encompass;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using SamuraiGunn2.Components; using MyGame.Components;
using SamuraiGunn2.Extensions; using MyGame.Extensions;
using System; using System;
using System.Numerics; using System.Numerics;
namespace SamuraiGunn2.Renderers namespace MyGame.Renderers
{ {
public class Texture2DRenderer : OrderedRenderer<Texture2DComponent> public class Texture2DRenderer : OrderedRenderer<Texture2DComponent>
{ {

View File

@ -66,13 +66,13 @@ public class MyGame : Game
But you can call these methods wherever you see fit. But you can call these methods wherever you see fit.
{{% notice tip %}} {{% notice tip %}}
It is valid for your game to have multiple Worlds, but be warned that it is difficult to share information between Worlds by design. It is valid for your game to have multiple Worlds, but be warned that it is difficult to share information between Worlds by design and this may not be a good choice.
{{% /notice %}} {{% /notice %}}
**What's that whole dt business about?** **What's that whole gameTime business about?**
*dt* stands for delta-time. Correct usage of delta-time is crucial to make sure that your game does not become *frame-dependent*, which is very bad. We'll talk more about frame-dependence later in the tutorial, but to briefly summarize, if your game is frame-dependent you will run into very frustrating behavior if you ever want your game to run at a different framerate than the one you are developing with. `gameTime` lets us utilize the concept of delta-time. Delta-time means that we take into account the amount of time that has elapsed between frames. Correct usage of delta-time is crucial to make sure that your game does not become *frame-dependent*, which is very bad. We'll talk more about frame-dependence later in the tutorial, but to briefly summarize, if your game is frame-dependent you will run into very frustrating behavior if you ever want your game to run at a different framerate than the one you are developing with.
Even if you lock your game to a fixed timestep, writing your game with delta-time in mind can be the difference between changing the timestep being a one-line tweak or a weeks long hair-pulling nightmare. Even if you lock your game to a fixed timestep (which can be a very good idea for achieving precise game physics), writing your game with delta-time in mind can be the difference between changing the timestep being a one-line tweak or a months long hair-pulling nightmare.
That's it! Now that we have these high-level concepts down, let's build an actual, for-real game. That's it! Now that we have these high-level concepts down, let's build an actual, for-real game.

View File

@ -6,7 +6,7 @@ weight: 35
WorldBuilder is used to construct a World from Engines, Renderers, and an initial state of Entities, Components, and Messages. WorldBuilder is used to construct a World from Engines, Renderers, and an initial state of Entities, Components, and Messages.
The WorldBuilder enforces certain rules about Engine structure. It is forbidden to have messages create cycles between Engines, and no Component may be mutated by more than one Engine without declaring a priority. The WorldBuilder enforces certain rules about Engine structure. It is forbidden to have messages create cycles between Engines, and no Component may not be mutated by more than one Engine without declaring a priority.
The WorldBuilder uses Engines and their Message read/send information to determine a valid ordering of the Engines, which is given to the World. The WorldBuilder uses Engines and their Message read/send information to determine a valid ordering of the Engines, which is given to the World.

View File

@ -4,8 +4,22 @@ date: 2019-05-22T10:38:01-07:00
weight: 12 weight: 12
--- ---
FNA is a game development framework that uses C# as its scripting language. FNA is a game development framework that uses C# as its scripting language.
It is an accurate re-implementation of the XNA 4.0 API, and has been used to ship several dozen successful games.
If you are new to C# scripting, (pointer to some good C# tutorial goes here) I like using FNA because it is highly stable, time-tested, low level enough to be flexible and high-level enough to be easy to use. It's really great!
(FNA setup tutorial goes here) If you are new to C# scripting, Microsoft has a decent high-level intro here: https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/intro-to-csharp/
I recommend giving the FNA wiki a quick read as an intro: https://github.com/FNA-XNA/FNA/wiki
I have created a template for getting started with an empty FNA project: https://gitea.moonside.games/MoonsideGames/FNA-VSCode-Template
Download it and follow the install instructions.
If you are using VSCode, you can hit Ctrl-Shift-B to bring up the build menu.
`Framework` tasks will execute using .NET Framework or Mono, and `Core` tasks will execute using .NET Core runtime.
If you are not using VSCode, you can execute build and execute tasks from the command line.
I assume if you are savvy enough to not be using some kind of IDE you probably don't need handholding here, but let me know if I'm wrong!
Once you can Build and Run and the cornflower blue screen of life pops up, we are ready to go!

View File

@ -24,7 +24,7 @@ Here are some engines that I have used:
[FNA](https://fna-xna.github.io/) is a free-software implementation of the XNA 4.0 libraries. It is very portable and stable and dozens of games have shipped with it. We are using it to build Samurai Gunn 2 and it has been delightful to work with. [FNA](https://fna-xna.github.io/) is a free-software implementation of the XNA 4.0 libraries. It is very portable and stable and dozens of games have shipped with it. We are using it to build Samurai Gunn 2 and it has been delightful to work with.
[MonoGame](http://www.monogame.net/) is another popular cross-platform 2D/3D framework, also based on XNA but deviates in a few ways. [MonoGame](http://www.monogame.net/) is another popular cross-platform 2D/3D framework, also based on XNA but deviates in a few crucial ways.
[sokol-sharp](https://github.com/lithiumtoast/sokol-sharp) is a C# wrapper for the Sokol game library. It features powerful support for modern 3D APIs. [sokol-sharp](https://github.com/lithiumtoast/sokol-sharp) is a C# wrapper for the Sokol game library. It features powerful support for modern 3D APIs.

View File

@ -4,8 +4,8 @@ date: 2019-06-09T20:12:16-07:00
weight: 9 weight: 9
--- ---
You will want some kind of text editor to develop Encompass projects. You will want some kind of text editor or IDE to develop Encompass projects.
I _highly_ recommend [VSCodium](https://vscodium.com/) if you are on Windows or OSX, and Code - OSS if you are on Linux. These are free-software distributions of Microsoft's VSCode editor, which features excellent C# integration and various convenient features, like an integrated Git interface and terminal. (Make sure you set the terminal to Git Bash if you are on Windows - this is under File -> Settings.) [VSCode](https://code.visualstudio.com/) has worked pretty well for me across Windows, OSX, and Linux. A C# extension is available which has excellent C# integration and the editor has various convenient features, like an integrated Git interface and terminal. (Make sure you set the terminal to Git Bash if you are on Windows - this is under File -> Settings.)
Of course, if you prefer some other editor, that will be perfectly fine. Visual Studio is a popular choice for Windows that features good performance measuring tools, and many people sing the praises of [JetBrains Rider](https://www.jetbrains.com/rider/). Of course, if you prefer some other editor, that will be perfectly fine. Visual Studio is a popular choice for Windows that features good performance measuring tools, and many people sing the praises of [JetBrains Rider](https://www.jetbrains.com/rider/). And there's always Vim or Emacs.

View File

@ -1,19 +1,28 @@
--- ---
title: "LÖVE Project Structure" title: "Project Structure"
date: 2019-05-22T12:19:55-07:00 date: 2019-05-22T12:19:55-07:00
weight: 15 weight: 15
--- ---
TODO: CREATE AN ENCOMPASS-FNA STARTER TEMPLATE
Structuring your project is a crucial component of keeping your Encompass development sane. Let's review how an Encompass project is typically structured. Structuring your project is a crucial component of keeping your Encompass development sane. Let's review how an Encompass project is typically structured.
If we look at the Encompass/FNA starter project, it looks like this: If we look at the Encompass/FNA starter project, it looks like this:
(screenshot goes here)) (screenshot goes here))
(describe top-level FNA entry point) The top-level of your game will be located in the (GameName)Game.cs file.
FNA's top level functions are described as follows.
(Describe FNA Game setup here)) `LoadContent` runs once after the initial setup is done. I usually like to put my initialization and, you guessed it, content loading code in here.
`UnloadContent` is where you clean up after yourself so the game can dispose of itself properly.
`Update(GameTime gameTime)` gets called once per frame. It's where the update code goes. For Encompass there probably won't be anything in here except `World.Update(gameTime.ElapsedGameTime.TotalSeconds)`.
`Draw` also gets called once per frame, after `Update`. All rendering logic should go in here. For Encompass you'll probably just put `World.Draw()` in here but there might be some externalities as well.
The rest of it is pretty straightforward. Put your sprites and sound effects and such in the **Content** folder. Define your components in the **Components** folder, your engines in the **Engines** folder, your messages in the **Messages** folder, and your renderers in the **Renderers** folder. (Again, we'll start getting into exactly how to define these in a minute.) The rest of it is pretty straightforward. Put your sprites and sound effects and such in the **Content** folder. Define your components in the **Components** folder, your engines in the **Engines** folder, your messages in the **Messages** folder, and your renderers in the **Renderers** folder. (Again, we'll start getting into exactly how to define these in a minute.)
Finally, a quick note about **Helpers**. I like to use classes with static methods for common behaviors that will be useful for many different engines, for example a `Color` class with a `hsv_to_rgb` conversion function. Be careful not to abuse helpers. If your helpers aren't static, that is usually a sign that the behavior belongs in an engine. Finally, a quick note about **Helpers**. I like to use classes with static methods for common behaviors that will be useful for many different engines, for example a `Color` class with a `HSVToRGB` conversion function. Be careful not to abuse helpers. If your helpers aren't static, that is usually a sign that the behavior belongs in an engine instead.

@ -1 +1 @@
Subproject commit 41c6cc522f13ac324e99e8bf84f2b67d88510ceb Subproject commit 3efb32712c5cc77e644852d13ce3525780374b10