Which scripting technic is better?

I was using one script for everything. Like, let’s say the player needs to fly, add flying! Player needs to shoot? Add a shooting system. This is my technic right now. But I was wondering when I watch a scripting tutorial. People mostly use more scripts.

My question is: Is using more scripts better rather than using one script? And if it is/isn’t then why?

You’ll find that pretty much everyone here with some degree of experience will be breaking down things down into smaller scripts. The point of an object oriented programming language like C# and Unity’s component architecture is to break things down into smaller pieces.

Hence the S of SOLID, referring to Single Responsibility Principle. Smaller objects are easier to debug and rationalise their purpose, along with being more reusable. When you have massive scripts with who knows how many internal parts affecting one another, it becomes a chore to change anything, add anything, or debug errors, not to mention very hard to understand what each individual internal part does.

Case in point, I’ve been working on the terrain generation for a 2d mining game thing (think Motherlode). I’ve just got the very basics working and it’s comprised of 26 different scripts so far, mostly plain C# classes to encapsulate various parts of the data structure.

It might seem like a lot, but with everything broken down, it’s easy to understand what everything does, and I can change any individual part without greatly affecting any other part.

On the flip side you have games like Subnautica and it’s dreaded Base.cs script, which is a monstrous 10K+ line mess of hard coded garbage and why it took them multiple years to add even a single new base structure, because most of the game’s building system was structure around this hot, giant mess of code.

So yes, you generally want to breaking things down into smaller objects where appropriate.

Just consider whether it scales. Would you have a ten thousand lines of code game in a single script? Will it be manageable for a human being?

I will disagree with the above advice. I like big manager scripts. If the functionality is related and the scripts need to talk with each other (can you shoot while flying? what happens then?), splitting into tiny scripts where you can’t skim through the whole thing and get an idea of what’s going on is bad for workflow and performance.

With that said, if you are reaching tens of thousands of lines, then it’s time to split.

From what I understood through time, it’s purely on what you’re comfortable with. A prime example of big scripts is the game Terraria, a massively popular indie game, which has 5 scripts total, and probably over 100,000 lines of code. However the Developer did mention “If I ever have to make a change to all Items/Weapons, it’s just not happening, ever…” lol, so take that as you will. :wink:

Personally I’m a fan of Inheritance, which the counter argument to that is Scriptable Objects or Interfaces. But to be fair, they all do the same thing, allowing a container or grouping method that gives a base line that can be easily changed in one spot to then change in every sub-class connected to it.

So as much as I could technically agree with Terraria’s dev? I would mostly disagree, as I prefer to have a script for each individual object. So just 5 scripts, to me, would be equivalent to a game like Pong…

And as most have, or will mention that it also depends if you plan on working with a team. If everyone has their own way of doing things, that project will never get off the ground. So plan for the future in this regard, and at least be comfortable with common structuring of code.

But first time game? Learning the ins and outs? Just get it done anyway you see fit… Progress and completion is the most important thing ever. You can always go back later in time and “Remaster”. :slight_smile:

Amen AcidArrow!

And this too ^ ^ ^ . No sense confusing yourself by putting everything in little silly boxes just because your CS-100 teacher says you should.

In computer games, especially during prototyping and developing new complex features, larger agglomerations of “multifunction” code simply make more sense.

Tactically I love the C# partial class modifier, which lets me break apart complex large classes into at least smaller editable files, which greatly helps with source control conflicts when working on a team.

Like my Jetpack Kurt controller. Let’s talk about the input → physics chain.

Input can come from many sources: touch, keyboard, mouse, or Android TV.

Each of those is a class. Each input goes through a different filter mechanism actually contained within the class.

But when it comes into the flight control system it is ALWAYS the same: pitch, power, yaw, roll, four float variables from -1 to +1. Yes even power goes from -1 to +1 for consistency.

Now that we’re inside the main player controller, a bunch more processing has to happen, and all of it is related.

First the power is modulated according to engine type: how responsive is the engine control? Instant? Slow?

