Any way to completely learn to script in C# using Unity?

Too Long Didn’t Read / TL;DR: I wanted to add a tag team feature similar to Donkey Kong Country (SNES), asked Unity Answers a while ago, was given a script to work with, I don’t understand why a few things are written the way they are, and how they work. Want to know how to fully learn C# to make the needed changes I know I need to make to the script. Current version of the script is posted below.


Background: Self taught, and been using Unity3D for 2.5 yrs. Started with Javascript/Unityscript. Decided to learn C# and convert all of my Javascripts on a project to C#. I’ve been working on this project for 2 years. I believe I did so correctly because everything works as it should. Learned how to script through YouTube tutorials, forums, Google searching problems, going to a college game dev club (graduated college with a different degree, and moved, so I can’t visit with the Game Dev instructors anymore. They are busy.), and Unity’s learning section on their website.


Issue: I wanted to add a tag team feature similar to Donkey Kong Country’s follower in a 2D platformer. Searched online for ways to logically understand how it works in the game, but for some reason it seems to be hard to find. A year ago I thought to use this as a visual health system. Couldn’t get the feature to happen after 3 months of trying and decided not to use it to continue working on other features.

I believe 2 months ago I decided it is needed. Gave 1 month to try and figure it out myself, and last month (Feb) asked here on Unity Answers for guidance. Was given a script to work with yet I did not know why somethings were where they were and how somethings worked. I will add notes to the script below on what parts I do not understand fully below. It didn’t work completely the way I needed it to but it was close enough for me to tweak and edit around to get the result I needed. Tried to stayed in touch with the user who the example script, and update when I could.

I’m at a point where I feel I know logically what needs to be added, but I don’t know where to add these lines of code yet.


Question 2: I know that some users help in their free time, and are kind enough to give their time, but I know no one wants to “work” for free. Is there another effective way to get help or a second brain to help rethink the issue, or scan through a script? Like a Unity tutor program or paid bounty method? Thank you.


Current Version of Script (TargetFollower.cs):

using UnityEngine;
using System.Collections;
using System;

public class TargetFollower : MonoBehaviour
{
    public Transform target = null;                   // This is the Player. In editor drag the Player here for now. Will script how to search for the Player later on.
    public float followSpeed = 5f;                    // follow speed
    public float targetDistance = 1.25f;              // This value sets the distance the Player is from the Follower.
    public bool targetDistanceY = true;               // if near then update y only                      

    [Serializable] //Shows in inspector ////####### Looked this up. Still not sure what this does entirely. With out it will the variables not show in the editor.
    public class TargetRecord
    {
        public Vector3 position;                      // world position
        public TargetRecord(Vector3 position)         ////####### Not sure why "Vector3 position" is in (). What does this do? 
        {
            this.position = position;                 ////####### Why does "this.position = position" need to be here? What does "this.position" do?
        }
    }

    public TargetRecord[] _records = null;          // keeps target data in time // type[] nameOfArray = lengthOfArray 
    ////####### How does this create an array? and why is "_records = null" after TargetRecord[]?
    private int _i = 0;                             // keeps current index for recorder
    private int _j = 1;                             // keeps current index for follower
    private TargetRecord _record = null;            // current record
    ////####### Why is this some what repeated? Why have "public TargetRecord[] _records = null;" and then "private TargetRecord _record = null;"? Are they not the same thing?
    private bool _recording = true;                 // stop recording if true
    private int _arraySize = 6;                     // This needs to change to frames per second that way the follower follows at a specific distance on any frame rate on different devices.
    public bool followSwitch = false;               //Decide when to start following the player.
    public bool recordData = false;                 //Start or stop recording data

    public void Start()
    {
        _records = new TargetRecord[_arraySize]; ////####### What does this do? Why is it needed in the Start function?
    }

    // update Follower transform data
    public void Update()
    {
        if (recordData)
        {
            RecordData(Time.deltaTime); ////####### How does this work exactly?
        }

        // move to the target
        if ((target.position - transform.position).magnitude >= targetDistance)
        {
            followSwitch = true;
        }
        if (followSwitch == true)
        {
            //This triggers when the follower moves.
            if (!_recording)
            {
                ResetRecordArray();
                _recording = true;
                ////####### Never happens so not sure if this if statement is needed.
            }

            if (_record != null)
                transform.position = Vector3.Lerp(_records[_i].position, _records[_j].position, Time.deltaTime); //Player is moving and they're position is being recorded. The follower then moves to the 
            //Debug.Log("1 - _records[_jiposition = " + _records[_i].position);
            //Debug.Log("2 - _records[_j].position = " + _records[_j].position);
        }
        if (targetDistanceY && Mathf.Abs(target.position.y - transform.position.y) > targetDistance)
        {
            followSwitch = false;
            if (_record != null) ////####### When would this be true? Only when "_record" is empty?
                transform.position = Vector3.Lerp(transform.position, new Vector3(transform.position.x, target.position.y, transform.position.z), Time.deltaTime); //The follower is adjusting its Y axis because the Player is close and not moving.
        }
    }


