3D游戏编程7--巡逻兵(总结之前所有出现过的设计模式)

巡逻兵

游戏设计要求

  • 创建一个地图和若干巡逻兵
  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算
  • 巡逻兵碰撞到障碍物如树,则会自动选下一个点为目标
  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家
  • 失去玩家目标后,继续巡逻
  • 计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束

程序设计要求

  • 必须使用订阅与发布模式传消息、工厂模式生产巡逻兵

游戏演示

演示了玩家左键劈砍,右键范围攻击,space跳跃,
巡逻兵触发追踪时候扑到动作,以及碰到玩家挥砍动作
还有玩家攻击时候碰到巡逻兵,巡逻兵会直接消失
游戏演示
游戏演示

准备工作

首先需要下载模型,推荐一个网站还算比较好的免费网站,其中我的作业模型就是从里面下载的,场景模型本来也下载了,但是建立一个场景实在太费时间,所以就直接借用了之前大佬的场景。

人物的动作

首先打开下载好的人物模型,点开人物旁边的按钮可以看到很多部件以及动作,可以点开玩一下。如果决定用这个人物模型,就放置到Resources中的Prefabs文件夹里。放到
下载好的人物模型

在Prefabs中的人物就没有那些动作了,这时候就需要自己设计AnimatorController。
Prefabs中的人物模型

新建一个文件夹AnimatorController,里面用来控制动作,在里面Create,Animator Controller,就比如新建了一个skeleton。
Animator Controller

双击skeleton打开,我已经建立好了,稍微有点复杂,首先在左边的Parameters新建变量,我建立了五个变量,bool型的run,bool型的death,以及Trigger型的attack1,attack2,jump。Trigger类型给的解释是,先设置true,然后自动设置为false,但似乎运行的时候有点奇怪,这个是作为skeleton的动作设计,鼠标左键,从右向左劈砍,鼠标右键,旋转劈砍,空格键跳跃。所以从AnyState开始有三个动作,都是Trigger触发,执行完毕后进入到idle状态,在idle状态下,如果run为true就run,death为true就death,其他同理,这时候不能连到Exit,原因是连到Exit就会从入口再次进入,就会发现死了一遍又又一遍。
skeleton设计图

状态绑定动作,就是Motion那里选择动作,一定要记住将动作的Animation Type改成Generic,才可以从Motion里面选择,并且会生成结尾为Avatar的东西,这个在与物体绑定的时候有用。
Rig
动作绑定

下面是转换时候的设置,至于下面为什么有两个run,这个原因是有时候动作切换很不自然,或者延迟太高,多个组合的话会比较好调整,具体设计方法,把loop打勾,然后apply一下就好。这里还有个小技巧,就是两个状态之间的连线怎么删掉的问题,在Transition下面的点减号就行。
run->death
loop

然后在人物增加新组件,Animator,在Controller和Avatar,选择刚刚做好和生成的文件。这样的话一个具有动作的人物就设置好了。
人物Animator

碰撞检测

这个需要设置碰撞盒,这个是我player的碰撞盒,差不多是药丸形状的,注意IsTrigger不打勾,说明碰到了就是碰到了,而不是碰到了触发一下。
碰撞盒

巡逻兵也需要设置碰撞盒,但是比玩家复杂一点,因为巡逻兵需要检测玩家的范围,所以还要给巡逻兵建立一个box Collider,用户触发追踪事件。
巡逻兵碰撞盒
巡逻兵碰撞盒

人物效果看起来是这样的
人物碰撞盒

当然场景也是需要设置9个碰撞盒的,并且勾上IsTrigger,用来判断人物在哪一块区域。
场景碰撞盒

到此为止准备工作算是做完了。

代码实现

这次我整理和参考了以前的PPT和大佬的代码,严格按照所将的设计模式组织代码。

动作代码的实现

