ch07-模型与动画——巡逻兵小游戏

要求

在这里插入图片描述


订阅与发布模式

  • 我的理解是相当于一些社交软件,明星发布信息,通过中间平台,订阅者可以收到其信息。
  • 订阅者与发布模式的优点:
    • 松耦合:可以将众多需要通信的子系统(Subsystem)解耦,每个子系统可以分别管理,子系统的状态不影响整体系统的运行。
    • 高伸缩性:增加了系统的可伸缩性,并提高了发送者的响应能力。发送方只需要向“平台”发送信息,就可以继续处理自己的核心业务,而不必关心子系统的运行情况。“平台”负责将信息传到订阅者。
    • 高可靠性:提高了可靠性。异步的消息传递有助于应用程序在增加的负载下继续平稳运行,并且可以更有效地处理间歇性故障
    • 灵活性:不必关心子系统是如何组合到一起的,只要整体系统平稳运行即可
    • 可测试性:提高了可测试性。通道可以被监视,消息可以作为整体集成测试策略的一部分而被检查或记录。
  • 我们采用了GameEventManager作为订阅者和发布者模式的“公众号”

资源一览

  • Asset
    在这里插入图片描述
  • Perfabs
    在这里插入图片描述
  • Scripts在这里插入图片描述
  • Animations
    在这里插入图片描述
    • tt:使用了巡逻兵动画的一些组件,在资源包中,这里没显示出来
      在这里插入图片描述
    • New Animator 1:这个是从主角小姐姐自带的动画改的
      在这里插入图片描述

游戏内截图

在这里插入图片描述
这里主角小姐姐跟巡逻兵碰撞,游戏失败


主要代码

  • FirstSceneController: 统筹全局,这里控制了玩家的攻击方式和计分
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;                                        //玩家
    private List<GameObject> patrols;                                //场景中巡逻者列表
    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()
    {
        
        SSDirector director = SSDirector.GetInstance();
        director.CurrentScenceController = this;
        _PatorlFactory = Singleton<PatorlFactory>.Instance;//巡逻者工厂
        _PatrolActionManager = Singleton<PatrolActionManager>.Instance;//巡逻者的动作管理器

        //ISceneController接口中函数的实现
        LoadResources();
       
        _ScoreRecorder = Singleton<ScoreRecorder>.Instance;//拿到计分的单实例对象
    }
    //加载资源
    public void LoadResources()
    {
        player = _PatorlFactory.LoadPlayer();//player
        patrols = _PatorlFactory.LoadPatrol();
        for (int i = 0; i < patrols.Count; i++)
        {
            _PatrolActionManager.GoPatrol(patrols[i]);//让巡逻兵有动作
        }
    }

    public void Attack()
    {
        if (!game_over)
        {
            //Fire1对应鼠标左键
            if (Input.GetButtonDown("Fire1"))
            {
                Debug.Log("Fire1");
                player.GetComponent<Animator>().SetTrigger("yep");
            }
            
        }
    }


    //获得分数
    public int GetScore()
    {
        return _ScoreRecorder.score;
    }
    //判断是不是game_over的函数
    public bool GetGameover()
    {
        return game_over;
    }
    //重新开始,这个用的是重新加载场景
    public void Restart()
    {
        Debug.Log("游戏重新开始!");

        SceneManager.LoadScene("Scenes/SampleScene");

    }

    //发布与订阅模式
    void OnEnable()
    {
       
        GameEventManager.ScoreChange += AddScore;
        GameEventManager.GameoverChange += Gameover;
    }
    
    void OnDisable()
    {
        GameEventManager.ScoreChange -= AddScore;
        GameEventManager.GameoverChange -= Gameover;
    }
    
    void AddScore()
    {
        _ScoreRecorder.AddScore();
    }
    //游戏失败
    void Gameover()
    {
        game_over = true;
        _PatorlFactory.StopPatrol();
        _PatrolActionManager.DestroyAllAction();
    }
}

  • AreaDetection:在场景中创建若干个Collider来检测Player进入场景,这个脚本挂载在这些Collider上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//检测范围的函数
