3D游戏设计读书笔记七

智能巡逻兵

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

设计与实现

代码:

模型预置:

对巡逻兵的要求是有多个,且每个巡逻兵走一个3~5个边的凸多边型。同时,巡逻兵需要规避障碍物,并且感知玩家以自动追击。我们需要存储巡逻兵(Patrol)的自身位置及玩家(Player)的位置,以确认是否追击玩家。
按作业要求,我们使用工厂模式来生产巡逻兵(Patrol)。
Patrol有如下属性:

PatrolData:

public class PatrolData : MonoBehaviour
{public int sign;                      //标志巡逻兵在哪一块区域public bool follow_player = false;    //是否跟随玩家public int wall_sign = -1;            //当前玩家所在区域标志public GameObject player;             //玩家游戏对象public Vector3 start_position;        //当前巡逻兵初始位置
}

使用工厂模式设置每个巡逻兵(Patrol)的初始位置。游戏中共有9个区域,每个区域各有一个巡逻兵,我们使用sign将这9个区域分别编号,生成时设置对应的sign。

PropFactory:

    public List<GameObject> GetPatrols(){int[] pos_x = { -6, 4, 13 };int[] pos_z = { -4, 6, -13 };int index = 0;//生成不同的巡逻兵初始位置for(int i=0;i < 3;i++){for(int j=0;j < 3;j++){vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);index++;}}for(int i=0; i < 9; i++){patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));patrol.transform.position = vec[i];patrol.GetComponent<PatrolData>().sign = i + 1;patrol.GetComponent<PatrolData>().start_position = vec[i];used.Add(patrol);}   return used;}

巡逻兵的动作有巡逻(即走一个3~5个边的凸多边型)和追击玩家。我们使用move_sign判断是否达到目的地,并且设置EAST、WEST、SOUTH、NORTH四个移动方向,如果距离目的地小于0.9则转换方向,理论上设置每次移动距离在一定范围内随机。

GoPatrolAction:

设置方向:

    void Gopatrol(){if (move_sign){//不需要转向则设定一个目的地,按照矩形移动switch (dirction){case Dirction.EAST:pos_x -= move_length;break;case Dirction.NORTH:pos_z += move_length;break;case Dirction.WEST:pos_x += move_length;break;case Dirction.SOUTH:pos_z -= move_length;break;}move_sign = false;}this.transform.LookAt(new Vector3(pos_x, 0, pos_z));float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));//当前位置与目的地距离浮点数的比较if (distance > 0.9){transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);}else{dirction = dirction + 1;if(dirction > Dirction.SOUTH){dirction = Dirction.EAST;}move_sign = true;}}

调用:

public class GoPatrolAction : SSAction
{private enum Dirction { EAST, NORTH, WEST, SOUTH };private float pos_x, pos_z;                 //移动前的初始x和z方向坐标private float move_length;                  //移动的长度private float move_speed = 1.2f;            //移动速度private bool move_sign = true;              //是否到达目的地private Dirction dirction = Dirction.EAST;  //移动的方向private PatrolData data;                    //侦察兵的数据private GoPatrolAction() { }public static GoPatrolAction GetSSAction(Vector3 location){GoPatrolAction action = CreateInstance<GoPatrolAction>();action.pos_x = location.x;action.pos_z = location.z;//设定移动矩形的边长action.move_length = Random.Range(4, 7);return action;}public override void Update(){//防止碰撞发生后的旋转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);}//侦察移动Gopatrol();//如果侦察兵需要跟随玩家并且玩家就在侦察兵所在的区域,侦查动作结束if (data.follow_player && data.wall_sign == data.sign){this.destroy = true;this.callback.SSActionEvent(this,0,this.gameobject);}}public override void Start(){this.gameobject.GetComponent<Animator>().SetBool("run", true);data  = this.gameobject.GetComponent<PatrolData>();}

给巡逻兵(Patrol)添加两个Collider,一个为Capsule Collider,用于检测巡逻兵是否与玩家产生碰撞,然后再添加一个Box Collider,用于检测玩家是否进入Patrol的跟随范围。示意图如下:

PatrolFollowAction

要实现巡逻兵(Patrol)朝玩家位置移动。当玩家(Player)的位置超出上面的Box Collider的检测范围,巡逻兵停止跟随行动。同时我们设置一个参数follow_player用于确定玩家是否进入了巡逻兵的管辖区域,如果该值为0,巡逻兵也不会朝玩家的位置移动。
开始追击动作时,巡逻动作会结束,同样,结束追击动作时,巡逻动作又重新开始,所以我们需要回调函数。当游戏结束时,所有的动作也都结束,巡逻兵(Patrol)也必须停止运动。

public class PatrolFollowAction : SSAction
{private float speed = 2f;            //跟随玩家的速度private GameObject player;           //玩家private PatrolData data;             //侦查兵数据private PatrolFollowAction() { }public static PatrolFollowAction GetSSAction(GameObject player){PatrolFollowAction action = CreateInstance<PatrolFollowAction>();action.player = player;return action;}public override void Update(){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);}Follow();//如果侦察兵没有跟随对象,或者需要跟随的玩家不在侦查兵的区域内if (!data.follow_player || data.wall_sign != data.sign){this.destroy = true;this.callback.SSActionEvent(this,1,this.gameobject);}}public override void Start(){data = this.gameobject.GetComponent<PatrolData>();}void Follow(){transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);this.transform.LookAt(player.transform.position);}
}

场景控制器调用PatrolActionManager让Patrol开始巡逻,结束时调用它停止巡逻。

public class PatrolActionManager : SSActionManager
{private GoPatrolAction go_patrol;                            //巡逻兵巡逻public void GoPatrol(GameObject patrol){go_patrol = GoPatrolAction.GetSSAction(patrol.transform.position);this.RunAction(patrol, go_patrol, this);}//停止所有动作public void DestroyAllAction(){DestroyAll();}
}

UserGUI

在GUI中获取方向键输入,改变Player的移动。

    void Update(){//获取方向键的偏移量float translationX = Input.GetAxis("Horizontal");float translationZ = Input.GetAxis("Vertical");//移动玩家action.MovePlayer(translationX, translationZ);}

FirstSceneController:

处理键盘操作:

   //玩家移动public void MovePlayer(float translationX, float translationZ){if(!game_over){if (translationX != 0 || translationZ != 0){player.GetComponent<Animator>().SetBool("run", true);}else{player.GetComponent<Animator>().SetBool("run", false);}//移动和旋转player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime);player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);//防止碰撞带来的移动if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0){player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);}if (player.transform.position.y != 0){player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);}     }}

AreaCollide

在九个区域中各设置一个Box Collider,当Player进入区域时,修改玩家(Player)的sign值,使巡逻兵(Patrol)确定玩家在哪个区域。

public class AreaCollide : MonoBehaviour
{public int sign = 0;FirstSceneController sceneController;private void Start(){sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;}void OnTriggerEnter(Collider collider){//标记玩家进入自己的区域if (collider.gameObject.tag == "Player"){sceneController.wall_sign = sign;}}
}

订阅与发布模式

订阅与发布模式就像订阅报纸,报纸会不定期推送的邮箱,当你想看的时候,打开邮箱就可以看。

定义一个发布事件的类。

GameEventManager

public class GameEventManager : MonoBehaviour
{//分数变化public delegate void ScoreEvent();public static event ScoreEvent ScoreChange;//游戏结束变化public delegate void GameoverEvent();public static event GameoverEvent GameoverChange;//水晶数量变化public delegate void CrystalEvent();public static event CrystalEvent CrystalChange;//玩家逃脱public void PlayerEscape(){if (ScoreChange != null){ScoreChange();}}//玩家被捕public void PlayerGameover(){if (GameoverChange != null){GameoverChange();}}//减少水晶数量public void ReduceCrystalNum(){if (CrystalChange != null){CrystalChange();}}
}

CrystalCollide

订阅者为场景控制器,发布者为发生的各个事件。

public class CrystalCollide : MonoBehaviour
{void OnTriggerEnter(Collider collider){if (collider.gameObject.tag == "Player" && this.gameObject.activeSelf){this.gameObject.SetActive(false);//减少水晶数量Singleton<GameEventManager>.Instance.ReduceCrystalNum();}}
}

SSActionEvent

碰撞水晶后得分。

public class SSAction : ScriptableObject
{public bool enable = true;                      //是否正在进行此动作public bool destroy = false;                    //是否需要被销毁public GameObject gameobject;                   //动作对象public Transform transform;                     //动作对象的transformpublic ISSActionCallback callback;              //动作完成后的消息通知者protected SSAction() { }//子类可以使用下面这两个函数public virtual void Start(){throw new System.NotImplementedException();}public virtual void Update(){throw new System.NotImplementedException();}
}

PlayerCollide

逃脱后发布。

public class PlayerCollide : MonoBehaviour
{void OnCollisionEnter(Collision other){//当玩家与侦察兵相撞if (other.gameObject.tag == "Player"){other.gameObject.GetComponent<Animator>().SetTrigger("death");this.GetComponent<Animator>().SetTrigger("shoot");Singleton<GameEventManager>.Instance.PlayerGameover();}}
}

ScoreRecorder

记录分数,当玩家(Player)与巡逻兵(Patrol)相撞时游戏结束,计分也结束。
玩家通过获得水晶得分。

public class ScoreRecorder : MonoBehaviour
{public FirstSceneController sceneController;public int score = 0;                            //分数public int crystal_number = 12;                  //水晶数量// Use this for initializationvoid Start(){sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;sceneController.recorder = this;}public int GetScore(){return score;}public void AddScore(){score++;}public int GetCrystalNumber(){return crystal_number;}public void ReduceCrystal(){crystal_number--;}
}

FirstSceneController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{public PropFactory patrol_factory;                               //巡逻者工厂public ScoreRecorder recorder;                                   //记录员public PatrolActionManager action_manager;                       //运动管理器public int wall_sign = -1;                                       //当前玩家所处哪个格子public GameObject player;                                        //玩家public Camera main_camera;                                       //主相机public float player_speed = 5;                                   //玩家移动速度public float rotate_speed = 135f;                                //玩家旋转速度private List<GameObject> patrols;                                //场景中巡逻者列表private List<GameObject> crystals;                               //场景水晶列表private bool game_over = false;                                  //游戏结束void Update(){for (int i = 0; i < patrols.Count; i++){patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;}//水晶收集完毕if(recorder.GetCrystalNumber() == 0){Gameover();}}void Start(){SSDirector director = SSDirector.GetInstance();director.CurrentScenceController = this;patrol_factory = Singleton<PropFactory>.Instance;action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;LoadResources();main_camera.GetComponent<CameraFlow>().follow = player;recorder = Singleton<ScoreRecorder>.Instance;}public void LoadResources(){Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;crystals = patrol_factory.GetCrystal();patrols = patrol_factory.GetPatrols();//所有侦察兵移动for (int i = 0; i < patrols.Count; i++){action_manager.GoPatrol(patrols[i]);}}//玩家移动public void MovePlayer(float translationX, float translationZ){if(!game_over){if (translationX != 0 || translationZ != 0){player.GetComponent<Animator>().SetBool("run", true);}else{player.GetComponent<Animator>().SetBool("run", false);}//移动和旋转player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime);player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);//防止碰撞带来的移动if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0){player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);}if (player.transform.position.y != 0){player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);}     }}public int GetScore(){return recorder.GetScore();}public int GetCrystalNumber(){return recorder.GetCrystalNumber();}public bool GetGameover(){return game_over;}public void Restart(){SceneManager.LoadScene("Scenes/mySence");}void OnEnable(){GameEventManager.ScoreChange += AddScore;GameEventManager.GameoverChange += Gameover;GameEventManager.CrystalChange += ReduceCrystalNumber;}void OnDisable(){GameEventManager.ScoreChange -= AddScore;GameEventManager.GameoverChange -= Gameover;GameEventManager.CrystalChange -= ReduceCrystalNumber;}void ReduceCrystalNumber(){recorder.ReduceCrystal();}void AddScore(){recorder.AddScore();}void Gameover(){game_over = true;patrol_factory.StopPatrol();action_manager.DestroyAllAction();}
}

结果展示

更多请访问github

3D游戏设计读书笔记七相关推荐

  1. 3D游戏设计读书笔记二

    3D游戏设计读书笔记二 一.简答题 • 解释 游戏对象(GameObjects) 和 资源(Assets)的区别与联系.   GameObjects是一个具体的实例,Assets是包括诸多游戏素材的资 ...

  2. 3d游戏设计读书笔记六

    3d游戏设计读书笔记六 一.改进飞碟(Hit UFO)游戏: 游戏内容要求: 按 adapter模式 设计图修改飞碟游戏 使它同时支持物理运动与运动学(变换)运动 更改原 UFO_action 类 为 ...

  3. 3D游戏设计读书笔记一

    3D游戏设计读书笔记一 二.游戏分类与热点探索 1.使用思维导图描述游戏的分类.(游戏分类方法特别多) 2. 结合手机游戏市场的下载量与排名等数据,结合游戏分类图,描述游戏市场的热点. (1)2016 ...

  4. 3D游戏设计读书笔记九

    3D游戏设计读书笔记九 本次作业五选一,我选择制作血条预制设计,要求如下: 分别使用 IMGUI 和 UGUI 实现 使用 UGUI,血条是游戏对象的一个子元素,任何时候需要面对主摄像机 分析两种实现 ...

  5. 3d游戏设计读书笔记四

    3d游戏设计读书笔记四 一.基本操作演练[建议做] 下载 Fantasy Skybox FREE, 构建自己的游戏场景 a. 在AssetStore中搜索Fantasy Skybox FREE并下载. ...

  6. 中山大学3D游戏设计读书笔记 unity3D Note6

    本文利用订阅与发布模式实现智能巡逻兵游戏 游戏演示视频地址:http://www.iqiyi.com/w_19rxsmp5kx.html 游戏具体代码地址:https://github.com/dic ...

  7. 《MFC游戏开发》笔记七 游戏特效的实现(一):背景滚动

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9344721 作者:七十一雾央 新浪微博:http:// ...

  8. 3D游戏设计和创作工具学习教程 3D Game Design Creation Tools

    语言:英语+中英文字幕(根据原英文字幕机译更准确) 大小解压后:1.94G 1280X720 mp4 三维游戏设计和创作工具 用扎实的工作流程开始开发游戏 课程获取:3D游戏设计和创作工具学习教程 3 ...

  9. 3D游戏设计作业(四)

    一.下载 Fantasy Skybox FREE, 构建自己的游戏场景 1.下载Fantasy Skybox   2.创建material   3.将下载好的素材拖动到material中  4.显示效 ...

最新文章

  1. jdbc mysql连接测试_JDBC测试计划-连接mysql
  2. MySql 5.7 重置root密码
  3. mysql之 double write 浅析
  4. Collaborative Filter - Data Mining基础(ACM暑校)
  5. 2016年第七届蓝桥杯 - 省赛 - C/C++大学A组 - I. 密码脱落
  6. ContextMenuStrip 类
  7. [Java基础]反射获取成员变量并使用
  8. JDK8的JVM优化实操及部分原理加深理解
  9. JAVA循环结构、break、continue、循环嵌套
  10. linux 系统命令和方法
  11. java编码规范概述_Java 编码规范 (转)
  12. ectouch v1 thinkphp的搜索问题
  13. cdr怎么抠图轮廓线条_cdr怎么快速抠图呢 需要技巧
  14. Pycharm导入已有的Project
  15. 浅谈TPM设备管理系统、推行要素和目的
  16. Python之父退休,龟叔与Python的渊源
  17. Longhorn 企业级云原生分布式容器存储-券(Volume)和节点(Node)
  18. 高德地图路径轨迹起点标点不变_竞品分析之高德地图与百度地图
  19. 三维动画与企业宣传片的制作方案
  20. linux 多个文件内容查找,Linux 根据一个文件内容查找另一个文件中的内容

热门文章

  1. 云服务器、个人服务器、软路由、NAS的奇特用法(一)you-get下载视频 以b站为例(可支持网易云音乐、acfun、土豆、优酷等详情见附录)
  2. 7-24 约分最简分式
  3. 根据身份证号码取得此CID户籍所在地
  4. JAVA常见压缩包解压工具类(支持:zip、7z和rar)
  5. 利用红外线接收器触发中断信号
  6. 高等工程数学期末试题2015
  7. HP3par 多路径存储磁盘使用方法
  8. (三)Server和Service
  9. 微信能不能定时发朋友圈 ?
  10. InSAR之数据裁剪和DEM拼接