首先动作代码可以复用的,一点都不用改的是,SSActionManager.csSSAction.csISSActionInterface.cs其中ISSActionInterface.csISSActionCallback接口。需要改动的就是CCSequenceAction.csCCMoveToAction.cs以及CCActionManager.cs,似乎CC开头的都是自己定义的。这个图还是牧师与魔鬼那章PPT的图,其中CCSequenceAction.cs是定义动作序列,因为坐船是,人物先移动船的上方,在移到船上面,相当于两个动作,所以用动作序列,而船的移动只有左右,所以用CCMoveToAction.cs实现单个动作。本次作业设计的动作也就两个,第一个是PatrolAction.cs实现巡逻动作,一个是PatrolFollowAction.cs实现跟踪动作,然后这个两个动作的管理在PatrolActionManager.cs中,因为PatrolActionManager.cs实现了ISSActionCallback的接口,所有动作的都是靠这个接口来完成的,所以具体的动作管理,也是靠这个接口。
动作设计模式

PatrolAction.cs 巡逻动作实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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;
        //移动的距离随机,在4-7之间
        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();
        //如果有跟随的玩家,并且玩家在自己所在的区域,就会调用ISSActionCallback接口中的函数
        //所以说动作的管理是靠ISSActionCallback实现的,同样在跟踪的时候的动作也会有切换到巡逻状态的方法。
        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>();
    }

    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;
        }
    }
}

这段代码可以和CCMoveToActon的代码比较一下,因为两个是同一个框架的不同实现,可以发现除了实现细节不一样,其他都一样,包括函数类型都一样。可以看到这部分的代码,只要实现特定的动作,所以以后在写新动作处理的时候,可以直接在这部分修改,或者另开一个文件继承SSAction
CCMoveToActon

PatrolFollowAction.cs 追踪动作实现

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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();
        //如果没有跟随的玩家,或者玩家不在自己所在的区域,就会调用ISSActionCallback接口中的函数,切换到巡逻状态
        //所以说动作的管理是靠ISSActionCallback实现的,同样在巡逻的时候的动作也会有切换到追踪状态的方法。
        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.cs 具体的动作管理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolActionManager : SSActionManager, ISSActionCallback
{
    //这个函数是游戏初始化的时候用到,最重要的实现还是在SSActionEvent函数中实现,也就是ISSActionCallback接口的函数
    public void GoPatrol(GameObject patrol)
    {
        GoPatrolAction go_patrol = GoPatrolAction.GetSSAction(patrol.transform.position);
        this.RunAction(patrol, go_patrol, this);
    }
    //停止所有动作
    public void DestroyAllAction()
    {
        DestroyAll();
    }
    //动作管理最终要的部分,实现各个动作的切换,以及条件判断。
    public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
    {
        if (intParam == 0)
        {
            PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
            this.RunAction(objectParam, follow, this);
        }
        else
        {
            GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
            this.RunAction(objectParam, move, this);
            //委托事件,Singleton直接生成实例,调用PlayerEscape就相当于发出通知,那么在OnEnable函数就会触发加分的函数
            Singleton<GameEventManager>.Instance.PlayerEscape();
        }
    }
}

这段代码再和CCActionManager比较下,发现最重要的还是SSActionEvent函数的实现,start和Update,可以换成其他函数,直接被调用,而不是默认调用,大同小异。
CCActionManager

门面模式

对于单个场景的游戏来说,完全不用改动的代码是SSDirector.csISenceInterface.cs,需要改动的是FirstSceneController.csUserGUI.csIUserInterface.cs,其中FirstSceneController.cs主要处理场景的资载入,并且实现IUserAction接口,由于是但场景,对ISceneController接口实现的并不多。UserGuI主要实现界面的呈现,用户交互,其中的接口是在FirstController中实现,所以通过UserGuI可以控制游戏人物,以及游戏的重新开始之类的。
门面模式

