- Home /
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[i] = new TargetRecord(transform.position);
}
_record = _records[_j];
}
/// Gets the current record.
public TargetRecord currentRecord
{
get
{
return _record;
}
}
}
Answer by Masterio · Mar 24, 2016 at 08:39 PM
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[i].
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[i] = new TargetRecord(transform.position);
}
_record = _records[_j];
}
/// <summary>
/// Gets the current record.
/// </summary>
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[i] != null && _records[i+1] != null)
Gizmos.DrawLine(_records[i].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
}
Thanks again. I decided to look into buying a book that would walk through how to write and read C#, similar to a classroom. I got "Learning C# Program$$anonymous$$g with Unity 3D" by $$anonymous$$ Okita. Read 225 pages and now understand your script a lot better, and how to think about my scripts structure when writing.
I made some edits to your script to mold it into the result I am trying to create. I also renamed some variables to make it clearer for me to see where they are being used.
With the script you have provided, have you noticed that the array size deter$$anonymous$$es the distance the Helper is from the Player?
I know the frames per second (fps) can fluctuate depending on the specs of the hardware being used, or the amount of tasks being processed increasing or decreasing. I felt this might affect the Helpers distance from the Player while the game is running.
I'll post the most up-to-date script in the old Question link below to be dragged and dropped into a project. I'll also create a Sample Game Project folder to make faster to setup, but this will take a few hours. Going to make the Game Project as barebones as possible.
Answer by lassade · Mar 24, 2016 at 08:49 PM
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.
Thanks for mentioning the $$anonymous$$icrosoft S2B program. I will look into it after I finish reading a book I got, "Learning C# Program$$anonymous$$g with Unity 3D" by $$anonymous$$ Okita.
The book covered OO to a point where I feel much more confident knowing what is happening and why. And thank you for covering a few questions I had. There was too much to cover and 225 pages later I finally was able to go back and understand exactly what is happening.