I am making a quiz game in Unity and I've come across architectural problem.
I want the game to have few game modes, like standard, faster answer - more points, etc. Each of which will behave in its own specific way but some things will be very similar like answering questions, starting timer, etc.
Currently its structured based on this. There is a QuizSystem
that holds reference to QuestionDatabase
, UIReferences
(buttons,score text, etc) and GameSettings
(questions per game/per mode etc).
To start the game you need to call QuizSystem.Start()
and it starts its current GameMode
which derives from abstract StateMachine
and is a monobehaviour (dont know if neccesary). I also have abstract State
class from which different game states will derive from. It has a constructor with (GameMode owner)
as paramenter and 3 functions: Start(), Tick(), End()
.
So, this way I can have Standard
game mode which will instatiate lets say StandardPreparationState
, which on end will call StandardAnswerState
which will start the timer and wait for user input and again call StandardPreparationState
. Cycle will repeat until questions per mode amount is reached and then delegate next action to QuizSystem
.
The advantage of this approach is that every mode can behave in its own way like add additional steps in between but it kinda limits reusability. What I mean by that is if some OtherMode
would have the same preparation functionality but different action afterwards, it wouldn't work beacause
StandardPreparationState
would transition to StandardAnswerState
.
I could add another parameter such as (GameMode owner, State transitionTo)
to the State
constructor but that somehow seems wrong I don't know why xD
What I want to know is how do you guys implement different game modes for your games? Do you make each mode as separate scene? Or maybe use States Machine pattern and have Manager
class that takes care of starting/swaping modes?
I know that each game is different but are there maybe some common approaches for that?
Thanks in advance!
CodePudding user response:
This question is quite open and opinion-based. However there are few "common" approaches, one of the most important is to make game "data-driven".
What? Why? How?
Imagine you are having space shooter, where you have your ship flying around and picking guns. Each time you add new gun, you will have to code its damage, kind of projectiles and how many of them you shoot, their color, in what pattern they spawn, speed, size, ...
Everytime you would want to add a new gun, you would need to enter the code and change it there, compile, ... Lot of work.
Instead people thought, "why don't we create simple class that holds all the parameters? We will make it editable from Unity, instatiate it in the project and we won't need to code that much."
This is when Unity brought Scriptable objects.
A
ScriptableObject
is a data container that you can use to save large amounts of data, independent of class instances. One of the main use cases forScriptableObjects
is to reduce your Project’s memory usage by avoiding copies of values.
The idea is to create scriptable object for your mode and set up multiple kinds of modifiers that will the mode use. Folder structure might look like:
> ScriptableObjects
| |--> Modes
| |-> NormalSO (instance)
| |-> HardWithLotOfExpSO (instance)
| |-> EasyWithLowerExpSO (instance)
> Script
|--> ScriptableObjects
|-> ModeSO
ScriptableObject
is class that doesn't really have the logic inside, just creates "structure" for keeping data. Example of such class would be:
public class ModeSO : ScriptableObject
{
public string modeName;
public float scoreMultiplier;
public int numberOfEnemiesMaxAlive;
public int numberOfEnemiesTotal;
public Vector3[] spawnPoints;
}
In the Unity itself you would then create instance of such objects. And what about interaction with other classes? Well, they would just work as:
- Game manager hold single instance of active mode
- Class that would be handling score (e.g.
player
/scoreboard
) orEnemy
would askGameManager
what is current multiplier for score WorldSpawner
would askGameManager
how many enemies should he spawn, where, and when to spawn next ones- At the beginning of the game you would be able to select difficulty by its name
Example of one of the classes (Scoreboard):
public class ScoreBoard: MonoBehavior
{
GameManager manager;
private float totalScore;
OnEnemyDestroyed(float scoreForEnemy)
{
totalScore = scoreForEnemy * (manager?.activeMode?.modifier ?? 1);
}
}
And the best is, whenever you will change some data, you will just modify the existing instance in the Unity. No need to go into code. No need to recompile whole game.
CodePudding user response:
I think having different scenes for each game mode, especially if the modes are very similar save for a few settings, is unnecessary. I'd have to see more of your design to know how your game logic is being handled (is it all managed in one GameManager script? Or are there multiple scripts in the scene that take values from this manager script to handle game mechanics?)
One way I've handled different game modes before is to use a public int value in the GameManager that represents the different modes (i.e 1 = easy, 2 = medium, 3 = hard). I would then use a switch statement in any scripts whose behavior/values depend on this mode and reference that public int to determine game settings.
Game Manager:
public class GameManager: MonoBehavior
{
public int gameMode = 0; //set to 1,2, or 3 by UI
[...] //rest of game manager code
}
Example Behavior Script:
public class EnemySpawn: MonoBehavior
{
public GameObject enemy;
GameManager gm;
private float spawnRate;
Start()
{
switch(gm.gameMode)
{
case 1:
spawnRate = 15f;
break;
case 2:
spawnRate = 10f;
break;
case 3:
spawnRate = 5f;
break;
default:
spawnRate = 10000f;
Debug.log("invalid mode");
break;
}
}
Awake()
{
InvokeRepeating("SpawnEnemy", spawnRate, 0f);
}
[...] //rest of EnemySpawn code, including a SpawnEnemy() function.
}
This example would reference the GameManager's gameMode int to determine what speed to spawn enemies at, assuming there is a SpawnEnemy() function somewhere down in the code. I can't verify if this example is syntactically correct, but it's just to show one way to handle game modes.