#I try and keep my monobehaviors as
1 messages · Page 1 of 1 (latest)
Can I ask you if you are a professional developer and if yes then do your colleagues do the same?
What do you think of Simferoce's comment that such an architecture would lead to bad design choices and it isn't like you are gonna change engine midway?
Ive been a C# dev for over a decade now, professionally, yeh. However I mostly have been an application/web dev, unity I am newer to
I strongly dislike the practice of using the MonoBehavior scripts attached to individual game objects as your "place" to keep your game state and handle inter-object interactions, it produces an absolute mess of spaghetti code
What do you mean by as your "place" to keep your game state and handle inter-object interactions?
like if you have say, PlayerEntity which is a MonoBehavior on your player gameobj, and you decide to store all your Player's stats on that thing.
Now everything that wants to mutate any player stats needs a reference to the entire monobehavior
everything that can damage the player, everything that can change player stats, everything that can heal them, you have to keep drag and dropping a PlayerEntityref onto everything
you pretty quickly end up with a big messy spiderweb of all sorts of monobehaviors referencing other monobehaviors
@bleak geode
So effectively speaking:
MonoBehaviors only have 2 types of things on em, exposed "mutator" methods to mutate them, ie changing their transforms, their animator state(s), deleting/killing them, etc etc... and exposed interaction events like PointerEnter, ColliderEnter, etc
Then your EntityService keeps track of all these MonoBehaviors and handles all CRUD operations.
All your other services utilize EntityService to get refs to your entities, IE "GetEnemies" or "GetPlayer", "SpawnEnemy" etc
And then ALL of your game state is stored up in a set of INotifyPropertyChanged implementing classes, which I call the GameState
If you have used WPF before, this might be familiar to you as being quite similiar (not identical, but sort of same family as) "MVVM" pattern
Pixxel...do you create a gamemanager/statemanager and just have everything public static in there to access it?
@bleak geode @queen onyx
I've been doing Unity for a while now (5 years), professionally for 2 years. I have studied video game development specifically and done a master degree. So, I'm not necessary the one with the most practical knowledge, however I am able to maneuver around a lot of architecture and academic term.
At the end of the day, you should implement something, then figure what are the issue, irritant point of it. Those would generally differ from one person to an other and definitely will be affected by the person background.
The architecture that @queen onyx is promoting will most likely work, however, I feel that it is overcomplicated for what the needs are in Video Game and that the strong points of such architecture are more or less lost in video game development. (I have implemented such architecture and it works really well for Desktop Application)
The issue I have with such architecture in Video Game, it is that most of the interaction come from the game itself, not from the User. (Enemy interact with DeadZone) They are also really granular. (Player interact with a Door) Finally, they are really frequent. (Each Frame, Player interact with Ground). All those characteristic means that there is a need of a high level of indirection to achieve correctly the intended architecture. (It is way more complicated than a standard application)
It's just that I am doing a test for an interview now, so I don't exactly have the time and place to build everything and then see what is better. The test is supposed to take only 3 days, with this one being the last
For an interview, that would be a really bad idea to do things that you do not completely know
not static no, I use Dependency Injection
The reason I like such an architecture is very straightforward:
When you decouple the things that modify game state apart from the things that listen for game state changes, you reduce duplicate code, and keep everything nice and neatly compartmentalized.
When you have something that modifies GameState.PlayerHealth, you dont need to do any further logic beyond "the health is modified"
Anything else that cares about the player health subscribes to listening for changes to player health, if it is relevant to them, and they will handle their end of the logic on what to do when it changes.
A "Single Source of Truth" in your code vastly helps you avoid the Byzantine Generals Problem
Due to the nature of the interaction, it is impossible to define what is being listen than what is modifying the game. The player health is a prime example of something that can be modify and listen at the same time. Also note that what you are doing might most likely turn into an event hell with multiple indirection causing a real hard time to the reader to know what is the actual implication of a given functionality.
That being said, event is a really powerful tool, however you should limit the depth of the callback and try to use it in situation where you might have multiple possible candidate for listener. Health being an example.
What is more, is that usually, game are made of a ton of serialize data that varies a lot in nature. This is not the case of a normal web application/desktop application. (There is what we call Design and Level Artist that their job is to populate/script such data) It implies, that your GameState, is not something that you can define in code due to the unpredictable nature of it, it is highly volatile and it varies in nature a lot.
I've never really experimented the Byzantine General Problem out of the initialization sequence of a game or level. Most of the time, you design things to be resilient to every situation you encounter and you limit the number of pre-supposition or be sure to always keep them true.
You are not wrong to want to centralize things and compartmentalize. It is just the way you are approaching is not optimal and biases by probably your extensive experience in other domain of application.
The best way to confront yourself, is to try what you want to do. Note the things that are not working well, and then search for alternative.
At the end of the day, you might find that your architecture work for you. I'm just not convinced that it will suit well the video game space.
What is more, is that usually, game are made of a ton of serialize data that varies a lot in nature. This is not the case of a normal web application/desktop application.
Not sure what you mean by this part tbh
How do you think level are made ?
What is the primal means to create a level ?
Who creates a level, design it ?
Take by example a labyrinth.
In a labyrinth, you would place walls, enemies, goal, obstacles, traps, chests.
A designer would define rooms, scripted event, action base on event.
Those would all be define as serialized data.
They would be part of the GameState, however, they would it would be hard and exhaustive to include them all in the particular state.
A type of game that work really well with what you want, would be a tabletop game.
Lost me here, why do you think it all needs to go into the GameState as I defined it above
I mean some of it would absolutely
but not all of that
What would be consider the GameState ?
Did you state it was the only truth ?
That it was trying to centralized everything.
To reduce the amount of component
Only the stuff that you actually need to keep track of
Divide by what is being modifed and what is listening
How would you define what needs to be tracked ?
A player always need to know where the walls are.
Those would be part of the state.
Well if your wall never changes and is immutable, you dont need to keep track of that
Some are, others are not.
Its only the mutable stuff you need to bother tracking and you can safely also ignore the things already baked into your MonoBehaviors (Transform primarily)
So, you would ignore the chest the designer placed ?
I would only track if its opened or not probably
yeah prolly just a bool is all you need, thats the part that mutates, IsOpened or whatever
though for chests, assuming its not randomly generated, Id just store all the chests in a bitmask
I have implemented a chest in a production game.
It starts with just that
Then it finish with a lot of state
well is it a moveable chest?
A chest can be unlocked, or locked
A chest can be visible or invisible
A chest can be trackable or not trackable
yeah thats all fine, those are things I would store in Gamestate too
The list goes on
Would just have a ChestState object for each chest, which holds that data
No, the Chest MonoBehavior contains it states
It is serialized by the designer
Which define what the inital state
Then the chest save its own state whenever we close the game. (Or not)
Depending on what is wanted.
It largely boils down to whether I wanna be saving this data or not
So, the game state is only the save of the game ?
Well the two go hand in hand
Ideally the two go hand in hand
If done right it should be as simple as serializing your gamestate to the file, and you should be able to deserialize it "as is" and have the exact same game to the degree you care to
This is what you would do eitherway for a save game.
like if you dont care that reloading the game resets positions of stuff or perhaps items dropped on the ground vaporize, then those are things Id be fine with leaving on the MBs and not storing in the game state
The sort of litmus test is simply whether you need to save it or not
Like what animation is currently playing for a given object, Id say that doesnt need to be serialized and is a "glass box" property of the MonoBehavior
so Id put that on the MB, not the GameState
I understand that, but at the same time I do not see what can it give you if you follow that rules.
It is only a save file
You cant use it as a "GameState" if you do not consider the non-persistent data.
They still have an impact on the state of the game. Defining it as well.
So just as an example...
Lets say you have multiple conceptual distinct things that can mutate the Player's Health (poison debuff, taking an enemy hit, stepping on a trap, casting cure spell, and resting)...
And you have multiple conceptual distinct things that care about what your health currently is (health bar, text that shows health as a number, inventory screen also shows your health, when health is low it plays a sound, and when your health hits zero you wanna trigger death scene)
Without a pub/sub model, what would be your plan to wire all that up?
okay so, you would use a pub sub model
Yes
Not sure how it is relevant to the GameState
I'm sure you gonna link that to make it obvious
my GameState is just the name for where my PubSub model lives, thats all there is to it
it is, thats what INotifyPropertyChanged does
Also, it would be way more convient to define multiple pub/sub.
you have properties, and your "pub" is the setters, and your "sub" is the PropertyChanged event
I do, my "GameState" is many classes, nested
And, you should always prioritze observer over pub/sub.
The difference being that only the observable can publish
There is no properties in a pub/sub.
You may have multiple channel or whatever we call that.
There is for INotifyPropertyChanged, its just the interface for doing it
instead of calling methods the setters of the props are the publishers
You trigger a publish literally by just doing GameState.SomeValue = SomeNewValue and it will trigger the publish event (called PropertyChanged) out to all subscribed
Also, the channel needs to be as general at it can be.
By example, you better have channel for ALL the health modification.
Which is only use for those that actually wants to see most of the health modification.
Otherwise, you simply use an event.
You have only 1 single event, PropertyChanged which gives you the param PropertyName which is simply a string for what got changed, and then you just go get it
Didnt you say that you had PlayerHealth as a properties (channel) in GameState ?
Sure, you could have something like GameState.Player.CurrentHealth
Don't you find it to much precise
What do you mean?
How many variable do you think you gonna have there.
In the player (which terribly design), we have maybe 500 variables.
Only on the player
You can further subdivide as you need
Yeah, needing to replicate most of what you have done if you subdivide the player.
GameState.Player.Stats.CurrentHealth and then you could have GameState.Player.Inventory[0] or whatever, as you need
(Though arrays can be tricky, its possible but takes a bit of know-how to do it)
You gonna need to have a MonoBehavior eitherway. So, you gonna have a replication of the player in the GameState ?
Also, where the data resides if your GameState is only a pub/sub
Nah, I would just have say a SetText on the PlayerHealthText object
The properties' values is the data
nah that would be a string
I would only expose a simple SetText method or whatever on the game object, it should be extremely small and simple
Alright, I propose you try it out.
Already implemented, its quite smooth feeling
It won't scale well
what makes you say that?
Maybe you will not scale as much as have experience.
First, the data should always be closer to the owner. OOP basics
Uhhhh wat
Mate have you never used a database before? XD
Mate, have you ever wonder why we have repository pattern ?
Your data can be in an entire different system running on an entire different server in an entire different country
Let alone even in the same codebase XD
And the data is close to the owner, its inside of it
The GameState is the owner
If you have like 5 things that all care about your health in the UI, how do you pick one of them as the "owner", thats silly
No, that is what we call an Anemic Domain Model.
Terrible idea.
You should read on it.
Simple, the owner is the Health component or the Enemy or the Player depending on your current representation of health.
So how does a person know which one owns it at first glance
You have to go hunting around to find which one actually has it? Sounds like a huge pain
What do you mean ?
The player has health.
This is literally a business rule.
No ambiguity.
So you what, have to drop an entire reference to the whole player into your health bar just for the 1 value it actually needs?
So you need to have like 30 interfaces on the player for all its pieces?
Also interface doesnt magically change the fact you have to ref the whole player, you've just put a mask on it
If you have system that wants to know every health of all enemies, that you consider a pub sub as well.
Interface inverse the dependency.
Dependency Inversion Principle
The issue here is simple, the players "health" doesnt have an x/y/z position, or a rotation
So I see no reason for it to be on the game object
The player does though.
The player does yes, but not the players health
The only stuff that belongs on an MB as far as I can tell, is stuff that "exists" in the scene that actually has a position/rotation/etc
In Unity, GameObject always have a transform. Not something that is necessary, but it is like that.
The amount of GameObject that are not truly in the world is astonishing.
The amount of performance and memory lost is none.
Since player health is an ambevilant conceptual thing and doesnt "exist" in a "position" in the scene, I see no reason to bind it to a game object
You are stopping at the wrong definition. Follow any Unity official tutorial and you gonna understand that it does not matter if an object as or not a position.
Heck, we even use GameObject as seperator.
Nah I honestly find it to be weird to put stuff on game objects that have no need to "exist" in space in the scene, it makes no sense
You might find it that way now, however you gonna change you perspective.
All asset are made this way.
All Unity is made this way.
I see no reason for my character's stats to have an X/Y/Z position
They do not truly have a X/Y/Z positions, they reside on a GameObject that has a X/Y/Z position by default
which makes no sense, thats still giving em an X/Y/Z position
They have no reason to exist in "space"
Anyway, this is how it works. This is how everythings has been made in Unity.
No... you can just use a POCO
You gonna work against the engine.
No, if you do that you not gonna benefit from the serialization and compartmentalization that come with MonoBehavior.
They is no object that takes a POCO as reference in Unity.
All reference are UnityEngine.Object.
You cannot drag and drop a POCO.
Why would you?
Because, you need to setup your reference
Nope, you dont
once you decouple away from putting everything on MBs, you dont need to do that anymore
Not from a MB though
Yes, from an MB.
You invert the entire thing, your POCOs own the MBs, not the other way around
when done this way, not a single one of your MBs need to know anything about the surrounding world
they all just operate as glass boxes
What do you mean.
How would you setup which VFX to play when your chest opens ?
As a non-programmer
Without using custom editor
public class PlayerHealthBar : MonoBehavior {
...
private int _health;
private int _maxHealth;
public void SetHealth(int health) {
health = health;
Redraw();
}
public void SetMaxHealth(int maxHealth) {
_maxHealth = maxHealth;
Redraw();
}
private void Redraw() {
this.TMP.Text = $"{_health}/{_maxHealth}";
}
}
Note how designed this way, this monobehavior doesnt "get" the health, it just exposes logic for setting the values and redrawing
Do not store a copy of _health though.
it has absolutely no concept of the outside world, it exists in a glass box
yeah just an example, it can be optimized of course
It would be exactly the same, except that it would have reference to something that has health.
but I just would have all my MBs exist in "glass boxes", they dont look outside, all they do is stay in their lanes and handle their own domain of what is drawn on the screen
And what better way to setup the whole thing that with a good old serialize field.
they dont "get" stuff, they have stuff passed into them
because you have like 8 things that all care about the player health
Its there for that.
Nope, hard pass
The health bar only care for 1 health.
I see no reason to use that
I only use it for serializing "glass box" values within the given object
You gotta hate your life.
nah its very slick
Always fight agaisnt Unity.
Its not "against" it at all, it works quite smoothly
No other library will work this way.
Do you know Netcode for GameObject works ?
Or Photon
Or many other directly online library made for Unity.
They all have a NetworkObject/Identity
Which is component
That resides on a GameObject
And have no need of a position
This is how things are done.
Yeah I know a lot of people like to weirdly slap an X/Y/Z position on stuff that doesnt need it
thats not gonna convince me its a good idea lol
It is not a lot of people, it is the people that made the engine.
So? The person who invented the rubiks cube isnt the person I would talk to about how to speedcube, I would talk to the world champion of speedcubing
Not at all the samethings. The people that made the engine made choice in function of what is intended to be used at.
Just because they intended something to be some way doesnt mean its the right call
Doing the opposite is only working agaisnt what they suppose that you should do.
How many world first speedrunners were also the game developers who made the same game?
Meaning, that the tool the made will not be available for you.
Like are you gonna ask the game developer how to beat the game better... or someone whos actually good at beating the game?
theres tonnes of game devs that struggle to beat their own games well, so they arent the person I will ask about how to play their game "right"
Yeah, and there is also a lot of people that utilize "bad build" that were not made for what they should do
You do not heal with a DPS
Nor tank with a healer
Well... the point here is "who" is saying that
The developper say that.
If the game devs go "tanks shouldnt be healing" but the seasoned players are going "Cool story but thats the meta right now and optimal way to do it", who do you think knows better?
Then both are in agreement lol