#How I do currently do game level state

1 messages · Page 1 of 1 (latest)

willow sapphire
#

There's 4 base implemetnations
StateModel - 1 state at once
StateModelWithHistory - 1 state at once tracking history to go back
StateFlagModel - multiple states at once
StateFlagModelWithHistory - multiple states at once tracking history to go back

#

This can be used for anything and i originally envisioned it for various movement controlled states (follow path, track unit etc) however my current use case is just game level states, UI etc. The slightly older nature of structural changes for tags lends it more for long time state changes.

#

So for game level states I basically have 1 ISystem for each major game state in my game. From this point I'll use ClientState as my example as it's what I currently use this the most for.

#

To set this up you need two components, current/previous state. For example I use this.

    public struct ClientState : IComponentData
    {
        public BitArray256 Value;
    }

    public struct ClientStatePrevious : IComponentData
    {
        public BitArray256 Value;
    }```

 You can use component field on this, the state model is implemented entirely with dynamic type handles and cares nothing about the type except the size of the component
#

(if you want to see some horror, check out the StateFlagModelWithHistory code I wrote to implement this without generics)

valid sierra
#

I just want StateModel implementation

willow sapphire
#

yep that's what i use for client state

#

an example of setting htis up

#
    [UpdateInGroup(typeof(ClientStateSystemGroup), OrderLast = true)]
    public partial struct ClientStateSystem : ISystem, ISystemStartStop
    {
        private StateFlagModel impl;

        /// <inheritdoc/>
        [BurstCompile]
        public void OnStartRunning(ref SystemState state)
        {
            this.impl = new StateFlagModel(ref state, ComponentType.ReadWrite<ClientState>(), ComponentType.ReadWrite<ClientStatePrevious>());
        }

        /// <inheritdoc/>
        [BurstCompile]
        public void OnStopRunning(ref SystemState state)
        {
            this.impl.Dispose();
        }

        /// <inheritdoc/>
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator);
            this.impl.Run(ref state, ecb);
            ecb.Playback(state.EntityManager);
        }
    }```
#

you don't have to do this on main thread, that's up to you

#

then all you need to do to create a new game level state is called

StateAPI.Register<ClientState, StateInit, ClientStates>(ref state, "init");

#

the string is because I have this setup to automatically use K under the hood

#

if you don't wnat to use K that's fine you can just specifically pass in state key

