sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.08k stars 1.11k forks source link

Suggestion for updated setup guide with Unity, Roslyn Code Generator and Unit tests #957

Closed matthiashermsen closed 3 years ago

matthiashermsen commented 3 years ago

Hi, based on this guide

https://github.com/sschmid/Entitas-CSharp/wiki/Unity-Installation-Guide

and Simon's Youtube series

https://www.youtube.com/watch?v=L-18XRTarOM

I thought it would be useful for complete beginners (like me) to have a tutorial in the Wiki. I wrote a detailed guide but before creating a new page or editing an existing one I would like you to review it, maybe there are some things you want to add/change/remove :)


Overview

This is a very small introduction on how to setup a new Github project with Unity and tests (for this I'm using xUnit but it shouldn't matter). I think it's important to mention that this is not about best practises here, it just explains the setup in its simplest form. The optional parts are mostly based on my personal preferences, except the setup for the Roslyn code generator.

Prerequisites

For this guide I'm using the following environment

but it should work for all Unity versions above 2018.3.0.

Setting up the repository

First of all create a new repository on Github. I selected Github's Unity .gitignore template as a starting point.

Repository setup

Setting up the .gitignore file

Currently there is no golden rule for it. Github's Unity .gitignore template does not cover all the files that should be ignored. Below I will post my personal .gitignore file but please keep in mind to add files to the .gitignore file as you go. There might be more files which can be ignored later on. Edit the generated .gitignore file in the repository so that it meets your requirements.

# OS
Thumbs.db
.DS_Store

# Base
[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
[Bb]uilds/
[Ll]ogs/
[Mm]emoryCaptures/

# IDEs
**/bin/Debug
**/bin/Release
**/packages
*.userprefs
*.user
test-results
DerivedData
xcuserdata

# Jetbrains
.idea
[Aa]ssets/Plugins/Editor/JetBrains*

# Visual Studio
.vs

# Visual Studio Code
.vscode

# Unity
*.pidb.meta
*.pdb.meta
*.mdb.meta
sysinfo.txt
*.unitypackage
Assembly-CSharp*.csproj
*.sln

# Entitas
Jenny.zip
Jenny.zip.meta
*.userproperties

# Gradle
.gradle

# Builds
*.apk

Setting up the Unity project

First of all clone the repository locally by running (don't forget to update the git clone url to your own)

git clone https://github.com/matthiashermsen/my-new-project.git

in the terminal. Open the Unity Hub and create a new Unity project inside the root of that local repository.

Create a new project with the Unity Hub

Importing Entitas

In the editor open the Package Manager (which can be found under the Window tab) and switch to your own assets. Entitas should show up in the list. For now I will simply import everything

Import Entitas

After that you should see multiple files in the root of your assets folder

Assets folder

(Optional) Organize external libraries

Create a new folder inside the assets folder and call it "Libraries" (you can name it anything you want) and move the imported files into it. There might be more libraries so you can create a subfolder "Entitas" and move everything into it. If everything was fine you should have this structure

Folder structure

Setting up Entitas

In the editor head over to the Tools section and select Jenny => Preferences. A window should pop up, simply hit the "Auto Import" button to setup the settings. A warning message appears, select "Continue and Overwrite".

The preferences should now looke like this

Jenny preferences

You can close the window after that for now.

(Optional) Organize Entitas code

The Jenny preferences have a section "Target Directory". The current value is "Assets". Jenny will create a "Generated" folder in the assets folder and put the generated code into it. If you want to keep Entitas related code in a subfolder, simply change the value to "Assets/Sources" (you can name it anything you want). Now generated code will be in "Assets/Sources/Generated".

Making sure the Assembly-Csharp.proj file exists

You will need a script acting as the glue between Unity and Entitas. Simply create a new script and call it "GameController" (you can name it anything you want). For now this script is empty but your IDE will create the needed script assemblies

using UnityEngine;

public class GameController : MonoBehaviour
{
}

You can close your IDE after that, Unity will refresh. Now click on Generate (which is found under the Tools section => Jenny). There should be no errors, in the console should be two logs

Console Logs

(Optional) Organize Unity code

In the assets folder create a new folder called "Scripts" (you can name it anything you want) and move the "GameController" into it. Unity related code will be here. The structure should look like

Script structure

(Optional) Setting up the Roslyn Code generator

You can close Unity for now. Navigate to the root of your repository directory. Now search for the Jenny files which should be at

Repository => Unity Project => Assets => Libraries => Entitas

First delete the "Jenny.zip.meta" file. After that move the "Jenny.zip" file to the root of your Unity project (next to the assets folder, not the root of the repository). Extract the files from the .zip file, after that you can delete the .zip file.

You should see a "Jenny" directory now. Open up a terminal in that directory and run

mono Jenny/Jenny/Jenny.exe

A CLI should open up. Simply select the first (default) option "Use Jenny.properties"

First dialog

On the next dialog select everything and after that select the first option "Save and continue (auto import)"

Second dialog

If everything is fine you should see this

Last dialog

Now run

mono Jenny/Jenny/Jenny.exe auto-import -s

once. After that you can run

mono Jenny/Jenny/Jenny.exe gen

to generate from your Entitas related code. So whenever you want to update your Entitas sources simply run this command (based on the directory of the Unity project).

Creating your first system

Open up your project with Unity. For the sake of simplicty create a initialize system creating a new entity on initialization. Create a new script called "CreateEntitySystem".

using Entitas;

public sealed class CreateEntitySystem : IInitializeSystem
{
    private readonly Contexts contexts;

    public CreateEntitySystem(Contexts contexts)
    {
        this.contexts = contexts;
    }

    public void Initialize()
    {
        contexts.game.CreateEntity();
    }
}

(Optional) Organize systems

Move your system to "Assets/Sources/Systems/InitializeSystems".

Initialize systems

Initilialize systems go here.

Creating your first feature

Create a new script called "GameSystems".

public sealed class GameSystems : Feature
{
    public GameSystems(Contexts contexts)
    {
        Add(new CreateEntitySystem(contexts));
    }
}

(Optional) Organize features

Move your feature to "Assets/Sources/Features".

Features

Features go here.

Adding the features

Open up the script "GameController" and update it to

using UnityEngine;

public class GameController : MonoBehaviour
{
    private GameSystems gameSystems;

    private void Start()
    {
        Contexts contexts = Contexts.sharedInstance;
        gameSystems = new GameSystems(contexts);
        gameSystems.Initialize();
    }

    private void Update()
    {
        gameSystems.Execute();
    }
}

Now regenerate the code.

Setting up the scene

Create an empty GameObject and call it "GameController" (you can name it anything you want). Attach the script "GameController" to it.

Scene setup

When running the play mode the scene hierarchy should look like this

Scene hierarchy

You can close Unity now.

Setting up tests

Head over to the root of your repository. Create a new solution project with a unit test project

Unit test project

In your IDE add an existing project to the solution. Select the "Assembly-CSharp.csproj" file from your Unity project which should be at

/.../my-new-project/my-new-project/Assembly-CSharp.csproj

Your project should now look similiar to this

Solution overview

Referencing the Unity project

In your tests project add a new reference. Select the Unity project.

Reference the Unity project

Creating the test

Create a new .cs file or use the generated one (UnitTest1) and put in this code

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        Contexts contexts = new Contexts();
        Systems systems = new Systems();
        CreateEntitySystem createEntitySystem = new CreateEntitySystem(contexts);
        systems.Add(createEntitySystem);
        systems.Initialize();

        Assert.True(contexts.game.count == 1);
    }
}

