Proper Coding Format

Now we all know there is no right way to code, although many of us have a general format we try to use. I was wondering what you all consider acceptable amoung programming in unity. I’m hoping experienced people will provide their input on writing efficient clean code, and new people will learn something from this.

Firstly regarding scripts I was taught a script should only affect the object its attached to it and its children. If you needed a script that managed over several objects such as a game manager for instance you create that script and make variables for each script you need to access. For example, a GameManager might be seen with a UIManager variable, PlayerScript variable, and etc… Also always name your scripts relative to their purpose and or object they are on for example you have a script responsible for moving the player you would call this “PlayerController” or “PlayerMover”. And scripts that “manage” over several other objects would be things like “GameManager”, “UIManager”, “WorldManager” these managers would also commonly be singletons.

Next, there are variables, I was taught to always declare whether variables are public, protected or private and to always assign their get/set conditions. Regarding naming variables always use cammelCase. Also, take advantage of attributes for example if you need to input a big string. Use the [TextArea] attribute. or if you have integers and floats you need your designer to mess around with, use the [Range(min, max)] attribute.

Now for the biggest debate functions. The number one rule I’ve been taught is to keep functions single purpose. They should do one thing and one thing only. If a function is adding experience to the player for killing a monster it should not check if the player can use an ability from a cooldown. Other than that I don’t have much to comment on with functions. Remember to look for the monobehaviour base functions there are many good ones such as OnApplicationQuit(), OnClick(), etc.

I look forward to hearing what you all have to say regarding scripting sorry to those javascript scritors I only use c# so I used those terms.

PS: Some shameless self-promotion I am looking to join a team who needs a programmer I’ve been using Unity for 4 years have released an app entirely by myself. I do free volunteer work and would love a paid job if any takers :slight_smile:

Finally using everything I said here is my example of a “perfect” gun script. Feel free to leave your comments on this :slight_smile:

using UnityEngine;

[RequireComponent(typeof(AudioSource))]
public class WeaponController : MonoBehaviour
{
    //Attaches to a child object of the object this script is on.
    public ParticleSystem C_muzzleFlash;
    //Attaches to the same object this script sits on
    public AudioSource C_gunShot;

    [Range(0.05f, 1.00f)]
    public float V_coolDown = 0.01f;
    public float V_nextFire {get; private set;} //I would normally keep this variable private but I did it this way to demonstrate the get/set stuff I mentioned.

    /*I use the start function to verify and assign any variables I need to do at the start of the game. 
    //This seems redundant and uncessary but is actually very good information to check. Otherwise you get the error
    "unity object reference is not set to an instance of an object" and I think my wording makes more sense than that.*/
    void Start()
    {
        if(GetComponentInChildren<ParticleSystem>())
        {
            C_muzzleFlash = GetComponentInChildren<ParticleSystem>();
        }
        else
        {
            Debug.LogError("No Particle System found in child objects");
        }

        //Although I have the RequireComponent attribute at the top I still double check it as a double measure.
        if(GetComponent<AudioSource>())
        {
            C_gunShot = GetComponent<AudioSource>();
        }
        else
        {
            Debug.LogError("No Audio Source found on object");
        }
    }

    void Update()
    {
        //I don't check to see if the axis exists because A. this is a default axis included when you make a unity project and unity has its own logerror which explicatly states if you spell a axis wrong.
        if(Input.GetAxis("Fire1") > 0 && V_nextFire < Time.time)
        {
            Shoot();
        }
    }
   
    //This is an example of a single purpose function
    void Shoot()
    {
        V_nextFire = Time.time + V_coolDown;

        C_muzzleFlash.Play();
        C_gunShot.Play();

        //I'm not gonna include the whole raycasting part because its been done a million times and its 11pm and I need sleep...
    }
}

//Final note Include lots and lots of comments in your scripts. WEEEEEEEEEEEEEEEEEE!!!

First of all I think there is no perfect script of any kind, everyone’s needs are different and every project has different requirements. In general I can agree with what you wrote above, but those are just fundamental rules, how the semantics are desigend can be vastly different.

Why use C_ and V_?

I’d rather assign the variable C_muzzleFlash in the inspector instead of using GetComponent.
You can just use if( C_muzzleFlash == null) to make your error handling.