public static void Register<TState, TInstance, TSettings>(ref SystemState systemState, string stateName, bool queryDependency = true)
public static void Register<TState, TInstance>(ref SystemState systemState, byte stateKey, bool queryDependency = true)```
valid sierra
#

what is ClientStateSystemGroup?

#

is it just group where I have state dependent systems?

willow sapphire
#

it's just a system group where i put all my client states

#

nothing special about it, i just like grouping my systems a little

#
    [UpdateInGroup(typeof(ClientStateSystemGroup))]
    public partial struct ClientHostGameStateSystem : ISystem, ISystemStartStop
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            StateAPI.Register<ClientState, StateHostGame, ClientStates>(ref state, "host-game");
        }

        // Can't be burst compiled as of 1.0.0-pre.15 due to NetworkStreamDriver.Listen
        public void OnStartRunning(ref SystemState state)
        {
            ClientServerBootstrap.CreateServerWorld("ServerWorld");
            ClientAPI.StateSet(ref state, "join-game");

            // Set the load screen-here, join-game will be responsible for hiding it as this state is always followed
            ClientAPI.UISet(ref state, "load-screen");
        }

        public void OnStopRunning(ref SystemState state)
        {
            // Once system has run once, it should not run again
            state.Enabled = false;
        }

        private struct StateHostGame : IComponentData
        {
        }
#

so this is my host game state, which is switched to once a user clicks start game

#

to switch states I use ClientAPI StateSet which is just a convenient single place for API

#

for UI i use a StateFlagModelWithHistory

#

(but you have your own UI implementation so we'll skip over that)

#

StateSet is really just a wrapper for
new ClientState { Value = new BitArray256 { [state] = true } }

#

and that's it

#

once the value changes ClientStateSystem detect it via change filter, remove the ClientHostGameStateSystem.StateHostGame component, and in this case add the StateJoinGame component

#

and the ClientJoinGameStateSystem will start running

#

anyway this might be overkill for you because you probably have knowledge of all states in your application

#

you could just add/remove them manually

#

but I want like a base library I can build multiple games on so i need to be able to remove components i'm not aware of

valid sierra
willow sapphire
#

ClientAPI.SetState is jsut a wrapper for my game

#

it's not in the core library

#

SystemAPI.SetSingleton(new ClientState { Value = new BitArray256 { [state] = true } });

#

is all it's doing really

valid sierra
#

I see

willow sapphire
#

anyway that's pretty much the summary of how I'm handling game level states atm

#

i'm really happy with how it's working for really high level states

#

in menu
host game
load game
show load screen
in game
etc

#

but i'm not sold on the viability of this simplicity for in game states

#

like where I can walk around world, and then I load some build UI or show world map etc

#

I might just substate this all inside the top level game state

valid sierra
#

it's too early for me to think about any of that

#

games I do are usually primitive

#

😅

#

hmm

#

is there a way to register system to rely on multiple states? with Any logic

willow sapphire
#

yeah sure

#

SystemAPI.Register

#

bool queryDependency = true

#

set that false and it won't add the RequireForUpdate

#

and you can manually control the RequireForUpdate yourself

valid sierra
#

what's the point of register anyway?

willow sapphire
#
        public static void Register<TState, TInstance>(ref SystemState systemState, byte stateKey, bool queryDependency = true)
            where TState : unmanaged, IComponentData
            where TInstance : unmanaged, IComponentData
        {
            if (queryDependency)
            {
                var query = new EntityQueryBuilder(Allocator.Temp).WithAll<TInstance>().Build(ref systemState);
                systemState.RequireForUpdate(query);
            }

            var stateTypeIndex = TypeManager.GetTypeIndex<TState>();
            var instanceTypeIndex = TypeManager.GetTypeIndex<TInstance>();

            systemState.EntityManager.AddComponentData(systemState.SystemHandle, new StateInstance
            {
                State = stateTypeIndex,
                StateKey = stateKey,
                StateInstanceComponent = instanceTypeIndex,
            });
        }```
valid sierra
#

yeah, I just don't get why that component is relevant

willow sapphire
#

if you look at StateImpl

#

In StateModel

#

when it's created it queries all StateInstance to find all components that have been registered for the type

#

it's basically just telling the system with the StateModel what components are mapped to what indices

valid sierra
#

ah, so it's important to register in OnStartRunning

willow sapphire
#

annoyingly yes

#

register in oncreate

#

create the statemodel in OnStartRunning

#

unless you want to have CreateBefore() on all your systems

valid sierra
#

this way you can avoid having additional component

willow sapphire
#

not sure what you mean about additional component?

valid sierra
#

StateInstance

willow sapphire
#

oh well i've never considered registering multiple states to a single system

#

but you can do that anyway with just creating a new entity instead of using systemhandle

valid sierra
#

that's not what I meant

#

I just mean

willow sapphire
#

i just used systemhandle because it was simple/made sense for my 1:1 mapping

valid sierra
#

instead of relying on components

#

rely on reflection

#

it's all resolved in compile time anyway

willow sapphire
#

🤷‍♂️ original implementation actually kind of did what you say

#

i used reflection for it

#

i'm trying to reduce my usages of it so I can spin up worlds faster

#

i think systems communicating via components in ecs is a bit more thematic anyway

#

you can change the implementation how you want though, this was all just more of a, here's some inspiration from how i'm doing it, go make something that works for your game!

valid sierra
#

