Do you have scripts to mark prefabs/classes

I already posted this question on “Unity Answers”, however since this is a somewhat diffucult question with no clear anser, I’m also posting this here, giving it a bit more room for discussion.

Basically, my question is: If you have a, let’s say, ‘Player’ prefab. Do you have a ‘Player’ script on this prefab, holding references to all the other scripts a player has?
I think this is pretty useful, since with a prefab you have sort of a building plan for GameObjects. So maybe you have a ‘Hellth’ script, an ‘Inventory’ script and a ‘Movement’ script which you drag onto the ‘Player’ prefab, so every ‘Player’ technically has those scripts.
However, from a programming point of view, you can’t directly access them. A prefab is still only a GameObject and GameObjects of course don’t have some components always attached to them. So you know if you create a player prefab, that those scripts are there, but to still need to get them via ‘GetComponent()’. With a ‘Player’ script you only need to fetch this one script and can access all the others present on the player with its references to them.
Do you think this is a good approach or a desperate call to get some of the “static-ness” of a inheritance based approach, at the cost of loosing/complicating the “dynamic-ness” of a Entity-Component-Model, back? Do you use it yourself? Have you ever thought of using it and why did you decide to (not) use it?

I do this a lot, especially with the new GUI system. We generally create a prefab class for each type of control we use, with pointers to all the controls it uses. For instance, we might have a GUI element which represents one of your characters in a selection screen. It will have pointers to the text label for the name, stats displays, health bars, etc. The class will have some kind of update function which takes a structure of data, it will go through each control and apply the data to the control if the control exists. Then, if the UI designer wants a different representation of that data (say, the image and text, but not the health bars) they can make another prefab using the same class backing, but without filling out the pointers for the health bars.

This allows them to refactor the display without having to change any code. I simply provide all the data, and the class does the mapping of that data to the display if the display elements exist…

1 Like

Hey @jbooth_1 , thank you very much for your answer! This seems to be a very useful technique, especially if you have several people who all care about something else. Did you ever run into problems with this (broken references, null-pointers, etc.) and is it worth the effort to create a script for every prefab, or do you only use it when, for instance, the designer has to do some stuff without needing to bother with some code only prigrammers should be interested in?

Ugh. It seems horribly dependent to me.

You’ve gone for a quick check to see “does this GameObject I’m trying to interact with have the component (or interface) I know about?” To “is this object a player, wall, barrel, or item that I know about?”

A scripts knowledge of your code base should be limited to what it has to know about, and nothing else. A bullet can hit anything that has the IDamagable interface. It doesn’t know or care if it’s hitting players or exploding barrels or brick walls or fish. This way if you decide you don’t want fish any more, you want to shoot balloons instead, the bullet does not care or need any modification.

Your proposed system will scale horribly as the project size increases.

1 Like

No issues. To further illustrate the point, lets say we have a screen where the user can choose from some number of characters (this kind of screen is common in our game). I’ll set up the screen and take a pointer to a ‘CharacterPortrait’ prefab, which is what I’ll instantiate into the screen. I don’t need to care which variation of the character portrait the artist is using, as long as he supplies one. If he later decides he doesn’t want health displayed on this screen, he can create a new version of the prefab with no health bar and sub it in. If you were to go the interface route here, it would create massive amounts of code duplication as you’d need a separate backing class for each variation of the character portrait in the game.

@Kiwasi
I think you’re talking about something different than I am. I never call GetComponent on the game objects because they are instantiated as such. I already have pointers to those components. CharacterPortrait, in my example, is simply a class that maps a set of data to a bunch of optional display fields (Text, Image, Sliders, and such). The screen class knows it needs to instantiate some prefab of that type, but knows nothing of what data it is or isn’t going to actually display. This keeps the code free of knowing about the art data, which tends to change rapidly. Using this kind of abstraction greatly reduces the amount of changes necessary when iterating on a UI.

1 Like

I don’t always, because some prefabs are just simple things that get thrown in the world.

What I do have is a 2 part deal.

  1. Tags

For this I have a script called ‘MultiTag’ so that I’m not restricted to a single tag on a GameObject.

There’s an inspector, and utility methods, that work in tangent with this.

