• 一、主体代码架构说明
  • 二、发布与订阅模式
    • 相关类图
    • C#中的实现:delegate委托类型和event事件机制
    • 意义
    • 在本次游戏中的具体应用
  • 三、游戏主要行为分析
    • 1. 巡逻兵的行为

      • 与其他物体/角色相遇的处理
      • 相关动作:行走,转向
    • 2. 玩家行为
    • 3. 其他道具

Github上的项目代码和视频链接

一、主体代码架构说明

作用
GameController 场记,加载资源,管理得分和游戏状态
PatrolFactory 巡逻兵工厂
PatrolEventManager 事件发布者(抓捕成功和逃跑成功的事件)
PlayerController 玩家控制器,负责玩家的移动、碰撞检测、动画调用
PatrolController 巡逻兵控制器,负责检测巡逻兵的状态,动画调用
PatrolBody 检测巡逻兵的近距离碰撞,并告知PatrolController
PatrolAction 巡逻兵动作
PatrolActionManager 巡逻兵动作管理器
UserGUI 接收用户输入,传递给场记;显示得分

其中GameController.csUserGui.cs挂载在Main Camera上。

其余代码为基类或接口,和门面模式、动作管理器模式等有关,在此不作介绍。


二、发布与订阅模式

本周使用了发布与订阅模式。

相关类图

C#中的实现:delegate委托类型和event事件机制

在C#中,可以通过delegate委托类型和event事件机制来实现。

就我个人的粗浅理解来看,事件相当于一个列表,它可以存放多个有类似的签名、定义在不同类内的方法。定义某个委托类型,并用它创建事件,就是给这个列表指定方法的签名类型。这些方法内部可以有不同的实现/事件处理。

比如说,有一个要求,篮子里的苹果必须是绿色的。苹果是方法,“绿色”是方法的签名,这个要求是一个委托。由这个要求指定的篮子,就是由这个委托定义的事件。深绿色、浅绿色、黄绿色等等,就是加到这个事件上的不同的事件处理程序了。它们有类似的签名(“绿色”),但是有不同的方法体(颜色的深浅等)。

那么,在C#里,delegate相当于以上设计模式类图里的接口AttackHandle,event相当于Subject下的List< AttackHandle >。包含这个event的类就是Publisher,往这个event上添加自己的事件处理程序的类,就是Subscriber.

意义

那么这种模式有什么意义呢?我觉得有以下几点好处:

  • 在有多个类需要处理某个事件时,可以只用一个事件就通知到它们,让它们各自作出反应,而不必在这些类里分别检测事件并处理。这样可以将事件源与事件处理者解耦。
  • 允许有不同的处理方法。

在本次游戏中的具体应用

PatrolEventManager.cs 发布者

public class PatrolEventManager : MonoBehaviour {public static PatrolEventManager instance;public delegate void HuntAction();              //巡逻兵抓获成功public static event HuntAction OnHuntAction;public delegate void FleeAction();              //玩家逃跑成功public static event FleeAction OnFleeAction;// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {}public void flee(){if (OnFleeAction != null){OnFleeAction();}}public void hunt(){if (OnHuntAction != null){OnHuntAction();}}public static void Hunt(){instance.hunt();}public static void Flee(){instance.flee();}
}

GameController.cs 订阅者

//注册事件处理程序
public void OnEnable()
{PatrolEventManager.OnHuntAction += hunt;PatrolEventManager.OnFleeAction += flee;
}//注销事件处理程序
public void OnDisable()
{PatrolEventManager.OnHuntAction -= hunt;PatrolEventManager.OnFleeAction -= flee;
}//玩家成功逃脱后的处理:加分
void flee()
{score += 5;
}//玩家被抓到后的处理:结束游戏
void hunt()
{gameState = "Game over!";recycleAll();
}

三、游戏主要行为分析

窃以为,本次游戏的难点在于角色的行为,以及相关碰撞体/触发器的使用。

1. 巡逻兵的行为

  • 在遇到障碍物(包括别的角色)时不弹开,也不穿过。
  • 在遇到墙时转向,继续巡逻。
  • 在玩家进入其追捕范围时,追捕玩家。

与其他物体/角色相遇的处理