@willow sapphire what is TSettings?

#

I don't see it mentioned

willow sapphire
#

that's the overload for K

#

if you aren't using K just use the byte overload

valid sierra
#

There are only 2 overloads

#

and both require TState and TInstance

willow sapphire
#
public static void Register<TState, TInstance>(ref SystemState systemState, byte stateKey, bool queryDependency = true)```
#

talking about these 2 right?

valid sierra
#

yeah

willow sapphire
#

yeah but it doesn't require TSettings

#

which is what you just asked about

valid sierra
#

what about IInstance?

willow sapphire
#

TInstance is the component that will be added / the system owns

valid sierra
#

so I can just create empty?

willow sapphire
willow sapphire
valid sierra
#

what abou byte stateKey?

willow sapphire
#

that's the key that the component is linked to

#

if stateKey = 2
then when you set
SystemAPI.SetSingleton(new ClientState { Value = new BitArray256 { [2] = true } });

#

it will add TInstance linked to 2

#

and remove all other systems TInstance

valid sierra
#

ah, so I need to keep track of them

#

manually

willow sapphire
#

yeah so this is what I use K for

valid sierra
#

all right, any example how to use K?

#

😅

willow sapphire
#
{
}```
#

all you need

#

and it's done

#

just open bovinelabs/settings

#

and it will generate the SO for you

#

currently it unfortunately puts it in the config folder

#

and it needs to be moved to resources

#

i really should prioritize fixing this

#

let me do that now while i remember

#

i always forget until i make a new config

valid sierra
#

oh well

#

NRE

#

with StateModel

#
    [UpdateInGroup(typeof(InitializationSystemGroup), OrderLast = true)]
    public partial struct ClientStateSystem : ISystem, ISystemStartStop
    {
        private StateModel _impl;

        /// <inheritdoc/>
        [BurstCompile]
        public void OnStartRunning(ref SystemState state)
        {
            _impl = new(ref state, ComponentType.ReadWrite<GameState>(),
                ComponentType.ReadWrite<GameStatePrevious>());
        }

        /// <inheritdoc/>
        [BurstCompile]
        public void OnStopRunning(ref SystemState state)
        {
            _impl.Dispose();
        }

        /// <inheritdoc/>
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var ecb = new EntityCommandBuffer(state.WorldUpdateAllocator);
            _impl.Run(ref state, ecb);
            ecb.Playback(state.EntityManager);
        }
    }
#

on Run

willow sapphire
#

don't even recall why that was changed 🤔

#

this might be one of those times i was just testing api and committed it

#

some completely random code often ends up my libraries

valid sierra
#

hm wait

#

it's not all

#

seems like _impl is deallocated for some reason

#

before being allocated

#

wtf

willow sapphire
#

your stop running is running before start running? 😱

valid sierra
#

it feels like it

#

wait no

#

jeeez

#

so many errors 😅

#

I think

willow sapphire
#

oh so ok yeah i know what this one is

valid sierra
#

it doesn't like nameof(T)

willow sapphire
#

oh?

#

can't do that in burst i don't believe 😦

valid sierra
#

it's in OnCreate

#

var stateKey = K<TSettings>.NameToKey(stateName);

willow sapphire
#

oh yeah ok

valid sierra
#

what the

#

my settings are gone

willow sapphire
#

o_O?

#

this one is new to me

valid sierra
#

yep

willow sapphire
valid sierra
#

settings are gone as soon as I restart editor

willow sapphire
#

0 capacity hashmap

#

% 0 is divide by 0 exception

valid sierra
#

hmm

willow sapphire
#

i'm going to throw a safety check catch in here for this

#

so it's a bit more human readable

valid sierra
#

hold on

#

I might know what's wrong

#

maybe it was me, kek

#

no

#

asset is empty on every restart

willow sapphire
#

what asset? mine?

#

(as in K?)

#

does the filename match the type in file?

valid sierra
#