UserGUI.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour {

    private IUserAction action;
    private GUIStyle score_style = new GUIStyle();
    private GUIStyle text_style = new GUIStyle();
    private GUIStyle over_style = new GUIStyle();
    private int show_time = 8;
    void Start ()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
        text_style.normal.textColor = new Color(205, 179, 128, 1);
        text_style.fontSize = 16;
        score_style.normal.textColor = new Color(3,101,100,1);
        score_style.fontSize = 16;
        over_style.fontSize = 25;
        //好的方法实现一个时间差,StartCoroutine函数和yield return成对出现。
        StartCoroutine(ShowTip());
    }

    void Update()
    {
        //人物的动作和移动,得到水平两个方向的偏移量来控制移动,通过按键控制动作。
        float translationX = Input.GetAxis("Horizontal");
        float translationZ = Input.GetAxis("Vertical");
        action.MovePlayer(translationX, translationZ);
        action.Attack();
    }
    private void OnGUI()
    {
        GUI.Label(new Rect(10, 5, 200, 50), "分数:", text_style);
        GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), score_style);
        if(action.GetGameover() && action.GetScore() != 10)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏结束", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始"))
            {
                //重新开始
                action.Restart();
                return;
            }
        }
        else if(action.GetScore() == 10)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "恭喜胜利!", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始"))
            {
                //重新开始
                action.Restart();
                return;
            }
        }
        if(show_time > 0)
        {
            GUI.Label(new Rect(Screen.width / 2 - 87 ,10, 100, 100), "按WSAD或方向键移动", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 87, 30, 100, 100), "成功躲避巡逻兵追捕加1分", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 87, 50, 100, 100), "获得十分就胜利", text_style);
        }
    }

    public IEnumerator ShowTip()
    {
        while (show_time >= 0)
        {
            yield return new WaitForSeconds(1);
            show_time--;
        }
    }
}

结构类似于PPT的结构,这部分大部分都是界面要素,接口的使用很简单
UserGUI

FirstSceneController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
    public PatorlFactory _PatorlFactory;                             //巡逻者工厂
    public ScoreRecorder _ScoreRecorder;                             //记录员
    public PatrolActionManager _PatrolActionManager;                 //运动管理器
    public int wall_sign = -1;                                       //当前玩家所处哪个格子
    public GameObject player;                                        //玩家
    public Camera main_camera;                                       //主相机
    private List<GameObject> patrols;                                //场景中巡逻者列表
    private float player_speed = 5;                                  //玩家移动速度
    private float rotate_speed = 135f;                               //玩家旋转速度
    private bool game_over = false;                                  //游戏结束

    void Update()
    {
        for (int i = 0; i < patrols.Count; i++)
        {
            patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
        }
        //20分结束游戏
        if(_ScoreRecorder.score == 20)
        {
            Gameover();
        }
    }
    void Start()
    {
        //这一块注意一下,关于实例化,和实例化的查找
        //首先director是单例模式,所以用GetInstance方法,即使没有实例也会创建实例,有实例就确保只有一个实例
        //那么_PatorlFactory和_PatrolActionManager是怎么回事
        //首先看到代码可以这样写,用Singleton,这个是直接查找有没有这个类,没有就报错了
        //所以使用这个有个前提条件,就是必须已经存在了,所以在游戏开始之前必须把代码绑定到对应的物体上
        //如果不采用这种方法怎么办,可以手动添加,用AddComponent就相当于把手动添加的部分,用代码实现了,就是注释掉的部分
        SSDirector director = SSDirector.GetInstance();
        director.CurrentScenceController = this;
        _PatorlFactory = Singleton<PatorlFactory>.Instance;
        _PatrolActionManager = Singleton<PatrolActionManager>.Instance;
        //_PatrolActionManager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;

        //ISceneController接口中函数的实现
        LoadResources();
        //设置摄像机跟随的对象,也就是玩家,如果不用工厂模式的话,直接在场景中生成玩家,然后把玩家拖到摄像机的Targer就可以了
        //这一块也要注意,main_camera的位置必须在LoadResources之后,因为main_camera跟随的对象是player,如果player没有实例化就无法运行
        //所以如果一开始在场景中就有player的话,下面的代码位置就可以随便放。
        main_camera.GetComponent<CameraFlow>().follow = player;
        _ScoreRecorder = Singleton<ScoreRecorder>.Instance;
    }

    public void LoadResources()
    {
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        //导入资源的时候为了巡逻兵有动作
        patrols = _PatorlFactory.GetPatrols();
        for (int i = 0; i < patrols.Count; i++)
        {
            _PatrolActionManager.GoPatrol(patrols[i]);
        }
    }
    public void Attack()
    {
        if (!game_over)
        {
            //Fire1对应鼠标左键
            if(Input.GetButtonDown("Fire1"))
            {
                player.GetComponent<Animator>().SetTrigger("attack1");
            }
            //Fire2对应鼠标右键
            if (Input.GetButtonDown("Fire2"))
            {
                player.GetComponent<Animator>().SetTrigger("attack2");
            }
            //Jump对应空格键space
            if (Input.GetButtonDown("Jump"))
            {
                player.GetComponent<Animator>().SetTrigger("jump");
            }
        }
    }


    public void MovePlayer(float translationX, float translationZ)
    {
        if(!game_over)
        {
            //移动的时候播放run动画,否则停止run动画
            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 _ScoreRecorder.score;
    }
    public bool GetGameover()
    {
        return game_over;
    }
    public void Restart()
    {
        SceneManager.LoadScene("Scenes/mySence");
    }

    //发布与订阅模式
    void OnEnable()
    {
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
    }
    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
    }

    void AddScore()
    {
        _ScoreRecorder.score++;
    }
    void Gameover()
    {
        game_over = true;
        _PatorlFactory.StopPatrol();
        _PatrolActionManager.DestroyAllAction();
    }
}

