Chapter 3

Player Structure

A player can take just about any form—humans, aliens, animals, vehicles; designing a structure that can deal with all of them is a challenge in itself. The way that players store data, the data they need to store, the various types of movement codes and their control systems make for hundreds of different possible combinations. Dealing with a player structure in a modular way requires a little careful consideration not just for how to deal with all these different scenarios but also for how our components will need to communicate with each other and communicate with the rest of the game—for example, player objects often need to communicate with the game controller, the game controller often needs to communicate with players (to relay game states, etc.), and players may also need to interact with the environment and other objects within it.

The overall player structure for this book may be broken down into several main components, as shown in Figure 3.1.

  1. Game-specific player controller. This is a script to deal with game-specific player actions. For example, some games may require a vehicle that requires weapons, whereas some games may not. The game-specific control script will “add on” the specific extra functionality and tie together the main components to work together or communicate with other game-specific elements.
  2. Movement controller. The movement controller takes the job of moving the player around and defines the type of player we are using. For example, a vehicle-based player would have a vehicle-based movement control script that would drive the wheels, turn them, or operate extra vehicle-specific functions like a horn, headlights, etc. A human would require a code to drive a character controller. A spaceship might use physics forces to drive a spaceship.
  3. Player manager. This script is intended to be a bridge between the player controller and the user data manager. The user data manager takes care of storing player stats, such as score, lives, level, etc.
  4. Data manager. The data manager takes care of the manipulation of player data and how the data are stored and retrieved. Score and health values would be stored here, and the data manager would also provide functions for saving and loading its data.

Along with these main components, we can add some extra components to mix and match player types into different forms. A weapon control script may be attached to a human to create the player in a first-person shooter, or the same weapon control script could be added to a vehicle-type player to help turn it into a tank. An AI control script may be added to drive the player or to operate the weapons and so on.

In this chapter, we will be going through some of the main components that will be used later in the book to create a user-controlled player.

3.1 Game-Specific Player Controller

The player controller derives from a movement controller (which specializes in nothing more than movement of a particular player type) and builds on its movement functionality to provide full player logic.

The movement controllers are explained later in this book. Chapter 5 outlines three different movement controllers for three different player types: a spaceship, a humanoid player, and a wheeled vehicle.

What logic goes into the player controllers depends on the game; the example games in this book use the movement controllers from Chapter 5 and add extra functionality to them, such as combining the movement controller with the weapon controllers to make armed players or combining the AI controller to have computer controller players.

For real examples of player controllers, take a look at the example games in Chapters 10 to 13.

In this section, we build a simple player controller to move a spaceship. It derives from BaseTopDownSpaceShip.cs, which can be found in full in Chapter 5. This script also includes a player manager.

using UnityEngine;
using System.Collections;
public class SimplePlayerController : BaseTopDownSpaceShip 
{
	public BasePlayerManager myPlayerManager;
	public BaseUserManager myDataManager;
	
	public override void Start()
	{
		// tell our base class to initialize
		base.Init ();
		
		// now do our own init
		this.Init();
	}
	
	public override void Init ()
	{			
		// if a player manager is not set in the editor, let's try 				// to find one
		if(myPlayerManager==null)
			myPlayerManager= myGO.GetComponent<BasePlayerManager>();
		
		myDataManager= myPlayerManager.DataManager;
		myDataManager.SetName("Player");
		myDataManager.SetHealth(3);
		
		didInit=true;
	}
	public override void Update ()
	{
		// do the update in our base
		UpdateShip ();
		
		// don’t do anything until Init() has been run
		if(!didInit)
			return;
		
		// check to see if we're supposed to be controlling the
		// player before checking for firing
		if(!canControl)
			return;
	}
	
	public override void GetInput ()
	{
		// we're overriding the default input function to add in the
		// ability to fire
		horizontal_input= default_input.GetHorizontal();
		vertical_input= default_input.GetVertical();
	}
	
	void OnCollisionEnter(Collision collider)
	{
		// React to collisions here
	}
	
	void OnTriggerEnter(Collider other)
	{
		// React to triggers here
	}
	
	public void PlayerFinished()
	{
		// Deal with the end of the game for this player
	}
	
	public void ScoredPoints(int howMany)
	{
		myDataManager.AddScore(howMany);
	}
}

3.2 Dealing with Input

Separate scripts provide input for the movement controllers to react to. This is so they may be switched out for whatever input is required, without having to change anything within the movement control script. The movement controller remains intact, regardless of what you “feed” into it, which means you could use the exact same movement control script for the main player, for AI players, and perhaps even for remote players connected over a network—just using different inputs.

BaseInputController.cs provides a collection of common variables and functions. Other input scripts will derive from it:

public class BaseInputController : MonoBehavior
{
	// directional buttons
	public bool Up;
	public bool Down;
	public bool Left;
	public bool Right;
	
