2
\$\begingroup\$

I'm not a noob in Unity although not the most experienced one, but during one of my project a noob question came to my mind! Strange but this never was an issue for me during my old projects... I became confused where should I initialize the basic properties of my agents.

Example: I have some workers (agents) in a strategy game and they can extract resources. For this I can create a script which executes the process; also I need a variable which define the maximum amount of collected resource the agent can carry. Normally I would create this property in the "Extracting" script which is fine, but should I really create different scripts for (almost) every property of an agent?

There's some properties which are very general like "HP" for example... the HP can be used in many scripts and has no proper script to define/initialize it, but there's ton of other examples...

My idea to organize this thing to create a 'Property' script where I would write all of the properties which need for the given agent type. But in this case I have to connect the script to other ones which would use any property of this agent (by GetComponent<>)

Under the hood a GameObject is a class right? There's any way to add some variable (for properties) to the specified GameObject and access these variables from any scripts of which is attached to that GameObject?

What is the proper way to do this?

\$\endgroup\$

3 Answers 3

5
\$\begingroup\$

For HP, I'd recommend creating a Health component, something like this...

public class Health : MonoBehaviour {

    public float maxHP = 100;

    public UnityEvent OnDeath;

    float _currentHP;

    void Start() {
        _currentHP = maxHP;
    }

    public void TakeDamage(float damage) {
        if (_currentHP <= 0f) return;

        _currentHP -= damage;
        if (_currentHP > 0f) return;

        _currentHP = 0f;
        if (OnDeath != null) OnDeath.Invoke();

        Destroy(gameObject);
    }
}

Now you can add a Health component to anything that needs to be able to take damage: characters, breakable objects, etc.

Any script that needs to interact with the Health component on itself or on a target it's damaging / healing can do so with GetComponent<Health>()

Now you don't need to re-invent the wheel for each one - you can share one common implementation of HP-tracking and death-invoking across everything that needs it, and make new damageable or invulnerable entities and variants as simply as adding or removing a Health component from them.

You can follow this pattern for each identifiable function your agents need. Some agents extract resources? Put the properties they need in a ResourceExtractor component. Some agents can attack? Put the properties they need in an Attack component. etc.

This is an approach called Composition Over Inheritance - instead of stacking all possible behaviours we might need on the parent GameObject, we import just the ones we care about for a given entity by attaching the right combination of components to it.


For properties that are the same for a large number of objects (like max HP or max carrying capacity, not current HP/current load which can vary for each instance), you can use the flyweight pattern or type object pattern to store them in a common object, something like...

[CreateAssetMenu(fileName = "newAgentProperties.asset", menuName = "Properties/Agent Properties")]
public class AgentProperties : ScriptableObject {

    public float movementSpeed;
    public float carryingCapacity;
    // ... etc.  
}

Then your agent script can simply store a reference to these shared properties:

public class Agent : MonoBehaviour {

    public AgentProperties properties;

    // ... Add per-instance properties and actions here.
}
\$\endgroup\$
8
  • \$\begingroup\$ Thanks! Looks this idea is a bit different from my level in Unity, but at first look I would say, this is the same solution what I mentioned above although it's more complex as your way contains methods as well. Sorry but this confuse me now! Correct me if I'm wrong (probably) but you create a new script called 'Health' and simply add it to the agent. So from the other scripts must access by GetComponent for each "property" which is makes more mess in the inspector view as I will be a lot of scripts there for each properties, instead of only one which would contain all of the properties. \$\endgroup\$
    – BushWookie
    Commented Jan 21, 2021 at 12:02
  • \$\begingroup\$ You don't create a script.for every property, but a script for each responsibility, following the Single Responsibility Principle. Your resource extraction script's responsibility is to extract resources. Health is not its job. So if you put health into that script, you end up making it more complicated than you need, and you have to repeat that work for everything else that needs health. By making health and death its own separate job, modularly, you can re-use it on any object that needs it, while keeping your other scripts lean and focused and easy to maintain. \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 12:10
  • \$\begingroup\$ Having just one script on the object that manages everything sounds like an "uber-class", and it's generally considered an anti-pattern. Before components became popular in games, it was common for game classes, especially the Player, to become giant beasts that did a little bit of everything, becoming a maintenance nightmare and a breeding ground for bugs. Chopping it up into separate logical responsibilities helps avoid this, and gives you more flexibility too. I explain in more detail in this answer \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 12:14
  • \$\begingroup\$ You just explained what I want to avoid :D I know this it the "normal" way to do things! I have same script for resource extraction, and a different one for moving etc.. I just wanted to know about properties. How can I organize/arrange them as I don't welcome the idea that all of the agent properties initialized in different scripts/components even if they have a strong relation to the property (like res extract script contain the storage capacity), that would be nice if all of the properties would located in the same place. But you're right, this is the general way \$\endgroup\$
    – BushWookie
    Commented Jan 21, 2021 at 12:30
  • \$\begingroup\$ "It would be nice if all the properties would be located in the same place" I have to disagree with that claim very strongly. I worked on a game that had one centralized repository for properties and it was a chaotic mess, especially for collaboration. The lack of encapsulation led to bugs and duplication of code, not to mention long compile times because everything touched the same few files. There are good reasons why that isn't the norm anymore. So if you post on a site to get expert advice, experts will generally recommend that you not do that. \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 12:41