yes

#

yes

#

jeez

#

that happens in OnCreate

willow sapphire
#

how can the system entity be missing?

valid sierra
#

it's system group

#

if that's relevant

willow sapphire
#

oh hmm

#

i don't think i've tried use system handle on groups

valid sierra
#
        protected override void OnCreate()
        {
            base.OnCreate();
            StateAPI.Register<GameState, GameState.Aiming>(ref CheckedStateRef, (byte)StateValue.Aiming);
        }
willow sapphire
#

i wonder if they setup entities

#

i did have plans to use this on groups

#

i'm probably better off just creating a new entity i guess

valid sierra
#

doesn't seem like they do

#

so

#

quick fix will be just create new entity?

willow sapphire
#

yeah

#

it should work fine if you just setup your own register method iwth create entity instead of using system handle

valid sierra
#

that requires making package local, bruuuh

willow sapphire
#

i was just trying to be slightly more efficient

valid sierra
#

all right

#

utulity it is

willow sapphire
#

i'm making some changes based off this convo atm

#

i need to update upm anyway so i'll probably have an updated copy for you sooner than later

valid sierra
#

I still get NRE on StateModel query though

#

hmm

#

oh wait

#

the first error is in creation of impl

willow sapphire
#

give me 1 sec

valid sierra
#

Assert.AreEqual(UnsafeUtility.SizeOf<byte>(), TypeManager.GetTypeInfo(stateComponent.TypeIndex).ElementSize);

willow sapphire
#

i suspect i;ve forgot an authoring step

#

oh

#

(one day I'll document this 😢 )

#

yeah so for StateModel it only supports byte

#

so 256 states

willow sapphire
#

this was an example from flags

#
    {
        public byte Value;
    }

    /// <summary> The previous state component for the camera. </summary>
    internal struct CameraStatePrevious : IComponentData
    {
        public byte Value;
    }```
#

for example

valid sierra
#

instead of bit array

#

just byte

#

ok

willow sapphire
#

yeah the bit arrays are for flags

#

state model is just 255 states

valid sierra
#

all right, no more errors

#

😅

#

is there an easy way to author state from subscene though?

#

for testing purposes

willow sapphire
#
    public class CameraStateAuthoring : MonoBehaviour
    {
        [SerializeField]
        public byte DefaultState;
    }

    public class CameraStateBaker : Baker<CameraStateAuthoring>
    {
        /// <inheritdoc/>
        public override void Bake(CameraStateAuthoring authoring)
        {
            var entity = this.GetEntity(TransformUsageFlags.None);
            this.AddComponent(entity, new CameraState { Value = authoring.DefaultState });
            this.AddComponent<CameraStatePrevious>(entity);
        }
    }```
#

i just author default states

#

but you can just change the state in the editor inspector and it will update

#

just find your state component and change its value in the inspector

#

if you set a state that hasn't had a component up it'll just throw a warning
Debug.LogWarning($"State {state} not setup");

#

but will work fine

valid sierra
#

inspector, yeah

#

that's nice

#

this is what I lacked

#

with my solution

valid sierra
#

I figured a really nice solution without K

#
    public struct GameState : IComponentData
    {
        public StateValue value;

        public struct Aiming : IComponentData
        {
        }

        public struct Playing : IComponentData
        {
        }
    }

    public enum StateValue : byte
    {
        Aiming = 10,
        Playing = 20,
        MainMenu = 30
    }

    public struct GameStatePrevious : IComponentData
    {
        public StateValue value;
    }
#

works like a charm

willow sapphire
#

Yeah this is totally fine

#

And much simpler for a single project

#

Only reason I use k for this is because im building a base framework to be used in multiple games

#

i really tried to make the state stuff as flexible as possible

#

and if you ever need history to go to previous state, can just swap over to it

#

i wrote it all with dynamic handles and memory manipulation so i didn't want to worry about scheduling burst jobs