一、前言

本次3D游戏编程我们将设计一个智能巡逻兵游戏。

二、游戏基本内容及规定

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

三、人物模型与动画制作

人物模型采用了官方的一个第三人称控制资源包Starter Assets,下载导入即可。

由于该资源包自带人物动画,因此我们只需要Assets->Create->Animator Controller创建一个新的动画状态机,然后将走、跑、站立三个状态的动画拖进去设置好状态即可。

此外本次游戏还涉及到了墙、地板等其他预制件的制作,在这里就不再赘述,项目地址已留在文章末尾,感兴趣的读者可以在下载下来看看。

三、游戏实现

本次游戏实现运用到了之前所学的诸多编程理念,诸如MVC模式、工厂模式、单例模式、消息订阅/发布模式等等,具体内容可以参考之前的作业。
由于篇幅限制,之前作业中分析过的代码就不再做分析了,我们主要关注新增加的以及发生变化的代码。

  • SceneController
    SceneController负责加载资源,比如房间、玩家、巡逻兵等,以及调度调用巡逻兵的动作管理器使其开始走动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class SceneController : MonoBehaviour, IUserAction, ISceneController{public GuardFactory guardFactory;  //巡逻兵工厂public ScoreRecorder recorder;  //记分员public GuardActionManager actionManager;    //运动管理器public List<GameObject> cells;  //每个小房间public GameObject player;   //玩家public UserGUI gui;     //交互界面public PlayerEventPublisher playerEventPublisher;   //事件发布者private List<GameObject> guards;    //场景中所有的巡逻兵private bool game_over = false;     //游戏结束private void Start() {//初始化这个脚本的相关控件SSDirector director = SSDirector.GetInstance();director.CurrentScenceController = this;guardFactory = gameObject.AddComponent<GuardFactory>() as GuardFactory;recorder = gameObject.AddComponent<ScoreRecorder>() as ScoreRecorder;actionManager = gameObject.AddComponent<GuardActionManager>() as GuardActionManager;gui = gameObject.AddComponent<UserGUI>() as UserGUI;LoadResources();}/*ISceneController接口相关函数*/public void LoadResources(){//加载房间for(int i=0;i<9;i++){GameObject cell = Instantiate(Resources.Load<GameObject>("Prefabs/Cell"));int a = i % 3;int b = i / 3;cell.transform.position = new Vector3(12 * a, 0, 12 * b);cells.Add(cell);}//加载玩家player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(24, 0, 24), Quaternion.identity) as GameObject;playerEventPublisher = player.GetComponent<PlayerEventPublisher>();     //加载巡逻兵guards = guardFactory.GetGuards();//让每个巡逻兵都订阅玩家的事件发布者//并让每个巡逻兵调用动作管理器开始走动for (int i = 0; i < guards.Count; i++) {IEventHandler e = guards[i].GetComponent<PatrolData>() as IEventHandler;playerEventPublisher.AddListener(e);actionManager.GuardWalk(guards[i], player);}}/*IUserAction接口相关函数*/public int GetScore() {return recorder.GetScore();}public bool isOver() {return game_over;}public void Restart() {//重新生成这个场景即可SceneManager.LoadScene("Scenes/SampleScene");}/*动作管理器会回调的函数*/public void Record() {recorder.Record();}/*玩家管理器会回调的函数*/public void Gameover() {game_over = true;}
}
  • PlayerController
    PlayerController负责:
    ①控制玩家的动画设置,通过检测方向键以及shift的变化使得玩家对象进行行走或奔跑动作;
    ②综合第三人称视角方向以及方向键来设置角色移动方向;
    ③根据鼠标移动更新相机的位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerController : MonoBehaviour
{private Animator animator;  //动画组件private new Rigidbody rigidbody;    //刚体组件private new Camera camera;  //视角跟随相机组件private Vector3 offset;     //相机偏移量public float walk_speed = 3.0f; //行走速度public float run_speed = 6.0f;  //跑步速度private float speed;    //当前速度private float rotate_speed = 3.0f;//旋转速度private float camera_rotate;    //当前旋转速度public bool isLive = true;      //游戏是否进行中void Start(){//获取主相机camera = Camera.main;//设置相机的偏移offset = this.transform.position;camera.transform.position = offset + new Vector3(0, 2, -4);//需要刚体组件来协助移动rigidbody = GetComponent<Rigidbody>();//初始状态为行走speed = walk_speed;     //获取动画组件animator = GetComponent<Animator>();}private void FixedUpdate() {//获取方向键输入Vector3 vel = rigidbody.velocity;float v = Input.GetAxis("Vertical");float h = Input.GetAxis("Horizontal");bool isMove = (Mathf.Abs(v) > 0.05 || Mathf.Abs(h) > 0.05);//防止误触//判断游戏是否进行,来确定是否可以采用走路、跑步动画animator.SetBool("isLive", isLive);//如果有方向键变化,则设置动画为走if(isMove){animator.SetBool("isWalk", true);}if(!isMove){animator.SetBool("isWalk", false);}//Shift切换走跑动作if(Input.GetKeyDown("left shift")){bool ret = animator.GetBool("isRun");animator.SetBool("isRun", !ret);speed = run_speed;}//在游戏进行状态下,控制角色移动if(isMove && isLive){//获得相机的正对方向camera_rotate = camera.transform.eulerAngles.y / 180 * Mathf.PI;//给刚体一个速度,让角色向镜头方向,并综合方向键进行移动float sr = Mathf.Sin(camera_rotate);float cr = Mathf.Cos(camera_rotate);rigidbody.velocity = new Vector3((v * sr + h * cr) * speed, 0, (v * cr - h * sr) * speed);//角色也要面向镜头前方的位置transform.rotation = Quaternion.LookRotation(new Vector3((v * sr + h * cr), 0, (v * cr - h * sr)));}else{rigidbody.velocity = Vector3.zero;}}/*采用LateUpdate来加载相机的相关属性*/private void LateUpdate() {//更新相机的位置,跟随camera.transform.position += this.transform.position - offset;offset = this.transform.position;//鼠标控制相机水平移动float mouseX = Input.GetAxis("Mouse X") * rotate_speed;camera.transform.RotateAround(this.transform.position, Vector3.up, mouseX);}
}
  • SSEventPublishier和PlayerPublishier
    SSEventPublishier负责:
    ①通知所有订阅者;
    ②在SSEventPublishier的子类PlayerPublishier每一次进行Update时,进行检测角色是否出界以及目前所在的房间位置
