C#
Since I happen to be ranked somewhat high when it comes to mentioning Unity 3D and XML, and some of the posts1 happen to be outdated by now since I learned stuff2. But most of all, I intend to learn even more.
That's why I choose to release those two projects into the wild and publish them on github.
-
Like the one on writing log files in Unity 3D or the one on getting strings out of XML files. ↩
-
Yes, that happens from time to time. ↩
GUIs are not exactly easy to program in Unity 3D, since there is only one method available (OnGUI()) that obviously has to take up everything.
This becomes a problem as soon you try to create different menu screens that have to be switched out. One way to do it would probably be to use different scenes (which would be cumbersome); another way to use a switch, which would be just as cumbersome.
My way of doing it is using delegates, which allows to keep each screen encapsulated in a distinct method, while still retaining the same scene all the time.
using UnityEngine; using System.Collections; using System; //Necessary to access Action /// <summary> /// An example for using delegates to show different menu screens. /// </summary> public class DelegateGUI : MonoBehaviour { #region Setup /// <summary> /// The delegate that is going to be used. /// </summary> private Action OnGUIMethod; /// <summary> /// The standard GUI method, as inherited by MonoBehaviour. /// </summary> private void OnGUI () { if (OnGUIMethod != null) { OnGUIMethod(); } } /// <summary> /// Define what screen to show first. /// </summary> private void Start () { OnGUIMethod = Screen1; } #endregion #region Screens /// <summary> /// A very simple function that will theoretically show two buttons, /// which in turn show the other screens.
Since I'm currently cleaning up my code to hand it in with my project, I figured I could write some of the stuff down I learned during my work on our game.
All of it applies, of course, to C#, and was used in Unity 3D.
Checking for a type
Using the is keyword, you can easily check whether a certain object is of a desired type. Also, you should be aware of the as keyword, that allows you to cast an object as something else (given that this is possible).
if (obj is ISource) { ISource s = obj as ISource; if (!s.isEndpoint) { Connect (s); } }
Modelling Behaviour Over Multiple Frames Without Update()
Sometimes, you need to model some behaviour over several frames (like fading stuff in or out), but doing it in Update() or FixedUpdate() would require unwieldy if constructions.
So ... coroutines to the rescue! Using WaitForFixedUpdate(), you can model a behaviour over several frames without using FixedUpdate(), and quits as soon as it is done.
float i; private void SomeMethod() { // do some stuff here i = 1f; StartCoroutine(FadeOut()); } private IEnumerator FadeOut() { while (i > 0f) { yield return new WaitForFixedUpdate(); i -= 0.1f; } }
Delegates – The Easier Way
I wrote about delegates before, explaining how they could be used.
Yes, this is yet another C#-in-Unity3D post. I hope you will forgive me ;)
Up until now, I would usually reference other GameObjects or components like this:
public class Foo : MonoBehaviour { public Bar b; void Start () { b = GameObject.FindObjectByTag("Player").GetComponent<Bar>(); } }
This works fine until you suddenly want to access a GameObject without knowing if it is already there.
My first solution would have been to rewrite the whole thing as a property:
public class Foo : MonoBehaviour { public Bar b { get { return GameObject.FindObjectByTag("Player").GetComponent<Bar>(); } } }
Of course, since there is no caching mechanism on the compiler level, this "solution" comes at a huge performance cost. The real solution: adding our own caching mechanism.
public class Foo : MonoBehaviour { private Bar _b; public Bar b { get { if (_b == null) { _b = GameObject.FindObjectByTag("Player").GetComponent<Bar>(); } return _b; } } }
Of course, this pattern allows for even more sophisticated mechanisms, like fetching new data once the underlying object has changed, but getting the cached version in all other cases.
Thanks to UnityAnswers, that got me that answer so fast.
Animation curves in the new Unity 3 can not only be used for animation, but can be directly accessed by scripts.
By defining
AnimationCurve curve;you can add a specific curve to the script in the editor. Using AnimationCurve.Evaluate() you can get the y value at a specific time. Using those curves, you are no longer limited to use Lerp() at every corner (or one of the additional functions provided by Mathfx).
Thanks to Mario von Rickenbach who mentioned that in a lesson as an aside …
(This is only here for personal reference, since this is rather basic programming …)
/// <summary> /// Formats the input string (in seconds) as a human readable /// string in the form of MM:SS. /// </summary> /// <returns> /// A <see cref="System.String"/> of the current playing time. /// </returns> private string formatedTimeString (string input) { int seconds; int minutes; minutes = Mathf.FloorToInt(input / 60); seconds = Mathf.FloorToInt(input - minutes * 60); string r = (minutes < 10) ? "0" + minutes.ToString() : minutes.ToString(); r += ":"; r += (seconds < 10) ? "0" + seconds.ToString() : seconds.ToString(); return r; }
Finite State Machines are quite a convenient design pattern in Game Design, as they allow for quite some flexibility when programming AI behaviour.
Unfortunately, our first encounter with FSMs was poorly explained, and badly executed in ActionScript 3 using a bunch of switch statements – disregarding the fact that there would have been clearly more elegant solutions.
So, let's do the same for Unity 3D in C#, using delegates.
using UnityEngine; using System.Collections; public class FiniteStateMachine : Monobehaviour { // Create the delegate private delegate void FState (); // Create a holder for the delegate private FState stateMethod; #region UnityEngine methods /// <summary> /// Sets up the controller and puts the object into the new mode. /// </summary> void Start () { stateMethod = new FState (EnterStateA); } /// <summary> /// Executes in every frame once. Only calls the current state method. /// </summary> void FixedUpdate () { stateMethod (); } #endregion #region State A /// <summary> /// Sets up State A. /// </summary> private void EnterStateA () { // Things to prepare State A come here. stateMethod = new FState (StateA); // The next line will ensure that the first run of State A // will occur in the same frame.
When preparing my game for Fantoche, I ran into problems several times, since upon building my game, the screen either remained black or parts of my game did not work, with the log showing a NullReferenceException.
In both times, it was related to me tagging objects in the Unity 3D editor. This is caused by the somewhat peculiar implementation of tagging in Unity which only allows one tag to be added to an object.
Pitfall No. 1: EditorOnly
Tagging objects in the editor as EditorOnly will make the game work in the editor – but not in the build, as those objects won't be included when building your game.
Pitfall No. 2: Trying to Access One of Several Objects with the Same Tag
When accessing GameObjects using the FindObjectsWithTag() function, make sure that only one object has this tag added.
The function returns the first object with this tag it encounters.
Placing stuff in 3D space in Unity 3D is simple, right? Just use this.transform.position and you are all set – or so we have been taught in art school. Turns out there are a few pitfalls I happen to hit whenever I work with that stuff.
Reading and Writing Values of transform.position
Yes, you can access the x, y, and z-values of the transform.position. But you can not change them directly (at least in C#). You have to use a Vector3 object, as in:
this.transform.position = new Vector3(newX, newY, newZ);
Obviously, by feeding the original values in, you can change just one value at a time:
this.transform.position = new Vector3(this.transform.position.x, newY, this.transform.position.y);
Global vs. Local
Also of note: transform.position actually refers to the position in global space, it is the absolute position.
In many cases, you want to calculate movement relative to the parent object. In order to do this, you need to use transform.localPosition, which works exactly as the other one, but is calculated relatively to the position of the parent object. If there is no parent object, localPosition is the same as position.
In my opinion, it is in many cases better to use localPosition instead of position, since most of the time, you want to have objects move with or along their parent objects. After all, also the editor shows you the local position.
How not to do it:
using System; using UnityEngine; using System.Collections; using System.Collections.Generic; /// <summary> /// Needs work. /// </summary> public class Inventory : MonoBehaviour { private Dictionary<string, int> backpack; private bool showInventory; void Awake () { } void Start () { DisplayInventory (true); } public void DisplayInventory (bool state) { showInventory = state; } public bool RemoveObject (string iObject, int number) { try { backpack[iObject] = backpack[iObject] - number; if (backpack[iObject] <= 0) { backpack.Remove (iObject); } return true; } catch (Exception ex) { print ("[" + this.GetType ().ToString () + "] The object " + iObject + " does not exist."); return false; } } public bool AddObject (string iObject, int number) { if (backpack.ContainsKey (iObject)) { backpack[iObject] += number; } else { backpack[iObject] = number; } return true; } void OnGUI () { if (showInventory) { int counter = 0; foreach (KeyValuePair<string, int> kv in backpack) { string displayString = kv.Key + ": " + kv.Value.ToString (); GUI.Label (new Rect (10, counter * 40, 200, 30), displayString); counter++; } } } }
How to do it instead:
using System; using UnityEngine; using System.Collections; public class ChickenCounter : MonoBehaviour { private int counter = 0; public GameObject particleEffect; void OnTriggerEnter (Collider hit) { if (hit.tag == "bonus") { counter++; Instantiate (particleEffect, hit.transform.position, hit.transform.rotation); Destroy (hit.gameObject); } } void OnGUI () { GUI.Label(new Rect(10,10,200,30),
Since I already played around with XML in C#, this part of the project was easier to do than before.
What is it supposed to do?
Basically, I could simply hard-code most of my strings used in the game directly into the code – no one would notice the difference anyway. But obviously, this is not a very good idea, both because editing strings and later translating them becomes a pain.
Creating some data that would allow me to get strings out of an XML file would solve this problem – and, if the code is good enough, be reusable in later games.
It would allow me to edit text independently of the game code and add translations on a later date.
Since I want to make some basic statistics for my game at Fantoche, I needed some basic logging function of the player's position.
Of course, this could also be done using a simple CSV file, but the perfectionist in me insisted on an XML format. A preliminary test showed me, that I would be able to transform the XML to a CSV later on, so that my S.O. would be able to use it in his own programs.1
Having set up my development environment in MonoDevelop, I started to work on the problem on how to get my data into a well-formed XML representation and onto the hard disk.
The first approach was to use serialisation. I created a Location class, with all the necessary attributes – until I realised that this would only allow me to get one dataset into a file. I wouldn't be able to add more data to the file.
Conclusion: Serialisation is only good when you have one clearly defined object you want to dump onto the drive as a well-formed XML file.
So I tried to work with the XMLWriter.
-
So why again am I doing it with XML? Good question. Because I can? Does that make me a nerd? ↩