Anyways, with that said. I have a tag called ‘root’, that I flag the root of pretty much anything. This includes prefabs. This gameobject as root signals that this is the GameObject who, with all its children, is considered one contiguous object in the scene.

Then with utils like those in here:
GameObjUtil
https://code.google.com/p/spacepuppy-unity-framework/source/browse/trunk/SpacepuppyBase/Utils/GameObjUtil.cs
ComponentUtil

I treat everything relative to this ‘root’. For instance on a ‘OnTriggerEnter’ event I can do this:

private void OnTriggerEnter(Collider other)
{
    var otherRoot = other.gameObject.FindRoot();
    //manipulate root
}
  1. Entity

The other thing I do for more robust ‘entities’ like above. On that root element I stick an ‘Entity’ script (or some script that inherits from ‘Entity’). This ‘Entity’ script is very game specific… but will store references to major aspects of the entity.

For example in my current game Entity is for the player/mobs/other stuff in scene that has a MobileMotor, Health, etc on it.

Here’s the abstract class IEntity that all my entity scripts inherit from (there’s 3 entity scripts in total that inherit from this for special case stuff: Entity, PlayerEntity, SimpleEntity).

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

using com.spacepuppy;
using com.spacepuppy.Utils;

using com.apoc.Movement;
using com.apoc.Projectile;
using com.apoc.Widgets;
using com.apoc.Scenes.Levels;

namespace com.apoc
{

    public abstract class IEntity : SPNotifyingComponent, IIgnorableCollision
    {

        public enum StallMode
        {
            Active = 0,
            Paused = 1,
            Animating = 2
        }

        #region Static Multiton

        private static List<IEntity> _entities;
        private static System.Collections.ObjectModel.ReadOnlyCollection<IEntity> _readonlyEntities;

        /// <summary>
        /// A readonly list of all active entities.
        /// </summary>
        public static IList<IEntity> ActiveEntities { get { return _readonlyEntities; } }

        static IEntity()
        {
            _entities = new List<IEntity>();
            _readonlyEntities = new System.Collections.ObjectModel.ReadOnlyCollection<IEntity>(_entities);
        }

        #endregion

        #region Fields

        public const string ID_RIG = "Rig";
        public const string ID_AI = "AI";
        public const string ID_MOBILEMOTOR = "MobileMotor";
        public const string ID_COMBATMOTOR = "CombatMotor";
        public const string ID_ATTRIBUTES = "Attributes";
        public const string ID_EVENTHITBOX = "EventHitBox";

        public bool Debris;

        private StallMode _stallState;

        #endregion

        #region CONSTRUCTOR

        protected override void OnStartOrEnable()
        {
            base.OnStartOrEnable();

            if (!_entities.Contains(this)) _entities.Add(this);
            Notification.RegisterGlobalObserver<GamePausedNotification>(this.OnGamePaused);
        }

        protected override void OnDisable()
        {
            base.OnDisable();

            _entities.Remove(this);
            Notification.RemoveGlobalObserver<GamePausedNotification>(this.OnGamePaused);
        }

        #endregion

        #region Properties

        public StallMode StallState { get { return _stallState; } }

        #endregion

        #region Methods

        public com.spacepuppy.Geom.Sphere GetBoundingSphere()
        {
            if(this.MovementController != null)
            {
                var cap = this.MovementController.GetGeom(false);
                return new com.spacepuppy.Geom.Sphere(cap.Center, cap.Height / 2.0f);
            }

            if(this.RigContainer != null)
            {
                MeshFilter meshFilter = this.RigContainer.GetComponent<MeshFilter>();
                if (meshFilter == null) meshFilter = this.RigContainer.GetComponentInChildren<MeshFilter>();
                if (meshFilter != null && meshFilter.sharedMesh != null)
                {
                    return com.spacepuppy.Geom.Sphere.FromMesh(meshFilter.sharedMesh);
                }

                SkinnedMeshRenderer renderer = this.RigContainer.GetComponent<SkinnedMeshRenderer>();
                if (renderer == null) renderer = this.RigContainer.GetComponentInChildren<SkinnedMeshRenderer>();
                if(renderer != null && renderer.sharedMesh != null)
                {
                    return com.spacepuppy.Geom.Sphere.FromMesh(renderer.sharedMesh);
                }
            }

            //TODO - calculate a total bounding sphere around entire entity... will be expensive... ugh
            return new com.spacepuppy.Geom.Sphere(this.transform.position, 0f);
        }