Next the flight stability augmentation system judges user input and filters it for actual intent: the thrusters all take direct impulse actions but the user inputs are positional, as in “tilt to X degrees.” Therefore a PD controller filters each position input into a rate control according to aircraft attitude:

  • if you are upright within normal ranges - standard stability augmentation
  • if you are too far over or upside down? stability is disengaged and controls revert to rate law.

Now the inputs are mapped into the visual cockpit control stick, so you see the stick deflecting. That’s just a local piece of geometry within your field of view.

Next up is the fuel system… do you even have the fuel required at this moment to do what you want? If not, controls are nulled out and warnings are generated. The fuel consumer is a separate class instance with a “can I burn?” and “get remaining quantity” and “refuel yourself” API.

Now we have conditioned and filtered pitch, power, yaw and roll… time to scale them for this spacecraft’s particular reaction jets, which is mostly just a scaling operation.

Power is also remapped to 0 to +1 based on current engine mode settings.

Finally in FixedUpdate() the inputs are applied as torques around the three axes, plus power as a force.

Almost all of that happens in one straight line of code, with the tiny classes to one side. Almost every one of those classes began life as a function within the main controller originally, and was gradually extracted out.

But the primary controller still has the bulk of the logic. Partials let me rip it apart, and scenes let me set up the entire player in its own single scene, like so:

9234516--1290192--Screen Shot 2023-08-19 at 6.58.24 AM.png

As you can see there are several other subsystem “chains” of data: instrumentation, damage

https://www.youtube.com/watch?v=34wbtTIWdDQ

Thanks to all of you for your great responses. My question is answered thanks to all of you! Have a good day everyone!

A single script is easier to work with in the earlier stages of a project and is more performant. But as the project grows the script will take longer to compile and will become a pain to navigate. You’ll then feel compelled to split the script into smaller scripts but it may not be possible because it’s all become too entangled and so you end up stuck with a monster.

I haven’t measured this but I don’t think this is true. Unity compiles everything anyway, so I don’t see any advantage in compilation time by having multiple scripts as opposed to a big one if we are talking about roughly the same amount of code in both cases.

It’s a great approach to prototyping, but you sacrifice modulatory, adaptability and scalability. It also violates every principal in SOLID.

You probably don’t need a modular, scalable and adaptable code base. If you did, this wouldn’t be the correct approach.

It’s true, if you’re just splitting scripts for the sake of it, you’re not achieving anything. Correct approach would be to separate and encapsulate the concerns of the class.

Here’s the issue with multiple concerns in the same class. Each concern is a possible vector for bugs and breakages. I could change logic in my shoot behaviour which breaks how my flying works.

If each concern is a node than the potential interdependencies are calculated with 1/2n(n-1). 2 concerns = 1 vulnerability. 3 concerns = 3 vulnerabilities etc.

This is manageable and acceptable, for a while. Especially because the vulnerabilities aren’t guaranteed to produce any side effects, but each one is a ticking time bomb.

If we imagine a class with 10 concerns. Move, health, flying, shooting, scoring etc we’re now looking at 45 possible ways your concerns might step on each other’s toes.

We can see how DOTs ECS solves this issue. Components (usually) contain 1 value. Systems (should) only do one thing.

I guess the point I’m trying to make is, given enough development time, your game will out grow a single script where it’s no longer feasible to keep working on it.

This was a common problem for large applications which is why SOLID was devised in the first place.

I’m not saying you need to stick to SOLID, but I think you should at least know how to. Mega scripts put a cap on how large you can make your project. I’ve unfortunately seen mega scripts kill a few commercial projects for that reason.

I don’t think it’s about number of lines of code, but the logic.
If you make simple/small game I think it’s totaly okay to make several bigger “yolo” scripts or repeating functionalities, but when you try to create something bigger there will be things that repeat or your components blocks does not fit each other. The reason why people make more scripts in general is to avoid classes like FlyingCoffyMachineEventManager.

Take rigidbody and collider as example, rigidbody has a lot of functions and settings, it relies on colliders, but in general you wouldn’t want to mix collider and rigidbody into single thing or you would end up with seperate rigidbody type for each shape.

I guess he meant that you can put scripts into assemblies and domain reload is faster if you do it correctly.