public class SSEventPublisher : MonoBehaviour {private List<IEventHandler> listeners;public SSEventPublisher(){listeners = new List<IEventHandler>();}//通知所有的订阅者public void Notify(int e){for(int i=0; i<listeners.Count; i++){IEventHandler eventHandler = listeners[i];eventHandler.Reaction(e);}}public void AddListener(IEventHandler e){listeners.Add(e);}public void RemoveListner(IEventHandler e){listeners.Remove(e);}
}public class PlayerEventPublisher : SSEventPublisher{private int player_sign;    //当前玩家所处的房间public SceneController sceneController;private void Start() {sceneController = SSDirector.GetInstance().CurrentScenceController as SceneController;}private void Update() {//获得当前所在房间号int ret = FindPosition(this.transform.position);//发生变化if(ret != player_sign){if(ret >= 0 && ret <= 8){//玩家还在9个房间中,通知所有巡逻兵,让他们的GuardData变化,从而采取不同操作player_sign = ret;Notify(player_sign);}else{//玩家出界GameOver();}}}private int FindPosition(Vector3 pos){int a = Mathf.FloorToInt((pos.x + 6) / 12); //以中心定位,因此需要加上偏移int b = Mathf.FloorToInt((pos.z + 6) / 12);if(a >= 0 && a <= 2 && b >= 0 && b <= 2){return 3 * a + b;}else{return -1;}}private void OnCollisionEnter(Collision other) {if(other.gameObject.name.Contains("Guard")){//撞到巡逻兵,游戏结束GameOver();}}private void GameOver(){//通知三方做处理://玩家控制器灭活,取消玩家的动画和移动PlayerController playerController = GetComponent<PlayerController>();playerController.isLive = false;//场景总控制器sceneController.Gameover();//巡逻兵的GuardData变化,取消巡逻兵的动作(立刻回调)并取消动画Notify(-1);}
}
  • PatrolData
    PatrolData负责提供巡逻兵所需的数据以及一些事件处理,例如结束灭活等。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PatrolData : MonoBehaviour, IEventHandler
{public float walk_speed = 0.5f;        //移速public float run_speed = 0.5f;public int sign;                      //巡逻兵所在区域public bool isRun = false;            //当前是否在追逐玩家public bool isLive = true;            //当前是否活跃public Vector3 start_position;        //巡逻兵初始位置   /*IEventHandler响应函数*/public void Reaction(int pos){//游戏结束,灭活,取消巡逻兵的动作(立刻回调)并取消动画if(pos == -1){isLive = false;return;}//若玩家处于巡逻兵一样的位置,开始追逐;否则,结束追逐isRun = (pos == sign);}
}
  • PatrolFactory
    PatrolFactory用于生成巡逻兵,因此有一个队列放置创建好的巡逻兵object并且用作返回,具体生成逻辑见代码注释。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GuardFactory : MonoBehaviour
{private GameObject guard = null;                               //巡逻兵private List<GameObject> used = new List<GameObject>();        //正在使用的巡逻兵列表private Vector3[] vec = new Vector3[9];                        //每个巡逻兵的初始位置public List<GameObject> GetGuards() {//为每一个巡逻兵,在一定范围内随机生成位置。int[] pos_x = {-5, 7, 19};int[] pos_z = {-5, 7, 19};int index = 0;for(int i=0;i < 3;i++) {for(int j=0;j < 3;j++) {pos_x[i] += Random.Range(0,3);pos_z[i] += Random.Range(0,3);vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);index++;}}//加载巡逻兵,初始化位置和sign,并放在数组中。for(int i = 0; i < 8; i++) {guard = Instantiate(Resources.Load<GameObject>("Prefabs/Guard"));guard.transform.position = vec[i];guard.GetComponent<PatrolData>().sign = i;guard.GetComponent<PatrolData>().start_position = vec[i];used.Add(guard);}   return used;}
}
  • PatrolWalkAction
    PatrolWalkAction负责巡逻兵行走动作的管理,具体包括:①初始化的时候, GuardPatrolAction加载参数, 并且让巡逻兵播放行走的动画;②随机生成一个边长数值,令巡逻兵沿着某个方向走随机距离
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GuardWalkAction : SSAction
{         private float pos_x, pos_z;     //移动位置记录private float move_length;      //移动的边长private bool turn_sign = true;  //是否转向        private int dirction = 0;       //当前方向private PatrolData data;         //巡逻兵数据private Animator anim;          //巡逻兵动画private Rigidbody rigid;        //巡逻兵刚体public override void Start(){//加载组件data = gameobject.GetComponent<PatrolData>();anim = gameobject.GetComponent<Animator>();rigid = gameobject.GetComponent<Rigidbody>();//设置走动动画anim.SetBool("isLive", true);anim.SetBool("isWalk", true);anim.SetBool("isRun", false);}public static GuardWalkAction GetSSAction(Vector3 location) {//确定初始位置GuardWalkAction action = CreateInstance<GuardWalkAction>();action.pos_x = location.x;action.pos_z = location.z;//设定移动矩形的边长action.move_length = Random.Range(6, 10);return action;}public override void FixedUpdate(){//观察GuardData中的变量isLive(Reaction会改变这个变量)//若被灭活,则取消巡逻兵的动作(立刻回调)并取消动画if(!data.isLive){anim.SetBool("isLive", false);this.destroy = true;this.callback.SSActionEvent(this, SSActionEventType.Competeted, -1, this.gameobject);return;}//若不被灭活,则按规则走动Walk();//观察GuardData中的变量isRun(Reaction会改变这个变量)//如果玩家进入该区域则开始追击if(data.isRun){this.destroy = true;this.callback.SSActionEvent(this, SSActionEventType.Competeted, 0, this.gameobject);}}private void Walk(){if (turn_sign) {//按照矩形逆时针移动switch (dirction) {case 0:pos_x += move_length;break;case 1:pos_z += move_length;break;case 2:pos_x -= move_length;break;case 3:pos_z -= move_length;break;}turn_sign = false;}//获取目标位置,并转向目标位置Vector3 dir = new Vector3(pos_x, gameobject.transform.position.y, pos_z);gameobject.transform.LookAt(dir);//由于有高低差等缺陷,因此采用距离而不是比较直接相等float distance = Vector3.Distance(dir, this.transform.position);if(distance > 0.9){//未到达,需要借助RigidBody移动Vector3 vec = data.walk_speed * gameobject.transform.forward;rigid.MovePosition(gameobject.transform.position + 10 * vec * Time.deltaTime);}else{//到达,转向Turn();}}private void Turn(){dirction = (dirction + 1) % 4;turn_sign = true;}private void OnCollisionEnter(Collision other) {if(other.gameObject.name.Contains("Wall")){//撞墙,转向Turn();}}
}
  • PatrolRunAction
    PatrolRunAction负责巡逻兵奔跑动作的管理,具体逻辑见代码注释。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GuardRunAction : SSAction
{private GameObject player;        //玩家private PatrolData data;           //巡逻兵数据private Animator anim;            //巡逻兵动画private Rigidbody rigid;          //巡逻兵刚体public override void Start() {//初始化组件data = gameobject.GetComponent<PatrolData>();anim = gameobject.GetComponent<Animator>();rigid = gameobject.GetComponent<Rigidbody>();//设置跑动的动画anim.SetBool("isLive", true);anim.SetBool("isWalk", true);anim.SetBool("isRun", true);}public static GuardRunAction GetSSAction(GameObject player) {GuardRunAction action = CreateInstance<GuardRunAction>();action.player = player;return action;}public override void FixedUpdate() {//观察GuardData中的变量isLive(Reaction会改变这个变量)//若被灭活,则取消巡逻兵的动作(立刻回调)并取消动画if(!data.isLive){anim.SetBool("isLive", false);this.destroy = true;this.callback.SSActionEvent(this, SSActionEventType.Competeted, -1, this.gameobject);return;}//若不被灭活,则跑向玩家Run();//观察GuardData中的变量isRun(Reaction会改变这个变量)//如果玩家脱离该区域则继续巡逻if (!data.isRun) {this.destroy = true;this.callback.SSActionEvent(this, SSActionEventType.Competeted, 1, this.gameobject);}}private void Run(){//控制巡逻兵跑向玩家Vector3 dir = player.transform.position - player.transform.forward; //玩家偏后一点的位置gameobject.transform.LookAt(dir);   //转向Vector3 vec = data.run_speed * gameobject.transform.forward;    //设置移动向量rigid.MovePosition(gameobject.transform.position + 5 * vec * Time.deltaTime);   //借助RigidBody移动}
}
  • PatrolActionManager
    PatrolActionManager负责管理巡逻兵的两个动作之间的切换,即行走和奔跑,对上文所提及的两个动作类进行了调用,具体逻辑见下面代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GuardActionManager : SSActionManager, ISSActionCallback {private GameObject player;    //玩家public SceneController sceneController;private void Start() {sceneController = SSDirector.GetInstance().CurrentScenceController as SceneController;sceneController.actionManager = this;}public void GuardWalk(GameObject guard, GameObject player) {//首先需要让巡逻兵走动this.player = player;GuardWalkAction walkAction = GuardWalkAction.GetSSAction(guard.transform.position);this.RunAction(guard, walkAction, this);}public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,int intParam = 0, GameObject objectParam = null) {if (intParam == 0) {//巡逻状态返回,开始追逐GuardRunAction runAction = GuardRunAction.GetSSAction(player);this.RunAction(objectParam, runAction, this);} else if(intParam == 1){//追逐状态返回,开始巡逻GuardWalkAction walkAction = GuardWalkAction.GetSSAction(objectParam.GetComponent<PatrolData>().start_position);this.RunAction(objectParam, walkAction, this);sceneController.Record();}//对于其他状态,则是动作直接销毁,不再生成新动作}
}

