1.作业要求:
1.1游戏设计要求
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
1.2程序设计要求:
- 必须使用订阅与发布模式传消息
- 工厂模式生产巡逻兵
2.具体设计
2.1知识准备
工厂模式
工厂模式我在之前的博客就已说明,在这里就不细讲了,想要了解的小伙伴可以到设计模式(一)工厂模式Factory(创建型)这里加深了解。
订阅与发布模式
订阅与发布模式的基本概念就是订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。比如有个界面是实时显示天气,它订阅了天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。
想要加深理解的可以到发布-订阅者模式和事件监听器模式这里学习一下
2.2分析需求
- 先决定巡逻兵是随机生成的,且巡逻的路径是矩阵,碰到地图中的墙壁后重新生成路径。在巡逻兵下挂载一个空对象,添加box collider来控制巡逻兵敏感的区域,只要玩家进入这些区域,巡逻兵就进行追击。
- 用一个工厂生产巡逻兵,在游戏刚开始时生产,在游戏重新开始时就重置巡逻兵,不用重新生成巡逻兵,这样可以节省资源。
- 玩家可以接受w、s、a、d以及↑、↓、←、→这些按键的输入进而前进、后退、转向左和转向右的操作。在前行的时候还可以通过按下空格键跳跃前行。
- 由于要用到订阅-发布模式,所以我将所有的游戏事件分离出来,用一个类来管理,并发布这些事件,而其他类则订阅这些事件,以便在游戏开始、游戏结束时执行相应的行为。
- 然后就要用一个最高级的管理类来管理上面的所有行为,同时用一个类来与用户交互,显示各种信息。
以上应该就是这次作业的重点了。
2.3具体设计
玩家、巡逻兵、地图都需要一些资源。其中玩家、巡逻兵可以到unity3d的Asset Store中搜索Player、Monster,然后下载这些游戏资源。而地图则可以用Cube加上Plane以及一些墙壁贴图、草地贴图解决。
然后用PlayerController和MonsterController来控制玩家和巡逻兵的行为,用AreaController来控制巡逻兵的感知范围。用MonsterFactory生产巡逻兵,用GameEventManager来管理像开始游戏这样的游戏行为,同时作为发布者,发布各种游戏行为事件。用FirstController来管理上面的一切,用UserGUI来与用户交互,最后还有一些细节就不多说了。
3.源代码
Director.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Director : System.Object {
private static Director _instance;
public SceneController sceneController { set; get; }
public static Director GetInstance()
{
return _instance ?? (_instance = new Director());
}
}
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(CapsuleCollider))]
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour {
private Animator animator;
private CapsuleCollider capsule;
private Rigidbody _rigidbody;
public float animatorSpeed;
public float forwardSpeed = 0.7f;
public float backwardSpeed = 0.2f;
public float rotateSpeed = 2.0f;
public float jumpPower = 1.5f;
private AnimatorStateInfo stateInfo;
private Vector3 velocity;
static int idleState = Animator.StringToHash("Base Layer.Idle");
static int locoState = Animator.StringToHash("Base Layer.Locomotion");
static int jumpState = Animator.StringToHash("Base Layer.Jump");
static int restState = Animator.StringToHash("Base Layer.Rest");
void Start () {
animator = gameObject.GetComponent<Animator>();
capsule = gameObject.GetComponent<CapsuleCollider>();
_rigidbody = gameObject.GetComponent<Rigidbody>();
animatorSpeed = 1;
}
void FixedUpdate ()
{
if (!GameEventManager.isContinue)
{
animator.SetFloat("Speed", 0);
animator.SetFloat("Direction", 0);
animator.SetBool("Jump", false);
return;
}
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
animator.SetFloat("Speed", v);
animator.SetFloat("Direction", h);
animator.speed = animatorSpeed;
stateInfo = animator.GetCurrentAnimatorStateInfo(0);
velocity = this.transform.TransformDirection(new Vector3(0, 0, v));
if (v > 0.1)
velocity *= forwardSpeed;
else if (v < -0.1)
velocity *= backwardSpeed;
// 按下空格,跳跃
if (Input.GetButtonDown("Jump"))
{
// 如果处于运动状态,且并非过渡时期
if (stateInfo.fullPathHash == locoState && !animator.IsInTransition(0))
{
_rigidbody.AddForce(Vector3.up * jumpPower, ForceMode.VelocityChange);
animator.SetBool("Jump", true);
}
}
// 移动 和 改变方向
transform.localPosition += velocity * Time.fixedDeltaTime;
transform.Rotate(0, h * rotateSpeed, 0);
// 重置,停止跳跃状态
if (stateInfo.fullPathHash == jumpState && !animator.IsInTransition(0))
{
animator.SetBool("Jump", false);
_rigidbody.isKinematic = false;
}
}
public void Reset()
{
transform.position = new Vector3(0, 0, -1f);
transform.rotation = Quaternion.identity;
animator.SetFloat("Speed", 0);
animator.SetFloat("Direction", 0);
animator.SetBool("Jump", false);
_rigidbody.isKinematic = false;
velocity = Vector3.zero;
}
}
MonsterController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(CapsuleCollider))]
public class MonsterController : MonoBehaviour {
// 怪物巡逻路径
public List<Vector3> path = new List<Vector3>();
// 怪物在路径上那个节点
private int currentNode = 0;
// 怪物行进的速度
private int speed = 4;
private Animator animator;
private Rigidbody _rigidbody;
private AnimatorStateInfo stateInfo;
static int idleState = Animator.StringToHash("Base Layer.Idle");
static int walkState = Animator.StringToHash("Base Layer.Walk");
static int runState = Animator.StringToHash("Base Layer.Run");
static int attackState = Animator.StringToHash("Base Layer.Attack");
private void Awake()
{
transform.GetChild(3).gameObject.AddComponent<AreaController>();
}
private void Start()
{
animator = gameObject.GetComponent<Animator>();
_rigidbody = gameObject.GetComponent<Rigidbody>();
SetPath();
}
void FixedUpdate()
{
stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (!GameEventManager.isContinue)
{
animator.SetBool("isAttack", false);
animator.SetBool("isWalk", false);
animator.SetBool("isIdle", true);
return;
}
// 查看四周后继续巡逻
if (stateInfo.fullPathHash == idleState && !animator.IsInTransition(0))
{
animator.SetBool("isIdle", false);
animator.SetBool("isWalk", true);
}
// 追踪玩家
else if (stateInfo.fullPathHash == runState && !animator.IsInTransition(0))
{
MoveToTarget(path[currentNode], 4);
}
// 巡逻
else if (stateInfo.fullPathHash == walkState && !animator.IsInTransition(0))
{
Vector3 targetPos = path[currentNode];
animator.SetBool("isWalk", true);
MoveToTarget(targetPos, 8);
if (Vector3.Distance(transform.position, targetPos) < 0.01)
{
// 巡逻一会后查看四周
currentNode = (currentNode + 1) % path.Count;
animator.SetBool("isIdle", true);
animator.SetBool("isWalk", false);
}
}
}
// 玩家进入怪物领域
public void Hit(Collider other)
{
if(other.gameObject.tag == "Player")
{
path.Clear();
path.Add(other.gameObject.transform.position);
animator.SetBool("isWalk", false);
animator.SetBool("isIdle", false);
animator.SetBool("isRun", true);
currentNode = 0;
}
}
// 玩家脱离怪物领域
public void Miss(Collider other)
{
if(other.gameObject.tag == "Player")
{
SetPath();
animator.SetBool("isRun", false);
Singleton<GameEventManager>.Instance.ChangeScore();
}
}
// 碰撞到墙或者其他怪物的对策
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Monster")
currentNode = (currentNode + 1) % path.Count;
if (collision.gameObject.tag == "Wall")
SetPath();
}
// 进行袭击动作
private void OnCollisionStay(Collision collision)
{
if(collision.gameObject.tag == "Player")
{
animator.SetBool("isAttack", true);
Singleton<GameEventManager>.Instance.PatrolGameOver();
}
}
// 移动到目标位置
public void MoveToTarget(Vector3 targetPos, int speedNum)
{
if (Vector3.Distance(transform.position, targetPos) > 0.01)
{
Vector3 direction = targetPos - transform.position;
Quaternion rotation = Quaternion.LookRotation(direction, Vector3.up);
transform.rotation = rotation;
direction = direction.normalized;
transform.localPosition += direction * Time.fixedDeltaTime / speedNum;
}
}
// 得到巡逻方位
private void SetPath()
{
path.Clear();
float length = Random.Range(1f, 2f);
Vector3 right = new Vector3(transform.right.x, 0, transform.right.z).normalized;
Vector3 forward = new Vector3(transform.forward.x, 0, transform.forward.z).normalized;
Vector3 pos = transform.position - forward * 2;
path.Add(pos);
path.Add(pos + right * length);
path.Add(pos - forward * length + right * length);
path.Add(pos - forward * length);
currentNode = 0;
}
// 重置
public void Reset()
{
SetPath();
animator.SetBool("isWalk", false);
animator.SetBool("isRun", false);
animator.SetBool("isAttack", false);
animator.SetBool("isIdle", true);
}
}
AreaController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AreaController : MonoBehaviour {
private MonsterController monsterController = null;
private void Start()
{
monsterController = this.transform.parent.GetComponent<MonsterController>();
}
// 玩家进入自己的区域
private void OnTriggerStay(Collider other)
{
if (other.gameObject.name == "player(Clone)")
monsterController.Hit(other);
}
// 玩家脱离区域
private void OnTriggerExit(Collider other)
{
if (other.gameObject.name == "player(Clone)")
monsterController.Miss(other);
}
}
MonsterFactory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterFactory : MonoBehaviour {
// 怪物预制
public GameObject monster;
// 生产的怪物
private List<GameObject> monsters;
// 怪物位置限制
private float min_X = -3.75f;
private float min_Y = -3.75f;
private float max_X = 3.75f;
private float max_Y = 3.75f;
// 墙的厚度
private float wallThick = 0.05f;
// 格子长度
private float cellLength = 2.5f;
// 地图的行数与列数
private int row = 3;
private int col = 3;
// 每个格子怪物的数量
private int monsterNumEveryCell = 2;
// 是否是第一次start game
private bool flag = true;
private void Awake()
{
monster = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/monster"), new Vector3(0, 100, 0), Quaternion.identity);
monsters = new List<GameObject>();
}
// 得到所有的怪物
public List<GameObject> GetMonsters()
{
int num = row * col * monsterNumEveryCell;
for(int i = 0; i < num; i++) {
GameObject monsterClone = Instantiate(monster, Vector3.zero, Quaternion.identity);
monsterClone.gameObject.AddComponent<MonsterController>();
monsters.Add(monsterClone);
}
SetMonstersPostion();
return monsters;
}
public void Reset()
{
if (flag)
flag = false;
else
SetMonstersPostion();
int num = row * col * monsterNumEveryCell;
for (int i = 0; i < num; i++)
monsters[i].GetComponent<MonsterController>().Reset();
}
private void SetMonstersPostion()
{
int index = 0;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
// 每个格子的大致范围
float minX = min_X + i * cellLength + wallThick;
float minY = min_Y + j * cellLength + wallThick;
float maxX = min_X + (i + 1) * cellLength - wallThick;
float maxY = min_Y + (j + 1) * cellLength - wallThick;
List<Vector3> allLocations = new List<Vector3>();
// 在格子内怪物设置怪物的位置
for (int l = 0; l < monsterNumEveryCell; l++)
{
float x = Random.Range(minX, maxX);
float y = Random.Range(minY, maxY);
// 防止怪物生成在玩家附近
if (x < 0.5 && x > -0.5)
x = Random.Range(minX, maxX);
if (y < 0.5 && y > -0.5)
y = Random.Range(minY, maxY);
// 防止怪物生成在同一个地方
Vector3 location = new Vector3(x, 0, y);
foreach (var loc in allLocations)
if(Vector3.Distance(loc, location) < 0.01)
{
l--;
continue;
}
monsters[index++].gameObject.transform.position = location;
}
}
}
}
}
SceneController.cs
using System.Collections.Generic;
using UnityEngine;
public interface SceneController {
void LoadResources();
}
IUserAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction {
// 得到分数
int GetScore();
// 游戏是否结束
bool IsGameOver();
}
FirstController.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, SceneController, IUserAction {
// 玩家预制体
private GameObject player;
// 所有的怪物
private List<GameObject> monsters;
// 工厂
private MonsterFactory monsterFactory;
// 游戏是否结束
private bool isGameOver = false;
// 得分
private int score = 0;
public void LoadResources()
{
// 场地
GameObject scene = Instantiate(Resources.Load("Prefabs/Scene", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
scene.gameObject.transform.localScale = new Vector3(0.5f, 0.2f, 0.5f);
// 玩家
player = Instantiate(Resources.Load("Prefabs/player", typeof(GameObject)), new Vector3(0, 0, -1f), Quaternion.identity, null) as GameObject;
GameObject camera = GameObject.FindGameObjectWithTag("MainCamera");
camera.transform.parent = player.transform;
camera.transform.position = player.transform.position + new Vector3(0, 0.2f, -0.4f);
player.AddComponent<PlayerController>();
// 工厂
gameObject.AddComponent<MonsterFactory>();
monsterFactory = gameObject.GetComponent<MonsterFactory>();
// 怪物
monsters = monsterFactory.GetMonsters();
// 游戏事件的管理者
gameObject.AddComponent<GameEventManager>();
}
public void GameOver()
{
player.GetComponent<Rigidbody>().isKinematic = true;
isGameOver = true;
}
public void AddScore()
{
if(GameEventManager.isContinue)
score++;
if(score == 30)
Singleton<GameEventManager>.Instance.PatrolGameOver();
}
public void ReStartGame()
{
player.GetComponent<PlayerController>().Reset();
monsterFactory.Reset();
score = 0;
}
public int GetScore()
{
return score;
}
public bool IsGameOver()
{
return isGameOver;
}
private void Awake()
{
Director director = Director.GetInstance();
director.sceneController = this;
LoadResources();
// 注册游戏结束的事件
GameEventManager.AttackPlayerEvent += GameOver;
// 注册加分的事件
GameEventManager.ScoreEvent += AddScore;
// 开始游戏
GameEventManager.StartGameEvent += ReStartGame;
}
private void OnDisable()
{
GameEventManager.AttackPlayerEvent -= GameOver;
GameEventManager.ScoreEvent -= AddScore;
}
}
GameEventManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEventManager : MonoBehaviour {
// 游戏结束
public delegate void AttackPlayer();
public static event AttackPlayer AttackPlayerEvent;
// 计分
public delegate void Score();
public static event Score ScoreEvent;
// 开始游戏
public delegate void StartGame();
public static event StartGame StartGameEvent;
// 游戏是否在继续
public static bool isContinue = false;
public void PatrolGameOver()
{
isContinue = false;
if (AttackPlayerEvent != null)
AttackPlayerEvent();
}
public void ChangeScore()
{
if (isContinue && ScoreEvent != null)
ScoreEvent();
}
public void GameBegin()
{
if (StartGameEvent != null)
StartGameEvent();
isContinue = true;
}
}
UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour {
private IUserAction userAction;
GUIStyle labelStyle;
GUIStyle buttonStyle;
GUIStyle finalStyle;
void Start () {
userAction = Director.GetInstance().sceneController as IUserAction;
labelStyle = new GUIStyle();
labelStyle.fontStyle = FontStyle.Bold;
labelStyle.fontSize = 14;
labelStyle.alignment = TextAnchor.UpperCenter;
buttonStyle = new GUIStyle("Button");
buttonStyle.alignment = TextAnchor.MiddleCenter;
buttonStyle.fontSize = 16;
finalStyle = new GUIStyle(labelStyle);
finalStyle.fontSize = 20;
finalStyle.normal.textColor = Color.red;
}
void OnGUI ()
{
int w = Screen.width;
int h = Screen.height;
GUI.Label(new Rect(w / 2 - 100, 10, 200, 30), "躲避过一个怪物加一分,超过30即可获得胜利", labelStyle);
if(!GameEventManager.isContinue && GUI.Button(new Rect(w / 2 - 40, 60, 80, 30), "Start", buttonStyle))
{
Singleton<GameEventManager>.Instance.GameBegin();
}
if (GameEventManager.isContinue)
{
GUI.Label(new Rect(w - 160, 10, 100, 30), "Score: " + userAction.GetScore().ToString(), labelStyle);
}
else if(userAction.IsGameOver())
{
GUI.Label(new Rect(w / 2 - 50, h / 2 - 15, 100, 30), userAction.GetScore() > 29 ? "You Win!" : "Fail!", finalStyle);
}
}
}
Singleton.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
public static T Instance {
get {
if (instance == null) {
instance = (T) FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none.");
}
}
return instance;
}
}
}