Unity巡逻兵游戏-与怪兽战斗!

Unity巡逻兵游戏-与怪兽战斗!

前言

这是中山大学数据科学与计算机学院2019年3D游戏编程与设计的第七次作业
所有项目与代码已上传至github当中,欢迎大家访问。
github个人主页: https://starashzero.github.io
3D游戏编程与设计主页: https://starashzero.github.io/3DGameDesign
本次作业项目地址: https://github.com/StarashZero/3DGameDesign/tree/master/hw7

游戏简介

操作迷路少女与迷宫的怪兽进行战斗,击败所有BOSS!

项目要求

参考潘老师博客
在此基础上我做了一些修改,将游戏方式从躲避改为战斗,从碰撞即游戏结束改为碰撞掉血等等,具体变动见玩法以及具体实现。

游戏玩法

  1. 使用WASD或者上下左右进行移动,行走1.5s后触发加速
  2. 按下J进行攻击,有2s冷却时间
  3. 按下K或空格跳跃规避怪兽攻击,有2s冷却时间
  4. 玩家拥有10滴血,扣完则游戏结束
  5. 攻击怪兽+1分,击杀怪兽+2分
  6. 击败地图上所有怪兽则玩家获胜

游戏演示

项目结构

UML图:
在这里插入图片描述

  • 程序代码大致分为6个模块: 用户交互、主控制、玩家管理、怪兽管理、地图管理、动作管理。
  • 其中主控制模块与剩余5个模块连接,除了特殊情况(可以通过修改玩家与怪兽的数据来影响动作的运作形式)其他5个模块互不影响。
  • 除了主控制模块以外不进行跨模块订阅与发布,例如: AreaController需要给monster加动作需要发布给FirstController,再由FirstController进行动作添加,而不是直接发布给CCActionManager。这样做的目的有两个:
    • 一是使得程序解耦度更低、结构更清晰,整个UML其实可以视作是一颗树,主控制模块是树根,其他模块是树叶;
    • 二是方便FirstController做一些中间处理(有时一个事件需要多模块进行协助)。

预制体

  • 地图全貌:
    在这里插入图片描述
  • 怪兽预制:
    怪兽资源来自Asset Store
    在这里插入图片描述
    为怪兽的右手添加一个Trigger,当怪兽执行攻击动画时这个Trigger会随着运动,用于进行攻击判定。
    在这里插入图片描述
    为怪兽设置Layer,用处稍后会说
    在这里插入图片描述
    设置怪兽的动画状态机
    在这里插入图片描述
    • speed<0.2时播放idle,0.2<speed<0.7时播放Walk Forward in Place,速度>0.7时播放Run Forward In Place
    • 触发Attack 01时播放Attack 01
    • 触发Take Damage时播放Take Damage
    • 触发Die时播放Die
    • ToIdle用于将状态归位
  • 玩家预制:
    玩家资源来自AssetStore
    在这里插入图片描述
    为玩家腿部增加一个Trigger,当玩家执行攻击动画时Trigger会随之运动,用于进行攻击判定
    在这里插入图片描述
    为玩家设置动画状态机
    在这里插入图片描述
    • speed<0.2时播放idle,0.2<speed<0.7时播放Walk,速度>0.7时播放running
    • jumpTrigger、KOTrigger、damageTrigger、hitTrigger、winTrigger分别触发jump、KO_big、damage、hit01、winpose状态
    • running用于暂停状态机
    • toIdle用于状态机归位
  • 地图预制:
    地图皮肤来自Asset Store
    在这里插入图片描述
    使用Cube搭建地图
    注意为地图设置空气墙,并设置空气墙的Layer,用于限制怪兽活动区域
    在这里插入图片描述
    设置碰撞属性,使得空气墙只对怪兽有效
    在这里插入图片描述
    这样怪兽就不能跨区域了