public class AreaDetection : MonoBehaviour
{
    //墙位置的标签,说明要用empty创建东西
    public int sign = 0;
    //场景Controller
    FirstSceneController sceneController;
    private void Start()
    {
        //利用单实例得到唯一的场景Controller
        sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
    }
    //碰撞器
    void OnTriggerEnter(Collider collider)
    {
        //如果是Player碰撞的话,这个场景就被激活了
        if (collider.gameObject.tag == "Player")
        {
            sceneController.wall_sign = sign;

            Debug.Log("你现在进入的教室是:" + sign);
        }
    }
}

  • GameEventManager:订阅者发布者模式中的“公众号”,这里主要发布玩家躲开追踪和玩家碰撞到巡逻兵的信息。(这个我不是很会,参考了师兄师姐们的代码)
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();
        }
    }
}

  • PatorlFactory:产生巡逻兵的工厂,值得注意的是,我只有七个房间却有八个巡逻兵,在命名的的后要注意
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[8];                             //保存每个巡逻兵的初始位置

    public GameObject LoadPlayer()
    {
        
        player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(8, 0, 8), Quaternion.Euler(new Vector3(0,180,0))) as GameObject;

        Debug.Log("初始化玩家");

        return player;
    }

    public List<GameObject> LoadPatrol()
    {
        
        //这里自定义八个位置,只有七个房间
        float[] pos_x = { 18, 18, 18, 18, -3.5f, -3.5f, -3.5f,18};
        float[] pos_z = { -0.5f,-11.5f, -22.5f, -32, -.5f,-11.5f, -22.3f,-34};
        int index = 0;

        for(int i=0;i < 8;i++)
        {
            vec[index] = new Vector3(pos_x[i], 0, pos_z[i]);
            index++;
            
        }
        for(int i=0; i < 8; i++)
        {
            int name = i + 1;
            patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol "+name));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData>().sign = i + 1;
            if(i==7) patrol.GetComponent<PatrolData>().sign = i + 1;
            patrol.GetComponent<PatrolData>().start_position = vec[i];
            patrolList.Add(patrol);
            Debug.Log("初始化老师!" + patrol.name);
        }   
        return patrolList;
    }

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

  • GoPatrolAction:控制巡逻兵移动的代码,学习了师兄师姐的巡逻函数
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-6之间
        action.move_length = Random.Range(4, 6);
        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();
  
        //Debug.Log("摧毁巡逻动作之前:"+ "data.follow_player"+ data.follow_player +" "+ data.wall_sign+" "+data.sign);
        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("walk", 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;
        }
    }
}

  • PatrolFollowAction:控制巡逻兵跟随玩家的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PatrolFollowAction : SSAction
{
    private float speed = 0.1f;            //跟随玩家的速度
    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()
    {

        //Debug.Log("follow:" + this.transform.position + " " + player.transform.position);
        transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
        this.transform.LookAt(player.transform.position);
    }
}

  • PlayerCollideDetection:检测巡逻兵与玩家碰撞的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//玩家与巡逻兵碰撞
public class PlayerCollideDetection : MonoBehaviour {
    //当玩家与巡逻兵碰撞
    void OnCollisionEnter(Collision other)
    {


        
        if (other.gameObject.tag == "Player" && other.gameObject.GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name=="yep")
        {

            Debug.Log(other.gameObject.GetComponent<Animator>().GetCurrentAnimatorClipInfo(0)[0].clip.name);
            this.gameObject.SetActive(false);
            return;
        }
        else if (other.gameObject.tag == "Player")
        {

            other.gameObject.GetComponent<Animator>().SetBool("death",true);
            this.GetComponent<Animator>().SetTrigger("attack");
            Singleton<GameEventManager>.Instance.PlayerGameover();
        }
    }
}

  • PlayerInDetection:检测玩家与进入巡逻兵的追踪范围
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//玩家进入巡逻兵的追踪范围
public class PlayerInDetection : MonoBehaviour
{
    //玩家进入巡逻兵的追踪范围
    void OnTriggerEnter(Collider collider)
    {
        
        if (collider.gameObject.tag == "Player")
        {
            Debug.Log("this.gameObject" + this.gameObject.name);
            this.gameObject.transform.GetComponent<PatrolData>().follow_player = true;
            this.gameObject.transform.GetComponent<PatrolData>().player = collider.gameObject;
            //触发巡逻兵向前扑的动作
            this.gameObject.transform.GetComponent<Animator>().SetTrigger("follow");
        }
    }
    //玩家离开巡逻兵的追踪范围
    void OnTriggerExit(Collider collider)
    {
        Debug.Log("玩家离开巡逻兵的追踪范围"+ collider.gameObject.tag);
        if (collider.gameObject.tag == "Player")
        {

            this.gameObject.transform.GetComponent<PatrolData>().follow_player = false;
            this.gameObject.transform.GetComponent<PatrolData>().player = null;
        }
    }
}


Unity中配置

  • 脚本挂载:挂在场景中的灯光下也行
    在这里插入图片描述

  • 巡逻兵
    在这里插入图片描述在这里插入图片描述

  • 主角小姐姐,可以使摄像头围着小姐姐360无死角旋转,主角小姐姐的移动考WASD实现
    在这里插入图片描述
    在这里插入图片描述

  • school:这个预制件内要放入Area用来检测Player的进入,并且Area要挂载检测脚本
    在这里插入图片描述


我的项目


参考链接

猜你喜欢

转载自blog.csdn.net/yesor_not/article/details/128232730