- Home /
How to create a system for tracking player progress/events
Hi all!
TL;DR - What should I look at utilising within Unity for a quest-like event system.
Relatively beginner coder/Unity user here. Looking for some guidance on which system I should utilise in order to create a Quest like system.
A friend and I are working on a project that is predominantly just using uGUI.
The game has an adventure element to it, where if certain actions are performed, you progress into a new room, or maybe get prompted with a bit of text. Pretty standard fair. As a basic mechanic of progression though, players move through rooms.
I'm at the point where I need to incorporate some sort of system for progressing players in the game, and what actions this creates accordingly.
So some base requirements:
- Based on events/triggers, perform actions.
- Track player progress through these events/triggers.
- Be able to jump to an event. Say you start at event 0, but you want to test event 34. Be able to jump to that via a debug command or similiar (keypress maybe, doesn't matter).
An example scenario might be:
- Player starts off in a dark room. If either a) 20 seconds elapses, and/or b) the player clicks anywhere on the screen, text should appear in a given area. Super basic scenario. This could branch out to other things happening in the room obviously.
Now, I have looked into a few different ways to approach an event/trigger system like this, that is both scale-able for crazier events, and also easy to manage and work with as the story grows and the events potentially get bigger or even change due to testing.
1) Unity EventSystem OR C# Delegations
This could be great, as I would be able to have actions announce themselves, and then have the respective objects listen for these events, and perform the relevant actions.
The big problem with this, is I don't see it being easy to track events on a "this event has fired" level. If things changed, the listeners etc would need to be altered. This might be a nightmare for management overhead.
~
Given the example scenario, my thoughts so far have been:
Player clicks -> Announce this event -> Text box (or controller) is listening, and shows the text.
and/or
20 seconds elapses -> Announce this event -> Text box (or controller) is listening, and shows the text.
2) Mecanim Animation System as a Finite State Machine
This is currently striking me as the better method to use. I could implement a node structure using Mecanim, except it wouldn't be handling animations as it was initially intended to do. I'd instead be using it purely as a Finite State Machine, for tracking events, and the states that they are in.
~
Given the example scenario, my thoughts so far have been:
Clicking, or 20 seconds passing, moves to the "Show text" State. This then has a script associated with it that does all the relevant work.
My thoughts were having a sub-state machine per room, that then has all the states of that various room contained in it.
3) A crazy class full of if statements and bools
A controller like class that simply tracks a giant list of bools to say whether an event has triggered or not.
~
Given the example scenario, my thoughts so far have been:
Clicking within the first 20 seconds of play triggers the text to show, and the bool to be switched.
What I am actually prodding for here, is what would you all suggest as something I should use? The node system of Mecanim appeals to me a lot, and makes sense with how big the structure could get - very easy to visualise and make changes. But is it a good option? Is there something better for this scenario? Am I talking smack and the obvious answer is X?
~
Thanks all!
~
Edit: Formatting.
i just did something like this for my game. more for help boxes that pop up when users arent doing stuff.
think about being as basic as possible. really you should have a basic number for all your events. maybe another set of numbers for what room your user has entered.
when your events trigger, send the corresponding number to a static function/list somewhere that detects if it is the first time the event happened.
if so, use it as an index number of an array of things to do!
if it is an array of a custom class, you should be able to do anything you need for your events.
Answer by I5 · Sep 13, 2018 at 04:18 AM
Addressing architecture up front, nice.
I only read through your questions once, and there's a lot to consider, and my experience is that 3 different developers could each pick one of the three solutions you've proposed/considered, and each could make great arguments for and against each solution. With that, my $0.02 is on solution 3, crazy class. But, you don't have to use switches/if/else's, you can just using mapping and reflection, or some type of bitwise reference (if there's not a lot of "states").
The arguments for 3 are this.
Performance, even if you had 100 switches in the "crazy" class, it'd likely be faster (outperform) all of the other solutions. It may only be by a few hundred milliseconds, but in an Update, and especally a FixedUpdate, the milliseconds will become very noticable.
Maintenance, one class, multiple developers (even if it's just two of you). Having one crazy class will greatly simplify your concurrent development and force you guys/girls to synchronize on that one class. It will essentially force you to "get on the same page" and stay there in terms of development, which is great thing. With Visual Studio and code comments/regions, you can each "own", edit and master one class with core logic.
Problems with the alternatives. Solution 1, forget about catching and tracking exceptions when one of your listeners erorrs out, sure you can see the stack trace, but then what, no way to deal with the exception. Another ding against Solution 1, memory leaks. You're going to have to put a lot of effort into ensuring the listeners are added and removed timely, and properly. A constant maintenance headache. Solution 2, that's actually a cool idea, i hate to ding it just because it's such a novel approach. But, the dings are, again, maintenance. Because now you've just added a huge amount of code that's going to be executing in support of mechanim. You won't be maintaining that code, but you'll be stuck with all the memory and cpu resources that Unity will grab to instantiate Animator components and state machines. And, if you're ok with this, you'll also have to consider the performance hits whenever you update the "state" by setting parameters. Making things confusing, you may have have a UI edit point in addition to setting the state in the code. At first that could seem appealing because you'd have a UI view to your "state" settings, but if you then use that UI to make config changes, that's going to ripple through the code and become a synching/versioning challenge
In a nutshell, solution 3, the crazy class is the simpler, better-performing, more-maintainable lesser evil.
First and foremost, thanks heaps for replying! $$anonymous$$y first post, so wasn't entirely sure what I was going to get :)
I figured thinking about the grander architecture upfront would save some headaches down the track when there are possibly hundreds of events. Having said that, I'm vary aware that if you don't just jump in and try things, you don't learn.
The arguments you make for 3 are great, and I'm going to do some reading up on mapping and reflection. Being relatively new, these aren't concepts I've dealt with a lot yet, nor really know how to implement either. Is there any particular guides or good spots you'd recommend I go away and look at in regards to these?
Another consideration I've since had, is option 3, using ScriptableObjects as the details and content for the events. Somehow parsing these using the crazy class. I think that may ultimately assist in keeping this scalable, and manageable.
Your welcome. I've been using other people's solutions (from StackOverflow, this site, Google searches , etc.) for some time so I thought I'd spend some time to try to contribute back. Hopefully I can.
please don't research reflection, not yet, it's not key to solution 3. Essentially, I'm advocating for a "controller" architecture that funnels as much logic as possible through a single "router" point/class. Using reflection isn't necessary, it just looks more elegant, and only becomes more performant if the number of decisions/states/behaviors is so large that you'd it be faster to lookup a function in a map/dictionary rather than going through hundreds of if/else's.
on that note, using a single "controller" class (or crazy class), it would likely use the "singleton" pattern. Such a pattern's implementation will be drastically different for unity than a web application, but it's a general architecture pattern used in almost all languages and systems. The asset store has several great script assets (including singleton "game mangers") to get you started, but what I've found with a lot of the code in the asset store is that in hamstrings you into limitations, or, the opposite, the asset/code is very customizable and extendable but, as a result, performs poorly (causes your game to lag/freeze/delay/etc). If you're confident with your ability to learn and code pretty quickly, I'd recommend you roll your own solution, using a singleton controller class that keeps track of all important game state scene loading and management.. Putting as much functionality and configuration in the code (vs the editor) will payoff exponentially over time (even considering code refactoring), and since you mentioned scalability, the crazy/singleton/controller class is a well proven, scalable design pattern, with many Unity-based implementations out there. All your game logic, in one place/class that can quickly and easily be version-controlled/backed up, edited, documented and debugged (where you'll spend a lot of time). And, you can jump in and star coding it immediately. Googling "unity game manager singleton" will give you many good videos and code examples. However, I think your type of game is much more logic based so you may also want to include "RPG" in the search terms. If you're thinking about architecture up front, I think you'll make any solution work though. The singleton/crazy class is just a pretty well-proven standard.