2
\$\begingroup\$

I'm working with a group of programmers and I'm trying to program different types of monsters in Unity. I want to make it so that all the monsters have values like attack power, attack speed, moving speed, etc, and basic functions like attack or walk.

Normally, if I wanted to do this in Java, I would just create one class called Monster and make child classes for each of the different types, but I'm not sure how to do this in Unity, since Unity c# uses Monobehavior and you have to attach scripts to a game object.

To go into more detail, I want to instantiate different monster types at random. I would have prefabs like slime, skeleton, etc with their corresponding scripts, and instantiate them randomly. And for each of these instantiated monsters, I want them to immediately move and attack according to its type's (slime, skeleton, ...) attack speed, attack power, etc.

How could I achieve this? As in, how would professional game developers organize their code in this situation? I'm looking for basic directions like: create this class, attach this script to this prefab, etc; I could look up the details on my own, and I just need simple guidelines.

\$\endgroup\$
4
  • \$\begingroup\$ Spawning your monsters at random is the same as picking randomly from a list of all prefabs that you want them to spawn from. About the data sructure, there are a lot of answers as well already like gamedev.stackexchange.com/questions/96079/… just check a few of the linked questions. If you encouter a problem implementing it in your game, post your code, the error and with what you are stuck in a single question \$\endgroup\$
    – Zibelas
    Commented Dec 4, 2021 at 9:01
  • 1
    \$\begingroup\$ Unity's component system emphasizes a design principle called Composition Over Inheritance. This says we should generally prefer to split a complex class like "slime" into modular components, like an attack component, movement component, AI component, etc. Then we can mix and match those components to make new monsters with no new code (like a stationary monster with no movement or a nuisance/blocking monster with no attack). We have lots of past Q&A about applying this principle in Unity — does this help? \$\endgroup\$
    – DMGregory
    Commented Dec 4, 2021 at 11:57
  • \$\begingroup\$ This question in particular could be considered a duplicate of this one. Does the description there give you the guidance you need? If not, could you edit your question to clarify what support you need in applying these existing answers to your project? \$\endgroup\$
    – DMGregory
    Commented Dec 4, 2021 at 12:02
  • 1
    \$\begingroup\$ when reading the answers to this question, keep in mind that software engineering is no exact science. Different developers will make different recommendations. There is no "best" or "standard" solution. Only solutions which work or don't work for your particular project. \$\endgroup\$
    – Philipp
    Commented Dec 8, 2021 at 10:46

2 Answers 2

1
\$\begingroup\$

This is how I would do it:

  • All the parameters are configured in a data file (JSON, XML) or ScriptableObject if you prefer. This data file will have a list of all enemies, and for each enemy parameters like - prefab name, damage, health, speed, etc.
  • A single animator that has the state machine that fits all enemies, then an AnimatorOverrideController for each specific enemy prefab, that assigns the enemy's specific animations.
  • A base enemy script that all enemies use. If for some reason you need a specific script for specific enemies (in most cases I don't think this should be necessary, see below), they can inherit from the base enemy script and add to it.
  • The code then instantiates prefabs based on the parameters configured in the data files, and activates animations using the same variables for all enemies (since they share a state machine, only the actual animations are overridden).
  • The data file can configure additional stuff like - what types of behavior the enemy has, and then the script can check that configuration and decide what actions to perform. If some enemies are missing actions (say static enemies that can't move), they can still use the same infrastructure and script as all other enemies, but will not execute the move commands.
  • If you have significantly different AI for each enemy and you want them in separate scripts, you can simply add different scripts to the prefab (all inheriting from a base AI class). Alternatively you can add the AI script in real-time using reflection, if you specify which AI each enemy has in the data file. The latter is preferred by me because it lets you control enemy behavior directly from the data file without having to open prefabs.
\$\endgroup\$
0
\$\begingroup\$

Prefab management

Unity can do inheritance of game objects by using prefab variants. A prefab variant inherits all the components and sub-objects from its original. When you change the original, then all the variants get changed too. But you can add additional components and sub-objects to the variants and you can override individual properties from the parent by changing them in the variant.

That means you can create on generic "Enemy" prefab which has everything all enemies have in common, and then create multiple variant prefabs inheriting from it which represent more specialized enemies. When you now do a change to the generic enemy, then that change gets automatically applied to all the enemies (unless that enemy overrides that specific property).

Spawning

When you have spawners which are supposed to spawn enemies randomly, then you can give it a public GameObject[] enemyPrefabs (or [SerializeField] private GameObject[] enemyPrefabs if you want to be thorough). You can then maintain the palette of enemies in the editor. At runtime, the spawner would then pick from that palette of prefabs.

You might perhaps want some enemies to spawn more frequently than others. You can do that with multiple spawners with different settings. For example, when you want more zombies than skeletons, you can have one spawner which spawns skeletons and zombies, and another which spawns only zombies. Or you can do that using the techniques from the question "How do I create a weighted collection and then pick a random element from it?".

Code architecture

On the script side, Unity's software architecture is based on the principle of Composition over Inheritance. Which means that instead of having one abstract base class "Enemy" with other more specialized inheriting classes, you usually have multiple classes which each implement one mechanic and which can be mixed and matched to create new enemies with very little effort.

For example, when you have enemies which move, enemies which attack and enemies which do both, then you create two MonoBehaviours "Attacker" and "Mover". Each enemy might have the first, the second or both behaviours. Enemy-specific details (How fast does it move? How strong does it hit?) can then be configured via serialized fields on those behaviours.

If you want to add some unique enemy-specific programming to control where these enemies moves and when they attack, then you can add a separate controller script to each prefab which makes high-level decisions, but references the "Attacker" and "Mover" components on the same game object (via GetComponent<T>()) and delegates the low level execution of those decisions to them by calling public methods.

One great advantage of this architecture is that it allows you to experiment in the editor. You can add and remove components and experiment with values without having to switch back to the IDE. For example, you can see how enemy A works if you give it the controller script of enemy B. Or if it is still challenging if you take away its Mover component, turning it into a stationary enemy. You can in many cases even perform such experiments while the game is running in the editor.

\$\endgroup\$

You must log in to answer this question.

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