        public abstract void ScanRig();

        /// <summary>
        /// Put the entity into a state where it's not considered active.
        /// </summary>
        /// <param name="active"></param>
        public void StallEntity(StallMode mode)
        {
            _stallState = mode;
            switch(_stallState)
            {
                case StallMode.Active:
                    if (this.AIContainer != null) this.AIContainer.SetActive(true);
                    if (this.CombatMotorContainer != null) this.CombatMotorContainer.SetActive(true);
                    if (this.MobileMotorContainer != null) this.MobileMotorContainer.SetActive(true);
                    //if (this.AttributesContainer != null) this.AttributesContainer.SetActive(true);
                    if (this.EventHitBoxContainer != null) this.EventHitBoxContainer.SetActive(true);
                    break;
                case StallMode.Paused:
                    if (this.AIContainer != null) this.AIContainer.SetActive(false);
                    if (this.CombatMotorContainer != null) this.CombatMotorContainer.SetActive(false);
                    if (this.MobileMotorContainer != null) this.MobileMotorContainer.SetActive(false);
                    //if (this.AttributesContainer != null) this.AttributesContainer.SetActive(true);
                    if (this.EventHitBoxContainer != null) this.EventHitBoxContainer.SetActive(false);
                    break;
                case StallMode.Animating:
                    if (this.AIContainer != null) this.AIContainer.SetActive(false);
                    if (this.CombatMotorContainer != null) this.CombatMotorContainer.SetActive(false);
                    if (this.MobileMotorContainer != null) this.MobileMotorContainer.SetActive(false);
                    //if (this.AttributesContainer != null) this.AttributesContainer.SetActive(true);
                    if (this.EventHitBoxContainer != null) this.EventHitBoxContainer.SetActive(true);
                    break;
            }
        }

        #endregion

        #region IEntity Interface

        public abstract EntityType EntityType { get; set; }
        public abstract bool RagdollsIn3D { get; set; }

        public abstract GameObject RigContainer { get; }
        public abstract ArmatureRig Armature { get; }

        public abstract GameObject AIContainer { get; }

        public abstract GameObject CombatMotorContainer { get; }

        public abstract GameObject MobileMotorContainer { get; }
        public abstract MovementController MovementController { get; }
        public abstract ApocMovementMotor MovementMotor { get; }

        public abstract GravityResolver GravityResolver { get; }
        public abstract SpeedResolver SpeedResolver { get; }

        public abstract GameObject AttributesContainer { get; }
        public abstract WeightedEntityController Weight { get; }
        public abstract HealthMeter Health { get; }


        public abstract GameObject EventHitBoxContainer { get; }

        public abstract ProjectileWeapon ProjectileWeapon { get; }

        #endregion

        #region IIgnorableCollision Interface

        public abstract void IgnoreCollision(Collider coll, bool ignore);

        public abstract void IgnoreCollision(IIgnorableCollision coll, bool ignore);




        public abstract bool KnockedOut { get; }
        public abstract void KnockoutEntity();
        public abstract void WakeupEntity();

        #endregion

        #region Notification Handlers

        private void OnGamePaused(object sender, GamePausedNotification n)
        {
            this.StallEntity((n.Paused) ? StallMode.Paused : StallMode.Active);
        }

        #endregion


        #region Static Utils

        public static bool IsMobileEntitySource(object obj)
        {
            if(GameObjectUtil.IsGameObjectSource(obj))
            {
                return GameObjectUtil.GetGameObjectFromSource(obj).EntityHasComponent<IEntity>();
            }

            return false;
        }

        public static IEntity GetMobileEntityFromSource(object obj)
        {
            if(GameObjectUtil.IsGameObjectSource(obj))
            {
                return GameObjectUtil.GetGameObjectFromSource(obj).FindComponent<IEntity>();
            }

            return null;
        }