You might disable the component if the muzzleFlash and gunShoot are null, so the update wouldn’t be called and would not cause errors.

Personally I’d use Time.deltaTime rather than Time.time.

Basically, I would make everything different as you do, which is not necessarily better or worse. :slight_smile: There are 1000 patterns and styles of programming. Everyhting depends on team structure, experience and personal taste.

Sorry, this might not be what you expected, but I personally don’t like your code. Here are the reasons:
First, the C_ and V_ are quite pointless to me, why not have a standard normal name for variable? like coolDown and muzzleFlash. Not only that, but what is that a cool down of? I’d also change it to shootingDelay in such case.

Another thing is that you are naming the variables but not what their type is, this can be quite handy to understand what it is without having to look at their type, or even just if you have similar variables to make their names distinct, for instance I would change the name of C_gunShot to gunShotAudio (Notice I don’t use the full AudioSource name as part of it is already enough for me).

The commends are quite useless, I usually don’t put comments on trivial things, as I do tend to read them whenever I go through other people’s code, if they tell me something obvious I feel like I just wasted my time. Try leaving meaningful comments, such like: // TODO review if there is a better way to do this, or // This code was coded like that because bla bla bla. Also they are imperative on public methods intended to be called by other people. As they should learn how a method, their parameters and return works by reading the designated descriptions.

I also agree with @McDev02 , I don’t see any point in assigning the instance on the Start method instead of referencing it via inspector. Maybe if this was a dynamically generated prefab with distinct options it would be useful, but not in this case.

Another nitpicking thing is the Debug.LogError used, if you’re going to actually keep that in a final release, at least define the exact object where the error happens, if someone reads a log like that trying to figure out what’s wrong, they would be very lost.

Thanks for the feedback I do agree with you on the C and V stuff this was me trying something different I see done in unreal engine c++ programming and I created a format for unity where C is equal to components aka Audio Sources and Particle Systems and makes them easier to access by a letter. However, this is not something I commonly do. I like trying new things and seeing how the community reacts to them.

Regarding assigning variables in the inspector this is a practice some people prefer I do not because sometimes people commonly forget to assign the variables as such I like to assign stuff in code.

Regarding using Time.deltaTime instead of Time.time I agree this would probably be better. This is a just age-old technique for simply programming a cooldown I learned several years ago and haven’t tried to change since. If a game runs on long too long this will probably become massive.

Since I already mentioned most of this in my reply to the other person I won’t restate myself I do like the variable name gunShotAudio better although I would probably start it audio_gunShot to signify what type of object you are storing. Much of my comments are they to explain why I did what I did I do agree with you on adding TODO’s and comments explaining what something might do.

I do feel like my Debug.LogError could be improved by simply adding the object that it is being called from thanks for the mention :slight_smile:

I would completely change your start function. Why do you want to get the component twice if you can do it once? You should include a check if the value is already set (from the inspector) before resetting the value. This is how I would rewrite your start function.

void Start()
{
    if (C_muzzleFlash == null)
    {
        C_muzzleFlash = GetComponentInChildren<ParticleSystem>();

        if (C_muzzleFlash == null)
        {
            Debug.LogError("No Particle System found in child objects", gameObject);
        }
    }
  
    if (C_gunShot == null)
    {
        C_gunShot = GetComponent<AudioSource>();

        if (C_gunShot == null)
        {
            Debug.LogError("No Audio Source found on object", gameObject);
        }
    }
}

Regarding the names, I tend to follow C# Code Conventions since that is how I have been coding for years. I have only made 2 modifications to it. Which is using m_ for private non static fields and M_ for private static fields.

bool m_foo;
static bool M_foo2;

In my opinion is more important that you are able to work with the team current standards, since most of the time they all are different.

This kind of code should not appear in production at all. It belongs only to debug build and editor runs…

I couldn’t tell anyone in this forum what is the correct standards for writing code.

That’s just a quagmire of flame wars and what not.

You do you boo.

Like my style, it’s way different from most everyone else I see around here. And that’s probably just due to my background.

I’ll give an example:

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

using com.spacepuppy;
using com.spacepuppy.Cameras;
using com.spacepuppy.Scenario;
using com.spacepuppy.SPInput;
using com.spacepuppy.Tween;
using com.spacepuppy.Utils;