巡逻兵需要两个Collider,一个是Sphere Collider,挂在巡逻兵模型上,范围较大,用于感应玩家。感应到后,若玩家在当前区域,则开始追击。

相关的检测代码写在PatrolController.cs里:

private void OnTriggerEnter(Collider other)
{//玩家在当前区域,则追击if (isInZone(other.transform) && other.tag.Equals("Player")){player = other.transform;gameObject.GetComponent<Animator>().SetBool("ToAttack", true);}
}private void OnTriggerExit(Collider other)
{//玩家在当前区域但已离开追捕范围,则追捕失败,玩家得分if (isInZone(other.transform) && other.tag.Equals("Player")){player = null;gameObject.GetComponent<Animator>().SetBool("ToAttack", false);PatrolEventManager.Flee();}
}void Update () {//...if(isHunting && !isInZone(player))  //检测追击过程中玩家离开当前区域的情况{player = null;gameObject.GetComponent<Animator>().SetBool("ToAttack", false);PatrolEventManager.Flee();}
}

另一个是Box Collider,挂在巡逻兵模型下的空的子物体上,范围较小,用于检测巡逻兵将要撞上其他物体的情况。相关的检测代码写在PatrolBody.cs里:

//检测近距离遇上其他物体的情况
private void OnTriggerEnter(Collider other)
{if (other.tag.Equals("wall")){patrolController.borderIsFront = true;}else if (other.tag.Equals("Player")){gameObject.transform.parent.GetComponent<PatrolController>().playerIsFront = true;}
}//离开其他物体
private void OnTriggerExit(Collider other)
{if (other.tag.Equals("wall")){gameObject.transform.parent.GetComponent<PatrolController>().borderIsFront = false;}else if (other.tag.Equals("Player")){gameObject.transform.parent.GetComponent<PatrolController>().playerIsFront = false;}
}

相关动作:行走,转向

这部分代码主要由PatrolAction.cs实现,PatrolController.cs提供相关的检测和标志。

//PatrolAction.cs//每次遇上区域边界后的旋转角度
public int rotateAngle;//检测巡逻兵状态,为PatrolAction提供改变的依据
private PatrolController patrolController;//..........public override void Update () {if (destroy){return;}if (patrolController.borderIsFront) //遇上边界则转向{gameobject.transform.Rotate(gameobject.transform.up, rotateAngle);patrolController.borderIsFront = true;}else if (patrolController.playerIsFront)    //遇上玩家,不移动,举枪攻击玩家{return;}else if (patrolController.isHunting)    //正在追击,则目标是玩家,要向着玩家移动{gameobject.transform.LookAt(patrolController.player);gameobject.transform.position = Vector3.MoveTowards(gameobject.transform.position, patrolController.player.position, 1.0f * Time.deltaTime);}else{   //以上情况都不是,进行普通的巡逻gameobject.transform.Translate(Vector3.forward * 1.0f * Time.deltaTime);}
}

其实可以把PatrolAction定义为几个更简单的动作,在动作结束时调用动作管理器的回调接口来告诉动作管理器,下一个动作应该怎么定义。但是为了方便,就将不同的具体动作写在同一个Action里来处理了。

  • 不能离开所在的区域。(避免巡逻兵从墙的缺口进入其他领域)
  • 只能在玩家进入所在的区域时追捕,不能隔着墙追捕玩家。

因为构建的地图坐标很有规律,所以使用坐标上下限来规定巡逻兵的巡逻区域。

PatrolController.cs

//巡逻区域的坐标范围
private float xmin, xmax, zmin, zmax;void Start () {patrol = gameObject.transform;xmin = (int)(patrol.position.x / 5) * 5 + 0.2f;xmax = (int)(patrol.position.x / 5 + 1) * 5 - 0.2f;zmin = (int)(patrol.position.z / 5) * 5 + 0.2f;zmax = (int)(patrol.position.z / 5 + 1) * 5 - 0.2f;
}

当感应到玩家时,用以下方法来检测玩家是否位于当前区域。这个方法也用在Update()里以检测巡逻兵是否要走出当前区域。

