Unity巡逻兵游戏制作

巡逻兵游戏

GitHu项目地址以及演示视频地址

github项目地址
视频演示地址

作业要求
  • 游戏设计要求:
    • 创建一个地图和若干巡逻兵(使用动画);
    • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确- 定下一个目标位置,用自己当前位置为原点计算;
    • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
    • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
    • 失去玩家目标后,继续巡逻;
    • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
  • 程序设计要求:
    • 必须使用订阅与发布模式传消息
    • 工厂模式生产巡逻兵
UML图

这里写图片描述
这里主要说明一下要求中的设计模式是如何完成的:

  • 工厂模式:工厂模式使用MonsterFactory类完成,这个类提供
    • getInstance函数,获取工厂类实例;
    • setPrefabs函数,实例化工厂类的预设;
    • getMonster函数,每次被调用都返回一个战龙的object,这样firstController就可以通过调用工厂方法,生产战龙monster;
    • free函数,重新开始游戏时调用,将战龙对象属性重新设置(setActive(false));
    • 具体实现见项目代码。
  • 发布与订阅者模式:通过ScoreRecorder类完成,这个类提供:
    • addScore方法,调用一次加一分;其他类的事件通过订阅这个函数(+=)完成游戏的计分:战龙类的OnTriggerExit订阅此函数,每次玩家逃出战龙巡逻范围都能够加一分;
    • setScore方法,设置分数为0,重开游戏时调用;
    • getScore函数,将分数显示在屏幕上;

订阅-发布模式:

订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码

在这个游戏中,记分员类就充当发布者角色,其他涉及加分的事件对它进行订阅,这样每次加分事件发生统一由记分员完成加分,不需要各个类分别处理,从而减少耦合,达成优化目的。

最终效果

这里只是给出几个截图,具体游戏情况见视频演示(地址上面已给出)
游戏场景俯瞰图
这里写图片描述
玩家距离巡逻兵很远的时候会,巡逻兵巡逻(播放行走动画)
这里写图片描述
玩家靠近巡逻兵(进入他的感知范围),巡逻兵追捕玩家(播放奔跑动画)
巡逻兵在巡逻
玩家跳跃从而躲避巡逻兵(这个图好难截啊)
这里写图片描述
巡逻兵追到玩家,播放攻击动画,unity酱播放倒地动画
这里写图片描述