Run your test, it should pass.

Test passed

(Optional) Organize tests

You can delete your test file, this will be a complete rewrite.

Base class for system tests

To take away boilerplate code you have to setup for every system test create a file "SystemTests" (you can name it anything you want) in Tests => SourceTests => SystemTests

SystemTests

and use this code

using System;
using Entitas;

namespace Tests.SourceTests.SystemTests
{
    public abstract class SystemTests : IDisposable
    {
        protected Contexts contexts;
        protected Systems systems;

        // ! This runs before every test !
        protected SystemTests()
        {
            contexts = new Contexts();
            systems = new Systems();
        }

        // ! This runs after every test !
        public void Dispose()
        {
        }
    }
}
Base class for initialize system tests

To take away boilerplate code you have to setup for every initialize system test create a file "InitializeSystemTests" (you can name it anything you want) in Tests => SourceTests => SystemTests => InitializeSystemTests

Initialize system tests

and use this code

using System;
using Entitas;

namespace Tests.SourceTests.SystemTests.InitializeSystemTests
{
    public abstract class InitializeSystemTests<TInitializeSystem> : SystemTests where TInitializeSystem : IInitializeSystem
    {
        // ! This creates a new instance and initializes it before every test !
        public InitializeSystemTests()
        {
            TInitializeSystem initializeSystem = (TInitializeSystem)Activator.CreateInstance(typeof(TInitializeSystem), contexts);
            systems.Add(initializeSystem);
            initializeSystem.Initialize();
        }
    }
}
Rewritten test

Right next to the file "InitializeSystemTests" create a new file called "CreateEntitySystemTests" (you can name it anything you want)

Create entity system tests

and use this code

using Xunit;

namespace Tests.SourceTests.SystemTests.InitializeSystemTests
{
    public sealed class CreateEntitySystemTests : InitializeSystemTests<CreateEntitySystem>
    {
        [Fact]
        private void ItCreatesOneEntity()
        {
            Assert.True(contexts.game.count == 1);
        }
    }
}

When running the test it should pass.

Test still passed

All done!

gif

matthiashermsen commented 3 years ago

For now I put it here :)

https://github.com/sschmid/Entitas-CSharp/wiki/How-to-setup-a-new-Github-project-with-Unity-and-tests

sschmid commented 3 years ago

Awesome thanks for your contribution!