[System.Serializable]
public class AgentLocomotion
{ ... }
[System.Serializable]
public class ChaseLocomotion : AgentLocomotion
{ ... }
[System.Serializable, SelectionBase]
public class Mob : MonoBehaviour
{
[SerializeField] protected AgentLocomotion[] entityLocomotions = new AgentLocomotion[] { new AgentLocomotion() };
public int entityLocomotionID { get; protected set; }
public virtual AgentLocomotion entityLocomotion { get; protected set; }
}
[System.Serializable, SelectionBase]
public class Monster : Mob
{
[SerializeField] protected ChaseLocomotion[] chaseLocomotions = new ChaseLocomotion[] { new ChaseLocomotion() };
public virtual ChaseLocomotion chaseLocomotion { get; protected set; }
}
Iâd like to avoid to create chaseLocomotions-array in Monster-class, instead is there a way to change the array initializer
new AgentLocomotion()
// to
new ChaseLocomotion()
Is there another solution to ScriptableObjects, so it is defined by class and not need to be manually set by editor? Maybe a complete different approach, which isnât too complex?
No, you canât write AgentLocomotion[ ] entityLocomotions = new ChaseLocomotion[0]; thatâs not valid. But you can populate the array with ChaseLocomotion, this has nothing to do with the array initializer tho.
And when you add ChaseLocomotion to your ChaseLocomotion[ ] array, unity will not serialize it. It might work when using [SerializeReference] but youâd need a custom inspector to add those classes.
Also ⌠you have to cast to ChaseLocomotion every single time you access the array. Why would you even write it this way?
Why use inheritance here? Use composition instead. Whatever Mob and Monster have in common â move it over to itâs own component and have Mob and Monster do their own specific thing without inheritance.
Also I suggest better naming for your classes, Enemies in Games can get very complicated over time, health, stun, various attacks, loot drop, physics, animations, (death) particles - you shouldnât call any class just âEnemyâ or in this case âMonsterâ or âMobâ, it should be called âMonsterMovementâ, âMonsterDeathâ, âMonsterLootDropâ, âMonster Attackâ, âMonster Animationâ and so on.
Thank you very much for your informations, but can you please provide an example.
What I understood is that I will have to create another class/struct for my SerializedFields like runSpeed in AgentLocomotion etc., but then how to efficiently pass it to the ILocomotion? Also my issue is that AgentLocomotion does not have all functions ChaseLocomotion has. In case of interfaces will I have to declare properties/functions twice or the new âChaseLocomotionâ would compose AgentLocomotion?
public class AgentLocomotion : ILocomotion
{
[SerializeField, HideInInspector] protected Transform transform;
[SerializeField, HideInInspector] protected Mob entity;
[SerializeField, HideInInspector] protected NavMeshAgent agent;
[SerializeField] protected float baseSpeed = 1, runSpeed = 2, sprintSpeed = 4;
protected float bonusSpeed = 1;
public bool canMove { get; protected set; } = true;
#if UNITY_EDITOR
public virtual void Validate(Mob newEntity)
{ // called by OnValidate() of GameManager
entity = newEntity;
transform = entity.transform;
agent = entity.Agent;
}
#endif
public virtual void InDestinationRange()
{
agent.isStopped = true;
}
public virtual void MoveTo(...) { ... } // uses baseSpeed and runSpeed
...
}
I did put speed SerializeFields into AgentLocomotion because in terms of efficiency it is where they are directly accessed.
All in all, I am still unsure how to make Monster use ChaseLocomotion-logic and Mob use AgentLocomotion by interfaces, which canât be serialized.
It would be huge work to distribute all kinds of entities like Mob, Monster etc. into interface with individual components, if this was what you implied. Partly this is already the case for Mob-class i.e. ISubject, IInteractable, IEquipable, ISkillable, IDamageable, ICrowdControlable etc. ISkillable expects Mob to have the attached SkillsController-MonoBehaviour as property.
The idea more so is to express these on components rather than the encapsulated classes themselves. Ergo:
public interface IAgentLocomotion
{
public AgentLocomotion AgentLocomotion { get; }
}
public class SomeMonster : Monobehaviour, IAgentLocomotion
{
[SerializeField]
private AgentLocomotion _agentLocomotion = new();
public AgentLocomotion AgentLocomotion => _agentLocomotion;
}
It will be overall more flexible than inheritance. The idea of ânot having some propertiesâ expressed in derived class is completely against the purpose of inheritance (which is moreso to enable polymorphism) and object oriented programming, and will create more and more problems as time goes on.
If you have many more then just Monster and Mob this will become gigantic. I did experienced it before, where I inherited Stats for all types i.e. EntityStats, MobStats, NpcStats, MonsterStats, EliteStats, PlayerStats etc. and same for Controller, but then I came to the conclusion that actually they just differ by its entity type, so I united the 3 (Stats, Controller, Entity) for all types into Entity, just kept the bases and PlayerController and PlayerStats. I mean if you need another entity type you would have to add 3 classes and the auto-property that refered from Stats â Entity â Controller (= 6 auto-property per entity type with case and variable shadowing using new-operator), is just too much, increases memory usage and slows due to additional casts ⌠But I havnât found a way to solve this all by interfaces. Maybe you can give an raw example? What I already have is ILootable for Monster with a LootController (loot table and drops). Just wanted to avoid it this time because all Mobs need AgentLocomotion, so it is not optional and I will have to add this to all Mob Prefabs, which feels like extra work.
And yes I plan to rename i.e. Monster-class to MonsterController-class, but currently it is more about how to script this all taking limits of serialization, unity and c# into account.
I havnât gotten much into customizing Inspector. How to solve this by SerializeReference? Will the custom inspector overwrite entityLocomotions = new AgentLocomotion[ ] { new AgentLocomotion() } into new AgentLocomotion[ ] { new ChaseLocomotion() } if it is a Monster? If this is the case it doesnât feel like a clean solution, but it might work and prevent me from having to set it manually via editor.
Itâs worth noting, if you donât know already, that you can GetComponent<T>() for interfaces, and it will return a component that implements said interface type (as said interface type, too).
Piece meal-ing functionality with small components expressed via interfaces is a well understood technique that perfectly meshes well with Unityâs general architecture.
Monster or MonsterController is pretty much the same. MonsterMovement would be more specific for example.
I guess your script is about movement? It has an array of locomotion that contains⌠Sprites? And timings?
Without understanding what you are trying to do itâs pretty hard to suggest anything.
A screenshot/video with a short description/genre of the game plus an explanation of what youâre trying to do with those scripts would do wonders here.
This can be achieved using generics and the where clause with the new constraint:
using System;
using UnityEngine;
[Serializable]
public abstract class AgentLocomotion
{
public const int CHASE_ID = 1;
public abstract int id { get; }
}
[Serializable]
public sealed class ChaseLocomotion : AgentLocomotion
{
public override int id => CHASE_ID;
}
public abstract class Mob<TAgentLocomotion> : MonoBehaviour where TAgentLocomotion : AgentLocomotion, new()
{
protected readonly TAgentLocomotion[] entityLocomotions = new TAgentLocomotion[] { new TAgentLocomotion() };
public int entityLocomotionID => entityLocomotions[0].id;
public virtual AgentLocomotion entityLocomotion => entityLocomotions[0];
}
[SelectionBase]
public sealed class Monster : Mob<ChaseLocomotion>
{
public ChaseLocomotion chaseLocomotion => entityLocomotions[0];
}
Yes more flexible, but less performant? I think where I am stuck now is how the best way to pass speed
The locomotion should have a default depending on entity type, also modifiable in code, like set different default or even have several and can be changed during runtime.
Just creating a simple medieval fantasy rpg. You are right about MonsterMovement, but for now this feels too specific I might come back to it if I have time and really need it, but thanks for this important advice. The Monster script composed everything, also how it is handled on death, on health change, how stats are calculated etc. but also derive from parents, so I donât have to code twice or create additional class like you say. I understood now, that i need to make everything more modular.
I think mostly my speeds, canMove needs to be put out so i can pass it as argument, which I did want to avoid because of performance. Instead I did seperate AgentLocomotion and ChaseLocomotion now, but still unsure if this is the best approach.
public enum MovementMode : byte
{
Idle = 0, Walk = 1, Run = 2, Sprint = 3, Patrol = 4, Auto = 5
}
[System.Serializable]
public class AgentLocomotion
{
public delegate void MoveUntilDelegate();
[SerializeField, HideInInspector] protected Transform transform;
[SerializeField, HideInInspector] protected Mob entity;
[SerializeField, HideInInspector] protected NavMeshAgent agent;
[SerializeField] protected float baseSpeed = 1, runSpeed = 2, sprintSpeed = 4, rotationSpeed = 1, autoSpeed = 2;
protected float bonusSpeed = 1; // modify current speed, i.e. updated by stats
public bool canMove { get; protected set; } = true;
public float modeSpeed { get; protected set; }
public MovementMode _movementMode;
public MovementMode movementMode { get { return _movementMode; }
set {
_movementMode = value;
modeSpeed = baseSpeed * bonusSpeed * GetSpeed(); }
} // ai mode or user requested by input
#if UNITY_EDITOR
public virtual void Validate(Mob newEntity)
{
entity = newEntity;
transform = entity.transform;
agent = entity.Agent;
}
#endif
public float GetSpeed()
{
if (_movementMode == MovementMode.Auto) return autoSpeed;
else if (_movementMode == MovementMode.Run) return runSpeed;
else if (_movementMode == MovementMode.Patrol) return 1f;
else if (_movementMode == MovementMode.Walk) return 1f;
else if (_movementMode == MovementMode.Sprint) return sprintSpeed;
else return 0;
}
public void SetSpeed()
{ // i.e. called by AI
agent.speed = modeSpeed;
}
public void SetBonusSpeed(float addSpeedQuote)
{ // increase by quote
bonusSpeed = 1 + addSpeedQuote;
}
public virtual void BlockMovement(bool blocked)
{
canMove = !blocked;
agent.isStopped = blocked;
}
public virtual void InRange()
{
agent.isStopped = true;
}
public virtual void NotInRange()
{
agent.isStopped = false;
}
public virtual bool IsReachable()
{
return agent.remainingDistance != Mathf.Infinity || agent.pathStatus != NavMeshPathStatus.PathInvalid || agent.pathStatus != NavMeshPathStatus.PathPartial;
}
IEnumerator MoveToCoroutine(float walkRange = 0f, float range = 0f)
{
bool pathReachable;
while (pathReachable = IsReachable() || agent.remainingDistance > walkRange)
{
yield return new WaitForSeconds(1f);
}
movementMode = MovementMode.Walk;
SetSpeed();
while (pathReachable = IsReachable() || agent.remainingDistance > range || agent.pathStatus != NavMeshPathStatus.PathComplete)
{
yield return new WaitForSeconds(1f);
}
WithinRange(); // stop agent
}
IEnumerator MoveUntilCoroutine(Vector3 endPosition, float blinkDuration, System.Action onEnd = null)
{
float fixedStep = Time.fixedDeltaTime / blinkDuration;
float currentTime = 0;
while (currentTime < 1f)
{
currentTime += fixedStep; // goes from 0 to 1, incrementing by step each time
transform.position = Vector3.Lerp(transform.position, endPosition, currentTime); // move transform closer to end position
yield return new WaitForFixedUpdate(); // leave the routine and return here in the next frame
}
transform.position = endPosition;
if (onEnd != null) onEnd();
}
public virtual void MoveTo(Vector3 position, float walkRange = 0f, float range = 0f)
{ // range to start walk instead run/sprint and range
movementMode = MovementMode.Run;
SetSpeed();
agent.destination = position;
NotWithinRange();
entity.StartCoroutine(MoveToCoroutine(walkRange, range));
}
public virtual void MoveUntil(Vector3 endPosition, float blinkDuration, System.Action onEnd = null)
{
entity.StartCoroutine(MoveUntilCoroutine(endPosition, blinkDuration, onEnd));
}
}
[System.Serializable]
public class ChaseLocomotion
{
[SerializeField, HideInInspector] protected Mob entity;
[SerializeField, HideInInspector] protected NavMeshAgent agent;
[SerializeField, HideInInspector] protected AgentLocomotion entityLocomotion;
#if UNITY_EDITOR
public virtual void Validate(Mob newEntity)
{
entity = newEntity;
agent = entity.Agent;
entityLocomotion = entity.entityLocomotion;
}
#endif
public virtual void ChaseTarget(Vector3 targetPosition)
{
entityLocomotion.movementMode = MovementMode.Auto;
entityLocomotion.SetSpeed();
agent.destination = targetPosition;
}
public virtual void ResetChaseTarget(Vector3 resetDestinationPosition)
{
agent.isStopped = true;
agent.isStopped = false;
agent.destination = resetDestinationPosition; // i.e. spawnPoint for AI
}
}
Wow, thanks that is awesome didnât know it is possible. This might be good for some other cases I have, but what could cause another issue that it is tied, so that Monster would only allow ChaseLocomotion and not classes of AgentLocomotion-family.
For now I will go with modularity optionally add ChaseLocomotion to a entity class if it needs and call its functions seperately. Actually I wanted to do this for things like different npc behaviour as cook, hunter or monster, so depending on the behaviour that is currently selected the npc behaves differently.
Only one way to find out: attach the profiler. Also, how often are you making these objects? How important is it to shave off microseconds of time if youâre only making them at level load time?
DO NOT OPTIMIZE âJUST BECAUSEâŚâ If you donât have a problem, DO NOT OPTIMIZE!
If you DO have a problem, there is only ONE way to find out. Always start by using the profiler:
Window â Analysis â Profiler
Failure to use the profiler first means youâre just guessing, making a mess of your code for no good reason.
Not only that but performance on platform A will likely be completely different than platform B. Test on the platform(s) that you care about, and test to the extent that it is worth your effort, and no more.
Remember that optimized code is ALWAYS harder to work with and more brittle, making subsequent feature development difficult or impossible, or incurring massive technical debt on future development.
Donât forget about the Frame Debugger either, available right near the Profiler in the menu system. Remember that you are gathering information at this stage. You cannot FIX until you FIND.
At a minimum you want to clearly understand what performance issues you are having:
running too slowly?
loading too slowly?
using too much runtime memory?
final bundle too large?
too much network traffic?
something else?
If you are unable to engage the profiler, then your next solution is gross guessing changes, such as âreimport all textures as 32x32 tiny texturesâ or âreplace some complex 3D objects with cubes/capsulesâ to try and figure out what is bogging you down.
Each experiment you do may give you intel about what is causing the performance issue that you identified. More importantly let you eliminate candidates for optimization. For instance if you swap out your biggest textures with 32x32 stamps and you STILL have a problem, you may be able to eliminate textures as an issue and move onto something else.
This sort of speculative optimization assumes youâre properly using source control so it takes one click to revert to the way your project was before if there is no improvement, while carefully making notes about what you have tried and more importantly what results it has had.
I do appreciate your help, but you misunderstood me. It was about runSpeed, sprintSpeed etc. they are in AgentLocomotion. Once ChaseLocomotion donât inherit from AgentLocomotion it wasnât able to directly access these serialized fields. But I solved this by just make AgentLocomotion allow to access speed by public get/set functions. These values were used inside Update() of Mobs/Monsters. I am sure it must make a performance difference if you always access direct class field or instead of through 1 or 2 classes, but for modularity I will sacrifice a bit depending how often it needs to be accessed. I do have realtime combat in my rpg, the locomotion and further stuff is used by AIBrain.
The issue I still have is something like npc behaviours i.e. combat logics, cooking, vendoring, guarding etc. I have to use ScriptableObjects? I like it when it has a default by class-type on initialization without extra calls (OnValidate, AwakeâŚ).
Inheritance can be used several ways, most notably the way I use it requires no Abstract/Virtual/Override functions at all.
But Iâm having trouble giving an example, since you seem set on making a separate âmoveâ class(locomotion), which easily could just be a function in the parent class that handles children when they need it.
Again, this would be something in the parent class, that the child(npc) would use if itâs declared âjobTypeâ were to specify this.
So in conclusion, many(a lot of) âmoveâ functions are best handled by one Update(), instead of each class using itâs own Update()'s, as each call to a new Update() chews up performance(minor problem until mass index). So in this regard, a CharacterManager.cs class would be more appropriate for multiple transform repositioning.
And as far as jobs for the npcâs, you basically have âgo hereâ and âdo this at this positionâ as stated in the job type declaration of âwork positionâ, âget resource positionâ, âgo home, tired, day overâ would all be generic methods populated by each individual npc on what they do, and where they do it.
Sorry if my answers are vague, Iâm not quite fully sure on what it is you wish to have happen exactly.
But as youâve noticed, there are many ways to accomplish the same goal, when it comes to coding. It really boils down to what youâre comfortable with using, and what makes more sense to you(itâs your game, and your code).
You mean because virtual and override can limit you? On the other hand it reduces coding redundancy or you have to module it into extra class.
Not sure what you mean by âjust be a function in the parent classâ. This way even more is inside parent class and I lose modularity if I would want to use AgentLocomotion for other classes than parent one. Or you meant I should put the functions into i.e. MovementManager and call the functions with parameter (entity or agent)? Is it less optimized to pass additional object to function or to have AgentLocomotion attached to all classes that needs it and call without extra parameter? I mean you have to pass agent/entity always while it will never change for the corresponding entity.
I think I need to distinguish between the jobType and capabilities like entity is able to detectTarget (detectRange), inFieldOfView, huntTarget, protectSpawnInRadius. Maybe I need to split those like in Behaviour Tree Node?
You mean one Update for all entities (npcs, monsters, player) or per entity?
Can you populate / have initial set of âjobTypesâ per class i.e. EliteMonster has EliteBuffTriggers, but no InnkeeperJobs and the Innkeeper vice versa?
If youâre not overriding/implementing anything from a base class, then you should ask yourself if you should be using inheritance at all.
There is the âsubclass sandboxâ pattern approach, but honestly itâs a pretty old school approach that I feel is superseded by what Unity enables.
Inheritance is a means to and end for polymorphism.
In this situation OP direly needs a composition approach, and I think some form of pluggable data and behaviour with scriptable objects, possibly with [SerializeReference] as well.
Iâm not sure if virtual can technically limit you, although there was a reason why I strayed away from it long ago, so maybe? But I just refer to there being different ways of using Inheritance, as the way I use it are different than examples Iâve seen.
There are 2 different ways I use it, depending on the structure of the project. In some cases, from the âmasterâ class that houses all script-to-script communication, has all classes inherited, 2 sub-classes would be made from that, a âstatic objectâ and ânon-static objectâ base class. Anything with movement/rotation inherits from ânon-staticâ and same for still like objects inheriting from âstaticâ. And of course multiple sub-classes from there, i.e(Characters.cs, Animals.cs, Projectiles.cs, etcâŚ).
The other way I use in some cases, is the manager class only communicates with master, and can easily read the lists of instances through it and move them without having any inherited connection to moving entities, just their references.
Either way you boil it down, itâs about ease, less calls, and proper communication. Also about not having any limitations, as you will incur with scriptable objects or interfaces. Watch Code Monkeyâs video on issues with scriptable objects, as I canât remember the details, so it may(or may not) be a factor in your case, itâs just something I recall.
As I would do, would be having those functions within the parent class(however far up depending), and jobType to me would just be an enum, that once the state is set on the particular child class, it runs only the logic needed for that state. As most of my child classes are pretty empty, except for very specific things just for that particular class.
Yes, as stated, multiple classes with their own Update() methods, in high quantity will bog down performance. As this video can best explain:
Practical Optimizations
Easily, if it were something you wanted all sub-classes to have the possibility of using, then itâs function would just be within the proper parent class. With Inheritance everything is possible, this is why majority of classes inherit from âMonoBehaviourâ. All I do is keep on inheriting, lolâŚ
But Iâm not trying to convince you to do things the way Iâm doing them, all Iâm trying to say is there are other ways to reach the same goal in the end. How you wish to organize, structure, and script communicate? Is purely up to you, itâs your code, and your game.
With in conclusion, you âtechnicallyâ(keyword technically) can make a game using 1 script. So all Inheritance, Scriptable Objects, Interfaces, and Modules do is just split that up. So it all depends on how you want to set it up, as some like individual classes for each little thing, and others prefer to group them lowering the amount of classes needed.
I personally like having a fair amount of scripts.
I usually try to avoid using virtual methods, because it forces the programmer that is creating a derived class into the awkward position of having to decide at what point to execute the implementation in the base class.
Take these classes for an example:
public class Base
{
private bool setupDone = false;
public virtual void Setup()
{
SetupSomeStuff();
setupDone = true;
}
}
public class Derived : Base
{
public override void Setup()
{
//base.Setup(); // ?
SetupMoreStuff();
//base.Setup(); // ?
}
}
Should base.Setup be called at the very beginning of the method, so that all member variables in the base class will be initialized properly, and itâs safer to perform additional setting up on top of that without potential NullReferenceExceptions?
But then setupDone would get set to true before Setup has actually finished in the derived type, which would be misleading. So maybe itâs better to call base.Setup at the very end instead. You can always just test that nothing breaks in practice when doing it like this so it should be a pretty safe approach, right?
But wait⌠maybe base.Setup shouldnât actually even be called at all in this case⌠the derived type already sets up all the same member variables, so calling base.Setup would just be wasted work, or worse yet, result in overwriting the already initialized variables with wrong values. Maybe setupDone should just be changed to protected so it can be set to true directly by the derived type
Even if an XML documentation comment was added to the base class to tell the programmer how they should handle this conundrum, the programmer could easily just forget to call base.Setup, and the compiler wouldnât warn about it at all.
And the worst part is that with this kind of setup, the base type and all derived types are very tightly coupled together. Making even a slight change to the Setup method in the base class could cause any derived types to break without warning.
I think this is the big trap where the use of inheritance can start becoming detrimental to the project, and why the composition over inheritance principle is such a big thing.
Abstract methods on the other hand donât have the aforementioned problems.
As thereâs no base implementation that needs to be called at any point, the derived method wonât get tightly coupled with the base classâs implementation details to that level.
And if the programmer forgets to implement the method, the compiler will warn them.
Same story with using inheritance to automate some behaviour in the base class, like keeping track of all active instances by adding them to a list. It can just work automatically when a programmer derives from the type, and doesnât require the programmer to go and read the implementation details in the base class.
public class Base
{
private bool setupDone = false;
public void Setup()
{
SetupSomeStuff();
SetupInternal();
setupDone = true;
}
protected virtual void SetupInternal() { }
}
public class Derived : Base
{
protected override void SetupInternal()
{
SetupMoreStuff();
}
}
I take this approach a lot to remove the burden having having to call base methods a lot. The virtual methods are more so âadded extrasâ I can hook into.
Though mind you Iâm almost always using inheritance for the sake of composition.