四、游戏效果

游戏效果如下:

  • 游戏开始

  • 游戏结束

演示视频:智能巡逻兵 演示视频

项目地址:HW7

五、参考资料

[1]. unity官方文档

[2]. 师兄的博客

3D游戏编程学习笔记(七):模型与动画相关推荐

  1. 3D游戏编程学习笔记(五):与游戏世界交互

    一.前言 本次3D游戏编程我们将设计一个简单打飞碟(Hit UFO)有游戏. 二.游戏基本内容及规定 游戏基本内容 游戏有 n 个 round,每个 round 都包括10 次 trial: 每个 t ...

  2. 3D游戏设计读书笔记七

    3D游戏设计读书笔记七 智能巡逻兵 提交要求: 游戏设计要求: 创建一个地图和若干巡逻兵(使用动画): 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址.即每次确定下一个目标位置,用自己当前位 ...

  3. DirectX 11游戏编程学习笔记之1: 开场白

    本文由哈利_蜘蛛侠原创,转载请注明出处!有问题欢迎联系2024958085@qq.com 这是我之前的博客系列"DirectX9.0c游戏开发手记之'龙书'第二版学习笔记"的平行版 ...

  4. 【逐梦旅程Windows游戏编程学习笔记 ①】基本GDI绘图

    近半年各种忙碌,一直没写博客,现在得空学习记录一下,原书为<逐梦旅程 Windows游戏编程之从零开始>毛星云编著 实现样式与功能: 实现功能: 1,显示title:"致我们.. ...

  5. java2d游戏代码_JAVA游戏编程学习笔记(三)Java 2D游戏底层绘图框架

    前二篇记录了java如何绘制图形与动画,今天打算总结复习一下,把这些知识点集合起来,制作一个Java2D小游戏框架(暂且这么叫,好像挺牛逼似的!). Java AWT 下边提供一个 class Can ...

  6. Windows游戏编程学习笔记

    1.文件类型: ipch文件夹和.sdf文件:这两个文件都是Visual Studio用来保存预编译的头文件和Intellisense用的.(可删除,对于工程开发没有影响) Debug文件夹:存放着编 ...

  7. Python微信打飞机游戏编程学习笔记01

    刚学习Python,看别人写的小游戏,照搬照学照写,纯手工手打,一步步,加深印象,加深学习 运行环境是: Python 3.7.1   pygame 1.9.4 微信很火的打飞机游戏拿了学习下 第一步 ...

  8. Python微信打飞机游戏编程学习笔记02

    继上一段的代码.继续完善中 此段代码主要 1.增加了主飞机的载入,并且是动态效果的主飞机 2.增加了主飞机的移动控制 终于有了游戏互动的感觉...继续加油 import pygame #导入pygam ...

  9. Python计算机视觉编程学习笔记 七 图像搜索

    图像搜索 (一)基于内容的图像检索 (二)视觉单词 2.1 :创建词汇 2.2 :创建图像索引 2.3 :在数据库中搜索图像 (三)使用几何特性对结果排序 (一)基于内容的图像检索 CBIR(Cont ...

最新文章

  1. 最小割 ---- 二分图最大独立集(集合冲突模型) ---- 骑士共存 方格取数(网络流24题)
  2. ubuntu16.04 intel_rapl : no valid rapl domains found in packge0
  3. 索泰显卡超频软件测试要多少时间,索泰显卡专用超频软件_FireStorm显卡超频 V2.0.1 官方版...
  4. 无意间看到的浏览器记录......
  5. c java 系统开发_java开发系统内核:使用C语言开发系统应用程序
  6. mysql日活统计函数_如何通过简化日活模型,预估一个产品的日活(DAU)?
  7. 详谈P(查准率),R(查全率),F1值
  8. mongodb在aggregate lookup 进行分页查询,获得记录总数
  9. PHP利用PDO从mysql读取大量数据处理(可做大量数据集的导出,业务调整等)
  10. Oracle常用数据库操作SQL
  11. Open3d之内部形状描述子ISS
  12. 【有返回值的回溯法】剑指offer——面试题66:矩阵中的路径(回溯法)
  13. 【读书笔记《Android游戏编程之从零开始》】15.游戏开发基础(剪切区域)
  14. 汤家凤:九月前强化复习结束不了怎么办?
  15. Python数学建模 空间插值
  16. php 爬虫图片代码,python爬虫入门教程之糗百图片爬虫代码分享
  17. 信号分析的短时傅里叶变换(scipy.signal.stft)
  18. typescript元组
  19. php 单笔转账到支付宝账户,php之支付宝转账或发红包到指定账户(提现功能)
  20. shiro.crypto.CryptoException: Unable to correctly extract the Initialization Vector or ciphertext

热门文章

  1. 首席新媒体运营黎想教程:一文详细解析用户运营
  2. C# 托管内存与非托管内存之间的转换
  3. 百度云解决迅雷一直寻找资源无法开始下载的问题
  4. 什么是延时电路?6种延时电路原理讲解
  5. 【动态规划信奥赛一本通】1285:最大上升子序列和(详细代码)
  6. Android WatchDog(4) - watchdog线程(Android 12)
  7. php转繁体代码,php在gbk编码下繁体与简体互转函数
  8. 第一部分 思科九年 一(2)
  9. java 解析csr文件_ANS.1结合CSR文件学习笔记
  10. MATLAB AHP AHP层次分析法code 自写代码 完美运行。 权重设计