    private void RecordData(float deltaTime)
    {
        if (!_recording)
            return; ////####### Never understood how "return" worked.
        // record target data
        _records[_i] = new TargetRecord(target.position); ////####### I am not sure how this works.

        // set next record index
        if (_i < _records.Length - 1)
            _i++;
        else
            _i = 0;

        // set next follow index
        if (_j < _records.Length - 1)
            _j++;
        else
            _j = 0;

        // handle current record
        _record = _records[_j];
    }

    // used if distance is small
    private void ResetRecordArray()
    {
        _i = 0;
        _j = 1;

        _records = new TargetRecord[_arraySize];

        for (int i = 0; i < _records.Length; i++)
        {
            _records *= new TargetRecord(transform.position);*

}

_record = _records[_j];
}

/// Gets the current record.
public TargetRecord currentRecord
{
get
{
return _record;
}
}
}

https://store.unity3d.com/products/support

First you need to learn structural and object programming.
I am learning too btw so my code is not perfect for sure:) but it works for settings: fps 60 and speed 500+.

I pasted my oryginal code posted on this forum some time ago. With answers for your questions.

using UnityEngine;
using System.Collections;
using System;

/// Author: Matthew Mazan [Masterio]
public class TargetFollower : MonoBehaviour 
{
	public Transform target = null;					// follow by target
	public float reactDelay = 1f;					// how fast it react to target moves [edit it in edit mode only]
	public float recordFPS = 60f;					// how many record data in one second [edit it in edit mode only]
	public float followSpeed = 500f;					// follow speed
	public float targetDistance = 0f;				// don't move closer than this value
	public bool targetDistanceY = false;			// if near then update y only
	public bool targetPositionOnStart = false;		// set the same position as target on start
	public bool start = false;						// start or stop follow	
	#if UNITY_EDITOR
	public bool gizmo = true;						// draw gizmos
	#endif
	
	// ### I am serailze that only for one reason, _records array with values will be visible in debug mode in unity inspector.
	[Serializable] 
	public class TargetRecord
	{
		public Vector3 position;					// world position
		
		// we can add more data if we need here
		
		public TargetRecord(Vector3 position)
		{
			this.position = position;
		}
	}
	
	// ### The _recrods is not initialized here so this is why it is null, you can do the same thing with: TargetRecord[] _record;
	private TargetRecord[] _records = null;			// keeps target data in time
	private float _t = 0f;
	private int _i = 0;								// keeps current index for recorder
	private int _j = 1;								// keeps current index for follower
	private float _interval = 0f;
	// ### No these variables have another names (_record != _records). The _record keeps last recorded 'frame' this is reference to the _records array value at some index like: _records*.*
  • private TargetRecord _record = null; // current record*

  • private bool _recording = true; // stop recording if true*

  • private int _arraySize = 1;*

  • public void Start()*

  • {*

  •  // ### As you can see here we have an Initialize method so _records array initialized inside. First some statements must be checked. You have reworked version of my script.*
    
  •  Initialize();*
    
  • }*

  • public void Initialize()*

  • {*

  •  if(targetPositionOnStart)*
    
  •  	transform.position = target.position;*
    
  •  _interval = 1 / recordFPS;*
    

_arraySize = Mathf.CeilToInt(recordFPS * reactDelay);

  •  if(_arraySize == 0)*
    
  •  	_arraySize = 1;*
    
  •  _records = new TargetRecord[_arraySize];*
    
  • }*

  • // update this transform data*

  • public void LateUpdate()*