具体制作流程
  • 预设准备:

    • 在商店搜索unitychan,dragon,stonewall获取unity酱,战龙,石墙资源
      这里写图片描述
    • 动作控制设置:

      unity酱的动作控制如下:

      • 这里当玩家没有输入的时候,她会停在原地(idle状态);
      • 当玩家按下前(↑),就会触发奔跑条件(speed>0.1),播放奔跑的动画(locomotion);
      • 当玩家按下前(↓),就会触发后退条件(speed<-0.1),播放往后退的动画(walkback);
      • 当玩家按下空格(space),如果是在奔跑状态下就出发跳跃条件(Bool Jump),播放跳跃动画,否则播放rest动画(rest == true,播放伸懒腰动画);
      • 当unity酱被战龙追上就会出发cry条件(bool GameOver),播放unity酱哭泣动画;
        这里写图片描述

      战龙动作控制如下:

      • 当unity酱没有进入战龙的感知范围时,战龙处于巡逻状态(Iswalk == true),播放行走动画(巡逻);
      • 当unity酱进入战龙感知范围,战龙从行走状态进入奔跑状态(IsRun == true),播放奔跑动画(追击玩家);
      • 当unity酱被战龙追上的时候,战龙进入攻击状态(Attack1被触发,使用setTrigger函数),播放Attack1动画,播放完一次以后回到idle状态;
        这里写图片描述
    • 地图制作:使用4个cube制作四周的墙体,然后使用6个cube制作隔离墙壁,将地图分割为四块:
      这里写图片描述

  • 编写代码:

    • unity酱控制代码:
      主要是几个动作的函数,提供给firstSceneController调用:

      • run奔跑函数–接受用户输入控制unity酱的奔跑和转向;
      • jump跳跃函数–接受用户输入控制unity酱的跳跃;
      • cry哭泣–游戏结束时被调用;
      using UnityEngine;
      using System.Collections;
      
      // 必要なコンポーネントの列記
      [RequireComponent(typeof(Animator))]
      [RequireComponent(typeof(CapsuleCollider))]
      [RequireComponent(typeof(Rigidbody))]
      
      public class Player
      {
          GameObject myGirl;
          private bool isJump = false;
      
          public float animSpeed = 1.3f;
          public float jumpAnimSpeed = 1f;
          public float lookSmoother = 3.0f;
          public bool useCurves = true;
          public float useCurvesHeight = 1f;
      
          // 前進速度
          public float forwardSpeed = 7.0f;
          // 後退速度
          public float backwardSpeed = 5.0f;
          // 旋转速度
          public float rotateSpeed = 1.3f;
          // 跳跃时施加的力
          public float jumpPower = 5.0f;
      
          private CapsuleCollider col;
          private Rigidbody rb;
      
          private Vector3 velocity;
      
          private float orgColHight;
          private Vector3 orgVectColCenter;
      
          private Animator anim;
          private AnimatorStateInfo currentBaseState;
      
          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");
      
          public Player()
          {
              myGirl = GameObject.FindWithTag("Player");
              anim = myGirl.GetComponent<Animator>();
              col = myGirl.GetComponent<CapsuleCollider>();
              rb = myGirl.GetComponent<Rigidbody>();
              orgColHight = col.height;
              orgVectColCenter = col.center;
          }
      
          public void run(float h, float v)
          {
              anim.SetFloat("Speed", v);
              anim.SetFloat("Direction", h);
      
              rb.useGravity = true;
      
              velocity = new Vector3(0, 0, v);
              velocity = myGirl.transform.TransformDirection(velocity);
      
              if (v > 0.1)
              {
                  velocity *= forwardSpeed;
              }
              else if (v < -0.1)
              {
                  velocity *= backwardSpeed;
              }
      
              myGirl.transform.localPosition += velocity * Time.fixedDeltaTime;
              myGirl.transform.Rotate(0, h * rotateSpeed, 0);
          }
      
          public void jump()
          {
              if (isJump == false)
              {
                  if (currentBaseState.fullPathHash == locoState)
                  {
                      if (!anim.IsInTransition(0))
                      {
                          rb.AddForce(Vector3.up * jumpPower, ForceMode.VelocityChange);
                          anim.SetBool("Jump", true);
                      }
                  }
              }
          }
      
          public void cry()
          {
              if (myGirl.transform.position.y > 1)
              {
                  myGirl.transform.position = new Vector3(myGirl.transform.position.x, 0, myGirl.transform.position.z);
              }
              anim.SetBool("GameOver",true);
          }
      
          public void myUpdate()
          {
              if (myGirl.transform.position.y <= 0)
              {
                  isJump = false;
                  anim.speed = animSpeed;
              }
              else
              {
                  anim.speed = jumpAnimSpeed;
              }
      
              currentBaseState = anim.GetCurrentAnimatorStateInfo(0);
      
              if (currentBaseState.fullPathHash == locoState)
              {
                  if (useCurves)
                  {
                      resetCollider();
                  }
              }
              else if (currentBaseState.fullPathHash == jumpState)
              {
      
                  if (!anim.IsInTransition(0))
                  {
                      if (useCurves)
                      {
                          float jumpHeight = anim.GetFloat("JumpHeight");
                          float gravityControl = anim.GetFloat("GravityControl");
                          if (gravityControl > 0)
                              rb.useGravity = false;
      
                          Ray ray = new Ray(myGirl.transform.position + Vector3.up, -Vector3.up);
                          RaycastHit hitInfo = new RaycastHit();
      
                          if (Physics.Raycast(ray, out hitInfo))
                          {
                              if (hitInfo.distance > useCurvesHeight)
                              {
                                  col.height = orgColHight - jumpHeight;
                                  float adjCenterY = orgVectColCenter.y + jumpHeight;
                                  col.center = new Vector3(0, adjCenterY, 0);
                              }
                              else
                              {
                                  resetCollider();
                              }
                          }
                      }
                      anim.SetBool("Jump", false);
                  }
              }
      
              else if (currentBaseState.fullPathHash == idleState)
              {
      
                  if (useCurves)
                  {
                      resetCollider();
                  }
      
                  if (Input.GetButtonDown("Jump"))
                  {
                      anim.SetBool("Rest", true);
                  }
              }
      
              else if (currentBaseState.fullPathHash == restState)
              {
                  if (!anim.IsInTransition(0))
                  {
                      anim.SetBool("Rest", false);
                  }
              }
          }
      
          void resetCollider()
          {
              col.height = orgColHight;
              col.center = orgVectColCenter;
          }
      }
      
    • 战龙控制代码:
      主要是几个触发函数:

      • OnCollisionEnter函数–战龙发生碰撞时调用,如果撞到墙壁就转90度,撞到玩家就游戏结束;
      • OnTriggerEnter函数–玩家进入战龙巡逻范围,战龙将设置isRun为true,播放奔跑动画,对玩家进行追击;
      • OnTriggerStay函数–玩家停留在战龙巡逻范围的时候战龙将实时获取玩家位置,从而完成对玩家的追击(因为玩家的位置一直在变化,所以需要一直更新这个位置从而完成追击);
      • OnTriggerExit–游戏结束时被调用,setTrigger(Attack_1),播放一次攻击动画,然后进入idle状态;
      • FixedUpdate函数则根据战龙状态对战龙的位置进行移动(巡逻状态下没过一段时间转90度从而完成正方形巡逻、追击状态下速度加快并持续变换战龙方向使其朝向玩家,并且在朝向上发生位移达到追击效果、游戏结束则停止移动,播放一次攻击动作);
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class Monster : MonoBehaviour {
          private Rigidbody rg;
          private Animator anim;
          private CapsuleCollider col;
          private AnimatorStateInfo currentBaseState;
      
          static int walkState = Animator.StringToHash("Base Layer.walk");
          static int runState = Animator.StringToHash("Base Layer.run");
          static int idleState = Animator.StringToHash("Base Layer.idle");
          static int attackState = Animator.StringToHash("Base Layer.attack_1");
      
          private float time;
          private float timePassed;
      
          private bool isRun = false;
          private Vector3 pos;
      
          private float length = 13;
          private float walkSpeed = 3;
          private float runSpeed = 5;
      
          // Use this for initialization
          void Start () {
              anim = GetComponent<Animator>();
              col = GetComponent<CapsuleCollider>();
              rg = GetComponent<Rigidbody>();
              rg.useGravity = true;
              anim.SetBool("IsWalk", true);
              setTime();
          }
      
          private void FixedUpdate()
          {
              if (Director.getInstance().getFirstController().isGameOver())
              {
                  return;
              }
      
              if (isRun == true)
              {
                  anim.SetBool("IsRun",true);
                  Vector3 dir = pos - transform.position;
                  dir = dir.normalized;
                  Quaternion rotation = Quaternion.LookRotation(dir, Vector3.up);
                  rotation.x = 0;
                  rotation.z = 0;
                  transform.rotation = rotation;
      
                  Vector3 velocity0 = new Vector3(0, 0, runSpeed);
                  velocity0 = transform.TransformDirection(velocity0);
                  velocity0.y = 0;
      
                  transform.localPosition += velocity0 * Time.fixedDeltaTime;
                  return;
              } else
              {
                  anim.SetBool("IsRun", false);
              }
      
              if (timePassed >= time)
              {
                  transform.Rotate(0, 90, 0);
                  timePassed = 0;
                  return;
              }
      
              Vector3 velocity = new Vector3(0, 0, walkSpeed);
              velocity = transform.TransformDirection(velocity);
      
              transform.localPosition += velocity * Time.fixedDeltaTime;
              timePassed += Time.fixedDeltaTime;
          }
      
          private void OnCollisionEnter(Collision collision)
          {
      
              if (collision.gameObject.tag == "Wall")
              {
                  isRun = false;
                  transform.Rotate(0, 90, 0);
                  setTime();
              } else if (collision.gameObject.tag == "Player")
              {
                  Director.getInstance().getFirstController().gameOver();
                  anim.SetBool("IsWalk",false);
                  anim.SetTrigger("Attack_1");
              }
          }
      
          private void OnTriggerEnter(Collider other)
          {
              if (other.gameObject.tag == "Player")
              {
                  isRun = true;
                  pos = other.gameObject.transform.position;
              }
          }
      
          private void OnTriggerStay(Collider other)
          {
              if (other.gameObject.tag == "Player")
              {
                  pos = other.gameObject.transform.position;
              }
          }
      
          private void OnTriggerExit(Collider other)
          {
              if (other.gameObject.tag == "Player")
              {
                  isRun = false;
                  ScoreRecorder.getInstance().addScore();
              }
          }
      
          private void setTime()
          {
              System.Random random = new System.Random();
              time = random.Next(2,3);
              timePassed = 0;
          }
      }
      
    • 战龙工厂:用于生产战龙(之前UML图解释部分已经做出详细解释,这里就不说了)

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class MonsterFactory {
          private static MonsterFactory _instance;
          private List<GameObject> monsters;
          private GameObject dragon;
      
          public static MonsterFactory getInstance() {
              if (_instance == null)
              {
                  _instance = new MonsterFactory();
              }
              return _instance;
          }
      
          public void setPrefabs(GameObject prefab)
          {
              dragon = prefab;
              monsters = new List<GameObject>();
          }
      
          public GameObject getMonster()
          {
              for (int num = 0; num < monsters.Count; num++)
              {
                  if (monsters[num].activeInHierarchy == false)
                  {
                      monsters[num].SetActive(true);
                      return monsters[num];
                  }
              }
              Debug.Log("tets");
              monsters.Add(GameObject.Instantiate(dragon) as GameObject);
              monsters[monsters.Count - 1].SetActive(true);
              return monsters[monsters.Count - 1];
          }
      
          // 由于不需要销毁对象,所以free不用写
          public void free(GameObject temp)
          {
              temp.SetActive(false);
          }
      }
      
    • 场记代码 : 提供用户接口给UserInterface类调用

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using UnityEngine.SceneManagement;
      
      public class FirstController : MonoBehaviour, SceneController, UserAction
      {
          private bool isgameOver = false;
          Player myGirl;
          public GameObject monster;
      
          private void Awake()
          {
              Director.getInstance().setFirstController(this);
              MonsterFactory.getInstance().setPrefabs(monster);
          }
      
          // Use this for initialization
          void Start () {
              myGirl = new Player();
              Director.getInstance().getFirstController().LoadResources();
          }
      
          private void Update()
          {
              myGirl.myUpdate();
          }
      
          public void LoadResources () {
              GameObject temp1 = MonsterFactory.getInstance().getMonster();
              temp1.transform.position = new Vector3(3,0,0);
              GameObject temp2 = MonsterFactory.getInstance().getMonster();
              temp2.transform.position = new Vector3(-3, 0, 0);
              GameObject temp3 = MonsterFactory.getInstance().getMonster();
              temp3.transform.position = new Vector3(3, 0, 10);
              GameObject temp4 = MonsterFactory.getInstance().getMonster();
              temp4.transform.position = new Vector3(-3, 0, 10);
          }
      
          public void movePlayer(float h, float v) {
              myGirl.run(h, v);
          }
          public void jump() {
              myGirl.jump();
          }
          public bool isGameOver() {
              return isgameOver;
          }
          public void reStart() {
              SceneManager.LoadScene(SceneManager.GetActiveScene().name);
              ScoreRecorder.getInstance().setScore(0);
          }
      
          public void gameOver()
          {
              isgameOver = true;
              myGirl.cry();
          }
      }
      
      
    • 用户交换部分:UserInterface类–主要是接受并处理用户的前后左右输入、空格键输入、点击输入(重开游戏、游戏提示);

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using UnityEngine.UI;
      
      public class UserInterface : MonoBehaviour {
          private ScoreRecorder scoreRecorder;
          private UserAction action;
      
          // Use this for initialization
          void Start () {
              action = Director.getInstance().getFirstController();
              scoreRecorder = ScoreRecorder.getInstance();
          }
      
          // Update is called once per frame
          void Update () {
              if (action.isGameOver())
              {
                  action.movePlayer(0, 0);
                  return;
              }
              if (Input.GetButtonDown("Jump"))
              {
                  action.jump();
              }
              float h = Input.GetAxis("Horizontal");
              float v = Input.GetAxis("Vertical");
              action.movePlayer(h, v);
          }
      
          private void OnGUI()
          {
              // 游戏信息
              GUI.Box(new Rect(Screen.width - 260, 10, 250, 110), "操作方法");
              GUI.Label(new Rect(Screen.width - 245, 30, 250, 30), "上/下 键 : 向前跑/向后退");
              GUI.Label(new Rect(Screen.width - 245, 50, 250, 30), "左/右 键 : 向左转/向右转");
              GUI.Label(new Rect(Screen.width - 245, 70, 250, 30), "奔跑时按下空格键 : 跳跃");
              GUI.Label(new Rect(Screen.width - 245, 90, 250, 30), "停下的时候按下空格键 : 卖萌");
      
              string gameName = "躲避怪物";
              string gameRules = "操作任务躲避怪物的追捕,每躲过一只怪物分数加一";
              // 显示游戏名字和游戏规则信息
              if (GUI.RepeatButton(new Rect(10, 10, 120, 20), gameName))
              {
                  GUI.TextArea(new Rect(10, 40, Screen.width - 20, Screen.height / 2), gameRules);
              }
              // 重开游戏按钮
              else if (GUI.Button(new Rect(140, 10, 70, 20), "重新开始"))
              {
                  action.reStart();
              }
      
              if (action.isGameOver())
              {
                  GUI.Box(new Rect(Screen.width - 340, 37, 70, 23), "游戏结束");
              }
      
              GUI.Box(new Rect(Screen.width - 340, 10, 70, 23), "分数:" + scoreRecorder.getScore());
          }
      }
      
    • 记分员类: 这个类在前面的UML图中也说过了,就不说了

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class ScoreRecorder {
          public static ScoreRecorder _instance;
      
          private int Score = 0;
          public void addScore()
          {
              Score++;
          }
          public int getScore()
          {
              return Score;
          }
          public void setScore(int num)
          {
              Score = num;
          }
          public static ScoreRecorder getInstance()
          {
              if (_instance == null)
              {
                  _instance = new ScoreRecorder();
              }
              return _instance;
          }
      }
      
    • 导演类:

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public class Director : System.Object
      {
          private static Director _instance;
          private FirstController firstSceneController;
      
          public static Director getInstance()
          {
              if (_instance == null)
              {
                  _instance = new Director();
              }
              return _instance;
          }
      
          public FirstController getFirstController()
          {
              return firstSceneController;
          }
      
          internal void setFirstController(FirstController gom)
          {
              if (null == firstSceneController)
              {
                  firstSceneController = gom;
              }
          }
      }
      
    • 几个接口类

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public interface SceneController {
          void LoadResources();
      }
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      public interface UserAction {
          void movePlayer(float h, float v);
          void jump();
          bool isGameOver();
          void reStart();
      }
      
还可以与的优化

至此,游戏制作基本完成,我们还可用通过修改战龙/玩家的奔跑速度、战龙的巡逻范围、调整画面颜色等来增加游戏体验。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值