	// fire/action buttons
	public bool Fire1;

The slot-based weapon system uses the keyboard buttons from 1 to 9 to directly select which weapon slot to use. For that reason, the input system has nine Boolean variables to take input from the number keys:

	// weapon slots
	public bool Slot1;
	public bool Slot2;
	public bool Slot3;
	public bool Slot4;
	public bool Slot5;
	public bool Slot6;
	public bool Slot7;
	public bool Slot8;
	public bool Slot9;
	
	public float vert;
	public float horz;
	public bool shouldRespawn;
	
	public Vector3 TEMPVec3;
	private Vector3 zeroVector = new Vector3(0,0,0);
	

Input provides an interface to Unity’s input systems. It is used for everything from gyrometer input on mobile devices to keyboard presses. According to the Unity documentation, Input.GetAxis returns the value of the virtual axis identified by axisName. The axis names are set in Unity’s Input Manager (Edit → Project Settings → Input), although Horizontal and Vertical should already be set up by default:

	public virtual void CheckInput ()
	{	
		// override with your own code to deal with input
		horz=Input.GetAxis ("Horizontal");
		vert=Input.GetAxis ("Vertical");
	}
	
	public virtual float GetHorizontal()
	{
		// returns our cached horizontal input axis value
		return horz;
	}
	
	public virtual float GetVertical()
	{
		// returns our cached vertical input axis value
		return vert;
	}

Although the script includes a function called GetFire(), returning the value of the Boolean variable Fire1, the script does not include any code to set it. It’s just there for consistency.

	public virtual bool GetFire()
	{
		return Fire1;
	}

Again, the script contains GetRespawn() purely for protocol, with no actual functionality.

	public bool GetRespawn()
	{
		return shouldRespawn;	
	}

For convenience, BaseInputController.cs includes a simple system for returning a direction vector. The function will return a Vector3 with its x and y axes representing the levels of input:

	public virtual Vector3 GetMovementDirectionVector()
	{
		// temp vector for movement dir gets set to the value of an
		// otherwise unused vector that always has the value of 0,0,0
		TEMPVec3=zeroVector;
		
		// if we're going left or right, set the velocity vector's X
		// to our horizontal input value
		if(Left || Right)
		{
			TEMPVec3.x=horz;
		}
		
		// if we're going up or down, set the velocity vector's X to
		// our vertical input value
		if(Up || Down)
		{
			TEMPVec3.y=vert;
		}
		// return the movement vector
		return TEMPVec3;
	}
}

The default script for input is a basic keyboard setup script named Keyboard_Input.cs. It derives from BaseInputController.cs and overrides the CheckInput() and LateUpdate() functions to add a vertical axis, checking for the fire button and a respawn button set up as Fire3 in Unity’s Input Manager:

public class Keyboard_Input : BaseInputController 
{	
	public override void CheckInput ()
	{	
		// get input data from vertical and horizontal axis and
		// store them internally in vert and horz so we don't have to
		// access them every time we need to relay input data out
		vert= Input.GetAxis("Vertical");
		horz= Input.GetAxis("Horizontal");
		
		// set up some Boolean values for up, down, left and right
		Up	= (vert>0);
		Down	= (vert<0);
		Left	= (horz<0);
		Right	= (horz>0);	
		
		// get fire/action buttons
		Fire1= Input.GetButton("Fire1");
		shouldRespawn= Input.GetButton("Fire3");
	}
	
	public void LateUpdate()
	{
		// check inputs each LateUpdate() ready for the next tick
		CheckInput();
	}
}

3.3 Player Manager

The player manager script acts as a simple bridge between the player controller and the user data manager. It also creates the user data manager if one is not already attached to the player prefab.

using UnityEngine;
using System.Collections;
public class BasePlayerManager : MonoBehavior
{
	public bool didInit;
	
	// the user manager and AI controllers are publically accessible so
	// that our individual control scripts can access them easily
	public BaseUserManager DataManager;
	
	// note that we initialize on Awake in this class so that it is
	// ready for other classes to access our details when they
	// initialize on Start
	public virtual void Awake ()
	{
		didInit=false;
		Init();
	}
	
	public virtual void Init ()
	{
		// cache ref to our user manager
		DataManager= gameObject.GetComponent<BaseUserManager>();
		
		if(DataManager==null)
			DataManager= gameObject.AddComponent<BaseUserManager>();
		// do play init things in this function
		didInit= true;
	}
		
	public virtual void GameFinished()
	{
		DataManager.SetIsFinished(true);
	}
	
	public virtual void GameStart()
	{
		DataManager.SetIsFinished(false);
	}
}

3.3.1 Script Breakdown

There is nothing particularly outstanding about the start of the script—the Awake() function calls Init() after setting the didInit Boolean to false:

public class BasePlayerManager : MonoBehavior
{
	public bool didInit;
	
	// the user manager and AI controllers are publically accessible so
	// that our individual control scripts can access them easily
	public BaseUserManager DataManager;
	
	// note that we initialize on Awake in this class so that it is
	// ready for other classes to access our details when they
	// initialize on Start
	public virtual void Awake ()
	{
		didInit=false;
		Init();
	}

The Init() function does a quick check to see whether there is already an instance of BaseUserManager attached to the player gameObject using gameObject.GetComponent. If its result is null, the script uses AddComponent to attach a new instance to it. Finally, didInit is set to true:

	public virtual void Init ()
	{
		// cache ref to our user manager
		DataManager= gameObject.GetComponent<BaseUserManager>();
		
		if(DataManager==null)
		DataManager= gameObject.AddComponent<BaseUserManager>();
		
		// do play init things in this function
		didInit= true;
	}

The GameFinished() and GameStart() functions are there to pass a message to the user data manager. One good use for these might be a script that knows of the player’s gameObject and wants to tell about the state of the game. Rather than having to use a call to GetComponent to find this script, it may use gameObject.SendMessage to call a function instead. This would be slightly more efficient than having to use GetComponent and access the function directly:

	public virtual void GameFinished()
	{
		DataManager.SetIsFinished(true);
	}
	
	public virtual void GameStart()
	{
		DataManager.SetIsFinished(false);
	}
}

3.4 User Data Manager (Dealing with Player Stats Such as Health, Lives, etc.)

It is not uncommon to store player information in a game controller or similar script removed from individual player objects. This approach may be well suited to single-player games, but moving to games that have more than one player may prove to be problematic. One solution may be for the game controller to have an array of classes containing player stats, manipulating them when other functions tell it to; but why not attach the player stats to each player and make it self-sufficient?

The framework for this book uses a data manager class for each player, consisting of everything we need to store for a single player:

  1. Score
  2. High score
  3. Level
  4. Health
  5. Whether or not the player is finished (no longer active)
  6. Name of the player

Each player has the BaseUserManager.cs script attached to its gameObject, giving each player its own set of stats. The game controller or other script elsewhere in the game structure can easily search for and access a player’s information.

The user manager script looks like this:

public class BaseUserManager : MonoBehavior
{
	// gameplay specific data
	// we keep these private and provide methods to modify them
	// instead, just to prevent any accidental corruption or invalid
	// data coming in
	private int score;
	private int highScore;
	private int level;
	private int health;
	private bool isFinished;
	
	// this is the display name of the player
	public string playerName ="Anon";
		
	public virtual void GetDefaultData()
	{
		playerName="Anon";
		score=0;
		level=1;
		health=3;
		highScore=0;
		isFinished=false;
	}
	
	public string GetName()
	{
		return playerName;
	}
	
	public void SetName(string aName)
	{
		playerName=aName;
	}
	
	public int GetLevel()
	{
		return level;
	}
	
	public void SetLevel(int num)
	{
		level=num;
	}
	
	public int GetHighScore()
	{
		return highScore;
	}
		
	public int GetScore()
	{
		return score;	
	}
	
	public virtual void AddScore(int anAmount)
	{
		score+=anAmount;
	}
		
	public void LostScore(int num)
	{
		score-=num;
	}
	public void SetScore(int num)
	{
		score=num;
	}
	
	public int GetHealth()
	{
		return health;
	}
	
	public void AddHealth(int num)
	{
		health+=num;
	}
	public void ReduceHealth(int num)
	{
		health-=num;
	}
		
	public void SetHealth(int num)
	{
		health=num;
	}
	
	public bool GetIsFinished()
	{
		return isFinished;
	}
		
	public void SetIsFinished(bool aVal)
	{
		isFinished=aVal;
	}
}

3.4.1 Script Breakdown

After the variable declarations, the first thing BaseUserManager.cs contains is a function to populate the player stats with some default values, just to avoid any trouble when trying to access them.

	public virtual void GetDefaultData()
	{
		playerName="Anon";
		score=0;
		level=1;
		health=3;
		highScore=0;
		isFinished=false;
	}

The rest of the class deals with getting and setting the player data. The logic is literally just getting or setting variables, so if you need to look at it in detail, refer back to the full script at the start of this section. For clarity, here is a list of the rest of the functions:

	public string GetName()
	public void SetName(string aName)
	public int GetLevel()
	public void SetLevel(int num)
	public int GetHighScore()
	public int GetScore()
	public virtual void AddScore(int anAmount)
	public void LostScore(int num)
	public void SetScore(int num)
	public int GetHealth()
	public void AddHealth(int num)
	public void ReduceHealth(int num)
	public void SetHealth(int num)
	public bool GetIsFinished()
	public void SetIsFinished(bool aVal)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.209.201