  • {*

  •  if(start)*
    
  •  {*
    
  •  	// ### RecordData method add current position of target to the _records array.*
    
  •  	RecordData(Time.deltaTime);*
    
  •  	// move to the target*
    
  •  	if(targetDistance <= 0f)*
    
  •  	{*
    
  •  		if(_record != null)*
    

transform.position = Vector3.MoveTowards(transform.position, _record.position, Time.deltaTime * followSpeed);

  •  	}*
    
  •  	else if((target.position - transform.position).magnitude > targetDistance)*
    
  •  	{*
    
  •  		if(!_recording)*
    
  •  		{*
    
  •  			// ### resets whole _records array becouse we dont need to remember if follower is not moving. It must be cleared before next recording.*
    
  •  			ResetRecordArray();*
    
  •  			_recording = true;*
    
  •  			// ### in next statements _recording can be false*
    
  •  		}*
    
  •  		if(_record != null)*
    

transform.position = Vector3.MoveTowards(transform.position, _record.position, Time.deltaTime * followSpeed);

  •  	}*
    
  •  	else if(targetDistanceY && Mathf.Abs(target.position.y - transform.position.y) > 0.05f)*
    
  •  	{*
    
  •  		// ### It check an _record reference if it is null (empty) then dont apply an position. Some kind of error protection.*
    
  •  		if(_record != null)*
    

_ transform.position = Vector3.Lerp(transform.position, new Vector3(transform.position.x, target.position.y, transform.position.z), Time.deltaTime * followSpeed);_

  •  	}*
    
  •  	else*
    
  •  	{*
    
  •  		_recording = false;*
    
  •  	}*
    
  •  }*
    
  • }*

  • private void RecordData(float deltaTime)*

  • {*

  •  if(!_recording)*
    
  •  	return;			// ### return; is breaking the method in this place. Next lines of code in method will be skipped after this command.*
    
  •  // check intervals*
    
  •  if(_t < _interval)*
    
  •  {*
    
  •  	_t += deltaTime;*
    
  •  }*
    
  •  // record this frame*
    
  •  else*
    
  •  {*
    
  •  	// ### here we make an new instance of the TargetRecord class. We are usung custom constructor from this class. Definition of this class is in line 22.*
    
  •  	// record target data*
    
  •  	_records[_i] = new TargetRecord(target.position);*
    
  •  	// set next record index*
    
  •  	if(_i < _records.Length - 1)*
    
  •  		_i++;*
    
  •  	else*
    
  •  		_i = 0;*
    
  •  	// set next follow index*
    
  •  	if(_j < _records.Length - 1)*
    
  •  		_j++;*
    
  •  	else*
    
  •  		_j = 0;*
    
  •  	// handle current record*
    
  •  	_record = _records[_j];*
    
  •  	_t = 0f;*
    
  •  }*
    
  • }*

  • // used if distance is small*

  • private void ResetRecordArray()*

  • {*

  •  _i = 0;*
    
  •  _j = 1;*
    
  •  _t = 0f;*
    
  •  _records = new TargetRecord[_arraySize];*
    
  •  for(int i = 0; i < _records.Length; i++)*
    
  •  {*
    

_records = new TargetRecord(transform.position);
* }*

* _record = _records[j];
_
}*

* ///

*
* /// Gets the current record.*
* ///
*
* public TargetRecord currentRecord*
* {*
* get {*
* return record;
_
}*

* }*

* #if UNITY_EDITOR*
* public void OnDrawGizmos()*
* {*
* if(gizmo)*
* {*
* if(_records == null || records.Length < 2)
_
return;*

* Gizmos.color = Color.red;*
* for(int i = 0; i < i-1; i++)
_
{*

if(_records != null && _records[i+1] != null)
Gizmos.DrawLine(records*.position, records[i+1].position);*
* }*

* //Gizmos.color = Color.green;*
* for(int j = j; j < records.Length-1; j++)*
* {*

* if(_records[j] != null && _records[j+1] != null)
Gizmos.DrawLine(records[j].position, records[j+1].position);*
* }*

* //Gizmos.color = Color.yellow;*
* if(_records[0] != null && _records[_records.Length-1] != null)
Gizmos.DrawLine(_records[0].position, _records[_records.Length-1].position);*

* Gizmos.color = Color.white;*
* if(record != null)
Gizmos.DrawLine(record.position, transform.position);*
* }*

* }*

* #endif*
}

I use c# and unity for about 3 years now as far i know using scripting in c# is very similar to javascript, the most differences are in syntax of both languages. So you can just learn c#, most of learn for me is programing, the more you do the more you will learn.

Btw i learn most of c# and basic Object-Oriented (OO) concepts in the Microsoft S2B program. You should look at the OO to understand how c# works.

About the script here my best answers about your questions:

  • Line 12: [Serializable] is an attribute and
    tells to unity that this class can be
    saved into a xml format or other.
    This also allows the variable
    _records to be visible on inspector try to comment [Serializable] to see.

  • Line 16: public TargetRecord(Vector3 position)
    this is called a constructor this
    tells how this object will be created
    and what it needs as parameters to be
    build (in this case it need the
    position).

  • Line 18: this.position = position; the
    this.position is used to
    differentiate from the parameter
    position. So using this.position
    actually changes the world position
    field (line 15)

  • Line 26 and 35: _record array is created in
    the start function at line 35

For now i dont have more time.