Breaking up Code in Unity (Important game dev tips for beginners)

preview_player
Показать описание

► Subscribe here for more valuale tutorials

► Support me on Patreon:

► Project Files from this video:

desc:
In this game dev tutorial I show how to break code up in unity, separating scripts into different logic groups and adding them to a player controller as different components.

#gamedev #unity3d #programming
Рекомендации по теме
Комментарии
Автор

While your approach is certainly better than doing it all in one file, it still has a lot of dependencies. That is, the movement script still needs to know about the player script AND the input script in order to move. In some ways, you have actually made the dependencies worse, as everything now relies on the player script as well as any script that they would have relied on had you just referenced them directly. The only difference is they are getting it through the player script rather than accessing it directly. While this does have some positive implications for inheritence, as you can now specify subclasses in the player script and have your other script use those subclasses instead, there are still (in my opinion at least) better ways to do this. I would recommend making your scripts standard C# classes, rather than monobehaviours. This will lose your ability to drag and drop, but the benefits of having access to the constructor are worth it. Then, I would recommend passing in any needed dependencies through said constructors, creating (and passing) whatever is needed in the PlayerScript's Awake() function. For example, if the movementScript needs a character controller to move, it would be passed in as a constructor argument and assigned to a private variable within that class. By using this method, you can have your movement functions completely independent from input (and other scripts) entirely. This is good for things like code re-use, as your movement controller can now be used completely independently of your input source. The player script would be responsible for telling the objects how to move (by calling relevant functions in the movement controller) based on some events from the input controller. This way input can simply send events, no questions asked, the player can catch those events, and trigger movements based on them. Your input system can then be reused by any other monobehaviour that needs to capture input events, and your movement system can be reused for any charactercontroller independently of input (such as moving characters as part of a cutscene). You can even use interfaces or abstract classes here to completely overhaul functionality, something you can't do using monohebaviours since the unity inspector doesn't support interfaces at all. The only drawback is you lose drag and drop and instead have to create the other classes manually as part of your player script, but I feel the benefits are absolutely worth it, especially since you never have to worry about script execution order or any other unity shenanigans, and your object component heirarchy doesn't get bogged down with monobehaviours which really only exist to implement functionality and don't have/need options (they could all be set on the player component instead and passed to systems as needed instead).

pt
Автор

For the record, whilst messing with script execution order IS a solution, it's a terrible solution. Mostly because it's something you don't see in the code, so problems linked to execution order are horrible to debug. And if someone else goes and mess with execution order (granted, that kind of problem doesn't happen in a solo project) you're up for a headache.

GetComponent references should always be done in Awake, and pretty much any other case where you'd want to control the order in which some things happen can be dealt with in code with an Observer pattern.

jeangodecoster
Автор

I attempted to break up my code following your video and failed the first time, 2 hours of work and I almost gave up.... But I kept trying and actually did it, I have clean code!!! I learned a lot and I'm inspired learn and code more. Thank you :)

devyoung
Автор

Good job highlighting an important topic for making your Unity code more manageable! There's also a third method, which I prefer, of controlling the execution order of Start() functions of parent-child scripts such as those shown in this video. In this method you leave the parent script's start code in the Start() function, but rename the child scripts' private Start() functions as something else, such as public CustomStart() so that Unity will no longer call them automatically. Just remember to make the custom start functions public so you can call it from another script. Then, you can call the CustomStart() functions manually in your parent script's default Start() function. This way you have total control over the script execution order, and can even have the custom start functions called in the middle of the parent Start() function, exactly as you deem necessary.

Jazuhero
Автор

Very interesting video, but I think you are making a circular reference. I suggest for decoupled and performant code:

1. Store your input data on a scriptable object, and then reference only that scriptable object in your movement script. This way your InputClass writes data to the scriptable object and your MovementClass reads the data inside your scriptable object.

2. Try to not use an update() on each of your scripts, instead, call your function OnUpdate() and create another script (UpdateManager) that handles all of your scripts that need to run on Update(). For Unity it's better to handle one Update() that do a thousand of things that a thousand Updates() that do one thing.

andersencastaneda
Автор

I'm glad I found out about this before getting too far into my project