0
\$\begingroup\$

You could declare a public string, which will be the identifier, and make the specific workers have different roles, depending on the string’s value. For example, something like this:

public string Job;

void MineStone() 
{
   if (Job == miner) 
   {
       // mine function goes here
   }
}
\$\endgroup\$
5
  • 1
    \$\begingroup\$ This is what we call "stringly-typed" code, and it's generally considered something to avoid. One typo and it stops working because the strings no longer match, but the compiler won't alert you about where you made the typo because as far as it knows they're all still valid strings. \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 2:12
  • \$\begingroup\$ @DMGregory How can I make it “strong-typed” code? \$\endgroup\$ Commented Jan 21, 2021 at 2:13
  • \$\begingroup\$ One approach is to make the Miner job a component class of its own. Then the component is either present and doing its work, or absent so it's not even called, and you don't need an if at all. Or, if you want something closer to the version you've shown, you can use an enum that defines your various jobs, so you at least get compile-time checking. (And faster comparisons / switching too) \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 2:18
  • \$\begingroup\$ @DMGregory Thanks. I was originally going to use this in my own game, but your way is far better than mine :) \$\endgroup\$ Commented Jan 21, 2021 at 3:15
  • \$\begingroup\$ Yes, I was thinking on the same way, but I would create a different class called "Agents.cs" and there I would describe the different types of agents (like workers, swordsmen etc..) there with a state-machine and each state would describe all of the properties of the types (+ some methods), and I would access this from the scripts where I need to use a property or method. \$\endgroup\$
    – BushWookie
    Commented Jan 21, 2021 at 12:08
0
\$\begingroup\$

No, you can't extend GameObject to add more variables to it (The Unreal Engine works like that, by the way, but Unity doesn't). Properties of a gameObject belong to the component (aka MonoBehaviour) where they make the most sense thematically and technically.

So a property like HP belongs into the Component which handles reacting to damage and destroying the object. You can then add that component to any object which is supposed to be destructible. When a script now needs to know if the object it is on is damaged, then it would do so by accessing the Health component via GetComponent.

When you develop a MonoBehavior which only works when a different component is also on the same gameObject, then you can declare that through the RequireComponent attribute. That way the Unity editor will automatically add required components for you and warn you when they go missing.

\$\endgroup\$
6
  • \$\begingroup\$ It's a same idea what I use now and what DMGregory suggested. My "big" problem with this solution, that I have to create a many component (script) for each property and I would be a lot of extra components. An agent has many properties, so wouldn't be the best thing to use 10-15 different script/component for each agent, and apply a bunch of "GetComponent<>" line the scripts which do some functions with the properties. I know the "RequireComponent" thing, which would help with the GetComponot lines but still a many component in the inspector. \$\endgroup\$
    – BushWookie
    Commented Jan 21, 2021 at 12:15
  • \$\begingroup\$ It's nor true that you need to call GetComponent every time you want to access a property, BushWookie. You could store the references to the components you need as member variables. Then if you want to check your current health you can just write eg. healthComponent.health. "Many components in the inspector" isn't really a problem.. That's how Unity works, with Transform components and Collider components and Renderer components etc. Get used to composition, and working with Unity gets a lot smoother. 🙂 \$\endgroup\$
    – DMGregory
    Commented Jan 21, 2021 at 12:26
  • \$\begingroup\$ Maybe we misunderstood each other :) I don't want to say that I need to use GetComponent each time when I want to get/set a variable. I need to to it only once in Start then I can use its reference just how you write down. Currently I have a "PropertiesWorker" component on my agent obj, and I create reference for this if I need to use a basic property in a different script. I test this concept now, even if not this is the way how Unity dev guys (and almost everybody lol) think. I created the components what I need for functionality, just I don't store there most of the properties, that's all \$\endgroup\$
    – BushWookie
    Commented Jan 21, 2021 at 12:38
  • \$\begingroup\$ @BushWookie How to distribute functionality on different components is more of an art than a science. Most Unity beginners tend to create too powerful components. Like one component Player which handles every single thing a player can do. Experienced Unity developers usually create more granular components with single responsibilities. But you can of course also overdo that by being too granular and create separate components for thing which can just as well get combined into one. You have to find a middle path which works for you. \$\endgroup\$
    – Philipp
    Commented Jan 21, 2021 at 13:06
  • 1
    \$\begingroup\$ A good Litmus test is usually to consider "Would there ever be a case where I would not add either both or none of these two components"? When the answer is no, then these two components should likely be one. \$\endgroup\$
    – Philipp
    Commented Jan 21, 2021 at 13:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .