Politehnica University Timișoara Faculty of Automation and Computers Department of Computer and Information Technology Real-Time Strategy Game System… [301657]

[anonimizat]:

Raul HAMZA

Supervisor:

Ș-l. dr. eng. Răzvan CIOARGĂ

Timișoara

2018

Table of Contents

Introduction……………………………………………………….………5

Chapter 1. Brief History…………………………………………….…….7

1.1 An overview of the genre……………………………………….…….7

1.2 Herzog Zwei…………………………………………………….…….9

1.3 Dune II………………………………………………………….……10

1.4 Warcraft: Orcs & Humans……………………………….…………..11

1.5 Age of Empires………………………………………………………12

1.6 Starcraft………………………………………………………………13

Chapter 2. The Unity Engine………………………………………….….15

2.1 GameObjects………………………………………………….…..….16

2.2 Scenes………………………………………………………..…….…17

2.3 GUI. GUI Group. Rect………………………………………….……17

2.4 Screen Coordinates……………………………………………..…….17

2.5 Vector3………………………………………………………….……18

2.6 Quaternion……………………………………………………………18

2.7 The MonoBehaviour class…………………………………………….19

2.7.1 Awake()………………………………………………………19

2.7.2 Start()…………………………………………………………19

2.7.3 Update()………………………………………………………19

2.7.4 OnGUI()………………………………………………………19

2.8 The MonoDevelop Editor……………………………………………..20

Chapter 3. [anonimizat]……………….……….21

SystemData…………………………………………………….……21

Player…………………………………………………………….….21

3.2.1 The Player Component………………………………..………21

3.2.2 UserInput………………………………………………………22

Unit Creation. Buildings………………………………………….…25

Unit Movement………………………………………………………26

Workers and Gathering Resources………………………..…………28

Heads-Up Display……………………………………………………30

3.6.1 Cursor State……………………………………………………30

3.6.2 Resource Bar………………………………………..…………32

3.6.3 Orders Bar…………………………………………..…………33

3.6.4 Pause Menu…………………………………………..………..34

Chapter 4. System Demo……………………………………………………36

Main Menu ……………………………………………………….…36

Game Map……………………………………………………………37

Chapter 5. Conclusion. ……………………………………………….……40

5.1 Achievements …………………………………………………………..40

5.2 Future Developments……………………………………………..……40

Bibliography …………………………………………………………….…41

Table of Figures

Fig. 1.1.1. Modern recreation of the original Tennis for Two setup………………7

Fig. 1.1.2. A [anonimizat]………………………..8

Fig. 1.2.1. Herzog Zwei……………………………………………………………9

Fig. 1.3.1. Dune II…………………………………………………………………10

Fig. 1.4.1. Warcraft: Orcs & Humans…………………………………………….11

Fig. 1.5.1. Age of Empires………………………………………………………..12

Fig. 1.6.1. The StarEdit Campaign Editor…………………………………………13

Fig. 2.1.1. The Unity interface…………………………………………………….15

Fig. 2.1.2. Unity System Requirements……………………………………………16

Fig. 2.4.1. Screen Coordinates…………………………………………………….17

Fig. 2.4.3. Finding origin point for UI element placed at the center of the screen..18

Fig. 2.8.1. The MonoDevelop IDE………………………………………………..20

Fig. 3.2.2.2. Possible cursor graphics…………………………………………….22

Fig. 3.6.1. The game interface…………………………………………………….30

Fig. 3.6.4. The Pause Menu (center)………………………………………………34

Fig. 4.1.1. The Main Menu………………………………………………………..36

Fig. 4.2.1. Initial conditions……………………………………………………….37

Fig. 4.2.2. [anonimizat], 2 workers and 12 tanks…………37

Fig. 4.2.3. Hierarchy view in the project editor showing all the used components.38

Introduction

I [anonimizat]. [anonimizat], I have decided that I could gain more experience within this domain by creating a system from scratch. Having more than a passing interest in the Real-Time Strategy genre (also referred to in this thesis as “RTS”) as well as the lack of any innovation surrounding the genre since the release of Starcraft II in 2010, the idea of trying to create something useful in the long-term for the genre appealed to me immensely.

Unity has been chosen for the development of this system due to its ease of access, its free availability, as well as the functionalities provided and for allowing C# for the scripting of the game logic.

This thesis contains descriptions regarding the context surrounding the creation of this system, as well as detailing aspects of the game engine used and going into detailed specifications of the developed system.

The first chapter represents a short history of strategy games and tries to find a place and context for the system within the RTS market.

The second chapter presents a short description of the Unity game engine as well as the functionalities that were used for the development of the Game System.

The third chapter goes into the details of the system by presenting the architecture of the system as well as each individual component.

The fourth chapter presents the demo provided with the system as an example of what the system is capable of at the time of this writing.

The fifth and final chapter presents the possible future features that may arise through continuing developing this system as well as presenting the conclusions of this thesis.

Chapter 1. Brief History

1.1 An overview of the genre

Since ancient history, games have been an important facet of human culture and civilization. One of the oldest games in existence, Senet, was discovered to have been played since at least 3100 BC[1].

As far back as the Roman Empire[2], simulations have been used as tools for commanders to practice battle tactics. In 1812, German and Prussian military officers were trained in warfare using a board game setup as a training tool. Called Kriegsspiel (“war game”), the board was divided into a grid much like board games are played now and armies were represented by color-coded models[3].

Games have first reached the computing medium in 1958, when Tennis for Two was developed by American physicist William Higinbotham. Built with the use of vacuum tubes and transistors and using an oscilloscope in order to display graphics, Tennis for Two featured a side view of a tennis court and allowed two players to play tennis with the help of two controllers consisting of a button and a knob. The serving player could use the knob to adjust the angle at which the ball would be launched while the button would be used to hit the ball[4].

Fig. 1.1.1. Modern recreation of the original Tennis for Two setup

Game consoles have been available commercially since the release of the Magnavox Odyssey in 1972, which over the course of its lifetime managed to sell approximately 350,000 units sold[5]. The Odyssey could only draw simple rectangle shapes. Since the system lacked any writeable memory, players were forced to keep track of scores manually.[6]

The popularity of the video game medium increased exponentially during the “arcade era”, when by the early 1980s there were an estimated number of 35 million people playing video games on arcade cabinets[7].

The real-time strategy genre is defined as a game in which the player must control vast armies in order to conquer their opponents’ armies. Typically, resource management is important as resources allow players to recruit more units, build more buildings and research upgrades for their army.

The game area is divided into a navigable playable area and the user interface which provides the player with information regarding the current state of the game.

Gameplay usually consists of the players starting with a small base and few units and expanding to as much of the map as possible while trying to remove their opponents’ forces. Managing the army requires both macromanagement (managing one’s forces and economy and long-term strategic plans) and micromanagement (managing individual units in order to gain advantages from its strengths or to avoid its weaknesses).

Fig. 1.1.2. A typical battle in an RTS game – Warcraft III

1.2 Herzog Zwei

The first strategy game that was in complete real-time (and could therefore be labelled as a Real-Time Strategy game) was Herzog Zwei. Released worldwide in 1990 for the Sega Genesis game console, it did not reach mainstream success due to its lack of marketing and early release on the system[8]. The basic premise of the game has defined the entire genre and features two commanders trying to build an army in order to invade the other’s base.

There were several gameplay elements which separate it from more modern RTS games. The player was represented as a physical commander-type unit within the game. Resource management is straightforward, with the player receiving resources at a constant rate. Building locations are fixed and are hard-coded within the level, with the player unable to build more.

The game allowed a player to play against a rudimentary AI or to face off against a human opponent.

Fig. 1.2.1. Herzog Zwei

1.3 Dune II

Dune II was released in 1992 for MS-DOS and 1993 for the Amiga computers and the Sega Genesis console. It is the blueprint that most other RTS games have tried to emulate since then. The player is not directly represented in-game as a character, but as an abstract, “god-like” being that has permanent access to his units and buildings to issue orders.

Unlike Herzog Zwei, resource management is no longer automatic. In order to gather resources to build a base and create units, the player must first create specialized units called harvesters whose sole function is the gathering of “spice” which is deposited by the unit into a Refinery-type building owned by the player.

Base building was implemented in this game as well as the concept of a technology tree: to build more technologically advanced units and buildings, the player must follow a strict build order. For example, in order to build Silos which allow the player to stockpile more resources, the player must first build a Refinery. Because the player was forced to build on a restricted area of terrain, he was forced to think strategically about the plan of his base.

The “fog of war” is another element that brought strategic complexity to the game. When starting a level, the map would be covered in darkness. By ordering his units around the dark areas, more and more of the map would be revealed to the player. This was crucial to do in order to find resource nodes and the opponent’s base.

Although playing against another human opponent was no longer possible, the game featured three similar but distinct factions with multiple differences between their armies, each with their own single-player campaign storyline. In order to prevail, a player must utilize the strengths of his chosen faction and exploit the weaknesses of his opponent.

Fig. 1.3.1. Dune II

1.4 Warcraft: Orcs & Humans

During the mid-1990s, the RTS genre expanded with the addition of several new franchises. In 1994, Blizzard Entertainment released Warcraft: Orcs & Humans, the first installment of what would later be a billion-dollar franchise [9].

It featured direct improvements over Dune II by allowing orders to be issued simultaneously to groups of four units, two resource types to manage: gold which was gathered from mines and used to buy units, and lumber which was used for buildings, gathered from forests. Elements of city planning were added when expanding bases as all the buildings in a base were required to be connected by roads.

“Specialist” units were introduced. While in Dune II combat units could only shoot projectiles, the specialist units in this game had several special abilities which could be used to gain a strategic advantage against their opponents.

The titular Orcs and Humans were the two factions that the player could choose to play as. Although similar in most regards except graphical appearance, the specialist units for both factions had completely different abilities at their disposal. To gain an advantage, players had to learn in which situations they excelled at.

Warcraft: Orcs & Humans also featured support for multiplayer matches via modem or local network connections.

Fig. 1.4.1. Warcraft: Orcs & Humans

1.5 Age of Empires

In 1997, a strategy game based on human history would be released. Developed by Ensemble Studios and published by Microsoft, Age of Empires allowed players to take command of armies selected from 12 available civilizations, sufficiently distinct as to have different strengths or weaknesses. There are four different resource types to manage.

A large amount of emphasis is placed on technological advancements. Mimicking real history, civilizations in Age of Empires can progress through multiple technological eras: from the Stone Age, to Tool Age, to Bronze Age and ending in the Iron Age. Each advancement to a new age unlocks more advanced technologies, buildings and units.

Fig. 1.5.1. Age of Empires

1.6 Starcraft

In 1998 Blizzard Entertainment released Starcraft, a game which would sell over 11 million copies by 2009[10]. The game’s popularity became so widespread that it spawned professional sports leagues, with matches being broadcast over television and thousands of fans filling stadiums during Starcraft tournaments. [11]

The game was set in the far future and featured three completely distinct races, all with their own unique units, buildings, and strategies. Unlike previous entries in the genre, the pace of the game is fast and tense, and to a slow and unprepared player, the armies are destroyed quicker than they were built.

The multiplayer services are hosted on Blizzard Entertainment’s centralized system Battle.Net which allows for quick searching and joining matches.

Another notable aspect of Starcraft was the included level editor called StarEdit. A powerful tool, it allowed the creation of fan-made custom maps for regular play but it also implemented a system of triggers which could allow imaginative fans to create custom game types with alternative win conditions.

Fig. 1.6.1. The StarEdit Campaign Editor

Chapter 2. The Unity Engine

A game engine is a software framework which allows developers to build interactive applications by providing tools such as a scripting environment for application logic, a graphics renderer for generating the visuals of the application, support for audio output, memory management and more.

The Unity engine is an game engine first released in 2005 which supports creating both 2D and 3D games. It is easy to install and offers multiple user licenses. If the revenue gained from using Unity is less than $100,000 per year, a basic option for personal use is available for free download.

Unity is easy to use for beginners as many features are automatically handled by the engine instead of requiring manual programming.

Unity allows the building of games for over 25 platforms, including Windows, Android, Macintosh, iOS and Playstation 4.

The Unity interface is easy to navigate and is configurable if needed. The middle of the screen is occupied by the Scene inspector, which shows the level being developed in real-time. The bottom part of the screen is the Asset explorer, where the game files can be seen. The left part of the screen holds the Hierarchy View, where all the objects in the level can be seen. The Object Inspector is on the right part of the screen, which details every component of a selected object in the game. Moving components from one part to another can be done with drag’n’drop.

Fig. 2.1.1. The Unity interface

For a computer to be able to run Unity and games made in Unity, the following system specifications are needed:

Fig. 2.1.2. Unity System Requirements

2.1 GameObjects

GameObjects are the fundamental building blocks of all the objects within a Unity game. Because they have no effect by themselves, their behavior is defined by attaching Components to them. The most important Component is called the Transform. This Component defines the position and scale of the GameObject within the game world, and it is impossible for a GameObject to exist without it. Unity provides a list of pre-built Components, such as RigidBody (which allows the object to interact with its environment in a physical way), Colliders (which can be used to determine the behavior of collisions of the object with other objects), Light (which allows the object to emit light), but also allows the user to define their own components using a C# scripting API. A custom object can be saved as an editable template called a prefab for later instantiation. For multiple objects of the same type, it is more practical to edit the prefab than edit each instance separately.

2.2 Scenes

Scenes represent a collection of GameObjects ordered in a coherent way to form one cohesive experience. For example, each level in a game may be represented by a scene, as well as every menu screen.

2.3 GUI. GUI Group. Rect

In order to programmatically draw on the screen, the GUI functions are used. Before drawing a call must be made to the GUI class’ BeginGroup() method in order to define the areas on the screen that are being drawn on, and Rect() can be used to define rectangles within the Group in which the drawing will be made. The origin point for drawing within a group is the origin point of the group itself and not the screen coordinates. After drawing in the selected group, it must be closed with a call to GUI.EndGroup().

2.4 Screen Coordinates

When drawing two-dimensional user interface elements, screen coordinates are used. These are represented by a cartesian system for which the origin point starts at the upper left corner of the screen. As shown in figure 2.4.1, the X coordinate of a point increases as the point travels towards the right of the screen, and the Y coordinate increases as the point travels towards the bottom.

Fig. 2.4.1 – Screen Coordinates

For compatibility with different resolution settings, interface elements must be drawn dynamically so that any change in screen size will not result in any element being drawn at a different position than intended. To avoid this, all elements are placed on the screen according to a formula which keeps them at a constant distance ratio from the screen origin point. The formula is as follows:

(2.4.2)

For example, for placing an element having the center point in the exact center of the screen, its origin point is defined as having a distance ratio of 2. A geometrical proof for this particular ratio can be seen in figure 2.4.2, where the desired point illustrated at the position of the arrow head.

Fig. 2.4.3. Finding origin point for UI element placed at the center of the screen

2.5 Vector3

A Vector3 is a structure representing three-dimensional vectors and is used in the game world to determine an object’s position within the game world.

2.6 Quaternion

Quaternions are abstract objects used by Unity in order to determine a unit’s rotation.

2.7 The MonoBehaviour class

The MonoBehavior class is provided by Unity as a way to programmatically interact with GameObjects and define new Components for them. As such, classes which represent abstract concepts are not required to inherit from this class.

The MonoBehaviour class provides several important methods used to control the flow of events: Start(), Awake(), OnGUI() and Update().

2.7.1 Awake()

This method is called by Unity on each object after every object in the scene is loaded. Because of the random and uncontrollable way that this method is called, it cannot be used to reliably send messages between classes. This method is only called once per object lifetime.

2.7.2 Start()

This method is called when the script component of the GameObject that it is attached to is enabled. This allows the programmer to fine-tune the timing interaction between classes. Start() is always called after Awake().

2.7.3 Update()

This method is called once per frame and is used to define the continuous behavior of a GameObject.

2.7.4 OnGUI()

This method is called whenever the GUI is needed to draw something. It is called multiple times per frame. Any calls to GUI must be handled within this method.

2.8 The MonoDevelop Editor

For editing C# scripts, Unity comes prepackaged with the MonoDevelop Integrated Development Environment (IDE).

The text editor is intuitive and offers the possibility of quick refactoring, lookups of symbol references within the entire codebase, multiple tabs and split screen.

Due to its open-source nature, MonoDevelop also offers extensibility by including an built-in Add-in manager to add extra functionalities. Several functionalities that available add-ins can implement are: support for version control with Git or Subversion, the WakaTime programming time tracker or a simple hex editor.

Fig. 2.8.1. The MonoDevelop IDE

Chapter 3. The Real-Time Strategy Game System

This chapter shall be used to detail the specifics of every component and algorithm used to develop this application.

As it stands, the Real-Time Strategy Game System is capable of managing players and their units as well as individual actions for each of them.

3.1 SystemData

SystemData is a static class in which we define several values and methods which might be required globally, such as the speed of the camera movement.

3.2 Player

The Player object is the class through which players interact with the game world. A Player object is defined in the Real-Time Strategy Game System’s world by Player and UserInput components, and also contains a HUD object and collections of his owned Units and Buildings.

3.2.1 The Player Component

The Player Component contains methods which manage the player’s supply of resources, such as removing or incrementing. It also contains a method for spawning a unit in the game world and adding it to the Player’s control. This method is called by a player-owned Factory building when it has finished creating the unit. This method will be further discussed in Chapter 3.3.

public void AddUnit(string unit, Vector3 spawnPoint, Quaternion rotation) {
        Units units = GetComponentInChildren< Units >();
        GameObject newUnit = (GameObject)Instantiate(SystemData.GetUnit(unit), spawnPoint, rotation);
        newUnit.transform.parent = units.transform;
    }

A player also holds a maximum of one selected unit. When a unit is selected, its characteristics are displayed on the orders bar. This will be further discussed in Chapter 3.6.3.

3.2.2 UserInput

The UserInput component checks whether the player tries to interact with the game. There are two ways to do so: with the mouse and with the Escape key. The Escape key is only used to activate / deactivate the Pause Menu, while the Mouse Input has several functions: checking for camera panning and checking for clicks.

When the player reaches the edge of the screen with the mouse cursor, the game camera will pan towards that direction. The HUD is also notified to change the cursor graphic appropriately.

Fig. 3.2.2.2. Possible cursor graphics

Checking for the camera panning is done in every frame, by checking if the cursor is within CameraEdge pixels within the screen edges. The panning speed is defined as CameraSpeed per second. If we remove the multiplication by Time.deltaTime, the speed shall be CameraSpeed per frame. The code for this method can be seen below.

private void MoveCamera() {
        float width = Screen.width;
        float height = Screen.height;
        float newCoord = SystemData.CameraSpeed * Time.deltaTime;
        float mouseX = Input.mousePosition.x;
        float mouseY = Input.mousePosition.y;
        if (mouseX > width – SystemData.CameraEdge) {
            player.hud.SetCursorState (CursorState.PanRight);
            Camera.main.transform.position = new Vector3 (Camera.main.transform.position.x + newCoord, Camera.main.transform.position.y, Camera.main.transform.position.z);
        } else if (mouseX < SystemData.CameraEdge) {
            player.hud.SetCursorState (CursorState.PanLeft);
            Camera.main.transform.position = new Vector3 (Camera.main.transform.position.x – newCoord, Camera.main.transform.position.y, Camera.main.transform.position.z);
        } else if (mouseY > height – SystemData.CameraEdge) {
            player.hud.SetCursorState (CursorState.PanUp);
            Camera.main.transform.position = new Vector3 (Camera.main.transform.position.x, Camera.main.transform.position.y, Camera.main.transform.position.z + newCoord);
        } else if (mouseY < SystemData.CameraEdge) {
            player.hud.SetCursorState (CursorState.PanDown);
            Camera.main.transform.position = new Vector3 (Camera.main.transform.position.x, Camera.main.transform.position.y, Camera.main.transform.position.z – newCoord);
        } else {
            player.hud.SetCursorState (CursorState.Main);
        }
    }

Another function of the UserInput script is to handle mouse clicks. During every frame, checks are being made if the Left or Right mouse buttons were clicked. If the left mouse button was clicked, the player has no unit selected and the player has clicked on a unit (excluding the Ground plane that the game is set on), it will be selected. If the player has a unit already selected, the unit is responsible to check if any operation is available via a call to its MouseClick() function.

private void LeftMouseClick() {
                    GameObject hitObject = FindHitObject();
                    Vector3 hitPoint = FindHitPoint();
                    if(hitObject && hitPoint != SystemData.InvalidPosition) {
                        if(player.SelectedObject) player.SelectedObject.MouseClick(hitObject, hitPoint, player);
                            else if(hitObject.name!="Ground") {
                                WorldObject worldObject = hitObject.transform.parent.GetComponent< WorldObject > ();
                                if(worldObject) {
                              //we already know the player has no selected object
                                player.SelectedObject = worldObject;
                    worldObject.SetSelection (true);
                                 }
                        }
                }
    }

If the Right mouse button is clicked, the player’s unit selection shall be cleared, as seen in the code sample below.

private void RightMouseClick() {
        if (player.SelectedObject) {
            player.SelectedObject.SetSelection (false);
            player.SelectedObject = null;
        }
    }

3.3 Unit Creation. Buildings

Buildings are defined as immobile objects which can create units. Creation is not instantaneous and varies based on each unit (values are defined in a Dictionary in the SystemData class), but multiple units can be queued at a time. Units created by the building are spawned in front of it, at a distance of 10 pixels. The unit spawn location is determined when the building is first initialized. The exact spawn point algorithm is as follows:

float spawnX = transform.forward.x  + transform.forward.x * 5;
        float spawnZ = transform.forward.z  + transform.forward.z * 5;
        spawnPoint = new Vector3 (spawnX, 0, spawnZ);

There are several actions which buildings do. To add a unit to the queue, the controlling player must have sufficient resources to buy it. If the button to create a unit is pressed and the player does not have sufficient resources, nothing happens. The building does not add the unit to the queue, and no resources are subtracted from the player. If the player has sufficient resources, they are subtracted and the unit is added to the queue.

Every second, the queue is processed. If an unit is queued to be built, the queue progress is incremented, and when it has reached the build time defined for the queued unit, the unit is created under the controlling player’s turn and the queue moves to the next unit after resetting the progress counter.

protected void ProcessBuildQueue() {
        if (buildQueue.Count > 0) {
            currentBuildProgress += Time.deltaTime * SystemData.BuildSpeed;
            if (currentBuildProgress > SystemData.unitBuildTime[buildQueue.Peek()]) {
                if (player) {
                    player.AddUnit (buildQueue.Dequeue (), spawnPoint, transform.rotation);
                    currentBuildProgress = 0;
                }
            }

        }
    }

Each particular building has its own defined list of units it can build. These values are easy to modify as they are stored in an array.

3.4 Unit Movement

When the UserInput script has detected that the player has clicked with a unit selected, that unit’s MouseClick() method is called. After a check for redundancy that the object is selected and that the user has clicked on the Ground, the unit is confirmed to move to the target point. Before starting to move, the unit first turns so that it is facing the destination (using a Quaternion to determine the rotation) . If the player has clicked within an unselectable WorldObject (such as a resource node), the unit shall stop short of its destination, otherwise it will not stop until it has reached its destination.

protected override void Update()
    {
        base.Update();
        if (rotating)
        {
            TurnToTarget();
        }
        else if (moving)
        {
            MoveToDest();
        }
    }

    public override void MouseClick(GameObject hitObject, Vector3 hitPoint, Player controller)
    {
        base.MouseClick(hitObject, hitPoint, controller);
        if (currentlySelected && hitObject.name == "Ground")
        {
            float x = hitPoint.x;
            float y = transform.position.y;
            float z = hitPoint.z;
            Vector3 vector = new Vector3(x, y, z);
            StartMove(vector);
        }
    }

public void StartMove(Vector3 destination)
    {
        destinationTarget = null;
        this.destination = destination;
        targetRotation = Quaternion.LookRotation(destination – base.transform.position);
        rotating = true;
        moving = false;
    }

    private void TurnToTarget()
    {
        base.transform.rotation = Quaternion.RotateTowards(base.transform.rotation, targetRotation, rotateSpeed);
        if (base.transform.rotation == targetRotation)
        {
            rotating = false;
            moving = true;
        }
    }

    private void MoveToDest()
    {
        base.transform.position = Vector3.MoveTowards(base.transform.position, destination, Time.deltaTime * moveSpeed);
        if ((Object)destinationTarget != (Object)null)
        {
            
            if ((double)Vector3.Distance(base.transform.position, destinationTarget.transform.position) < 1)
            {
                moving = false;
            }

        }
        else if (base.transform.position == destination)
        {
            moving = false;
        }
    }
}

3.5 Workers and Gathering Resources

Unlike other units, units of the Worker class can gather resources. A worker has several attributes related to this: the amount of resources currently carried, the type of the carried resource, the maximum capacity that he can carry, the resource node currently being harvested, the gathering speed and whether the worker is currently gathering resources or depositing them.

When the user clicks on a resource node with a selected worker, the worker moves to the center of the node. After reaching it, he starts harvesting. Resources are deducted from the harvested node and added to the worker’s capacity. Once the worker reaches the maximum capacity, the carried resources are added to the controlling player’s stockpile. The operations are done on a per-frame basis and are handled within the Worker’s Update() method as shown below:

void Update () {
        base.Update ();    
        if (harvesting) {
            if (!(this.moving)) {
                resourceCarried += currentlyMinedResource.Remove (1);
            }
            if (resourceCarried == resourceMax) {
                harvesting = false;
                depositing = true;
            }
            if (currentlyMinedResource == null) {
                harvesting = false;
            }
        } else if (depositing) {
            player.AddResource (resourceType, resourceCarried);
            resourceCarried = 0;
            depositing = false;
            // if resource is empty stop
            if (currentlyMinedResource) {
                StartHarvest (currentlyMinedResource);
            }
        }
    }

Checking if the worker was ordered to harvest from a node is done when the Worker has received a MouseClick() call from UserInput. It checks whether the object is a resource node, in which case it will move towards it and start harvesting. Otherwise it will move to the point on the ground that the player has clicked on.

public override void MouseClick(GameObject hitObject, Vector3 hitPoint, Player controller) {
        base.MouseClick (hitObject, hitPoint, controller);
            // check if the clicked object was a resource node
                Resource resource = hitObject.transform.GetComponent<Resource> ();
                if (resource) {
                    player.SelectedObject = this;
                    resourceType = resource.GetResourceType ();
                    StartHarvest (resource);
                }  else {
                StopHarvest ();
            }
    }

3.6 Heads-Up Display

The Heads-Up Display (henceforth referred to as HUD) represents a collection of user interface elements that are shown to the player. As it can be seen in Fig. 3.6.1, the game interface is split into three parts: the resource bar occupies the top of the screen, the orders bar occupies the bottom of the screen, and the playing area sits in the middle.

Fig. 3.6.1. The game interface

3.6.1 Cursor state

As previously discussed in Chapter 3.2.2, when the mouse reaches the edges of the screen and the camera starts panning, calls are made to the HUD to change the cursor graphics to pointing arrows for easier visual clarity. The graphics are stored as Texture2D values in the HUD object. To draw the custom cursors, the default Windows cursor must not be visible. The draw position of the cursor can be anywhere, so we define the GUI group as covering the entire area of the screen. The position to draw the cursor is easily determined: same position the default cursor but we must also take into consideration the screen coordinates as discussed in Chapter 2.4, otherwise the movement on the Y axis are inverted.

private void DrawMouseCursor()
    {
        Cursor.visible = false;

GUI.BeginGroup(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height));
        Rect cursorDrawPosition = GetCursorDrawPosition();
        GUI.DrawTexture(cursorDrawPosition, activeCursor);
        GUI.EndGroup();
    }

private Rect GetCursorDrawPosition()
    {
        Vector3 mousePosition = Input.mousePosition;
        float x = mousePosition.x;
        float height  = (float)Screen.height;
        Vector3 mousePosition2 = Input.mousePosition;
        float y = height – mousePosition2.y;
        return new Rect(x, y, (float)activeCursor.width, (float)activeCursor.height);
    }

Finally, other methods are free to call the SetCursorState method in order to set the cursor graphic as needed.

public void SetCursorState(CursorState newState)
    {
        activeCursorState = newState;
        switch (newState)
        {
        case CursorState.Main:
            activeCursor = mainCursor;
            break;
        case CursorState.PanUp:
            activeCursor = upCursor;
            break;
        case CursorState.PanRight:
            activeCursor = rightCursor;
            break;
        case CursorState.PanDown:
            activeCursor = downCursor;
            break;
        case CursorState.PanLeft:
            activeCursor = leftCursor;
            break;
        }
    }

3.6.2 Resource Bar

The resource bar occupies the top part of the screen and gives information regarding the player’s amount of resources.

Firstly the background of the bar is drawn. It is a rectangle occupying the top part of the screen. Every type of resource has an icon and a player has both a quantity of a resource and a limit to how many resources of that type can be gathered.

Some padding is added for aesthetic reasons and the resource icon, values and limits are drawn onto the box.

private void DrawResourceBar()
    {
        GUI.skin = resourceSkin;
        float width = (float)Screen.width;
        GUI.BeginGroup(new Rect(0f, 0f, (float)Screen.width, 40f));
        GUI.Box(new Rect(0f, 0f, (float)Screen.width, 40f), string.Empty);
        int topPadding = 5;
        double leftPadding = (double)Screen.width * 0.85;
        DrawResource(ResourceName.Money, topPadding, (int)leftPadding);
        GUI.EndGroup();
    }

    private void DrawResource(ResourceName type, int topPadding, int leftPadding)
    {
        Texture2D image = resourceImages[type];
        string text = resourceValues[type].ToString() + "/" + resourceLimits[type].ToString();
        GUI.DrawTexture(new Rect((float)leftPadding, (float)topPadding, (float)SystemData.resourceIconWidth, (float)SystemData.resourceIconHeight), image);
        GUI.Label(new Rect((float)leftPadding, (float)topPadding, 128f, 32f), text);
    }

3.6.3 Orders Bar

The Orders bar is drawn on the bottom of the screen. It contains information regarding the currently selected unit. For units, the only values drawn are its name, hit points and owner player. Buildings also contain their unit creation buttons on this bar. Two buttons can be drawn on each column. The modulus 2 operation is used to determine the row of the button, while padding is used to place sufficient space between the buttons for aesthetic reasons.

private void DrawOrdersBar()
    {
        GUI.skin = ordersSkin;
        ordersBarY = (double)Screen.height * 0.75;
        ordersBarHeight = (double)Screen.height – ordersBarY;
        actionButtonsX = (double)Screen.width * 0.25;
        GUI.BeginGroup(new Rect(0f, (float)ordersBarY, (float)Screen.width, (float)ordersBarHeight));
        GUI.Box(new Rect(0f, 0f, (float)Screen.width, (float)ordersBarHeight), string.Empty);
        if (player.SelectedObject)
        {
            string objectName = player.SelectedObject.objectName;
            if (!objectName.Equals(string.Empty))
            {
                int hitPoints = player.SelectedObject.hitPoints;
                int maxHitPoints = player.SelectedObject.maxHitPoints;
                string text = hitPoints + " / " + maxHitPoints;
                GUI.Label(new Rect(0f, 0f, 64f, 30f), objectName);
                GUI.Label(new Rect(0f, 20f, 64f, 30f), text);
                GUI.Label(new Rect(0f, 40f, 64f, 30f), player.username);
            }
            if (player.SelectedObject.IsOwnedByPlayer(player))
            {
                DrawActions(player.SelectedObject.GetActions());
                lastSelection = player.SelectedObject;
            }
        }
        GUI.EndGroup();
    }

private void DrawActions(string[] actions)
    {
        int length = actions.Length;
        int padding = SystemData.portraitIconWidth + 100;
        for (int i = 0; i < length; i++)
        {
            int column = i / 2;
            int row = i % 2;
            Rect position = new Rect((float)(padding + column * 10 + column * SystemData.portraitIconWidth), (float)(row * 10 + row * SystemData.portraitIconHeight), (float)SystemData.portraitIconWidth, (float)SystemData.portraitIconHeight);
            Texture2D buildImage = SystemData.GetBuildImage(actions[i]);
            if (GUI.Button(position, buildImage))
            {
                player.SelectedObject.PerformAction(actions[i]);
            }
        }
    }

3.6.4 Pause Menu

By pressing Escape, the player pauses the game. To stop time passing in the game while the game is paused, the time scale used by Unity can be set to 0.

Fig. 3.6.4. The Pause Menu (center)

The action buttons are stored in a string array.

private string[] actions = { "Resume Game", "Quit to Main Menu", "Exit Game" };

Using the formula discussed in Chapter 2.4 (fig. 2.4.2), we can place the menu at the exact center of the screen while keeping in mind the dynamic nature of the menu:

height = buttonHeight * actions.Length + 2*paddingY;
        width = buttonWidth + 2*paddingX;
        startX = (Screen.width – width) * 0.5;
        startY = (Screen.height – height) * 0.5;

When drawing the buttons within the menu, we can set their actions as needed in a switch statement:

foreach(string action in actions) {
            if(GUI.Button(new Rect(paddingX, paddingY + buttonHeight * System.Array.IndexOf(actions, action), buttonWidth, buttonHeight), action)) {
                switch(action) {
                case "Resume Game": {
                        Resume(); 
                        break;
                    }
                case "Quit to Main Menu": {
                        QuitToMainMenu();
                        break;
                    }
                case "Exit Game": {
                        ExitGame();
                        break;
                    }
                default: {
                        break;
                }
                }
            }
        }

Chapter 4. System Demo

The system demo was conceived as a way to showcase the Real-Time Strategy Game system and its current usability.

4.1 The Main Menu

Fig. 4.1.1 The Main Menu