using com.mansion.Entities.Inventory;
using com.mansion.Entities.UI;
using com.mansion.Messages;
using com.mansion.UserInput;

namespace com.mansion.Scenarios.Episode2
{

    public class Ep2_FuseBox : SPComponent, IUIDisplay, IUpdateable, IFuseBlownGlobalHandler
    {

        public const string TOKEN_FUSEBOX = "*Ep2_FuseBox*";

        public enum Regions
        {
            Garden,
            LakeHouse,
            GreenHouse,
            Courtyard,
            WestTrail,
            EastTrail,
            Cemetary
        }

        #region Fields
      
        [SerializeField]
        [ReorderableArray()]
        private FuseNodeInfo[] _nodes;

        [Space(10f)]
        [SerializeField]
        private GameObject _uiContainer;

        [Header("Camera")]
        [SerializeField]
        private UnityCamera _camera;
        [SerializeField]
        private Transform _cameraMount;

        [SerializeField]
        private EaseStyle _cameraEaseIn = EaseStyle.Linear;
        [SerializeField]
        private float _cameraEaseInDur = 1f;
        [SerializeField]
        private EaseStyle _cameraEaseOut = EaseStyle.Linear;
        [SerializeField]
        private float _cameraEaseOutDur = 1f;

        [Header("Icons")]
        [SerializeField]
        private Transform _fuseIcon;
        [SerializeField]
        private GameObject _fusePrefab;
        [SerializeField]
        private GameObject _blownFusePrefab;
        [SerializeField]
        private Text _fuseCountText;
        [SerializeField]
        private InventoryItem _fuseItem;

        [SerializeField]
        private Trigger _onShow;
        [SerializeField]
        private Trigger _onHide;
        [SerializeField]
        private Trigger _onFuseAdded;
        [SerializeField]
        private Trigger _onFuseRemoved;
        [SerializeField]
        private Trigger _onBlownFuseRemoved;

        [System.NonSerialized]
        private int _currentNodeIndex;
        [System.NonSerialized]
        private Dictionary<int, GameObject> _activeFuses = new Dictionary<int, GameObject>();

        #endregion

        #region CONSTRUCTOR

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

            _uiContainer.SetActive(false);
            this.SyncNodes();

            Messaging.RegisterGlobal<IFuseBlownGlobalHandler>(this);
        }

        protected override void OnDisable()
        {
            base.OnDisable();
          
            GameLoopEntry.UpdatePump.Remove(this);
            Messaging.UnregisterGlobal<IFuseBlownGlobalHandler>(this);
        }

        #endregion

        #region Properties

        #endregion

        #region Methods

