巡逻兵小游戏
实验要求
- 游戏设计要求
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 程序设计要求
- 必须使用订阅与发布模式传消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
- 工厂模式生产巡逻兵
设计流程
1.资源
在unity资源商店中下载了迷宫和人物的模型
给玩家和巡逻兵加上走路的动画
2.导演
这是一个用来管理游戏场景的导演类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object
{
private static SSDirector _instance; //导演实例
public ISceneController CurrentScenceController { get; set; } //当前场景
public static SSDirector GetInstance() //获取导演实例
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
3.游戏场景
只是游戏的第一个场景,用来加载资源。运用观察者模式,在游戏场景中订阅了分数以及游戏结束的事件,并在该类中实现具体事件。此外还提供了UserGUI可以使用的接口。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum GameState:int { GAMEOVER,RUNNING,RESTART} //表示游戏状态的枚举
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
PatrolFactory df = null; //巡逻兵工厂
GameState gamestate; //游戏状态
private GameObject player; //玩家
private int score; //分数
void Awake()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
df = Singleton<PatrolFactory>.Instance;
director.CurrentScenceController.LoadResources();
gamestate = GameState.RUNNING;
score = 0;
this.gameObject.AddComponent<UserGUI>();
this.gameObject.AddComponent<GameEventManager>();
}
//加载游戏界面资源
public void LoadResources()
{
//产生迷宫
GameObject maze = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/maze"));
maze.name = "maze";
//产生玩家
player = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/FreeVoxelGirlBlackhairPrefab 1"));
player.name = "player";
player.transform.position = new Vector3(0, 0, 2);
//产生巡逻兵
df.GetPatrol(new Vector3(-13,0,8), PatrolDirection.RIGHT);
df.GetPatrol(new Vector3(3, 0, 8), PatrolDirection.DOWN);
df.GetPatrol(new Vector3(13, 0, 2),PatrolDirection.LEFT);
df.GetPatrol(new Vector3(-13, 0, -8),PatrolDirection.UP);
df.GetPatrol(new Vector3(-3, 0, -2),PatrolDirection.RIGHT);
df.GetPatrol(new Vector3(13, 0, -2),PatrolDirection.DOWN);
}
void OnEnable()
{
//订阅计分和游戏结束事件
GameEventManager.OnScoreAction += addScore;
GameEventManager.OnGameOver += SetGameOver;
}
void OnDisable()
{
//取消订阅
GameEventManager.OnScoreAction -= addScore;
GameEventManager.OnGameOver -= SetGameOver;
}
//获得当前游戏状态
public GameState getGameState()
{
return gamestate;
}
//获得当前分数
public int getScore()
{
return score;
}
//使游戏结束
public void SetGameOver()
{
gamestate = GameState.GAMEOVER;
}
//设置当前游戏状态
public void setGameState(GameState state)
{
gamestate = state;
}
//重新开始游戏
public void restart()
{
player.transform.position = new Vector3(0, 0, 2);
gamestate = GameState.RUNNING;
df.RestartAll();
score = -1;
}
//增加分数
public void addScore()
{
score++;
}
}
4.接口
这是场景的一个接口,其他类可以通过调用接口中的函数来与游戏场景进行交互。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction {
GameState getGameState(); //获得当前游戏状态
int getScore(); //获得当前分数
void setGameState(GameState state); //设置游戏状态
void restart(); //重新开始游戏
void addScore(); //增加分数
}
这是场景的另一个接口,里面声明了场景应实现的功能。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController {
void LoadResources(); //加载场景资源
}
5.事件源
这是观察者模式中的事件源,里面定义了所有事件触发的函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEventManager : MonoBehaviour {
public delegate void ScoreAction(); //分数事件
public static event ScoreAction OnScoreAction;
public delegate void GameoverAction(); //游戏结束事件
public static event GameoverAction OnGameOver;
public void PlayerEscape() //玩家逃离则触发分数事件
{
if (OnScoreAction != null)
{
OnScoreAction();
}
}
public void GameOver() //游戏结束则触发游戏结束事件
{
if(OnGameOver!=null)
{
OnGameOver();
}
}
}
6.单例模式
提供获取单例的方法。使用工厂,事件源等类时都会用到。
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;
}
}
}
7.工厂模式
这是管理巡逻兵的工厂。能产生巡逻兵和在游戏重新开始时将巡逻兵复原。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolFactory : MonoBehaviour {
List<PatrolData> used = new List<PatrolData>(); //正在使用的巡逻兵
List<PatrolData> free = new List<PatrolData>(); //未被使用的巡逻兵
//产生巡逻兵
public GameObject GetPatrol(Vector3 pos,PatrolDirection d)
{
GameObject patrol = null;
if (free.Count == 0)
{
patrol = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/FreeVoxelGirlPrefab 1"));
patrol.AddComponent<PatrolData>();
}
else
{
patrol = free[0].gameObject;
free.Remove(free[0]);
}
patrol.transform.position = pos;
patrol.SetActive(true);
patrol.GetComponent<PatrolData>().direction = (PatrolDirection)(((int)d+3)%4);
patrol.GetComponent<PatrolData>().initDirection = (PatrolDirection)(((int)d + 3) % 4);
patrol.GetComponent<PatrolData>().target = pos;
patrol.GetComponent<PatrolData>().initTarget = pos;
used.Add(patrol.GetComponent<PatrolData>());
return patrol;
}
//复原巡逻兵
public void RestartAll()
{
foreach(PatrolData p in used)
{
p.target = p.initTarget;
p.direction = p.initDirection;
}
}
8.巡逻兵
这是管理巡逻兵信息以及动作的类。作为观察者,触发事件源。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum state : int { PATROL, CATCH }
public enum PatrolDirection : int { RIGHT, DOWN, LEFT, UP }
public class PatrolData : MonoBehaviour {
public state patrolState; //巡逻兵状态
public PatrolDirection direction; //巡逻方向
public PatrolDirection initDirection; //最初的巡逻方向(用于还原)
public Vector3 target; //巡逻目标位置
public Vector3 initTarget; //最初的巡逻目标位置(用于还原)
public float speed = 1f; //巡逻速度
public float catchSpeed = 2f; //追捕速度
public GameObject player; //追捕的玩家
private void Start()
{
patrolState = state.PATROL;
}
private void Update()
{
if(patrolState == state.PATROL)
{
//巡逻
this.transform.position = Vector3.MoveTowards(this.transform.position,
target, speed * Time.deltaTime);
//巡逻兵转弯
if(transform.position == target)
{
direction= (PatrolDirection)(((int)direction + 1) % 4);
if(direction == PatrolDirection.RIGHT)
{
target = new Vector3(target.x+6, target.y, target.z);
}
else if(direction == PatrolDirection.DOWN)
{
target = new Vector3(target.x, target.y, target.z-6);
}
else if (direction == PatrolDirection.UP)
{
target = new Vector3(target.x, target.y, target.z + 6);
}
else if (direction == PatrolDirection.LEFT)
{
target = new Vector3(target.x-6, target.y, target.z);
}
}
}
else if(patrolState == state.CATCH)
{
//追捕玩家
this.transform.position = Vector3.MoveTowards(this.transform.position,
player.transform.position, catchSpeed * Time.deltaTime);
//追到玩家,发出游戏结束消息
if(this.transform.position == player.transform.position)
{
Singleton<GameEventManager>.Instance.GameOver();
}
}
}
void OnTriggerEnter(Collider collider)
{
//玩家进入追捕范围,换成追捕状态
if (collider.gameObject.tag == "Player")
{
player = collider.gameObject;
patrolState = state.CATCH;
}
}
void OnTriggerExit(Collider collider)
{
//玩家逃离追捕范围,换成巡逻模式,发出玩家逃离的消息
if (collider.gameObject.tag == "Player")
{
patrolState = state.PATROL;
Singleton<GameEventManager>.Instance.PlayerEscape();
}
}
}
9.游戏玩家
该类定义了玩家如何利用键盘操纵人物。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class joystick : MonoBehaviour {
public float speedX = 5f; //行走速度
public float speedY = 5f;
public float rotate_speed = 1500f; //转弯速度
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
//操作上下左右键盘使玩家移动
float translationY = Input.GetAxis("Vertical") * speedY;
float translationX = Input.GetAxis("Horizontal") * speedX;
translationX *= Time.deltaTime;
translationY *= Time.deltaTime;
transform.Translate(translationX,0 , translationY);
transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);
//防止玩家撞墙后拐弯
if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
{
transform.localEulerAngles = new Vector3(0,transform.localEulerAngles.y, 0);
}
if (transform.position.y != 0)
{
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
}
}
}