前两天一直在看书,看视频,今天总结一下代码哈~~~
官网Roguelike案例。
Loader,用于开始游戏,负责调用GameManager的单例,SoundManager的单例;
using UnityEngine;
using System.Collections;
public class Loader : MonoBehaviour
{
public GameObject gameManager; //GameManager prefab to instantiate.
public GameObject soundManager; //SoundManager prefab to instantiate.
void Awake()
{
//Check if a GameManager has already been assigned to static variable GameManager.instance or if it's still null
if (GameManager.instance == null)
//Instantiate gameManager prefab
Instantiate(gameManager);
//Check if a SoundManager has already been assigned to static variable GameManager.instance or if it's still null
if (SoundManager.instance == null)
//Instantiate SoundManager prefab
Instantiate(soundManager);
}
}
GameManager 在这里调用了BoardManager。并且在这里实现Player和Enemy的调用和管理;
using UnityEngine;
using System.Collections;
using System.Collections.Generic; //Allows us to use Lists.
using UnityEngine.UI; //Allows us to use UI.
public class GameManager : MonoBehaviour
{
public float levelStartDelay = 2f; //Time to wait before starting level, in seconds.
public float turnDelay = 0.1f; //Delay between each Player turn.
public int playerFoodPoints = 100; //Starting value for Player food points.
public static GameManager instance = null; //Static instance of GameManager which allows it to be accessed by any other script.
[HideInInspector] public bool playersTurn = true; //Boolean to check if it's players turn, hidden in inspector but public.
private Text levelText; //Text to display current level number.
private GameObject levelImage; //Image to block out level as levels are being set up, background for levelText.
private BoardManager boardScript; //Store a reference to our BoardManager which will set up the level.
private int level = 1; //Current level number, expressed in game as "Day 1".
private List<Enemy> enemies; //List of all Enemy units, used to issue them move commands.
private bool enemiesMoving; //Boolean to check if enemies are moving.
private bool doingSetup = true; //Boolean to check if we're setting up board, prevent Player from moving during setup.
//Awake is always called before any Start functions
void Awake()
{
//Check if instance already exists
if (instance == null)
//if not, set instance to this
instance = this;
//If instance already exists and it's not this:
else if (instance != this)
//Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
Destroy(gameObject);
//Sets this to not be destroyed when reloading scene
DontDestroyOnLoad(gameObject);
//Assign enemies to a new List of Enemy objects.
enemies = new List<Enemy>();
//Get a component reference to the attached BoardManager script
boardScript = GetComponent<BoardManager>();
//Call the InitGame function to initialize the first level
InitGame();
}
//This is called each time a scene is loaded.
void OnLevelWasLoaded(int index)
{
//Add one to our level number.
level++;
//Call InitGame to initialize our level.
InitGame();
}
//Initializes the game for each level.
void InitGame()
{
//While doingSetup is true the player can't move, prevent player from moving while title card is up.
doingSetup = true;
//Get a reference to our image LevelImage by finding it by name.
levelImage = GameObject.Find("LevelImage");
//Get a reference to our text LevelText's text component by finding it by name and calling GetComponent.
levelText = GameObject.Find("LevelText").GetComponent<Text>();
//Set the text of levelText to the string "Day" and append the current level number.
levelText.text = "Day " + level;
//Set levelImage to active blocking player's view of the game board during setup.
levelImage.SetActive(true);
//Call the HideLevelImage function with a delay in seconds of levelStartDelay.
Invoke("HideLevelImage", levelStartDelay);
//Clear any Enemy objects in our List to prepare for next level.
enemies.Clear();
//Call the SetupScene function of the BoardManager script, pass it current level number.
boardScript.SetupScene(level);
}
//Hides black image used between levels
void HideLevelImage()
{
//Disable the levelImage gameObject.
levelImage.SetActive(false);
//Set doingSetup to false allowing player to move again.
doingSetup = false;
}
//Update is called every frame.
void Update()
{
//Check that playersTurn or enemiesMoving or doingSetup are not currently true.
if (playersTurn || enemiesMoving || doingSetup)
//If any of these are true, return and do not start MoveEnemies.
return;
//Start moving enemies.
StartCoroutine(MoveEnemies());
}
//Call this to add the passed in Enemy to the List of Enemy objects.
public void AddEnemyToList(Enemy script)
{
//Add Enemy to List enemies.
enemies.Add(script);
}
//GameOver is called when the player reaches 0 food points
public void GameOver()
{
//Set levelText to display number of levels passed and game over message
levelText.text = "After " + level + " days, you starved.";
//Enable black background image gameObject.
levelImage.SetActive(true);
//Disable this GameManager.
enabled = false;
}
//Coroutine to move enemies in sequence.
IEnumerator MoveEnemies()
{
//While enemiesMoving is true player is unable to move.
enemiesMoving = true;
//Wait for turnDelay seconds, defaults to .1 (100 ms).
yield return new WaitForSeconds(turnDelay);
//If there are no enemies spawned (IE in first level):
if (enemies.Count == 0)
{
//Wait for turnDelay seconds between moves, replaces delay caused by enemies moving when there are none.
yield return new WaitForSeconds(turnDelay);
}
//Loop through List of Enemy objects.
for (int i = 0; i < enemies.Count; i++)
{
//Call the MoveEnemy function of Enemy at index i in the enemies List.
enemies[i].MoveEnemy();
//Wait for Enemy's moveTime before moving next Enemy,
yield return new WaitForSeconds(enemies[i].moveTime);
}
//Once Enemies are done moving, set playersTurn to true so player can move.
playersTurn = true;
//Enemies are done moving, set enemiesMoving to false.
enemiesMoving = false;
}
}
BoardManager 地图逻辑;
using UnityEngine;
using System;
using System.Collections.Generic; //Allows us to use Lists.允许我们使用列表;
using Random = UnityEngine.Random; //Tells Random to use the Unity Engine random number generator.告诉编译器Random要用UnityEngine的Random;
[System.Serializable] //Using Serializable allows us to embed a class with sub properties in the inspector.使得变量可以被Inspector界面获取到;
//namespace Completed
//{
public class BoardManager : MonoBehaviour
{
public class Count
{
public int minimum;
public int maximum;
public Count(int min, int max) //构造函数;
{
minimum = min;
maximum = max;
}
}
public int columns = 8;
public int rows = 8;
public Count wallCount = new Count(5,9); //wall的随机范围;
public Count foodCount = new Count(1,5); //food的随机范围;
public GameObject exit; //Prefab to spawn for exit.
public GameObject[] floorTiles; //Array of floor prefabs.
public GameObject[] wallTiles; //Array of wall prefabs.
public GameObject[] foodTiles; //Array of food prefabs.
public GameObject[] enemyTiles; //Array of enemy prefabs.
public GameObject[] outerWallTiles; //Array of outer tile prefabs.
private Transform boardHolder; //A variable to store a reference to the transform of our Board object.用于备份当前BoardObject的transform 的标记;
private List <Vector3> gridPositions = new List <Vector3> (); //A list of possible locations to place tiles.地图瓦片的坐标列表;
//Clears our list gridPositions and prepares it to generate a new board.
void InitialiseList()
{
//Clear our list gridPositions.
gridPositions.Clear();
//Loop through x axis (columns).
for (int x = 1; x < columns - 1; x++)
{
//Within each column, loop through y axis (rows).
for (int y = 1; y < rows - 1; y++)
{
//At each index add a new Vector3 to our list with the x and y coordinates of that position.
gridPositions.Add(new Vector3(x, y, 0f));
}
}
}
//Sets up the outer walls and floor (background) of the game board.
void BoardSetup()
{
//Instantiate Board and set boardHolder to its transform.
boardHolder = new GameObject("Board").transform;
//Loop along x axis, starting from -1 (to fill corner) with floor or outerwall edge tiles.
for (int x = -1; x < columns + 1; x++)
{
//Loop along y axis, starting from -1 to place floor or outerwall tiles.
for (int y = -1; y < rows + 1; y++)
{
//Choose a random tile from our array of floor tile prefabs and prepare to instantiate it.
GameObject toInstantiate = floorTiles[Random.Range(0, floorTiles.Length)];
//Check if we current position is at board edge, if so choose a random outer wall prefab from our array of outer wall tiles.
if (x == -1 || x == columns || y == -1 || y == rows)
toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
//Instantiate the GameObject instance using the prefab chosen for toInstantiate at the Vector3 corresponding to current grid position in loop, cast it to GameObject.
GameObject instance =
Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
//Set the parent of our newly instantiated object instance to boardHolder, this is just organizational to avoid cluttering hierarchy.使所有新建测实例都在一个父级下;
instance.transform.SetParent(boardHolder);
}
}
}
//RandomPosition returns a random position from our list gridPositions.
Vector3 RandomPosition()
{
//Declare an integer randomIndex, set it's value to a random number between 0 and the count of items in our List gridPositions.
int randomIndex = Random.Range(0, gridPositions.Count);
//Declare a variable of type Vector3 called randomPosition, set it's value to the entry at randomIndex from our List gridPositions.
Vector3 randomPosition = gridPositions[randomIndex];
//Remove the entry at randomIndex from the list so that it can't be re-used.用一个,删一个,防止再次使用;
gridPositions.RemoveAt(randomIndex);
//Return the randomly selected Vector3 position.
return randomPosition;
}
//LayoutObjectAtRandom accepts an array of game objects to choose from along with a minimum and maximum range for the number of objects to create.
void LayoutObjectAtRandom(GameObject[] tileArray, int minimum, int maximum)
{
//Choose a random number of objects to instantiate within the minimum and maximum limits
int objectCount = Random.Range(minimum, maximum + 1);
//Instantiate objects until the randomly chosen limit objectCount is reached
for (int i = 0; i < objectCount; i++)
{
//Choose a position for randomPosition by getting a random position from our list of available Vector3s stored in gridPosition
Vector3 randomPosition = RandomPosition();
//Choose a random tile from tileArray and assign it to tileChoice
GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
//Instantiate tileChoice at the position returned by RandomPosition with no change in rotation
Instantiate(tileChoice, randomPosition, Quaternion.identity);
}
}
//SetupScene initializes our level and calls the previous functions to lay out the game board
public void SetupScene(int level)
{
//Creates the outer walls and floor.
BoardSetup();
//Reset our list of gridpositions.
InitialiseList();
//Instantiate a random number of wall tiles based on minimum and maximum, at randomized positions.
LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
//Instantiate a random number of food tiles based on minimum and maximum, at randomized positions.
LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
//Determine number of enemies based on current level number, based on a logarithmic progression
int enemyCount = (int)Mathf.Log(level, 2f);
//Instantiate a random number of enemies based on minimum and maximum, at randomized positions.
LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
//Instantiate the exit tile in the upper right hand corner of our game board
Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
}
}
MovingObject 移动逻辑,包括Player和Enemy的移动;
using UnityEngine;
using System.Collections;
//The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
public abstract class MovingObject : MonoBehaviour
{
public float moveTime = 0.1f; //Time it will take object to move, in seconds.
public LayerMask blockingLayer; //Layer on which collision will be checked.//碰撞检测层;
private BoxCollider2D boxCollider; //The BoxCollider2D component attached to this object.
private Rigidbody2D rb2D; //The Rigidbody2D component attached to this object.
private float inverseMoveTime; //Used to make movement more efficient.使运动更高效;
//Protected, virtual functions can be overridden by inheriting classes.
protected virtual void Start()
{
//Get a component reference to this object's BoxCollider2D
boxCollider = GetComponent<BoxCollider2D>();
//Get a component reference to this object's Rigidbody2D
rb2D = GetComponent<Rigidbody2D>();
//By storing the reciprocal of the move time we can use it by multiplying instead of dividing, this is more efficient.
inverseMoveTime = 1f / moveTime;
}
//Move returns true if it is able to move and false if not.
//Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
protected bool Move(int xDir, int yDir, out RaycastHit2D hit)
{
//Store start position to move from, based on objects current transform position.
Vector2 start = transform.position; //隐式转换;
// Calculate end position based on the direction parameters passed in when calling Move.
Vector2 end = start + new Vector2(xDir, yDir);
//Disable the boxCollider so that linecast doesn't hit this object's own collider.
boxCollider.enabled = false;
//Cast a line from start point to end point checking collision on blockingLayer.
hit = Physics2D.Linecast(start, end, blockingLayer); //是判断一条直线的碰撞检测吗?;
//Re-enable boxCollider after linecast
boxCollider.enabled = true;
//Check if anything was hit
if (hit.transform == null)
{
//If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
StartCoroutine(SmoothMovement(end));
//Return true to say that Move was successful
return true;
}
//If something was hit, return false, Move was unsuccesful.
return false;
}
//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
protected IEnumerator SmoothMovement(Vector3 end)
{
//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter.
//Square magnitude is used instead of magnitude because it's computationally cheaper.
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;//sqrMagnitude效率更高;
//向量的长度是用勾股定理计算出来,计算机计算两次方和开根的运算量比加减法要费时的多。所以如果是想比较两个向量的长度,用sqrMagnitude可以快出很多;
//While that distance is greater than a very small amount (Epsilon, almost zero):
while (sqrRemainingDistance > float.Epsilon) //float.Epsilon无限趋于零的正浮点数;
{
//Find a new position proportionally closer to the end, based on the moveTime
Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);//MoveTowards是在一条直线上移动一个点;
//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
rb2D.MovePosition(newPostion);
//Recalculate the remaining distance after moving.
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//Return and loop until sqrRemainingDistance is close enough to zero to end the function
yield return null;
}
}
//The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
//AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
protected virtual void AttemptMove<T>(int xDir, int yDir)
where T : Component
{
//Hit will store whatever our linecast hits when Move is called.
RaycastHit2D hit;
//Set canMove to true if Move was successful, false if failed.
bool canMove = Move(xDir, yDir, out hit);
//Check if nothing was hit by linecast
if (hit.transform == null)
//If nothing was hit, return and don't execute further code.
return;
//Get a component reference to the component of type T attached to the object that was hit
T hitComponent = hit.transform.GetComponent<T>();
//If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
if (!canMove && hitComponent != null)
//Call the OnCantMove function and pass it hitComponent as a parameter.
OnCantMove(hitComponent);
}
//The abstract modifier indicates that the thing being modified has a missing or incomplete implementation.
//OnCantMove will be overriden by functions in the inheriting classes.
protected abstract void OnCantMove<T>(T component)
where T : Component;
}
Player
using UnityEngine;
using System.Collections;
using UnityEngine.UI; //Allows us to use UI.
//Player inherits from MovingObject, our base class for objects that can move, Enemy also inherits from this.
public class Player : MovingObject
{
public float restartLevelDelay = 1f; //Delay time in seconds to restart level.
public int pointsPerFood = 10; //Number of points to add to player food points when picking up a food object.
public int pointsPerSoda = 20; //Number of points to add to player food points when picking up a soda object.
public int wallDamage = 1; //How much damage a player does to a wall when chopping it.
public Text foodText; //UI Text to display current player food total.
public AudioClip moveSound1; //1 of 2 Audio clips to play when player moves.
public AudioClip moveSound2; //2 of 2 Audio clips to play when player moves.
public AudioClip eatSound1; //1 of 2 Audio clips to play when player collects a food object.
public AudioClip eatSound2; //2 of 2 Audio clips to play when player collects a food object.
public AudioClip drinkSound1; //1 of 2 Audio clips to play when player collects a soda object.
public AudioClip drinkSound2; //2 of 2 Audio clips to play when player collects a soda object.
public AudioClip gameOverSound; //Audio clip to play when player dies.
private Animator animator; //Used to store a reference to the Player's animator component.//存储Player动画的编号;
private int food; //Used to store player food points total during level.
private Vector2 touchOrigin = -Vector2.one; //Used to store location of screen touch origin for mobile controls.
//Start overrides the Start function of MovingObject
protected override void Start()
{
//Get a component reference to the Player's animator component
animator = GetComponent<Animator>();
//Get the current food point total stored in GameManager.instance between levels.
food = GameManager.instance.playerFoodPoints;
//Set the foodText to reflect the current player food total.
foodText.text = "Food: " + food;
//Call the Start function of the MovingObject base class.
base.Start();
}
//This function is called when the behaviour becomes disabled or inactive.当behavior禁用或无效时调用函数;
private void OnDisable()
{
//When Player object is disabled, store the current local food total in the GameManager so it can be re-loaded in next level.
GameManager.instance.playerFoodPoints = food;
}
private void Update()
{
//If it's not the player's turn, exit the function.
if (!GameManager.instance.playersTurn) return;
int horizontal = 0; //Used to store the horizontal move direction.
int vertical = 0; //Used to store the vertical move direction.
//Check if we are running either in the Unity editor or in a standalone build.
#if UNITY_STANDALONE || UNITY_WEBPLAYER
//Get input from the input manager, round it to an integer and store in horizontal to set x axis move direction
horizontal = (int)(Input.GetAxisRaw("Horizontal"));
//Get input from the input manager, round it to an integer and store in vertical to set y axis move direction
vertical = (int)(Input.GetAxisRaw("Vertical"));
//Check if moving horizontally, if so set vertical to zero.
if (horizontal != 0)
{
vertical = 0;
}
//Check if we are running on iOS, Android, Windows Phone 8 or Unity iPhone
#elif UNITY_IOS || UNITY_ANDROID || UNITY_WP8 || UNITY_IPHONE
//Check if Input has registered more than zero touches
if (Input.touchCount > 0)
{
//Store the first touch detected.
Touch myTouch = Input.touches[0];
//Check if the phase of that touch equals Began
if (myTouch.phase == TouchPhase.Began)
{
//If so, set touchOrigin to the position of that touch
touchOrigin = myTouch.position;
}
//If the touch phase is not Began, and instead is equal to Ended and the x of touchOrigin is greater or equal to zero:
else if (myTouch.phase == TouchPhase.Ended && touchOrigin.x >= 0)
{
//Set touchEnd to equal the position of this touch
Vector2 touchEnd = myTouch.position;
//Calculate the difference between the beginning and end of the touch on the x axis.
float x = touchEnd.x - touchOrigin.x;
//Calculate the difference between the beginning and end of the touch on the y axis.
float y = touchEnd.y - touchOrigin.y;
//Set touchOrigin.x to -1 so that our else if statement will evaluate false and not repeat immediately.
touchOrigin.x = -1;
//Check if the difference along the x axis is greater than the difference along the y axis.
if (Mathf.Abs(x) > Mathf.Abs(y))
//If x is greater than zero, set horizontal to 1, otherwise set it to -1
horizontal = x > 0 ? 1 : -1;
else
//If y is greater than zero, set horizontal to 1, otherwise set it to -1
vertical = y > 0 ? 1 : -1;
}
}
#endif //End of mobile platform dependendent compilation section started above with #elif
//Check if we have a non-zero value for horizontal or vertical
if (horizontal != 0 || vertical != 0)
{
//Call AttemptMove passing in the generic parameter Wall, since that is what Player may interact with if they encounter one (by attacking it)
//Pass in horizontal and vertical as parameters to specify the direction to move Player in.
AttemptMove<Wall>(horizontal, vertical);
}
}
//AttemptMove overrides the AttemptMove function in the base class MovingObject
//AttemptMove takes a generic parameter T which for Player will be of the type Wall, it also takes integers for x and y direction to move in.
protected override void AttemptMove<T>(int xDir, int yDir)
{
//Every time player moves, subtract from food points total.
food--;
//Update food text display to reflect current score.
foodText.text = "Food: " + food;
//Call the AttemptMove method of the base class, passing in the component T (in this case Wall) and x and y direction to move.
base.AttemptMove<T>(xDir, yDir);
//Hit allows us to reference the result of the Linecast done in Move.
RaycastHit2D hit;
//If Move returns true, meaning Player was able to move into an empty space.
if (Move(xDir, yDir, out hit))
{
//Call RandomizeSfx of SoundManager to play the move sound, passing in two audio clips to choose from.
SoundManager.instance.RandomizeSfx(moveSound1, moveSound2);
}
//Since the player has moved and lost food points, check if the game has ended.
CheckIfGameOver();
//Set the playersTurn boolean of GameManager to false now that players turn is over.
GameManager.instance.playersTurn = false;
}
//OnCantMove overrides the abstract function OnCantMove in MovingObject.
//It takes a generic parameter T which in the case of Player is a Wall which the player can attack and destroy.
protected override void OnCantMove<T>(T component)
{
//Set hitWall to equal the component passed in as a parameter.
Wall hitWall = component as Wall;
//Call the DamageWall function of the Wall we are hitting.
hitWall.DamageWall(wallDamage);
//Set the attack trigger of the player's animation controller in order to play the player's attack animation.
animator.SetTrigger("playerChop");
}
//OnTriggerEnter2D is sent when another object enters a trigger collider attached to this object (2D physics only).
private void OnTriggerEnter2D(Collider2D other)
{
//Check if the tag of the trigger collided with is Exit.
if (other.tag == "Exit")
{
//Invoke the Restart function to start the next level with a delay of restartLevelDelay (default 1 second).
Invoke("Restart", restartLevelDelay);
//Disable the player object since level is over.
enabled = false;
}
//Check if the tag of the trigger collided with is Food.
else if (other.tag == "Food")
{
//Add pointsPerFood to the players current food total.
food += pointsPerFood;
//Update foodText to represent current total and notify player that they gained points
foodText.text = "+" + pointsPerFood + " Food: " + food;
//Call the RandomizeSfx function of SoundManager and pass in two eating sounds to choose between to play the eating sound effect.
SoundManager.instance.RandomizeSfx(eatSound1, eatSound2);
//Disable the food object the player collided with.
other.gameObject.SetActive(false);
}
//Check if the tag of the trigger collided with is Soda.
else if (other.tag == "Soda")
{
//Add pointsPerSoda to players food points total
food += pointsPerSoda;
//Update foodText to represent current total and notify player that they gained points
foodText.text = "+" + pointsPerSoda + " Food: " + food;
//Call the RandomizeSfx function of SoundManager and pass in two drinking sounds to choose between to play the drinking sound effect.
SoundManager.instance.RandomizeSfx(drinkSound1, drinkSound2);
//Disable the soda object the player collided with.
other.gameObject.SetActive(false);
}
}
//Restart reloads the scene when called.
private void Restart()
{
//Load the last scene loaded, in this case Main, the only scene in the game.传入最后一个场景,在本例中只有一个场景---Main;
Application.LoadLevel(Application.loadedLevel);
}
//LoseFood is called when an enemy attacks the player.
//It takes a parameter loss which specifies how many points to lose.传入loss参数,用于具体说明失去多少分;
public void LoseFood(int loss)
{
//Set the trigger for the player animator to transition to the playerHit animation.
animator.SetTrigger("playerHit");
//Subtract lost food points from the players total.
food -= loss;
//Update the food display with the new total.
foodText.text = "-" + loss + " Food: " + food;
//Check to see if game has ended.
CheckIfGameOver();
}
//CheckIfGameOver checks if the player is out of food points and if so, ends the game.
private void CheckIfGameOver()
{
//Check if food point total is less than or equal to zero.
if (food <= 0)
{
//Call the PlaySingle function of SoundManager and pass it the gameOverSound as the audio clip to play.
SoundManager.instance.PlaySingle(gameOverSound);
//Stop the background music.
SoundManager.instance.musicSource.Stop();
//Call the GameOver function of GameManager.
GameManager.instance.GameOver();
}
}
}
Enemy
using UnityEngine;
using System.Collections;
//Enemy inherits from MovingObject, our base class for objects that can move, Player also inherits from this.
public class Enemy : MovingObject
{
public int playerDamage; //The amount of food points to subtract from the player when attacking.
public AudioClip attackSound1; //First of two audio clips to play when attacking the player.
public AudioClip attackSound2; //Second of two audio clips to play when attacking the player.
private Animator animator; //Variable of type Animator to store a reference to the enemy's Animator component.
private Transform target; //Transform to attempt to move toward each turn.
private bool skipMove; //Boolean to determine whether or not enemy should skip a turn or move this turn.
//Start overrides the virtual Start function of the base class.
protected override void Start()
{
//Register this enemy with our instance of GameManager by adding it to a list of Enemy objects.
//This allows the GameManager to issue movement commands.
GameManager.instance.AddEnemyToList(this);
//Get and store a reference to the attached Animator component.
animator = GetComponent<Animator>();
//Find the Player GameObject using it's tag and store a reference to its transform component.
target = GameObject.FindGameObjectWithTag("Player").transform;
//Call the start function of our base class MovingObject.
base.Start();
}
//Override the AttemptMove function of MovingObject to include functionality needed for Enemy to skip turns.
//See comments in MovingObject for more on how base AttemptMove function works.
protected override void AttemptMove<T>(int xDir, int yDir)
{
//Check if skipMove is true, if so set it to false and skip this turn.
if (skipMove)
{
skipMove = false;
return;
}
//Call the AttemptMove function from MovingObject.
base.AttemptMove<T>(xDir, yDir);
//Now that Enemy has moved, set skipMove to true to skip next move.
skipMove = true;
}
//MoveEnemy is called by the GameManger each turn to tell each Enemy to try to move towards the player.
public void MoveEnemy()
{
//Declare variables for X and Y axis move directions, these range from -1 to 1.
//These values allow us to choose between the cardinal directions: up, down, left and right.
int xDir = 0;
int yDir = 0;
//If the difference in positions is approximately zero (Epsilon) do the following:
if (Mathf.Abs(target.position.x - transform.position.x) < float.Epsilon)
//If the y coordinate of the target's (player) position is greater than the y coordinate of this enemy's position set y direction 1 (to move up). If not, set it to -1 (to move down).
yDir = target.position.y > transform.position.y ? 1 : -1;
//If the difference in positions is not approximately zero (Epsilon) do the following:
else
//Check if target x position is greater than enemy's x position, if so set x direction to 1 (move right), if not set to -1 (move left).
xDir = target.position.x > transform.position.x ? 1 : -1;
//Call the AttemptMove function and pass in the generic parameter Player, because Enemy is moving and expecting to potentially encounter a Player
AttemptMove<Player>(xDir, yDir);
}
//OnCantMove is called if Enemy attempts to move into a space occupied by a Player, it overrides the OnCantMove function of MovingObject
//and takes a generic parameter T which we use to pass in the component we expect to encounter, in this case Player
protected override void OnCantMove<T>(T component)
{
//Declare hitPlayer and set it to equal the encountered component.
Player hitPlayer = component as Player;
//Call the LoseFood function of hitPlayer passing it playerDamage, the amount of foodpoints to be subtracted.
hitPlayer.LoseFood(playerDamage);
//Set the attack trigger of animator to trigger Enemy attack animation.
animator.SetTrigger("enemyAttack");
//Call the RandomizeSfx function of SoundManager passing in the two audio clips to choose randomly between.
SoundManager.instance.RandomizeSfx(attackSound1, attackSound2);
}
}
Wall 墙,障碍物,关卡trigger;
using UnityEngine;
using System.Collections;
public class Wall : MonoBehaviour
{
public AudioClip chopSound1; //1 of 2 audio clips that play when the wall is attacked by the player.
public AudioClip chopSound2; //2 of 2 audio clips that play when the wall is attacked by the player.
public Sprite dmgSprite; //Alternate sprite to display after Wall has been attacked by player.
public int hp = 3; //hit points for the wall.
private SpriteRenderer spriteRenderer; //Store a component reference to the attached SpriteRenderer.
void Awake()
{
//Get a component reference to the SpriteRenderer.
spriteRenderer = GetComponent<SpriteRenderer>();
}
//DamageWall is called when the player attacks a wall.
public void DamageWall(int loss)
{
//Call the RandomizeSfx function of SoundManager to play one of two chop sounds.
SoundManager.instance.RandomizeSfx(chopSound1, chopSound2);
//Set spriteRenderer to the damaged wall sprite.
spriteRenderer.sprite = dmgSprite;
//Subtract loss from hit point total.
hp -= loss;
//If hit points are less than or equal to zero:
if (hp <= 0)
//Disable the gameObject.
gameObject.SetActive(false);
}
}
using UnityEngine;
using System.Collections;
public class SoundManager : MonoBehaviour
{
public AudioSource efxSource; //Drag a reference to the audio source which will play the sound effects.
public AudioSource musicSource; //Drag a reference to the audio source which will play the music.
public static SoundManager instance = null; //Allows other scripts to call functions from SoundManager.
public float lowPitchRange = .95f; //The lowest a sound effect will be randomly pitched.
public float highPitchRange = 1.05f; //The highest a sound effect will be randomly pitched.
void Awake()
{
//Check if there is already an instance of SoundManager
if (instance == null)
//if not, set it to this.
instance = this;
//If instance already exists:
else if (instance != this)
//Destroy this, this enforces our singleton pattern so there can only be one instance of SoundManager.
Destroy(gameObject);
//Set SoundManager to DontDestroyOnLoad so that it won't be destroyed when reloading our scene.
DontDestroyOnLoad(gameObject);
}
//Used to play single sound clips.
public void PlaySingle(AudioClip clip)
{
//Set the clip of our efxSource audio source to the clip passed in as a parameter.
efxSource.clip = clip;
//Play the clip.
efxSource.Play();
}
//RandomizeSfx chooses randomly between various audio clips and slightly changes their pitch.
public void RandomizeSfx(params AudioClip[] clips)
{
//Generate a random number between 0 and the length of our array of clips passed in.
int randomIndex = Random.Range(0, clips.Length);
//Choose a random pitch to play back our clip at between our high and low pitch ranges.
float randomPitch = Random.Range(lowPitchRange, highPitchRange);
//Set the pitch of the audio source to the randomly chosen pitch.
efxSource.pitch = randomPitch;
//Set the clip to the clip at our randomly chosen index.
efxSource.clip = clips[randomIndex];
//Play the clip.
efxSource.Play();
}
}