        void IUpdateable.Update()
        {
            var input = Services.Get<IInputManager>().GetDevice<MansionInputDevice>(Game.MAIN_INPUT);
            if (input == null) return;

            if(input.GetButtonState(MansionInputs.UIDownRepeat) == ButtonState.Down)
            {
                _currentNodeIndex++;
                this.UpdateFuseIconPosition();
            }
            else if(input.GetButtonState(MansionInputs.UIUpRepeat) == ButtonState.Down)
            {
                _currentNodeIndex--;
                this.UpdateFuseIconPosition();
            }

            if (input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
            {
                var pl = PlayerEntity.PlayerPool.Find(PlayerEntity.FindFirstPredicate);
                var inventory = pl != null ? pl.GetComponent<InventoryPouch>() : null;
                if(inventory != null)
                {
                    GameObject fuse;
                    if (_activeFuses.TryGetValue(_currentNodeIndex, out fuse))
                    {
                        if(_nodes[_currentNodeIndex].Fuse.Blown)
                        {
                            _nodes[_currentNodeIndex].Fuse.Blown = false;

                            //swap out inventory
                            inventory.Items.Remove(_nodes[_currentNodeIndex].Fuse);
                            _activeFuses.Remove(_currentNodeIndex);
                          
                            _onBlownFuseRemoved.ActivateTrigger(this, fuse);
                            ObjUtil.SmartDestroy(fuse);
                        }
                        else
                        {
                            //swap out inventory
                            inventory.Items.Remove(_nodes[_currentNodeIndex].Fuse);
                            inventory.Items.Add(_fuseItem);
                            _activeFuses.Remove(_currentNodeIndex);

                            _onFuseRemoved.ActivateTrigger(this, fuse);
                            ObjUtil.SmartDestroy(fuse);
                        }

                        //if was a blown fuse, unblow it
                        if (_nodes[_currentNodeIndex].Fuse.Blown) _nodes[_currentNodeIndex].Fuse.Blown = false;

                        this.MoveFuseCountToScreen(inventory);

                    }
                    else if(inventory.Items.Remove(_fuseItem))
                    {
                        inventory.Items.Add(_nodes[_currentNodeIndex].Fuse);
                        fuse = Instantiate(_fusePrefab, _nodes[_currentNodeIndex].Node);
                        fuse.transform.localPosition = Vector3.zero;
                        fuse.transform.localRotation = Quaternion.identity;
                        _activeFuses[_currentNodeIndex] = fuse;

                        this.MoveFuseCountToScreen(inventory);

                        _onFuseAdded.ActivateTrigger(this, fuse);
                    }
                }

            }
            else if(input.GetExitMenu(true, true))
            {
                //we pipe all to make sure all events get consumed
                this.Hide();
            }
        }



        private void SyncNodes()
        {
            var pl = PlayerEntity.PlayerPool.Find(PlayerEntity.FindFirstPredicate);
            var inventory = pl != null ? pl.GetComponent<InventoryPouch>() : null;

            foreach(var go in _activeFuses.Values)
            {
                ObjUtil.SmartDestroy(go);
            }
            _activeFuses.Clear();

            if(inventory != null)
            {
                for (int i = 0; i < _nodes.Length; i++)
                {
                    if (inventory.Items.Contains(_nodes[i].Fuse))
                    {
                        var prefab = _nodes[i].Fuse.Blown ? _blownFusePrefab : _fusePrefab;
                        var fuse = Instantiate(prefab, _nodes[i].Node);
                        fuse.transform.localPosition = Vector3.zero;
                        fuse.transform.localRotation = Quaternion.identity;
                        _activeFuses[i] = fuse;
                    }
                }
            }

            this.MoveFuseCountToScreen(inventory);
        }

        private void UpdateFuseIconPosition()
        {
            if (_nodes.Length == 0) return;
            _currentNodeIndex = Mathf.Clamp(_currentNodeIndex, 0, _nodes.Length - 1);
            _fuseIcon.position = _nodes[_currentNodeIndex].Node.position;
        }

        private void MoveFuseCountToScreen(InventoryPouch inventory)
        {
            if (_fuseCountText == null) return;

            int cnt = 0;
            if(inventory != null && _fuseItem != null && inventory.Items.Count > 0)
            {
                foreach (var item in inventory.Items)
                {
                    if (object.ReferenceEquals(item, _fuseItem)) cnt++;
                }
            }

            _fuseCountText.text = cnt.ToString();
        }

        #endregion

        #region IUIDisplay Interface

        public event System.EventHandler Shown;
        public event System.EventHandler Closed;

        public bool IsShowing
        {
            get;
            private set;
        }

        public IRadicalWaitHandle Show()
        {
            if (this.IsShowing) return null;

            Game.Scenario.GameStateStack.Push(GameState.InteractiveScene, TOKEN_FUSEBOX);
            _onShow.ActivateTrigger(this, null);
            UXUtils.SignalShow(this);
            var d = this.Shown;
            if (d != null) d(this, System.EventArgs.Empty);

            _currentNodeIndex = 0;
            this.UpdateFuseIconPosition();
            this.SyncNodes();

            var manager = Services.Get<ICameraManager>();
            var cam = (manager != null) ? manager.Main : null;
            if (_cameraEaseInDur > 0f && cam != null && cam.camera != null)
            {
                _uiContainer.SetActive(true);
                _camera.camera.cullingMask |= Constants.MASK_ENTITY;
                return SPTween.Tween(_camera.transform)
                              .FromTo("position", EaseMethods.GetEase(_cameraEaseIn), _cameraEaseInDur, cam.camera.transform.position, _cameraMount.position)
                              .FromTo("rotation", EaseMethods.GetEase(_cameraEaseIn), _cameraEaseInDur, cam.camera.transform.rotation, _cameraMount.rotation)
                              .UseRealTime()
                              .OnFinish((s, e) =>
                              {
                                  _camera.camera.cullingMask &= ~Constants.MASK_ENTITY;
                                  GameLoopEntry.UpdatePump.Add(this);
                              })
                              .Play(true, this);
            }
            else
            {
                _uiContainer.SetActive(true);
                _camera.transform.localPosition = Vector3.zero;
                _camera.transform.localRotation = Quaternion.identity;
                _camera.camera.cullingMask &= ~Constants.MASK_ENTITY;
                GameLoopEntry.UpdatePump.Add(this);
                return null;
            }
        }

        public IRadicalWaitHandle Hide()
        {
            UXUtils.SignalHideBegin(this);

            GameLoopEntry.UpdatePump.Remove(this);
            Game.Scenario.GameStateStack.Pop(TOKEN_FUSEBOX);

            _onHide.ActivateTrigger(this, null);
            var d = this.Closed;
            if (d != null) d(this, System.EventArgs.Empty);

            var manager = Services.Get<ICameraManager>();
            var cam = (manager != null) ? manager.Main : null;
            if (_cameraEaseOutDur > 0f && cam != null && cam.camera != null)
            {
                _camera.camera.cullingMask |= Constants.MASK_ENTITY;
                return SPTween.Tween(_camera.transform)
                              .To("position", EaseMethods.GetEase(_cameraEaseOut), _cameraEaseOutDur, cam.camera.transform.position)
                              .To("rotation", EaseMethods.GetEase(_cameraEaseOut), _cameraEaseOutDur, cam.camera.transform.rotation)
                              .UseRealTime()
                              .OnFinish((s, e) =>
                              {
                                  UXUtils.SignalHideComplete(this);
                                  _uiContainer.SetActive(false);
                              })
                              .Play(true, this);
            }
            else
            {
                return this.Invoke(() =>
                {
                    UXUtils.SignalHideComplete(this);
                    _uiContainer.SetActive(false);
                }, 0.25f, SPTime.Real);
            }
        }

        #endregion

        #region IFuseBlownGlobalHandler Interface

        void IFuseBlownGlobalHandler.OnFuseBlown(FuseInventoryItem fuse)
        {
            if(!this.IsShowing)
                this.SyncNodes();
        }

        #endregion

        #region Special Types

        [System.Serializable]
        public struct FuseNodeInfo
        {
            public Transform Node;
            [UnityEngine.Serialization.FormerlySerializedAs("UntrackedItem")]
            public FuseInventoryItem Fuse;
        }

        #endregion

    }

}

