#Best way to create a queue of different actions from different objects?

50 messages · Page 1 of 1 (latest)

proud igloo
#

I'm working on a turn based rogue-lite game with phases, and multiple objects have effects that trigger when the game phase changes. Currently, they all happen at the same time, but I'd like for them to instead happen one at a time. Like have them all wait their turn, only triggering when the one before it finishes. However, since there would be a variety of different types of actions, and one object could have multiple actions, something like an Interface or an array/list/stack/etc wouldn't work. Tried researching it on my own but couldn't figure out the best wording to get the answer Im looking for.

Is there a way to create a list/stack/whatever of functions, or some other structure to achieve this? Thanks in advance.

steep wharf
# proud igloo I'm working on a turn based rogue-lite game with phases, and multiple objects ha...

you can look into the "Command Pattern"
define an interface for a command, have it have methods like Update/Process with input parameters if you need them, maybe a dedicated Start method if you like, and a method where you can check if the commandf is finished,
the executing class of the commands has a list that they process, and in each MonoBehaviour Update you call Update/Process on the current command, and then check if the command has finished. And if so you move the "current" command to be the next one in the list, or you just always take the element[0] and if a command has finished you list.RemoveAt(0) so that the first element is removed
and other classes can add to that list if they wnna queue commands

I say list but it could probably be a Queue data type as that seems to fit better

proud igloo
wicked snowBOT
#

jukebox5845 thanked fmproductions

junior nebula
#

An interface + list/stack would definitely work btw, but you probably need to think POCO and not MonoBehaviour.

One "object/MonoBehaviour" can emit multiple POCO without any problem, and each POCO can implements your interface with some "Act" and "Priority" method.

potent egret
#

For my turn based game I turned every action into a Coroutine which starts with a CustomYieldInstruction at the beginning. The yield instruction hosts a static List of Keys.

Whenever an Action wants to do something, they enqueue a unique key into that List ( lets say the Objects HashCode) and then wait until their unique key is at position 0 of that List.

Then they do whatever action they want to do and at the end release their key from the List

junior nebula
#

................. Don't do that.

#

It's impossible to debug properly with breakpoints and it has poor performance (althroug this is not important here since there are only few actions at once).

potent egret
#

Im a traditional debug log debugger so I cant speak to the breakpoint issue

#

How much worse in terms of performance is it though? I havent seen any noticable lags from it

#

Might be worse in a non-turn based game

junior nebula
#

Basically what coroutines are is a stack of Coroutine classes which are generated as iterables and executed until the next step of the iterable. You can achieve exactly the same thing without the iterable part and without having to check every coroutine every frame, only the next one in order of priority.

steep wharf
#

I honestly don't think the amount of coroutines that might come from this will have any noticable performance impact

#

unity goes through the routines, evaluates the yields and yeah

#

might be a lot if you strictly compare it to what work those actions (or rather the current action) does each frame, or I mean if you see it relative to that

junior nebula
#

Same code, coroutine intensive, is 20% more expensive, but as I said, you'll never have a code that rely so much on coroutine, and you won't be able to notice that for 20-30 actions looking up for a key every frame.

#

But still, it's dirty and harder to debug to do it with coroutine than it would be with a proper list of interfaces.

steep wharf
#

personally, debugging aside, I do not like that you have to keep different life cycle aspects in mind with coroutines
if the behaviour that started it is disabled, it will keep running, because it's gameobject dependent.
if the gameobject is deactivated, coroutines will completely stop and drop
ofc that can easily be bypassed by just having a dedicated persistent gameobject for managing the action processing, but it's just one of those things you have to be aware of

potent egret
#

How often do you disable a custom component though but not their whole gsmeobject as well? Never had that as a use case

steep wharf
#

maybe when you pause a game,
I rarely have this either tbh

potent egret
#

I dont know how the implementation would look like using lists and interfaced but I like the fact that any custom component can use them without inheriting from an interface or other complex interweaving
I also find it quite ugly to have things like that queried in an update method that is also being used for other logic, but thats just preference

#

Im willing to accept though that using coroutines isnt the very best, its just the most readable and easy version to implement Actions Ive worked with so far

steep wharf
#

the less unity lifecycle reliant stuff you use, the less magic is going on,
again not an issue once you are aware,
but say you just have regular types/classes, all you might do is to process stuff in an Update() and i think more people know how that is tied to gameobject and behaviour enabled state

potent egret
#

Makes sense yeah

junior nebula
#

Disabling the game object means inter-interaction between component, which is in my sense a bad practice.

#

(when it comes to disabling scripts)

#

Just to clarify, I mean indirect-inter-component interactions.

#

Basically inability to track why something has been disabled easily.
Most of the time, I'll wrap up "this.enabled = false" in a Disable method just so I can track why some MonoBehavior has been disabled. gameObject.SetActive(false) is nightmare fuel.

potent egret
#

But you could wrap that around a disable method too, no?

#

What are some use cases where you only disable a component?

junior nebula
junior nebula
#

But I will definitely always wrap it with my own disable method for sanity.

#

So that you can look by reference who is calling the method.

#

This is what a player object may look like.

#

Sometimes you want to disable the ability to interact with objects because you are in a semi-cutscene that still allow player movement but not gameplay.

#

So in that case, you won't disable the whole game object (usually it makes little sense to disable the whole game object, if it's not to "kill" it and to return it to the pooling system).

#

Now, truth being said, I'm semi-lying in my case because I usually use a lock-system when it comes to disabling/enabling to handle multiple sources.

#

So it will look more like this:

    public override void AddInputLock(MonoBehaviour monoBehaviour)
    {
        InputsLock.Add(monoBehaviour);
        playerMovementController.SetMovement(Vector2.zero, resetVelocity: true);

        if (InputsLock.Count == 1)
            OnPlayerInputLocked?.Invoke(true);
    }

    public override void RemoveInputLock(MonoBehaviour monoBehaviour)
    {
        InputsLock.Remove(monoBehaviour);

        if (InputsLock.Count == 0)
            OnPlayerInputLocked?.Invoke(false);
    }
#

Or like this:

public class MouseVisibility : Singleton<MouseVisibility>
{
    #region Attributes
    // Locks asking for the cursor to be visible
    private HashSet<MonoBehaviour> locks = new HashSet<MonoBehaviour>();
    #endregion

    #region API
    public static void SetVisible(MonoBehaviour lockSource)
    {
        Instance.locks.Add(lockSource);

        if (Instance.locks.Count == 1)
        {
            Cursor.visible = true;
            Cursor.lockState = CursorLockMode.Confined;
        }
    }

    public static void SetInvisible(MonoBehaviour lockSource)
    {
        Instance.locks.Remove(lockSource);

        if (Instance.locks.Count == 0)
        {
            Cursor.visible = false;
            Cursor.lockState = CursorLockMode.Locked;
        }
    }
#

(not related to "update", but still a on/off logic)

#

A reason would be like:

The player enters a narrative sequence, which forbids feature A.
The player starts a dialogue within the sequence, which forbids feature A.

The dialogue ends, which enables feature A.
Feature A is now available, the narrative sequence is still running.

#

With a lock system, this is not possible, both sources need to enable back feature A for feature A to be available again.

#

(but anyway I'm disgressing)

sullen turret
#

@junior nebula +1 for lock system 😛 In my current project at work I'm doing this ref counting kind of thing so much that I've considered writing a class that does all that basic logic and adds a bit of structure to it (so that it becomes a simpler common pattern)
(And yes, I was randomly reading through advanced stuff because I was bored 😄 )

junior nebula