整体框架类似于PPT中的,另外实现接口类的函数就可以了
FirstSceneController

工厂模式

工厂模式相对来说比较简单,一般来说一个类型有一个工厂,由于本例是一个玩家角色,所以直接把玩家和巡逻兵放在一起生产了。其中只要写Factory就好了。
工厂模式

其中可以发现DiskFactory上面有Singleton,是用来找DiskFactory用的
singleton

PatrolFactory.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatorlFactory : MonoBehaviour {

    //工厂模式就负责生产,其他的不负责,所以可以看到这个类只和角色有关系和其他所有代码都没有关系
    private GameObject player = null;                                      //玩家
    private GameObject patrol = null;                                     //巡逻兵
    private List<GameObject> patrolList = new List<GameObject>();        //正在被使用的巡逻兵
    private Vector3[] vec = new Vector3[9];                             //保存每个巡逻兵的初始位置

    public GameObject LoadPlayer()
    {
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
        return player;
    }

    public List<GameObject> LoadPatrol()
    {
        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/Patrol1"));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().start_position = vec[i];
            patrolList.Add(patrol);
        }   
        return patrolList;
    }

    //游戏结束的时候会暂停所有动作
    public void StopPatrol()
    {
        for (int i = 0; i < patrolList.Count; i++)
        {
            patrolList[i].gameObject.GetComponent<Animator>().SetBool("run", false);
        }
    }
}

发布与订阅模式(观察者模式)

借用一下大佬的图,用观察者模式写代码会觉得很清爽,首先CystalCollide和PlayerCollide是直接挂载在相对应的物体上的,可以事先写好,然后用里面的函数触发GameEventManager。
订阅与发布模式