        public static bool GetMobileEntityFromSource(object obj, out IEntity entity)
        {
            if(GameObjectUtil.IsGameObjectSource(obj))
            {
                entity = GameObjectUtil.GetGameObjectFromSource(obj).FindComponent<IEntity>();
                return entity != null;
            }
            else
            {
                entity = null;
                return false;
            }
        }

        /// <summary>
        /// If you need access to the rig container during 'Awake', you can find them with this.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static GameObject FindRigContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_RIG, true);
        }

        public static GameObject FindAIContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_AI, true);
        }

        /// <summary>
        /// If you need access to the mobilemotor container during 'Awake', you can find them with this.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static GameObject FindMobileMotorContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_MOBILEMOTOR, true);
        }

        /// <summary>
        /// If you need access to the combatmotor container during 'Awake', you can find them with this.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static GameObject FindCombatMotorContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_COMBATMOTOR, true);
        }

        /// <summary>
        /// If you need access to the attributes container during 'Awake', you can find them with this.
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static GameObject FindAttributesContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_ATTRIBUTES, true);
        }

        public static GameObject FindEventHitBoxContainer(IEntity entity)
        {
            if (entity == null) return null;
            if (entity.entityRoot == null) entity.SyncEntityRoot();
            return entity.entityRoot.Find(ID_EVENTHITBOX, true);
        }

        #endregion

    }

}

Here you can see a general implementation of it as ‘Entity’

using UnityEngine;
using System.Collections;
using System.Linq;

using com.spacepuppy;
using com.spacepuppy.Utils;

using com.apoc.Movement;
using com.apoc.Projectile;
using com.apoc.Widgets;

namespace com.apoc.Role
{

    public class Entity : IEntity
    {

        #region Fields

        [SerializeField()]
        private EntityType _entityType;
        [SerializeField()]
        private bool _ragdollsIn3D = true;

        private GameObject _rigContainer;

        private GameObject _combatMotorContainer;

        private GameObject _aiContainer;

        //MobileMotor
        private GameObject _mobileMotorContainer;
        private MovementController _controller;
        private ApocMovementMotor _motor;
        private GravityResolver _gravResolver;
        private SpeedResolver _speedResolver;

        //Attributes
        private GameObject _attributesContainer;
        private WeightedEntityController _weight;
        private HealthMeter _health;

        private ArmatureRig _armature;

        //EventHitBox
        private GameObject _eventHitBoxContainer;

        //ProjectileWeapon
        private ProjectileWeapon _projectileWeapon;

        private bool _knockedOut = false;

        #endregion

        #region CONSTRUCTOR

        protected override void Awake()
        {
            base.Awake();

            this.AddTag(SPConstants.TAG_ROOT);
            this.ScanRig();
        }

        #endregion

        #region Methods

        protected virtual void ClearRig()
        {
            _rigContainer = null;
            _mobileMotorContainer = null;
            _combatMotorContainer = null;
            _attributesContainer = null;
            _eventHitBoxContainer = null;

            _armature = null;

            _motor = null;
            _gravResolver = null;
            _speedResolver = null;

            _weight = null;
            _health = null;


            _controller = null;
        }

        public override void ScanRig()
        {
            this.ClearRig();

            var root = this.FindRoot();

            _rigContainer = FindRigContainer(this);
            _aiContainer = FindAIContainer(this);
            _mobileMotorContainer = FindMobileMotorContainer(this);
            _combatMotorContainer = FindCombatMotorContainer(this);
            _attributesContainer = FindAttributesContainer(this);
            _eventHitBoxContainer = FindEventHitBoxContainer(this);

            if (_rigContainer != null)
            {
                _armature = _rigContainer.GetComponent<ArmatureRig>();
            }

            if (_mobileMotorContainer != null)
            {
                _motor = _mobileMotorContainer.GetComponent<ApocMovementMotor>();
                _gravResolver = _mobileMotorContainer.GetComponent<GravityResolver>();
                _speedResolver = _mobileMotorContainer.GetComponent<SpeedResolver>();
            }

            if (_attributesContainer != null)
            {
                _weight = _attributesContainer.GetComponent<WeightedEntityController>();
                _health = _attributesContainer.GetComponent<HealthMeter>();
            }

            _controller = root.FindComponent<MovementController>();
            _projectileWeapon = this.FindComponent<ProjectileWeapon>();
        }