The first screen that the user sees when loading the application is the main menu. The screen consists of two UI elements grouped separately: the title and a menu consisting of two buttons, labelled “Start Game” and “Exit Game” respectively. Drawing the main menu is similar to drawing the pause menu as discussed in Chapter 3.6.4. Upon pressing the Start Game button, the player will be loaded into the game map proper.

4.2. Game Map

Fig. 4.2.1. Initial conditions

The sample map provided with the demo consists of a Player with $150 in resources, a Factory which can build Tanks and Workers, and 3 resource nodes of $1000 each. As the cost of a Tank is $250, more than the player starts with, the only way for the player to build any tanks is to start by creating a worker then use it to gather more resources. For faster gathering, more workers can be used.

Fig. 4.2.2. Final state, with all resources mined, 2 workers and 12 tanks

Because of the data-driven approach of the system, creating this map in the Unity Editor was possible in an accessible way. Several assets were created beforehand: the prefabs for the Tank, Worker, Factory and ResourceNode, and their corresponding textures and portrait icons. The Player is created as an Empty Object, and the scripts handling the Player and UserInput classes are attached. The following Objects must be added as children for the Player: HUD, Units and Buildings, all with their own scripts. For the Pause Menu to work when the user presses Escape, an Empty Object under HUD must be placed under HUD with the PauseMenu script attached.

The ObjectList object is another Empty Object with its own script, into which we add the prefabs for all available WorldObject types in our game.

Finally, the Factory is added under the player’s control and several resource nodes are placed.

Fig. 4.2.3. Hierarchy view in the project editor showing all the used components

Chapter 5. Conclusion

5.1 Achievements

With the development of this system, I have managed to build almost from scratch a basic, bare-bones version of an RTS game while lacking the resources that most game development studios have at their disposal.

The algorithms in place will require few adjustments to accommodate future features as I have mostly used a data-driven approach to preserve the system’s extensibility.

My hopes for the system is that when it will have reached sufficient maturity to be released as a free toolkit for creating RTS games much in the same vein as other toolkits do for other genres, such as Adventure Game Studio for graphical point-and-click adventure games or RPG Maker for Role-Playing Games.

5.2 Future Developments

Because the system presented in this Bachelor’s Thesis will undergo future development, there are several critical points to implement which will improve this system:

Several features that have been staples of the RTS genre are lacking from this system. These include:

Networking play

Combat

Artificial Intelligence

Different types of terrain

Fog of war

Minimap

A single-player campaign spanning multiple Scenes against AI-controlled enemies, in order to showcase all the available features of the system

A more readily available way to edit certain values such as unit names, their cost, or other attributes so that they are loaded at run-time instead of compile-time. This could be easily handled by storing this value into a text file which can be easily readable and editable as a human and for which a reader class can be easily implemented, such as .json or .xml.

Bibliography

[1] Peter A. Piccione, “In Search of the Meaning of Senet”, [Online: https://web.archive.org/web/20080918080211/http://www.gamesmuseum.uwaterloo.ca/Archives/Piccione/index.html ]

[2] Roger Smith, “The Long History of Gaming in Military Training”, [Online: http://www.dtic.mil/dtic/tr/fulltext/u2/a550307.pdf ]

[3] Laurence Kay, “Kriegsspiel – The 19th Century War Game That Changed History”, [Online: https://militaryhistorynow.com/2018/05/01/kriegsspiel-the-19th-century-war-game-that-changed-history/ ]

[4] Alexander Smith, “Tennis Anyone?”, They Create Worlds, [Online: https://web.archive.org/web/20151225121214/https://videogamehistorian.wordpress.com/2014/01/28/tennis-anyone ]

[5] The National Museum of American History, “Magnavox Odyssey Video Game Unit, 1972” , [Online: http://americanhistory.si.edu/collections/search/object/nmah_1302004 ]

[6] D.S. Cohen, “Magnavox Odyssey – The First Gaming Console”, Lifewire. [Online: https://www.lifewire.com/magnavox-odyssey-the-first-gaming-console-729587 ]

[7] Ellen Wojahn, “The General Mills/Parker Brothers merger: playing by different rules”, Beard Books. p. 120

[8] Scott Sharkey, “Hail to the Duke”, 1Up, [Online: https://archive.is/20120604112047/http://www.1up.com/features/essential-50-herzog-zwei ]

[9] Jonathan Leack, “World of Warcraft Leads Industry With Nearly $10 Billion In Revenue”, GameRevolution, [Online: http://www.gamerevolution.com/features/13510-world-of-warcraft-leads-industry-with-nearly-10-billion-in-revenue]

[10] Kris Graft, “Blizzard Confirms One "Frontline Release" for '09”, [Online: https://web.archive.org/web/20100825135123/http://www.next-gen.biz/news/blizzard-confirms-one-frontline-release-09 ]

[11] Brian Ashcraft, “Why Is Starcraft So Popular In Korea”, Kotaku, [Online: https://kotaku.com/5595262/why-is-starcraft-so-popular-in-korea ]

Similar Posts