I found whether changing a + to a -, or 35 big changes in multiple scripts, the swap from Visual Studio to Unity still takes 5 minutes regardless… lol

The reason it’s difficult to untangle a mega scripts, is because mega scripts allow you to avoid having to think about architecture.

Mega scripts are expedient and don’t require planning or forthoght, just throw everything into a script in a linear fashion.

However the complexity of a project will eventually no longer be able to be embodied in such a script, and when you do try to split it up, your going to be overwhelmed by the tidal wave of architecture decisions you didn’t previously have to consider, or perhaps you’re not even aware of what those decisions are.

If you start out by following the SOLID principles, it may take you longer to get things going, because those difficult problems are going to come in a slower, drip feed manner. The trade off is that it is consistently hard. It’s not going to get harder as your project grows.

Mega scripts start off easy, but get increasingly, possibly exponentially, more difficult to work with over time.

I’m not sure I follow the reasoning of the “if it gets to big” argument but that aside why in Unity and why not in every software app ever written? Why not one gigantic file with all the logic?

Mostly testability, reusability and understandability. If you needed logging would you add all the logging logic in the huge file? Why not create a logger class that could be tested and used in this app and elsewhere?

Let’s say you had to make 10 different sounds would you duplicate whatever “isPlaying” code for each of them? Why not encapsulate common behaviors in a sound class so each had its own properties and state? And (back the original premise) you could test it independent of your app running, and use it in other projects now that you know the functionality works.

The last time I saw “one huge program” might have been in the early versions of BASIC where there were no alternatives. :slight_smile:

I think this applies more in a bigger enterprise setting, most indie games don’t get that big for that to matter, yet advice along those lines keeps getting repeated and some newbies interpret it as “I need one script for Jump, a different script for Run and a different script for Throw”, which is silly.

SOLID is an abstraction sinkhole. Sure, you should eyeball the single responsibility principle, but you shouldn’t think twice to just throw it into the waste basket whenever it’s more convenient. The only “principle” you should try to adhere to is DRY. If that’s covered, you will have okay time with your code and you can move things around quite quickly.
Never think about future events which never come. The 20 types of thing you will extend your code with.
In an enterprise software usually worth the time and resources to abstract away certain things, after all, if you store four things about a person, it is a high chance that you will store six or ten or twenty piece of personal data. Extending in the future is worth to make easy.
In a game, where you will spend more time to design your game and mechanics than actually writing code, it usually isn’t worth it, it’s a time-consuming, resource-hogging lie enterprise programmers will sell you who don’t know any better.
Sure, you should make rules about basic naming conventions and whatnot and you should familiarize yourself with the basic programming patterns and use them when it naturally occur to you that they are the best fit for the thing you are doing and don’t sweat if they don’t.

Sometimes you will need separate scripts for walking and running and jumping and throwing (especially if you actually have [NOT you may have at some point!] different actors using those behaviors on same-ish ways), but as @AcidArrow mentioned it, sometimes it doesn’t matter and they are unique to the player(s) so you can easily throw them together to one place and optimize them ways you can’t otherwise. Or just having the overview you wouldn’t.

I started using dependency injection once I realized how it allowed me to just stop thinking about implementation details in some of my code.

My only argument is that I think developers should know how to make large scalable code bases. It’s one thing to say “I’m not going to code well because it’s a waste of time” versus “I’m not going to code well because I don’t know how to”. If newbies are told they’ll never need any other pattern than mega-script, then they’ll never write anything else. If nothing else it’s a very career limiting mindset.

I just simply don’t think about implementation details when I don’t need to and I don’t need to maintain another set of abstraction blocking any attempt to properly optimize two things together.

I’d have agreed with you on that last part if it were any other framework than Init(args). I’ve seen entirely too many people recommend frameworks that are overly complex. The VContainer and Zenject examples immediately turned me off as soon as I saw them, but with Init(args) you just add an attribute to the class.

Here’s how you make a singleton that auto-instantiates the first time it is needed, and while I could have made the code to do that for me it’s either a base class I have to maintain or more cluttered code.

[Service(LazyInit = true, FindFromScene = true)]
public class InputManager : MonoBehaviour
var inputManager = Service<InputManager>.Instance;