        #endregion

        #region IEntity Interface

        public override EntityType EntityType { get { return _entityType; } set { _entityType = value; } }
        public override bool RagdollsIn3D { get { return _ragdollsIn3D; } set { _ragdollsIn3D = value; } }

        public override GameObject RigContainer { get { return _rigContainer; } }
        public override ArmatureRig Armature { get { return _armature; } }

        public override GameObject AIContainer
        {
            get { return _aiContainer; }
        }

        public override GameObject CombatMotorContainer { get { return _combatMotorContainer; } }

        public override GameObject MobileMotorContainer { get { return _mobileMotorContainer; } }
        public override MovementController MovementController { get { return _controller; } }
        public override ApocMovementMotor MovementMotor { get { return _motor; } }

        public override GravityResolver GravityResolver { get { return _gravResolver; } }
        public override SpeedResolver SpeedResolver { get { return _speedResolver; } }

        public override GameObject AttributesContainer { get { return _attributesContainer; } }
        public override WeightedEntityController Weight { get { return _weight; } }
        public override HealthMeter Health { get { return _health; } }


        public override GameObject EventHitBoxContainer { get { return _eventHitBoxContainer; } }


        public override ProjectileWeapon ProjectileWeapon { get { return _projectileWeapon; } }



        public override bool KnockedOut { get { return _knockedOut; } }

        public override void KnockoutEntity()
        {
            if (_combatMotorContainer != null) _combatMotorContainer.SetActive(false);
        }

        public override void WakeupEntity()
        {
            if (_combatMotorContainer != null) _combatMotorContainer.SetActive(true);
        }


        #endregion

        #region IIgnorableCollision Interface

        public override void IgnoreCollision(Collider coll, bool ignore)
        {
            if (_controller != null) _controller.IgnoreCollision(coll, ignore);
            if (_armature != null) _armature.IgnoreCollision(coll, ignore);
        }

        public override void IgnoreCollision(IIgnorableCollision coll, bool ignore)
        {
            if (coll == null) return;
            if (_controller != null) _controller.IgnoreCollision(coll, ignore);
            if (_armature != null) _armature.IgnoreCollision(coll, ignore);
        }

        #endregion

    }

}
1 Like

We are indeed. I didn’t read your first post before responding to the OP. For a UI it makes sense to have one component gather all the data.

I still would be reluctant to let outside users of the data see through the data class to the underlying components. The data class also would not know about every component, just the ones that contained data for the UI.

I’d also be reluctant to build a unique data viewer for each type of entity. That said in many game the player is unique, and needs unique code anyway.

Thank you very much to all of you, who contributed to this discussion!

@Kiwasi :
Well, if you don’t depend on the actual “main”-component, I think it’s fine. Let’s say you have your ‘Player’ script. This script also extends all the interfaces that are used/represented by it’s components. So if you have a ‘Health’ component inheriting from maybe ‘IHealth’, the ‘Player’ script also inherits from ‘IHealth’. But ‘Player’ doesn’t handle a call to maybe ‘Kill()’ itselft, it rather delegates it to the actual ‘Health’ component (very much like the ‘Strategy’ pattern). This might seems to be redundant/unpractical, but it gives you the opportunity to choose if you want to search for the component dynamically (I think this is somewhat similar to duck-typing) or use the static version, accessible via the ‘Player’ script if you already know it’s a player (e.g. if you just instantiated a player prefab).

@lordofduct
I also got a similar answer on Unity Answers, and I like this approach a lot. Summarized, the other person uses a script for every entity in the game (e.g. Rocket, Car, Mine, etc.) (and also things like “GameManager”, however derived from another base-class to be able to differentiate between entities and manager objects, if I understood him correctly): So basically for everything one would create a class for in a ‘classic’ programming approach. As stated above, I think this is very useful, since it combines the flexibility of an Entity-Component-System with the advantage of turning GameObjects into real logical units (classes).
Thank you very much for your code examples as well. I haven’t had enough time to study them yet, but I’ll do as soon as I can.