jampotjuh
Автор

Clean code is actually undervalued in indie game dev. Having object-oriented analysis and design skills really helps you to build the game and it will save you from hours of work.

Lucas-hhoh
Автор

I just discovered your videos and like most people here I'm so grateful you put so much emphasis on quality. While there are channels out there that use concepts of quality code, very few teach them. Thank you so much. I can't wait to learn more!

coobbyo
Автор

Your videos are so helpful my dude! moving from a web developer into game development it drives me mad how little information there is on good structure and best practices thanks again! :D

dankingswell
Автор

Excellent tutorial. Never knew we could set the execution order in the editor like that. Thanks a mil!

diliupg
Автор

Thanks a lot man! I am a programming noob myself and often run into problems because I don't exactly understand yet how I'm supposed to structure my code. Your video made that much more clear!

_ZEG
Автор

The best part of this practice is that you can reuse components like for example movement if your player controller and an enemy controller inherit from a unit controller script so you can have a unit controller field with either a enemy or player controller script in it. Now both your player and your enemy will be using the same movement script but they'll be called by an input script for your player and an AI script for you enemy, aka you have a bunch less duplicate code.

MorRochben
Автор

Great tip! I've started my own game recently and have heard a lot about splitting code up into different classes like this but wasn't sure how it would be done. This video helped a lot! More like this please, and post on reddit - gamedev or unity or whatever I'm sure others would find this useful :)

saultoons
Автор

omg you started right, but you did it wrong... all you did was just to split the PlayerScript.cs into 4 different files, but they are still highly dependant each other. I mean you changed the player state from the collision script! in order to move your rgb you check the InputScript inside the MovementScript.
At 8:37 you said "dispatch" but you did't use any advantages from an event system, which is the right solution to decouple the components.

ricciogiancarlo
Автор

How did you directly answer my question about script structure the day I thought to ask it? Awesome! You confirmed my thought process isn't crazy and stupid.

smashies
Автор

i've been looking for this for so long, thank youuu :)

marcossuel
Автор

the comment section is really educational and helpful! Cheers!

rashidfarhan
Автор

Not a fan of how you're doing it, the child scripts should never have to to depend on a different child script, with how you're doing it, removing one script means that the entire system is broken, because all the scripts are dependent on one another.
If it was me, I'd further break it down to different interfaces containing necessary properties. For example in your diagram at 3:10 there are the Input, Movement and Collision scripts, I'd have interfaces for those that need to feed information back to the main script to ensure the parent script has the necessary properties. So for example I would have an IMovementProperties interface that contains value for movement related stuffs, and have the "Input" script modifies those property while the "Movement" script reads them for movement purposes. This way you can for example, disable the Input script for either gameplay or debug purposes, and still have the movement script running (to manipulate it with other means, such as an auto-pathing feature).

saito
Автор

I'm sorry if I'm wrong because I'm a beginner still, but it seems a better way of ordering these particular scripts' executions would be the following:
- Disable the input/movement/collision scripts by unchecking their boxes in the inspector.
- Enabling them within the player script's Start() function by using
playerMovementScript.enabled = true;
playerInputScript.enabled = true;
etc.

This way the Start() functions of each script can be manually run and code could even be run in between if needed. Also it's much clearer what's going on because you don't have to use the unity settings window to figure it out.

Lastly, for me and assumedly all beginners (and probably long time unity users too), the way in which Awake(), Start(), and Onenable() act is very confusing and difficult to remember, so here's a useful pair of links I'm constantly going back to to help remind myself.

gimpdoctor
Автор

Hey great vids, youtube was absolutely right in suggesting you to me :-) here's an immediate sub.

One interesting other approach to take is to have one component (playerscript) internally reference interfaces (IInputController, IHealth, IMover) which are pure C# interfaces. This allows to use the PlayerScript component as entry point for game loop callbacks, and hold global configuration, and have the logic happen in dedicated implementations, which you can adapt and even interchange if the game mechanics calls for it.

It's a different approach, obviously, which may or may not be better depending on the use case, but it has the advantage to make for less granularity in your components, which once an object starts holding lots of logic, helps overall readability and usability.

jeangodecoster
join shbcf.ru