There is almost always a player class in a game. The player can generally do a lot in the game which means for me this class ends up being huge with a ton of variables to support each piece of functionality the player can do. Each piece is fairly small on its own but combined I end up with thousands of lines of code and it becomes a pain to find what you need and scary to make changes. With something that is basically a general control for the entire game how do you avoid this issue?
-
26\$\begingroup\$ Multiple files or one file, the code has to go somewhere. Games are complex. To find what you need, write good method names and descriptive comments. Don't be scared to make changes - just test. And backup your work :) \$\endgroup\$– Chris McFarlandFeb 21, 2017 at 21:16
-
7\$\begingroup\$ I get it has to go somewhere but code design matters in flexibility and maintenance. Having a class or group of code that is thousands of lines just doesn't strike me as either. \$\endgroup\$– user441521Feb 21, 2017 at 21:24
-
17\$\begingroup\$ @ChrisMcFarland do not suggest to backup, suggest to version code XD. \$\endgroup\$– CoffeDeveloperFeb 22, 2017 at 11:51
-
1\$\begingroup\$ @ChrisMcFarland I agree with GameDeveloper. Having version control like Git, svn, TFS,... makes development so much easier due to being able to undo big changes much more easily and being able to easily recover from things like accidentally deleting your project, hardware failure or file corruption. \$\endgroup\$– NzallFeb 22, 2017 at 12:29
-
3\$\begingroup\$ @TylerH: I strongly disagree. Backups do not permit merging many exploratory changes together, nor do they tie anywhere near as much useful metadata to changesets, nor do they enable sane multi-developer workflows. You can use version control like a very powerful point-in-time backup system, but that's missing a lot of the potential of those tools. \$\endgroup\$– PhoshiFeb 24, 2017 at 10:29
4 Answers
You'd usually use an entity component system (An entity component system is a component based architecture). This also makes creating other entities way easier, and can also make the enemies/NPCs have the same components as the player.
This approach goes in the exact opposite direction as an object oriented approach. Everything in the game is an entity. The entity is just a case without any game mechanics built into it. It has a list of components and a way to manipulate them.
For example, the player has a position component, an animation component and an input component and when user presses space, you want the player to jump.
You can achieve this by giving the player entity a jump component, which when called makes the animatiom component change to the jumping animation and you make the player have a positive y velocity in the position component. In the input component you listen for the space key and you call the jump component. (This is just an example, you should have a controller component for movement).
This helps breaking up the code into smaller, reusable modules, and can result in a more organized project.
-
\$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$– HouseFeb 21, 2017 at 22:08
-
9\$\begingroup\$ While I understand moving comments that need to be moved, don't move those that challenge the accuracy of the answer. That should be obvious, no? \$\endgroup\$ Feb 24, 2017 at 9:16
Games aren't unique in this; god-classes are an anti-pattern everywhere.
A common solution is to break down the big class in a tree of smaller classes. If the player has an inventory, don't make inventory management part of class Player
. Instead, create a class Inventory
. This is one member for class Player
, but internally class Inventory
can wrap a lot of code.
Another example: a player character may have relations with NPC's, so you may have a class Relation
referencing both the Player
object and the NPC
object, but belonging to neither.
-
\$\begingroup\$ Yeah, I was just looking for ideas on how to do this. What the mindset was because there is a lot of little pieces functionality so while coding it's not natural, to me anyway, to break out those little pieces of functionality. However it becomes obvious that all those little pieces of functionality start to make the player class huge. \$\endgroup\$ Feb 22, 2017 at 12:48
-
1\$\begingroup\$ People usually say something is a god class or god object, when it contains and manages every other class/object in the game. \$\endgroup\$– BálintFeb 22, 2017 at 21:02
1) Player: State-machine + component based architecture.
Usual components for Player: HealthSystem, MovementSystem, InventorySystem, ActionSystem. Those are all classes like class HealthSystem
.
I do not recommend using Update()
there (It makes no sense in usual cases to have update in health system unless you need it for some actions there every frame, these rarely occur. One case you may also think of - player gets poisoned and you need him to lose health from time to time - here I suggest using coroutines. Another one constantly regenerate health or running power, you just take current health or power and call coroutine to fill to that level when time comes. Break coroutine when the health is full or he was damaged or he started running again and so on. OK that was a little bit offtopic but I hope it was useful).
States: LootState, RunState, WalkState, AttackState, IDLEState.
Every state inherits from interface IState
. IState
has in our case has 4 methods just for an example. Loot() Run() Walk() Attack()
Also, we have class InputController
where we check for every Input of the user.
Now to real example: in InputController
we check if player presses any of the WASD or arrows
and then if he also presses the Shift
. If he pressed only WASD
then we call _currentPlayerState.Walk();
when this happends and we have currentPlayerState
to be equal to WalkState
then in WalkState.Walk()
we have all components needed for this state - in this case MovementSystem
, so we make the player move public void Walk() { _playerMovementSystem.Walk(); }
- you see what we have here? We have second layer of behavior and that is very good for code maintainance and debuging.
Now to the second case: what if we have WASD
+ Shift
pressed? But our previous state was WalkState
. In this case Run()
will be called in InputController
(don't mix this up, Run()
is called because we have WASD
+ Shift
check in InputController
not because of the WalkState
). When we call _currentPlayerState.Run();
in WalkState
- we know that we have to switch _currentPlayerState
to RunState
and we do so in Run()
of WalkState
and call it again inside this method but now with a different state because we don't want to lose action this frame. And now of course we call _playerMovementSystem.Run();
.
But what for LootState
when player can't walk or run untill he releases the button? Well in this case when we started looting, for example when button E
was pressed we call _currentPlayerState.Loot();
we switch to LootState
and now call its called from there. There we for example call collsion method to get if there is something to loot in range. And we call coroutine where we have an animation or where we start it and also check if the player still holds the button, if not coroutine breaks, if yes we give him loot in the end of the coroutine.
But what if player presses WASD
? - _currentPlayerState.Walk();
is called, but here is the pretty thing about state-machine, in LootState.Walk()
we have an empty method that does nothing or as I would do as a feature - players says: "Hey man, I haven't looted this yet, can you wait?".
When he ends looting, we change to IDLEState
.
Also, you could do another script that is called class BaseState : IState
that has all these default methods behavior implemented, but has them virtual
so you can override
them in class LootState : BaseState
type of classes.
The component based system is great, the only thing that bothers me about it are Instances, many of them. And it takes more memory and work for garbage collector. For example, if you have 1000 instances of enemy. All of them having 4 components. 4000 objects instead of 1000. Mb it's not so big deal (I haven't run performance tests) if we consider all components that unity gameobject has.
2) Inheritance based architecture. Though you will notice that we can't completely get rid of components - it's actually impossible if we want to have clean and working code. Also, if we want to use Design Patterns that are highly recommended to use in proper cases (don't overuse them too, it's called overengeneering).
Imagine we have a Player class that has all the properties it needs to exits in a game. It has health, mana or energy, can move, run and use abilities, has an inventory, can craft items, loot items, even can build some barricades or turrets.
First of all I'm going to say that Inventory, Crafting, Movement, Building should be component based because it's not player responsibility to have methods like AddItemToInventoryArray()
- though player can have a method like PutItemToInventory()
that will call previous described method (2 layers - we can add some conditions depending on different layers).
Another example with building. Player can call something like OpenBuildingWindow()
, but Building
would take care of all the rest, and when user decides to build some specific building, he passes all needed information to player to Build(BuildingInfo someBuildingInfo)
and player starts to build it with all animations needed.
SOLID - OOP principles. S - single responsibility: that what we have seen in previous examples. Yeah ok, but where is the Inheritance?
Here: should health and other characteristics of player be handled by another entity? I think not. There can't be a player without health, if there is one, we just don't inherit. For example, we have IDamagable
, LivingEntity
, IGameActor
, GameActor
. IDamagable
of course has TakeDamage()
.
class LivinEntity : IDamagable {
private float _health; // For fields that are the same between Instances I would use Flyweight Pattern.
public void TakeDamage() {
....
}
}
class GameActor : LivingEntity, IGameActor {
// Here goes state machine and other attached components needed.
}
class Player : GameActor {
// Inventory, Building, Crafting.... components.
}
So here I could not actually divide components from inheritance, but we can mix them as you see. We can also make some base classes for Building system for example if we have different types of it and we don't want to write any more code than needed. Indeed we also can have different types of buildings and there is actually no good way to do it component based!
OrganicBuilding : Building
, TechBuilding : Building
. You don't need to create 2 components and write code there twice for common operations or properties of building. And then add them differently, you can use power of inheritance and later of polymorphism and incapsulation.
I would suggest using something in between. And not overuse components.
I highly recommend reading this book about Game Programming Patterns - it's free on WEB.
-
\$\begingroup\$ I'll dig in later tonight but FYI I'm not using unity so I'll have to adjust some which is fine. \$\endgroup\$ Feb 21, 2017 at 23:07
-
\$\begingroup\$ Oh, sry I thought here was a Unity tag, my bad. The only thing is MonoBehavior - it's just a base class for every Instance on the scene in Unity editor. As for Physics.OverlapSphere() - it's a method that creates a sphere collider during the frame and checks what it touches. Coroutines are like fake Update, their calls can be reduced to smaller amounts than fps on players PC - good for performance. Start() - just a method called once when Instance is created. Everything else should apply everywhere else. Next part I will not use anything with Unity. Sry. Hope this clarified something. \$\endgroup\$ Feb 22, 2017 at 10:26
-
\$\begingroup\$ I've used Unity before so I understand the idea. I'm using Lua which has coroutines as well so things should translate fairly well. \$\endgroup\$ Feb 22, 2017 at 12:44
-
\$\begingroup\$ This answer seems a bit too Unity-specific considering a lack of the Unity tag. If you made it more generic and made the unity stuff more of an example, this would be a much better answer. \$\endgroup\$– PharapFeb 22, 2017 at 13:29
-
There's no silver bullet to this problem, but there are various different approaches, almost all of which revolve around the principle of 'separation of concerns'. Other answers have already discussed the popular component-based approach, but there are other approaches that can be used instead of or along with the component-based solution. I'm going to be discussing the entity-controller approach as it's one of my preferred solutions to this problem.
Firstly, the very idea of a Player
class is misleading in the first place. Many people tend to think of a player character, npc characters and monsters/enemies as being different classes, when in actual fact all of those have quite a lot in common: they are all drawn on the screen, they all move around, they might all have inventories etc.
This way of thinking leads to an approach where player characters, non player characters and monsters/enemies are all treated as 'Entity
s' rather than being treated differently. Naturally though, they do have to behave differently - the player character has to be controlled via input and npcs need ai.
The solution to this is to have Controller
classes which are used to control Entity
s. By doing this, all the heavy logic ends up in the controller and all the data and commonality is stored in the entity.
In addition, by subclassing Controller
into InputController
and AIController
, it allows the player to effectively control any Entity
in the room. This approach also helps with multiplayer by having a RemoteController
or NetworkController
class that operates via commands from a network stream.
This can result in a lot of the logic being shoehorned into one Controller
if you aren't careful. The way to avoid that is to have Controller
s that are composed of other Controller
s, or making the Controller
functionality depend on various properties of the Controller
. For example, the AIController
would have a DecisionTree
attached to it, and the PlayerCharacterController
could be composed of various other Controller
s such as a MovementController
, a JumpController
(containing a state machine with the states OnGround, Ascending and Descending), an InventoryUIController
. An added benefit of this is that new Controller
s can be added as new features are added - if a game starts without an inventory system and one is added, a controller for it can be tacked on later.
-
\$\begingroup\$ I like the idea of this but it seems to have transferred all the code to the controller class leaving me with the same issue. \$\endgroup\$ Feb 22, 2017 at 15:12
-
\$\begingroup\$ @user441521 Just realised there was an extra paragraph I was going to add but l lost it when my browser crashed. I'll add it now. Basically, you can have different controllers can compose them into aggregate controllers so each controller is handling different things. e.g. AggregateController.Controllers = { JumpController(keybinds), MoveController(keybinds), InventoryUIController(keybinds, uisystem) } \$\endgroup\$– PharapFeb 22, 2017 at 19:29