//用于检查某些角色是否在当前巡逻区域
private bool isInZone(Transform role)
{return zmin <= role.position.z && role.position.z <= zmax && xmin <= role.position.x && role.position.x <= xmax;
}void Update () {if(!isInZone(patrol))   //若巡逻兵走出了范围,则标记borderIsFront为true,将会通知PatrolAction来调整方向{//...........}if(isHunting && !isInZone(player))  //检测追击过程中玩家离开当前区域的情况{//...........}
}
  • 追上玩家时,杀死玩家。

在追捕过程中,会调用Shoot动画。

gameObject.GetComponent<Animator>().SetBool("ToAttack", true);

2. 玩家行为

这部分的相关代码写在PlayerController.cs里。

  • 由用户用方向键控制,按哪个方向键就沿哪个方向行走。

UserGUI.cs检测用户输入,调用GameController的movePlayer(),GameController的movePlayer()又调用PlayerController的movePlayer().

//移动玩家
public void movePlayer(float hor, float ver)
{if (isDead) return;if (ver != 0){//如果玩家不是在奔跑,则调用奔跑的动画if (!playerAnimator.GetBool("IsRunning")) playerAnimator.SetBool("IsRunning", true);if (ver > 0){player.forward = Vector3.forward;if (IsObstacleFront()) return; //如果玩家遇上了障碍,则不移动player.Translate(new Vector3(0, 0, ver));}else{player.forward = Vector3.back;if (IsObstacleFront()) return;player.Translate(new Vector3(0, 0, -ver));}}else if (hor != 0){if (!playerAnimator.GetBool("IsRunning")) playerAnimator.SetBool("IsRunning", true);if (hor > 0){player.forward = Vector3.right;if (IsObstacleFront()) return;player.Translate(new Vector3(0, 0, hor));}else{player.forward = Vector3.left;if (IsObstacleFront()) return;player.Translate(new Vector3(0, 0, -hor));}}else playerAnimator.SetBool("IsRunning", false);    //没有移动,则不奔跑
}
  • 在遇到障碍物(包括别的角色)时不弹开,也不穿过。

  • 在遇到墙时,只要用户不改变所按的方向键,就会一直在墙前。

玩家模型加了一个Box Collider,勾选了IsTrigger.

(PS:如果Box Collider的位置或大小不够好,玩家遇到墙时,因为待机动画本身的特性,头会撞进墙……)

在OnTriggerEnter()和OnTriggerExit()中会检测障碍物。

在movePlayer()中,会在确定玩家方向后检测障碍物是否在前方,是的话则不移动玩家。

//遇到的其他物体/角色
private Transform obstacle = null;
//以什么方向遇上障碍物
private Vector3 obstacleDirection = Vector3.zero;//是否正对着障碍物
private bool IsObstacleFront()
{return obstacleDirection == player.forward;
}//检测是否遇上障碍
private void OnTriggerEnter(Collider other)
{if (other.tag.Equals("wall")){obstacleDirection = player.forward;}else if (other.tag.Equals("PatrolBody"))    //遇上巡逻兵,死亡{isDead = true;playerAnimator.SetBool("IsDead", true);}
}//离开障碍
private void OnTriggerExit(Collider other)
{obstacleDirection = Vector3.zero;
}//movePlayer()中
if (IsObstacleFront()) return; //如果玩家遇上了障碍,则不移动
  • 被巡逻兵追上则死亡。

设置Animator的布尔类型的变量IsDead为true,播放Dead动画。

private void OnTriggerEnter(Collider other)
{if (other.tag.Equals("wall")){obstacleDirection = player.forward;}else if (other.tag.Equals("PatrolBody"))    //遇上巡逻兵,死亡{isDead = true;playerAnimator.SetBool("IsDead", true);}
}

3. 其他道具

墙砖加上了运动学刚体,以及勾选了IsTrigger的Box Collider。

墙砖模型:Asset Store, Destructible Wall Generator

巡逻兵模型:Asset Store, Toony Tiny WW1 Soldiers D.

玩家模型:Asset Store, SD Martial Arts Girl Xia-Chan

Unity3D制作巡逻兵小游戏相关推荐

  1. 基于Unity3D开发的智能巡逻兵小游戏

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

  2. Unity实现智能巡逻兵小游戏

    Unity实现智能巡逻兵小游戏 项目地址 演示视频 设计模式:对象的行为 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算 ...

  3. python连连看小游戏_利用Python制作一个连连看小游戏,边学边玩!

    导语 今天我们将制作一个连连看小游戏,让我们愉快地开始吧~ 开发工具 Python版本:3.6.4 相关模块: pygame模块: 以及一些Python自带的模块 环境搭建 安装Python并添加到环 ...

  4. c语言使用easyX图形库制作打气球小游戏

    大一c语言使用easyX图形库制作打气球小游戏 如果你是入门easyX图形库,那么这个打气球小游戏将会是和不错的入门项目选择,easyX开创了可视化窗口,使用户更加直观的了解到对象的变化,总代码以及素 ...

  5. C# 制作贪吃蛇小游戏,最简单的实现

    C# 制作贪吃蛇小游戏 目录 画蛇 实现蛇的上下左右移动 随机生成目标物 开始游戏 计分 重新开始 增加难度 死亡判定 1.1 画蛇的一节 Class Element()Graphics g;publ ...

  6. 小福利,用Excel VBA编程制作一个变色小游戏

    小福利,用Excel VBA编程制作一个变色小游戏 设计思想:在正方形的四条边上都是设置循环函数,不断改变颜色和单元格里面的数值. Option ExplicitSub 按钮1_Click() Dim ...

  7. 【源代码】Python制作的赛车小游戏,逆行飙车

    python制作的赛车小游戏,逆行飙车,通过键盘方向键控制 程序运行截图 源代码 import pygame, sys, time, random# pygame 初始化 pygame.init() ...

  8. android打地鼠设计报告,android开发中利用handler制作一个打地鼠小游戏

    android开发中利用handler制作一个打地鼠小游戏 发布时间:2020-11-25 15:21:11 来源:亿速云 阅读:136 作者:Leah 这期内容当中小编将会给大家带来有关androi ...

  9. Python制作的赛车小游戏源代码,逆行飙车

    python制作的赛车小游戏,逆行飙车,通过键盘方向键控制 程序运行截图: 源代码 import pygame, sys, time, random# pygame 初始化 pygame.init() ...

最新文章

  1. pytorch VIF(VIT 改)快了两倍
  2. mybatis 创建session, 缓存, 执行SQL
  3. 企业核心员工的长期激励计划
  4. jquery ajax POST/GET 请求至 ASP.NET WebAPI
  5. 使用SAP云平台Android SDK创建Mobile应用
  6. 【原】简单shell练习(四)
  7. React开发(138):ant design学习指南之anchor处理
  8. 【转】C# 温故而知新:Stream篇(—)
  9. OpenStack —— DevStack一键自动化安装
  10. PHP判断pc和移动端跳转,JS判断是PC还是移动端浏览器,并根据不同的终端跳转到不同的网址...
  11. 【答辩问题】计算机专业本科毕业设计答辩需注意的内容
  12. TcPlayer腾讯播放器
  13. 分享给学弟学妹们的一些成为全栈工程师的方向和方法
  14. 自动驾驶软件开发人才现状_一文读懂自动驾驶研究现状
  15. 史上最全的python基础语法知识清单!!!
  16. jenkins 触发 Rancher实现自动部署 流水线一键操作
  17. JavaScript中的三个点(...)扩展运算符
  18. Nginx 的配置和访问控制的理论实验操作详情
  19. chtMultiRegionFoam求解器及算例分析
  20. LTE Phich 分析

热门文章

  1. 2019国赛C题优秀论文机场的出租车问题
  2. HDMI与DisplayPort标准解析
  3. 华为面试应该怎么准备?
  4. 她是一位中学计算机老师的英文,计算机柳城中学英语组 讲课老师 : 陈文化.ppt...
  5. GUPNet:基于几何不确定性映射的单目3D检测网络(ICCV2021)
  6. Axure RP 新闻标题链接制作
  7. 专访阿里巴巴毕玄:异地多活数据中心项目的来龙去脉
  8. 图灵机是最早的计算机,计算机发展史之图灵机
  9. 机房常用动力环境设备远程集中监控及告警方案
  10. 小米5升级Linux内核,小米5 刷机LineageOS 14.1的详细教程