Creating your first moving GameObject

Creating your first moving GameObject

Part 5 – From idea to game: A beginner's guide to creating games with Unity!

Feb 9, 2023·

15 min read

In the previous articles I have shown you how I created the idea for my game and set up all the necessary tools to get started. In part 3 of the series I used the MoSCoW method to prioritise certain features of the idea, and as a result of that, I wanted to start with creating fish. Since most of the "must haves" are related to buying, fish and keeping them happy. Technically, it will just be a spherical object that represents a fish for now, but use your imagination, and it will be a fish 😉.

Initial push version control ✋

A great start for any development project is making sure you use version control. I briefly touched upon this topic in part 4, where I showed you how to set up PlasticSCM.

As I mentioned before, I have no experience with PlasticSCM, but what I do know from my experience with Git (The most common version control for code), is that you do not want to include every file change. This is where "ignore files" come in. They contain a list of files and directories that are to be excluded from the version control you are using. With Git, these are called .gitignore files. With Plastic it is called ignore.conf, and it was already in my project directory. This means it either comes with a new Unity project by default, or when creating a repository in PlasticSCM.

Regardless, I like clarity and clean code, so I applied the template Plastic provided in one of their blogs. It works the same way as the default but uses comments (Lines starting with #) to separate different groups, to increase clarity.

If you want to follow along, simply open up ignore.conf in your editor of choice, and paste the code below:

# Common directories
Library
library
Temp
temp
Obj
obj
Build
build
Builds
builds
UserSettings
usersettings
MemoryCaptures
memorycaptures
Logs
logs
**/Assets/AssetStoreTools
**/assets/assetstoretools
**/Assets/AddressableAssetsData/*/*.bin*
**/assets/addressableassetsdata/*/*.bin*
**/Assets/StreamingAssets/aa.meta
**/assets/streamingassets/*/aa/*
.collabignore

# Builds
*.apk
*.unitypackage

# Plastic SCM related
/ignore.conf
*.private
*.private.meta
^*.private.[0-9]+$
^*.private.[0-9]+.meta$

# Gradle cache directory
.gradle

# Autogenerated project files
/Assets/Plugins/Editor/JetBrains*
/assets/Plugins/Editor/JetBrains*
.vs
ExportedObj
.consulo
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta

# Unity3D Generated File On Crash Reports
sysinfo.txt

# Crashlytics generated file
crashlytics-build.properties

# Mac
.DS_Store*
Thumbs.db
Desktop.ini

As mentioned, this file simply lists all files and directories that are ignored for version control. For example, the folders in your project directory named "Builds" or "builds" (I believe these folders will include the final product after you build your game) will not be included in Plastic. The idea here is that your source code and assets, everything needed to build the product, are included.

Now that is all done, it is time to make sure our project directory as it is now, is pushed to the cloud, as a baseline and a starting point. Open up Plastic, go to "Pending Changes" in the sidebar, add a comment, ensure all files are included by hitting the checkmarks and press "Checkin".

A visual representation on how to do the initial checkin using PlasticSCM. The animation shows adding files to the repository, entering a commit message, and finally performing the checkin to the cloud.

Congratulations 🎉! You have now pushed a version of your project to the cloud!

One task, one branch ☝

Branches in version control are exactly what they sound like. It is a separate copy of your project that you can make changes to, without affecting the main (or other) copies. In simpler terms, think of it as creating a new folder for a project, and working on that folder without touching the original. You can make changes, add new files, or delete files in that folder without changing the original.

However, once you are done, you can combine your changes back into the original folder. This is what we call "merging". So while creating a branch is splitting off from the original, merging is bringing the branch back in.

I believe the image below (Taken from a great article by CPanel on version control) visualises this process very clearly. For example, you can see how "Bug fix" branches off from the "Master" branch, before merging back into it. One side note, the article is from 2018, and since October 2020 we tend to use "Main" instead of "Master" for the initial branch in Git.

A diagram showing the concept of branches in version control. It displays a main branch, with several additional branches branching off from it. The branches represent different changes and updates to the codebase, and can be merged back into the main branch at a later time.

So what do you mean by one task, one branch? PlasticSCM describes it best in their docs:

The pattern is super simple; you create a new branch to work on for each new task in your issue tracker.

We can replace "issue tracker" with "task list", "backlog", or whatever you want to call the collection of things yet to work on.

I do not capture all the tasks required to implement a certain feature. The reasons for this are twofold. First, I am learning and inexperienced. I do not know all the steps I need to take to finish a certain feature. Second, the project is small and I am the sole contributor to the project. Therefore, in my situation, I prefer to list features and have one branch per feature. Your mileage may vary, depending on your situation.

Regardless if you are using a branch per task or branch per feature or something else, you will need to have a baseline. In Plastic, this is done via labels. Right-click your first checkin, click "Label this changeset", and give it a name. I went with Baseline000 , as they do in their documentation.

A visual representation of the steps to add a label to a changeset in PlasticSCM. The animation shows the user navigating to the desired changeset, selecting the option to add a label, entering the label name, and finally confirming the label creation.

Once we have defined a baseline, we can create a new branch. Right-click the checkin as you did before and click "Create new branch from this changeset". Give it a meaningful name, in my case Create fish , ensure the checkbox "Switch workspace to this branch" is ticked, and click "Create".

A visual representation of the steps to create a branch in PlasticSCM. The animation shows the user selecting the desired changeset or file, choosing the option to create a branch, entering the name of the new branch, and finally confirming the creation of the branch.

Let's build cool shit 💪

Finally! Time to open Unity and get some work done! The main priority at this point is to create fish. What I understand from the Unity Docs is that we ideally create a prefab. This is like a template or a blueprint of a game object that you can instantiate later on, with all the required components attached to it already. Or, in their own words:

Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects as a reusable Asset

I created a new empty game object (GameObject > Create Empty), and named it Fish. I right clicked Fish in the Hierarchy tab and clicked 3D Object > Capsule. With the Capsule selected, I adjusted the Z rotation by 90.

A visual representation of creating an empty GameObject in Unity, to then add a 3D Capsule to it.

This will be what represents my fish for now. It is not much, it does not do much yet, but if we press the play button, we can see it is visible in-game.

An image showing the just created GameObject in-game.

New C# script 👨‍💻

Of course, creating a non-moving capsule, and calling it a "fish" and a "game" is pushing our imagination. But we have a vision of where to go and remember that game development is an iterative process. So let us move forward and create movement!

First up, navigate to the Project tab, and create a new folder, called Scripts. This is where we are going to store, as the name suggests, our scripts for now. Here is a fair warning though. I am a fan of structure and clarity, so we might rearrange our folder structure, as this series continues and we add more files to the project.

A visual representation of how to create a folder in your project in Unity.

Open the folder, and create a new C# script (Right-click > Create > C# Script) and name it FishMovement. From what I have seen it is best to use "PascalCase" for your class names. This means capitalising every word and removing spaces.

A visual representation on how to create a new C# script within Unity.

Next up open the file in your editor. You might be surprised to see that the default C# script is not empty. Let us quickly have a look at what is already there, and what it does.

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

public class FishMovement : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}

The first three lines use the using keyword. In C# (But there are many synonyms in other languages too) this is used to include code from the libraries in your script. For example, if we were to remove using UnityEngine; you would see that your editor would give an error on MonoBehaviour, as it does not know what it is, since it is part of the UnityEngine.

Then onto the next lines in the script. This is where a class is defined:

public class FishMovement : MonoBehaviour
{
    ...
}

You can see the name of the class matches that of the file, and that is by design. That is why it is crucial we stick to the same naming convention throughout the project.

A class in C# is like a blueprint for creating objects, which are instances of the class. Think of it like a cookie cutter. You have a cookie cutter that is a blueprint for how you want your cookies to look. When you want to make cookies, you use the cookie cutter to cut out the dough into the shape of the cookie. The cookies are the objects, and the cookie cutter is the class.

A class defines the properties and behaviours of said class. If we are talking cookies, you can have a class called ChocolateChipCookie , with an attribute size and chocolateChipsAmount, and a method called Bake and Eat.

However, there is more to the code above than just a class definition. I am referring to : MonoBehaviour. The colon in C# indicates inheritance. Simply put, it is a way of creating a new class based on an existing class.

In our cookie example, the class definition could be public class ChocolateChip : Cookie. This would mean that there are two classes, ChocolateChip (The so-called derived class) and Cookie (The so-called base class). The derived class inherits all the properties and methods from the base class but can add its own in addition. It is probably best to show you:

public class Cookie
{
    int size;
    void Bake()
    {
        ...
    }

    void Eat()
    {
        ...
    }
}

public class ChocolateChip : Cookie
{
    int chocolateChipsAmount;
    void AddChocolateChips()
    {
        ...
    }
}

Here we have the base class Cookie, with some methods that apply to all cookies in this example, namely Bake and Eat. Imagine we later add another cookie, for example, oatmeal cookies. These will also need to be baked and eaten. If we derive that class from the base cookie, we have access to those methods.

However, we can not simply dump everything on the base class and call it a day. In the example above, you can see I defined an attribute on the chocolate chip cookie, called ChocolateChipAmount. This attribute would not make sense on an oatmeal cookie, therefore it is defined on the derived class instead.

So to summarise, the colon indicates inheritance, where the derived class inherits the attributes and methods from the base class.

Let us continue with the next lines in our script, which are method definitions.

// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}

Luckily for us, Unity has added comments above the methods. They are marked by a double slash(//) if the comment is a single-line comment. However, sometimes they are bigger and then they will look like this:

/*
This is how you would typically do multi-line comments in C#.
Some people prefer to start the comment on the line where the
slash asterix is placed but its personal preference really.
I think this looks cleaner :)
*/
void ExampleMethod()
{

}

But back to the method definition. As you can see, methods names are also in PascalCase. In front of the method name is the return type. This can be many things, but here, it returns nothing, so we preface the method name with void.

Start and Update are inherited form MonoBehaviour as well, and can also be found in the Unity Documentation, but the comments in the script do a well enough job to describe what they do for now.

Time to move🏊‍♂️

When I started working on the movement of fish, I noticed that I quickly wanted to include too many different things. I was already thinking about random movement, pauses, etc. But then I remembered we are just getting started and learning.

Here are the changes I made to the script, I like to show the result first, and then break down what I changed and why.

using UnityEngine;

public class FishMovement : MonoBehaviour
{
    private float minimumMovement = -2.0f;
    private float maximumMovement = 2.0f;


    // Update is called once per frame
    private void Update()
    {
        transform.position = new Vector3(
            Mathf.PingPong(Time.time * 2, maximumMovement - minimumMovement) + minimumMovement,
            transform.position.y,
            transform.position.z
        );
    }
}

Let us work from the top down. You have noticed I removed the first two lines. Those were including libraries that I do not use in this script at the moment, therefore are not necessary. Visual Studio greys out the library names to indicate they are unused which is a nice notification. Depending on your editor you might be notified differently or not at all.

Next, you will see I have added two attributes: minimumMovementand maximumMovement. They are both Floats, which means they are decimal numbers. However, it is good to know that there are different types of decimal numbers in C#. For me, the Float will suffice. Float values are always assigned with an f behind it. In the script above I could also choose to use the values -2f and 2f and the result would be the same. This is again, personal preference.

They are both labelled private which means that only the instance of this class itself can interact with the attributes directly. I am certain I will touch upon this later in the series, to give more in-depth information about it, when that is relevant.

Then, you see I have removed the Start() method. This is simply because I had nothing to put in there for this simple script. I assigned values to the attributes when I defined them. You can also define attributes without a value, and then use the Start() method to populate them in runtime.

Last up is the Update() method. And this can be a little confusing if you are just starting so I will explain it as best I can. In the Update() method (Which runs once every frame) we are setting the transform.position. Which refers to "The world space position of the Transform", according to the Unity docs. Every GameObject has a transform, which defines where in the world that object is. So changing that in the Update() method means that it will move every frame.

So what am I setting this to? Well, the tranform.position expects a Vector3, namely the values for the X, Y and Z coordinates of the GameObject. So in the Update() method I create a new Vector3 via the new Vector3() method. This method takes 3 arguments, and you guessed it, these represent the 3 coordinates. Now for this example, I am not changing Y and Z, so I will just use the transform.position.y and transform.position.z to keep them the same. But for the X axis, I did something different.

Mathf.PingPong(Time.time * 2, maximumMovement - minimumMovement) + minimumMovement to be exact. Looking at the Unity docs for Mathf.PingPong we can read that the method returns a value that will increment and decrement between the value 0 and the given length (Which is the second variable in the method call).

So basically, I gave it a self-incrementing value like Time.time for how often/fast it happens, as they suggested in their docs, and used the maximum and minimum to define the boundaries. Then we add the minimumMovement to the outcome to ensure it does not go too slow.

All this together created the Vector3 to feed into the transform.location . Now all that is left to do is attach the script to the GameObject. Open up Unity Editor, select the Fish in the Hierarchy tab and drag the script into the Inspector tab.

A visual representation of attaching a C# script to a gameobject in Unity.

Once you press play at the top, you should see your fish move from left to right!

A visual representation of the game object moving from left to right after you press play in the Unity Editor.

Push changes to the cloud ☁

Now that we have made changes, it is good practice to save your progress and checkin your changes to the cloud. Open up Plastic, go to "Pending Changes" in the sidebar, make sure you include all files, write a (meaningful) checkin comment and click "Checkin".

Visualisation of the process of selecting files to checkin with a checin comment in PlasticSCM.

Congratulations 🎉

Well done! You have created your first GameObject, your first C# script and made your first checkin! This article turned out a bit longer than I anticipated, but I hope you could follow along and learn from it nonetheless.

As always, if you have questions, comments or feedback, please do so below! I am really curious if you were able to follow along, and with that what your experience is. This series is the first time for me to do technical writing, and I want to know what needs improving 🙌!

Did you find this article valuable?

Support BFranse by becoming a sponsor. Any amount is appreciated!