PlayerInDetection.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//玩家进入巡逻兵的追踪范围
public class PlayerInDetection : MonoBehaviour
{
    //玩家进入巡逻兵的追踪范围
    void OnTriggerEnter(Collider collider)
    {
        //加上判断Player的原因是,人物会与地板碰撞
        if (collider.gameObject.tag == "Player")
        {
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;
            //触发巡逻兵向前扑的动作
            this.gameObject.transform.parent.GetComponent<Animator>().SetTrigger("shock");
        }
    }
    //玩家离开巡逻兵的追踪范围
    void OnTriggerExit(Collider collider)
    {
        if (collider.gameObject.tag == "Player")
        {
            this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = false;
            this.gameObject.transform.parent.GetComponent<PatrolData>().player = null;
        }
    }
}

PlayerCollideDetection.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//玩家与巡逻兵碰撞
public class PlayerCollideDetection : MonoBehaviour {
    //当玩家与巡逻兵碰撞
    void OnCollisionEnter(Collision other)
    {
        //在这里皮了一下,因为一开始是想做攻击判定的,但是剑和人的模型是一起的
        //没法单独给剑加一个碰撞盒,所以只好放弃
        //于是想攻击的时候直接让巡逻兵的碰撞盒半径为0,然后播放死亡动画也可以达到目的
        //然后发现会鬼畜,就注释掉了,现在只要在攻击的时候碰到巡逻兵,巡逻兵会直接消失
        //一般情况下的,玩家会自己挂掉,并且播放死亡动画,巡逻兵也会砍一刀然后死掉。
        if (other.gameObject.tag == "Player" && other.gameObject.GetComponent<Animator>().GetBool("attack1"))
        {
            //this.gameObject.GetComponent<CapsuleCollider>().radius = 0;
            this.gameObject.SetActive(false);
            return;
        }
        else if (other.gameObject.tag == "Player")
        {
            other.gameObject.GetComponent<Animator>().SetBool("death",true);
            this.GetComponent<Animator>().SetTrigger("shoot");
            Singleton<GameEventManager>.Instance.PlayerGameover();
        }
    }
}

GameEventManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameEventManager : MonoBehaviour
{
    //玩家躲开追踪时候触发
    public delegate void ScoreEvent();
    public static event ScoreEvent ScoreChange;
    //玩家碰撞到巡逻兵的时候触发
    public delegate void GameoverEvent();
    public static event GameoverEvent GameoverChange;

    public void PlayerEscape()
    {
        if (ScoreChange != null)
        {
            ScoreChange();
        }
    }
    public void PlayerGameover()
    {
        if (GameoverChange != null)
        {
            GameoverChange();
        }
    }
}

这样所有的代码都已经说完了。

总结

所有C#文件,一共20个文件。
C#文件结构
其中可以挂载在预制物体上的有5个。

  • AreaDetection.cs挂载在场景的9个方格中,用于检测玩家是否进入该方格
  • CameraFlow.cs挂载在摄像机上,用于实现第三人称视角
  • PatrolData.cs挂载在巡逻兵上,用于表示巡逻兵的数据结构
  • PlayerInDetection.cs挂载在巡逻兵上,用于检测玩家是否进入追踪范围
  • PlayerCollideDetection.cs挂载在巡逻兵上,用于检测玩家是否与巡逻兵碰撞

其中动作设计的部分有6个

  • SSDirector.cs完全不用改动
  • SSActionManager.cs完全不用改动
  • PatrolAction.cs
  • PatrolActionManager.cs
  • PatrolFollowAction.cs
  • ISSActionInterface.cs完全不用改动

其中门面模式的部分

  • FirstSceneController.cs
  • ISenceInterface.cs单场景完全不用改动
  • IUserInterface.cs
  • UserGUI.cs
  • SSDirector.cs完全不用改动

工厂模式的部分

  • PatorlFactory.cs
  • Singleton.cs其实也不算工厂特有,都可以用,完全不用改动

    观察者模式

  • PlayerCollideDetection.cs

  • PlayerInDetection.cs
  • GameEventManager.cs

github代码及视频链接

资源以及视频演示

猜你喜欢

转载自blog.csdn.net/yaoxh6/article/details/80260237