具体实现

  • 用户交互:

    • UserGUI
      UserGUI用于接收玩家交互信息并显示一些画面
      UserGUI会将玩家当前血量、分数以及攻击的敌方血量显示在屏幕上
      当玩家触发相应操作时,UserGUI会将信息传给控制器
      public class UserGUI : MonoBehaviour
      {
          IUserAction userAction;             //控制器接口
          int points;                         //分数
          int playerHealth;                   //玩家血量
          int monsterHealth;                  //怪兽血量
          public bool gameOver;               //是否游戏结束
          public bool victory;                //是否胜利
      
          //增加分数
          public void AddPoints(int points)
          {
              this.points += points;
          }
      
          //设置分数
          public void SetPoints(int points)
          {
              this.points = points;
          }
      
          //设置玩家血量
          public void SetPlayerHealth(int health)
          {
              playerHealth = health;
          }
      
          //设置怪兽血量
          public void SetMonsterHealth(int health)
          {
              monsterHealth = health;
          }
      
          void Start()
          {
              gameOver = false;
              points = 0;
              userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;
          }
      
          void OnGUI()
          {
              //小字体初始化
              GUIStyle style = new GUIStyle();
              style.normal.textColor = Color.white;
              style.fontSize = 30;
      
              //大字体初始化
              GUIStyle bigStyle = new GUIStyle();
              bigStyle.normal.textColor = Color.white;
              bigStyle.fontSize = 50;
      
              GUI.Label(new Rect(20, 0, 200, 50), "Health: " + playerHealth, style);
              GUI.Label(new Rect(Screen.width-230, 0, 200, 50), "Enemy Health: " + monsterHealth, style);
              GUI.Label(new Rect(20, 60, 100, 50), "Points: " + points, style);
      
              //显示游戏结束画面
              if (gameOver)
              {
                  GUI.Label(new Rect(Screen.width/2-100, Screen.height/2 - 100, 200, 50), "You Die !", bigStyle);
                  if (GUI.Button(new Rect(Screen.width / 2 - 100, Screen.height / 2, 200, 50), "Restart"))
                  {
                      userAction.Restart();
                  }
              }
      
              //显示胜利画面
              if (victory)
              {
                  GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 50), "You Win !", bigStyle);
                  if (GUI.Button(new Rect(Screen.width / 2 - 100, Screen.height / 2, 200, 50), "Restart"))
                  {
                      userAction.Restart();
                  }
              }
      
              //获得玩家移动操作
              float speed = Input.GetAxis("Vertical");
              float direction = Input.GetAxis("Horizontal");
              userAction.MovePlayer(speed, direction);
      
              //获得玩家跳跃操作
              if (Input.GetButtonDown("Jump")|| Input.GetKeyDown(KeyCode.K))
                  userAction.Jump();
      
              //获得玩家攻击操作
              if (Input.GetKeyDown(KeyCode.J))
                  userAction.Hit();
          }
      }
      
  • 玩家管理

    • PlayerManager
      PlayerManager管理玩家的信息以及动画操作,提供订阅接口
      当玩家受伤时将事件发布出去,又订阅者进行处理
      当接收到Monster的攻击判定时判定是否符合受伤条件
      提供一些冷却机制,提高游戏可玩性
      public class PlayerManager : MonoBehaviour
      {
          public delegate void DealDamage(GameObject player);
          public static event DealDamage dealDamage;              //受伤事件发布
      
          public float speed;             //当前速度
          public float direction;         //当前方向位移
          public bool moveable;           //是否可移动          
          public int health;              //当前血量
          float jumpCounter;              //跳跃冷却计数器
          float hitCounter;               //攻击冷却计数器
          float damageCounter;            //受伤保护计数器
      
          Animator animator;
          private void Start()
          {
              animator = gameObject.GetComponent<Animator>();
              speed = 0;
              direction = 0;
              jumpCounter = hitCounter = damageCounter = 0;
          }
      
          //设置速度(动画)
          public void SetSpeed(float speed)
          {
              animator.SetFloat("speed",speed);
          }
      
          //跳跃
          public void Jump()
          {
              if (jumpCounter > 2)
              {
                  animator.ResetTrigger("jumpTrigger");
                  animator.SetTrigger("jumpTrigger");
                  jumpCounter = 0;
              }
          }
      
          //攻击
          public void Hit()
          {
              if (hitCounter > 2 && !IsName("jump"))
              {
                  animator.SetTrigger("hitTrigger");
                  hitCounter = 0;
              }
          }
      
          //受伤
          public void Damage()
          {
              animator.SetTrigger("damageTrigger");
          }
      
          //死亡
          public void Die()
          {
              animator.SetBool("running", false);
              animator.SetTrigger("KOTrigger");
          }
      
          //胜利
          public void Win()
          {
              animator.SetBool("running", false);
              animator.SetTrigger("winTrigger");
          }
      
          //归位
          public void ToIdle()
          {
              animator.SetTrigger("toIdle");
          }
      
          //判断动画状态
          public bool IsName(string name)
          {
              return animator.GetCurrentAnimatorStateInfo(0).IsName(name);
          }
      
          //复活
          public void Revive()
          {
              ToIdle();
              animator.SetBool("running", true);
          }
      
          //受伤判定
          private void OnTriggerEnter(Collider other)
          {
              //如果接受到怪兽的攻击Trigger判定则进行处理
              if (other.gameObject.name == "MonsterHitRange" && !IsName("jump") && damageCounter > 1)
              {
                  MonsterManager parentManager = other.gameObject.GetComponentInParent<MonsterManager>();
                  //只有怪兽攻击时Trigger有效,避免误判
                  if (!parentManager.IsName("Attack 01"))
                      return;
                  Damage();
                  damageCounter = 0;
                  health--;
                  if (health <= 0)
                  {
                      Die();
                  }
                  //发布受伤事件
                  dealDamage(gameObject);
              }
          }
      
      
          private void Update()
          {
              //及时进行归位消除,避免出现toIdle长时间存在的情况
              if (!IsName("KO_big") && !IsName("winpose"))
                  animator.ResetTrigger("toIdle");
              hitCounter = (hitCounter + Time.deltaTime) > 5 ? 5 : hitCounter + Time.deltaTime;
              damageCounter = (damageCounter + Time.deltaTime) > 5 ? 5 : damageCounter + Time.deltaTime;
              jumpCounter = (jumpCounter + Time.deltaTime) > 5 ? 5 : jumpCounter + Time.deltaTime;
          }
      }
      
  • 怪兽管理

    • MonsterManager
      MonsterManager管理怪兽信息与动画操作,提供订阅操作
      当Monster与玩家碰撞时触发攻击事件
      当接收到玩家攻击判定时,判断是否符合条件
      受伤后发布受伤事件,交由外部处理
      当Monster血量为0且死亡动画结束后发布释放事件
      public class MonsterManager : MonoBehaviour
      {
          public delegate void FreeMe(GameObject follower);
          public static event FreeMe freeAction;                  //释放事件发布
          public delegate void DealDamage(GameObject player);
          public static event DealDamage dealDamage;              //受伤事件发布
      
          public bool moveable;           //释放可移动
          public bool stop;               //释放暂停移动
          public bool change;             //需要改变方向
          public int health;              //当前血量
          Animator animator;
          FollowManager followManager;
          float damageCounter;            //受伤保护计数器
          float hitCounter;               //攻击冷却
          float collisionCounter;
          private void Start()
          {
              moveable = false;
              animator = gameObject.GetComponent<Animator>();
              followManager = gameObject.GetComponent<FollowManager>();
              collisionCounter = damageCounter = 0;
              stop = false;
          }
      
          //设置速度(动画)
          public void SetSpeed(float speed)
          {
              if (animator == null)
                  return;
              animator.SetFloat("speed", speed);
          }
      
          //跳跃
          public void Jump()
          {
              if (animator == null)
                  return;
              animator.SetTrigger("jumpTrigger");
          }
      
          //攻击
          public void Hit()
          {
              if (animator == null)
                  return;
              animator.SetTrigger("Attack 01");
          }
      
          //受伤
          public void Damage()
          {
              if (animator == null)
                  return;
              animator.SetTrigger("Take Damage");
          }
      
          //死亡
          public void Die()
          {
              if (animator == null)
                  return;
              animator.SetTrigger("Die");
          }
      
          //归位
          public void ToIdle()
          {
              if (animator == null)
                  return;
              animator.SetTrigger("ToIdle");
          }
      
          //判断动画状态
          public bool IsName(string name)
          {
              if (animator == null)
                  return false;
              return animator.GetCurrentAnimatorStateInfo(0).IsName(name);
          }
      
          //玩家攻击判定
          private void OnTriggerEnter(Collider other)
          {
              if (other.gameObject.name == "PlayerHitRange" && damageCounter > 2)
              {
                  PlayerManager parentManger = other.gameObject.GetComponentInParent<PlayerManager>();
                  //判断玩家是否正在攻击,避免误判
                  if (!parentManger.IsName("hit01"))
                      return;
                  Damage();
                  followManager.stop = true;
                  damageCounter = 0;
                  health--;
                  //发布受伤事件
                  dealDamage(gameObject);
                  if (health == 0)
                  {
                      Die();
                  }
              }
          }
      
          private void OnTriggerStay(Collider other)
          {
              if (other.gameObject.name == "PlayerHitRange" && damageCounter > 2)
              {
                  PlayerManager parentManger = other.gameObject.GetComponentInParent<PlayerManager>();
                  if (!parentManger.IsName("hit01"))
                      return;
                  Damage();
                  followManager.stop = true;
                  damageCounter = 0;
                  health--;
                  dealDamage(gameObject);
                  if (health == 0)
                  {
                      Die();
                  }
              }
          }
      
          //碰撞事件,与玩家碰撞时触发攻击操作,撞墙时改变方向
          private void OnCollisionStay(Collision collision)
          {
              if (collision.gameObject.tag == "ForbidMonster" && collisionCounter > 0.2)
              {
                  change = true;
                  collisionCounter = 0;
              }
              if (collision.gameObject.tag == "Player" && hitCounter > 4)
              {
                  Hit();
                  followManager.stop = true;
                  hitCounter = 0;
              }
          }
          private void OnCollisionEnter(Collision collision)
          {
              if (collision.gameObject.tag == "ForbidMonster" && collisionCounter > 0.2)
              {
                  change = true;
                  collisionCounter = 0;
              }
              if (collision.gameObject.tag == "Player" && hitCounter > 4)
              {
                  Hit();
                  followManager.stop = true;
                  hitCounter = 0;
              }
          }
      
          private void Update()
          {
              if (animator.GetCurrentAnimatorStateInfo(0).IsName("Die") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1.0f)
              {
                  ToIdle();
                  //释放操作发布
                  freeAction(gameObject);
              }
              damageCounter = (damageCounter + Time.deltaTime) > 5 ? 5 : damageCounter + Time.deltaTime;
              hitCounter = (hitCounter + Time.deltaTime) > 5 ? 5 : hitCounter + Time.deltaTime;
              collisionCounter = (collisionCounter + Time.deltaTime) > 5 ? 5 : collisionCounter + Time.deltaTime;
              if (followManager.stop && !IsName("Attack 01"))
                  followManager.stop = false;
          }
      }
      
    • FollowManager
      FollowManager实质上并不是Monster专属,而是FollowPlayerAction专属,主要是携带跟随信息,除了Monster以外主摄像头也会附带该部件以跟随玩家,为了方便(因为摄像头不在解释范围内)起见将其归为Monster
      public class FollowManager : MonoBehaviour
      {
          public bool followable;         //是否可跟随
          public bool stop;               //是否暂停
          public bool lookat;             //是否朝向玩家
          public float speed;             //跟随速度
          void Start()
          {
              followable = true;
              lookat = true;
              stop = false;
          }
      
      }
      
    • MonsterFactory
      MonsterFactory是怪兽工厂,提供怪兽的生成和释放操作
      MonsterFactory订阅MonsterManager的释放事件,当Monster需要释放时由它处理
      public class MonsterFactory : MonoBehaviour
      {
          public GameObject monster_Prefab;              
      
          List<MonsterManager> used;               
          List<MonsterManager> free;
          Vector3[] areaPositions;
          void Start()
          {
              used = new List<MonsterManager>();
              free = new List<MonsterManager>();
              monster_Prefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Boximon Fiery"), Vector3.zero, Quaternion.identity);
              monster_Prefab.SetActive(false);
              areaPositions = new Vector3[] { new Vector3(6,-1.5f,-3), new Vector3(-3, -1.5f, -3), new Vector3(6, -1.5f, -12), new Vector3(-7.5f, -1.5f, -12), new Vector3(-12, -1.5f, 6)};
          }
      
          //订阅MonsterManager释放事件
          private void OnEnable()
          {
              MonsterManager.freeAction += FreeMonster;
          }
      
          //取消订阅
          private void OnDisable()
          {
              MonsterManager.freeAction -= FreeMonster;
          }
      
          //生成Monster
          public GameObject GetMonster(int area)
          {
              GameObject monster;
              if (free.Count > 0)
              {
                  monster = free[0].gameObject;
                  free.Remove(free[0]);
              }
              else
              {
                  monster = GameObject.Instantiate<GameObject>(monster_Prefab, Vector3.zero, Quaternion.identity);
                  monster.AddComponent<FollowManager>();
                  monster.AddComponent<MonsterManager>();
              }
              monster.SetActive(true);
              monster.transform.position = areaPositions[area];
      
              if (area < 3)
              {
                  monster.transform.localScale = new Vector3(1, 1, 1);
                  monster.GetComponent<FollowManager>().speed = 0.8f;
                  monster.GetComponent<MonsterManager>().health = 2;
              }else if (area == 3)
              {
                  monster.transform.localScale = new Vector3(2, 2, 2);
                  monster.GetComponent<FollowManager>().speed = 1;
                  monster.GetComponent<MonsterManager>().health = 3;
              }
              else
              {
                  monster.transform.localScale = new Vector3(3, 3, 3);
                  monster.GetComponent<FollowManager>().speed = 1.2f;
                  monster.GetComponent<MonsterManager>().health = 5;
              }
      
              used.Add(monster.GetComponent<MonsterManager>());
      
              return monster;
          }
      
          //释放Monster
          public void FreeMonster(GameObject monster)
          {
              foreach (MonsterManager monsterManger in used)
              {
                  if (monsterManger.gameObject.GetInstanceID() == monster.GetInstanceID())
                  {
                      monster.GetComponent<FollowManager>().followable = false;
                      monster.GetComponent<MonsterManager>().moveable = false;
                      monster.SetActive(false);
                      free.Add(monsterManger);
                      used.Remove(monsterManger);
                      break;
                  }
      
              }
          }
      
          //释放所有Monster
          public void FreeAll()
          {
              while(used.Count!=0)
              {
                  used[0].gameObject.GetComponent<FollowManager>().followable = false;
                  used[0].moveable = false;
                  used[0].gameObject.SetActive(false);
                  free.Add(used[0]);
                  used.Remove(used[0]);
              }
          }
      }
      
    • AreaController
      AreaController负责控制各区域Monster的行为,提供一些订阅操作
      当玩家进入某个区域时,唤醒该区域的怪兽并发布追击
      当玩家不在区域内时,发布巡逻事件
      当所有区域怪兽均被击败时,发布胜利事件
      public class AreaController : MonoBehaviour
      {
          public delegate void FollowAction(GameObject follower, float distanceAway, float distanceUp, float speed);      
          public delegate void MonsterMoveAction(GameObject monster, float speed);
          public delegate void Victory();
          public static event FollowAction followAction;              //跟随事件发布
          public static event MonsterMoveAction monsterMoveAction;    //巡逻事件发布
          public static event Victory victory;                        //胜利事件发布
      
          MonsterFactory monsterFactory;
          GameObject[] monsters;
          int playerArea;
          void Start()
          {
              monsterFactory = Singleton<MonsterFactory>.Instance;
              playerArea = -1;
              monsters = new GameObject[5];
              GameStart();
          }
      
          //游戏开始,初始化Monster
          public void GameStart()
          {
              for (int temp = 0; temp < 5; temp++)
              {
                  monsters[temp] = monsterFactory.GetMonster(temp);
              }
          }
      
          //设置玩家区域
          public void SetArea(int area)
          {
              playerArea = area;
          }
      
          //释放所有Monster
          public void FreeAll()
          {
              monsterFactory.FreeAll();
          }
      
          void Update()
          {
              int cnt = 0;
              for (int temp = 0; temp < 5; temp++)
              {
                  if (!monsters[temp].activeSelf)
                      continue;
                  //当玩家进入区域时,唤醒该区域的Monster,使其追击玩家,否则巡逻
                  if (temp == playerArea && monsters[temp].GetComponent<FollowManager>().followable == false)
                  {
                      monsters[temp].GetComponent<MonsterManager>().moveable = false;
                      monsters[temp].GetComponent<MonsterManager>().SetSpeed(monsters[temp].GetComponent<FollowManager>().speed);
                      followAction(monsters[temp], 0, 0, monsters[temp].GetComponent<FollowManager>().speed);
                  }
                  else if (temp != playerArea)
                  {
                      monsters[temp].GetComponent<FollowManager>().followable = false;
                      monsters[temp].GetComponent<MonsterManager>().SetSpeed(0.5f);
                      monsterMoveAction(monsters[temp], 0.5f);
                  }
                  cnt++;
              }
              //如果所有怪兽都不再存在,发布胜利事件
              if (cnt == 0)
                  victory();
          }
      }
      
      
  • 地图管理

    • DetectPlace
      探测玩家移动区域,当玩家移动到该区域时发布区域事件
      public class DetectPlace : MonoBehaviour
      {
          public delegate void SetArea(float x, float y);
          public static event SetArea setArea;                  //区域事件发布
      
          private void OnTriggerStay(Collider other)
          {
              if (other.gameObject.tag == "Player")
              {
                  setArea(transform.position.x, transform.position.z);
              }
          }
      
      }
      
      
  • 动作管理

    • SSAction
      动作基类,与之前项目功能一致
      public class SSAction : ScriptableObject
      {
          public bool enable = true;
          public bool destroy = false;
      
          public GameObject gameObject { get; set; }
          public Transform transform { get; set; }
          public ISSActionCallback callback { get; set; }
      
          protected SSAction()
          {
      
          }
      
          // Start is called before the first frame update
          public virtual void Start()
          {
              throw new System.NotImplementedException();
          }
      
          // Update is called once per frame
          public virtual void Update()
          {
              throw new System.NotImplementedException();
          }
      
      }
      
    • SSActionManager
      动作管理者基类,与之前功能一致
      public class SSActionManager : MonoBehaviour
      {
          //动作集,以字典形式存在
          private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
          //等待被加入的动作队列(动作即将开始)
          private List<SSAction> waitingAdd = new List<SSAction>();
          //等待被删除的动作队列(动作已完成)
          private List<int> waitingDelete = new List<int>();
      
          protected void Update()
          {
              //将waitingAdd中的动作保存
              foreach (SSAction ac in waitingAdd)
                  actions[ac.GetInstanceID()] = ac;
              waitingAdd.Clear();
      
              //运行被保存的事件
              foreach (KeyValuePair<int, SSAction> kv in actions)
              {
                  SSAction ac = kv.Value;
                  if (ac.destroy)
                  {
                      waitingDelete.Add(ac.GetInstanceID());
                  }
                  else if (ac.enable)
                  {
                      ac.Update();
                  }
              }
      
              //销毁waitingDelete中的动作
              foreach (int key in waitingDelete)
              {
                  SSAction ac = actions[key];
                  actions.Remove(key);
                  Destroy(ac);
              }
              waitingDelete.Clear();
          }
      
          //准备运行一个动作,将动作初始化,并加入到waitingAdd
          public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback manager)
          {
              action.gameObject = gameObject;
              action.transform = gameObject.transform;
              action.callback = manager;
              waitingAdd.Add(action);
              action.Start();
          }
      
          // Start is called before the first frame update
          protected void Start()
          {
      
          }
      
      }
      
    • ISSActionCallback
      回调事件接口,与之前功能一致
      public interface ISSActionCallback
      {
          //回调函数
          void SSActionEvent(SSAction source,
              SSActionEventType events = SSActionEventType.Competed,
              int intParam = 0,
              string strParam = null,
              Object objectParam = null);
      }
      
    • IActionManager
      控制管理者对外接口,适用于Adapter模式,与之前功能一致
      public interface IActionManager
      {
          void MovePlayer(GameObject player, float speed, float direction);
          void FollowPlayer(GameObject follower, float distanceAway, float distanceUp, float speed);
      
          void MoveMonster(GameObject monster, float speed);
      }
      
    • FollowPlayerManager
      跟随玩家事件,用于实现摄像头跟随和怪兽追击
      跟随逻辑借鉴了网上的一部分代码
      public class FollowPlayerAction : SSAction
      {
          float distanceAway;             //与玩家的水平距离
          float distanceUp;               //与玩家的垂直距离
          float speed;                    //跟随速度
      
          Vector3 targetPosition; 
      
          Transform player;
      
          //生产函数(工厂模式)
          public static FollowPlayerAction GetSSAction(float distanceAway, float distanceUp, float speed)
          {
              FollowPlayerAction action = ScriptableObject.CreateInstance<FollowPlayerAction>();
              action.distanceAway = distanceAway;
              action.distanceUp = distanceUp;
              action.speed = speed;
              return action;
          }
      
          public override void Start()
          {
              player = GameObject.FindWithTag("Player").transform;
          }
      
          public override void Update()
          {
              if (gameObject.GetComponent<FollowManager>().followable)
              {
                  if (gameObject.GetComponent<FollowManager>().stop)
                      return;
                  targetPosition = player.position + Vector3.up * distanceUp + (gameObject.GetComponent<FollowManager>().lookat?-player.forward * distanceAway: player.forward * distanceAway);
                  transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * speed * 1.6f);
                  transform.LookAt(player);
              }
              else
              {
                  this.destroy = true;
                  this.enable = false;
                  this.callback.SSActionEvent(this);
              }
          }
      }
      
    • MoveMonsterAction
      Monster的巡逻事件
      每次往前移动一段距离
      当移动了3s后随机旋转45-135度,这样monster的行走路径就是3-8边形(平均4边形)
      public class MoveMonsterAction : SSAction
      {
          MonsterManager monsterManager;
          float speed;                    //速度
          float counter;                  //计数器
      
          public static MoveMonsterAction GetSSAction(float speed)
          {
              MoveMonsterAction action = ScriptableObject.CreateInstance<MoveMonsterAction>();
              action.speed = speed;
              return action;
          }
      
          public override void Start()
          {
              counter = 0;
              monsterManager = gameObject.GetComponent<MonsterManager>();
          }
      
          public override void Update()
          {
              if (monsterManager.moveable)
              {
                  if (monsterManager.stop)
                      return;
                  counter += Time.deltaTime;
                  //向前移动一段距离
                  gameObject.transform.Translate(0, 0, speed * 2.6f * Time.deltaTime);
                  //每3s换一个方向或撞墙改变方向
                  if (counter > 3 || monsterManager.change)
                  {
                      monsterManager.change = false;
                      gameObject.transform.Rotate(0, Random.Range(45, 135), 0);
                      counter = 0;
                  }
              }
              else
              {
                  this.destroy = true;
                  this.enable = false;
                  this.callback.SSActionEvent(this);
              }
      
          }
      }
      
    • MovePlayerAction
      玩家的移动动作
      为玩家速度提供缓冲,不允许直接加速到满值,当速度>0.7时进行计数,1.5s内最高0.7,这样玩家就会出现先走路再跑步的动画
      当玩家后退时,将玩家旋转180度且不让摄像头面向玩家,这样游戏界面就能看到玩家正面了(hhh)
      攻击时不允许移动(不然对monster太赖皮了)
      public class MovePlayerAction : SSAction
      {
          PlayerManager playerManager;
          float counter;                  //计数器
      
          //生产函数(工厂模式)
          public static MovePlayerAction GetSSAction()
          {
              MovePlayerAction action = ScriptableObject.CreateInstance<MovePlayerAction>();
              return action;
          }
      
          public override void Start()
          {
              playerManager = gameObject.GetComponent<PlayerManager>();
              counter = 0;
          }
      
          public override void Update()
          {
              if (playerManager.moveable)
              {
                  //攻击时不允许移动
                  if (playerManager.IsName("hit01"))
                      return;
                  float speed = playerManager.speed;
                  //玩家后退时将玩家旋转180度并让摄像头不再面向玩家
                  if (speed < -0.1)
                  {
                      speed = -speed;
                      if (GameObject.FindWithTag("MainCamera").GetComponent<FollowManager>().lookat)
                      {
                          gameObject.transform.Rotate(0, 180, 0);
                          GameObject.FindWithTag("MainCamera").GetComponent<FollowManager>().lookat = false;
                      }
                  }
                  else if (speed > 0.1)
                  {
                      if (!GameObject.FindWithTag("MainCamera").GetComponent<FollowManager>().lookat)
                      {
                          gameObject.transform.Rotate(0, 180, 0);
                          GameObject.FindWithTag("MainCamera").GetComponent<FollowManager>().lookat = true;
                      }
                  }
                  //速度>0.7时增加计数器,1.5s后转为跑步速度,否则最高为0.7
                  if (speed >= 0.7)
                  {
                      counter = (counter + Time.deltaTime) > 1.5f ? 1.5f : counter + Time.deltaTime;
                      speed = counter == 1.5f ? speed : 0.7f;
                  }
                  else
                  {
                      counter = (counter - Time.deltaTime) < 0 ? 0 : counter - Time.deltaTime;
                  }
                  //向前移动
                  gameObject.transform.Translate(0, 0, speed * 3.6f * Time.deltaTime);
                  playerManager.speed = playerManager.speed > 0 ? playerManager.speed - Time.deltaTime : playerManager.speed + Time.deltaTime;
                  //进行旋转
                  gameObject.transform.Rotate(0, playerManager.direction * 70 * Time.deltaTime, 0);
                  playerManager.direction = 0;
                  //设置玩家状态机速度
                  playerManager.SetSpeed(speed);
              }
              else
              {
                  this.destroy = true;
                  this.enable = false;
                  this.callback.SSActionEvent(this);
              }
      
          }
      }
      
    • CCActionManager
      动作管理者,主要是为传入的对象添加动作
      public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager
      {
          FollowPlayerAction follow;
          MovePlayerAction movePlayer;
          MoveMonsterAction monsterAction;
          protected new void Start()
          {
          }
      
          //移动玩家
          public void MovePlayer(GameObject player, float speed, float direction)
          {
              PlayerManager playerManager = player.GetComponent<PlayerManager>();
              if (playerManager == null)
                  return;
              playerManager.speed = speed;
              playerManager.direction = direction;
              if (!playerManager.moveable)
              {
                  playerManager.moveable = true;
                  movePlayer = MovePlayerAction.GetSSAction();
                  RunAction(player, movePlayer, this);
              }
          }
      
          //跟随玩家
          public void FollowPlayer(GameObject follower, float distanceAway, float distanceUp, float speed)
          {
              FollowManager followManager = follower.GetComponent<FollowManager>();
              if (followManager == null)
                  return;
              if (!followManager.followable)
              {
                  followManager.followable = true;
                  follow = FollowPlayerAction.GetSSAction(distanceAway, distanceUp, speed);
                  RunAction(follower, follow, this);
              }
          }
      
          //巡逻
          public void MoveMonster(GameObject monster, float speed)
          {
              MonsterManager monsterManager = monster.GetComponent<MonsterManager>();
              if (monsterManager == null)
                  return;
              if (!monsterManager.moveable)
              {
                  monsterManager.moveable = true;
                  monsterAction = MoveMonsterAction.GetSSAction(speed);
                  RunAction(monster, monsterAction, this);
              }
          }
      
          //回调函数
          public void SSActionEvent(SSAction source,
          SSActionEventType events = SSActionEventType.Competed,
          int intParam = 0,
          string strParam = null,
          Object objectParam = null)
          {
              
          }
      }
      
  • 主控制

    • ISceneController
      场景控制器接口,与以前功能一致
      public interface ISceneController
      {
          void LoadResources();
      }
      
    • SSDirector
      导演类,与之前功能一致
      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;
          }
      }
      
    • Singlton
      单实例类,与之前功能一致
      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;
              }
          }
      }
      
    • IUserAction
      用户事件处理接口,与之前功能一致
      public interface IUserAction
      {
          void MovePlayer(float speed, float direction);
          void Jump();
          void Hit();
          void Restart();
      }
      
    • FirstController
      主控制器,负责资源初始化与协调各模块的工作
      与其他5个模块通信,订阅其他模块发布的事件并进行处理
      接收玩家交互后产生的信息并进行相应处理
      public class FirstController : MonoBehaviour, ISceneController, IUserAction
      {
          GameObject map;                     //地图管理
          GameObject player;                  //玩家管理
          UserGUI userGUI;                    //用户交互
          IActionManager actionManager;       //动作管理
          AreaController areaController;      //怪兽管理
          void Start()
          {
              SSDirector.GetInstance().CurrentScenceController = this;
              gameObject.AddComponent<MonsterFactory>();
              gameObject.AddComponent<AreaController>();
              areaController = Singleton<AreaController>.Instance;
              gameObject.AddComponent<CCActionManager>();
              gameObject.AddComponent<UserGUI>();
              userGUI = Singleton<UserGUI>.Instance;
              actionManager = Singleton<CCActionManager>.Instance;
              LoadResources();
              GameObject.FindWithTag("MainCamera").AddComponent<FollowManager>();
              actionManager.FollowPlayer(GameObject.FindWithTag("MainCamera"), 3.5f, 5f, 6);
          }
      
          //订阅各事件
          private void OnEnable()
          {
              AreaController.followAction += FollowAction;
              AreaController.monsterMoveAction += MonsterMoveAction;
              AreaController.victory += Victory;
              PlayerManager.dealDamage += DealPlayerDamage;
              MonsterManager.dealDamage += DealMonsterDamage;
              DetectPlace.setArea += SetArea;
          }
      
          //取消订阅
          private void OnDisable()
          {
              AreaController.followAction -= FollowAction;
              AreaController.monsterMoveAction -= MonsterMoveAction;
              AreaController.victory -= Victory;
              PlayerManager.dealDamage -= DealPlayerDamage;
              MonsterManager.dealDamage -= DealMonsterDamage;
              DetectPlace.setArea -= SetArea;
          }
      
          //加载资源
          public void LoadResources()
          {
              if (map == null)
                  map = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Map"), Vector3.zero, Quaternion.identity);
              if (player == null)
              {
                  player = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/SapphiArtchan"), Vector3.zero, Quaternion.identity);
                  player.AddComponent<PlayerManager>();
              }
              player.GetComponent<PlayerManager>().health = 10;
              userGUI.SetPlayerHealth(10);
              userGUI.SetPoints(0);
              player.transform.position = new Vector3(6, -1.5f, 5);
              actionManager.MovePlayer(player, 0, 0);
          }
      
          //处理胜利事件
          public void Victory()
          {
              if (userGUI.gameOver|| userGUI.victory)
                  return;
              userGUI.victory = true;
              areaController.FreeAll();
              player.GetComponent<PlayerManager>().moveable = false;
              player.GetComponent<PlayerManager>().Win();
          }
      
          //处理玩家受伤事件
          public void DealPlayerDamage(GameObject sender)
          {
              userGUI.SetPlayerHealth(sender.GetComponent<PlayerManager>().health);
              if (sender.GetComponent<PlayerManager>().health == 0)
              {
                  areaController.FreeAll();
                  sender.GetComponent<PlayerManager>().moveable = false;
                  userGUI.gameOver = true;
                  userGUI.victory = false;
              }
          }
      
          //处理怪兽受伤事件
          public void DealMonsterDamage(GameObject sender)
          {
              userGUI.SetMonsterHealth(sender.GetComponent<MonsterManager>().health);
              userGUI.AddPoints(sender.GetComponent<MonsterManager>().health == 0 ? 2 : 1);
          }
      
          //玩家移动
          public void MovePlayer(float speed, float direction)
          {
              if (player.GetComponent<PlayerManager>().moveable)
                  actionManager.MovePlayer(player, speed, direction);
          }
      
          //玩家跳跃
          public void Jump()
          {
              player.GetComponent<PlayerManager>().Jump();
          }
      
          //玩家攻击
          public void Hit()
          {
              player.GetComponent<PlayerManager>().Hit();
          }
      
          //处理怪兽追击事件
          public void FollowAction(GameObject follower, float distanceAway, float distanceUp, float speed)
          {
              actionManager.FollowPlayer(follower, distanceAway, distanceUp, speed);
          }
      
          //处理怪兽巡逻事件
          public void MonsterMoveAction(GameObject monster, float speed)
          {
              actionManager.MoveMonster(monster, speed);
          }
      
          //游戏重开
          public void Restart()
          {
              LoadResources();
              player.GetComponent<PlayerManager>().Revive();
              areaController.GameStart();
              userGUI.gameOver = false;
              userGUI.victory = false;
          }
      
          //设置玩家区域
          public void SetArea(float x, float y)
          {
              int playerArea;
              x += 4.5f;
              y += 4.5f;
              if ((int)x == 9 && (int)y == 9)
                  playerArea = -1;
              else if ((int)x == 9 && (int)y == 0)
                  playerArea = 0;
              else if ((int)x == 0 && (int)y == 0)
                  playerArea = 1;
              else if ((int)x == 9 && (int)y == -9)
                  playerArea = 2;
              else if (((int)x == 0 || (int)x == -9) && (int)y == -9)
                  playerArea = 3;
              else
                  playerArea = 4;
              areaController.SetArea(playerArea);
      
          }
      
          void Update()
          {
      
          }
      }
      
      

小结

这是一个附带简单即时战斗系统的巡逻兵游戏。产生做这个游戏的想法的原因是看了师兄写的躲避怪兽游戏,预制很精美,所以我就想能不能用精美的预制来实现一个可以战斗的游戏,毕竟只是躲避太无聊了,所以就做了。
做这个游戏花了非常长的时间,中途有一些想法也不得不因为能力不足而舍弃,例如本来我希望将摄像头移动到玩家正身后,这样能更好的欣赏战斗画面,但是处理不了摄像头穿墙的问题(试过设置刚体,结果抖动太厉害了)。
还有不少改进升级空间,继续努力。

发布了34 篇原创文章 · 获赞 8 · 访问量 2751

猜你喜欢

转载自blog.csdn.net/qq_20549085/article/details/102798334