xyuuu

I completely agree with you, but since OP had them there I assumed that his code is not production ready.

Sorry, nothing personal, your post was closer to hit the reply button. :slight_smile:
Just wanted to note that it’s a bad practice to have them naked without UNITY_EDITOR and/or DEVELOPMENT_BUILD.

Not even for a project just a script I typed up to give examples of what I spoke about ;p

I agree somewhat with this but then again its still a matter of personal opinion when naming variables its just a matter of understanding them for yourself or if your writing the script with other people it would be better to write variables names more specific to the function method/type. In my opinion

I feel the gun script should not check for user input, but that depends on the type of game.
If you think of a real world example: a gun can be shot, but it can’t fire itself.
You need something to pull the trigger. The gun should only be concerned with things like “do i have ammo?”, “has my bullet loaded into the chamber?”(cooldown), “is the safety on?” etc.

So basically the gun should have a “PullTrigger” function on it, which is called by something else.

Typically you’d need a finger to pull a trigger… but lets skip the intermediaries and just say that we need a Humanoid to pull the trigger.

The great thing here is that we can check the state of the Humanoid before determining whether we can fire the gun.
Is the humanoid alive?, conscious?, holding a gun?, does he have fingers? :stuck_out_tongue: if he has all these things, then we are allowed to pull the trigger on the gun. What happens from there is up to the gun.

I wanted to point out that your commenting style is exactly what I recommend to many new coders. You explain the purpose in natural words, and then use code to achieve the desired purpose. Strategy in comments, Tactics in code. // swap out inventory is a great brief landmark so I don’t have to mentally derive the purpose of the next two or three lines of code, and doesn’t need to change if I decide on a different approach to solve the problem.

All too often I see // attach the CapsuleCollider (which is redundant to the code it explains). Or twenty lines of deep animator parameter lerping without any clue that this code is trying to transition to a natural pose when stepping off a ladder.