智能巡逻兵

本文介绍了一个使用Unity开发的智能巡逻兵游戏,包括游戏设计要求、工厂模式和观察者模式的应用。游戏设计中,巡逻兵需在特定路径上行走并追击玩家。程序设计采用工厂模式生产巡逻兵,观察者模式用于计分系统。游戏包含巡逻兵的动画、碰撞检测以及面向玩家的运动控制。作者表示对设计模式的理解仍有提升空间。
摘要由CSDN通过智能技术生成

智能巡逻兵

游戏视频 https://www.bilibili.com/video/av73240950/

项目地址 https://gitee.com/jenny_s/3DIntelligentPatrol

游戏设计要求:

  • 创建一个地图和若干巡逻兵(使用动画);

  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;

  • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;

  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;

  • 失去玩家目标后,继续巡逻;

  • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;

  • 程序设计要求:

    • 必须使用订阅与发布模式传消息
      • subject:OnLostGoal
      • Publisher: ?
      • Subscriber: ?
    • 工厂模式生产巡逻兵

游戏设计

在这里插入图片描述

本次游戏使用了Unity的Animator控件,给人物增加了动作,使得游戏更具有可玩性和观赏感。对角色增加了前进、后退、左拐、右拐和逗留的动作,通过wasd键来控制事件的触发和转移。

在这里插入图片描述

// 单例模式,全局唯一角色
public class Player
{

    private static Player _instance;             //Player类的实例

    public static Player GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Player();
        }
        return _instance;
    }

    private Animator animator;
    private AnimatorStateInfo stateinfo;
    GameObject Role;

    private Player()
    {
        Role = GameObject.FindWithTag("Player");
        animator = Role.GetComponent<Animator>();
    }

    private void setStateFalse()
    {
        animator.SetBool("Idle", false);
        animator.SetBool("Forward", false);
        animator.SetBool("Left", false);
        animator.SetBool("Right", false);
        animator.SetBool("Backward", false);
    }

    public void idle()
    {
        if(animator != null)
        {
            setStateFalse();
            animator.SetBool("Idle", true);
        }

    }

    public void forward()
    {
        if (animator != null)
        {
            setStateFalse();
            animator.SetBool("Forward", true);
        }

    }

    public void backward()
    {
        if (animator != null)
        {

        }

            setStateFalse();
        animator.SetBool("Backward", true);
    }

    public void left()
    {
        if (animator != null)
        {
            setStateFalse();
            animator.SetBool("Left", true);
        }

    }

    public void right()
    {
        if (animator != null)
        {
        setStateFalse();
        animator.SetBool("Right", true);
        }

    }

}

巡逻兵有走路、奔跑、跳跃和攻击四种动作,当玩家没有进入巡逻兵的视野范围内时,巡逻兵在一定区域内走路巡逻;当玩家进入巡逻兵的视野范围内时,巡逻兵根据玩家的位置进行追击;当巡逻兵追击到玩家时,游戏结束。

巡逻兵的视野范围是一个Cube触发器,而巡逻兵的攻击范围是一个Cube碰撞器,如下图所示,外面的立方体是触发器,里面的立方体是碰撞器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9V8IJz2-1571900689561)(pic/1571899590146.png)]

在这里插入图片描述

为了保证巡逻兵在追击的时候能够“面向”玩家,还需要计算一个偏转角度,如下所示。

            Vector3 rolepos = role.transform.position;
            float offX = (-rolepos.x + transform.position.x) * Time.deltaTime;
            float offZ = (-rolepos.z + transform.position.z) * Time.deltaTime;
            transform.Translate(offX, 0, offZ);
            if(offZ != 0)
            {
                float angle = Mathf.Atan(Mathf.Abs(offX / offZ))*Time.deltaTime;
                transform.Rotate(new Vector3(0, angle, 0));
            }

挂载在巡逻兵上的运动脚本如下所示:

public class Soldier : MonoBehaviour
{

    private Animator animator;
    private AnimatorStateInfo stateinfo;
    GameObject soldier;
    private int SoldierState = 0;   // Walk=0,Run and Follow=1, hit = 2
    GameObject role;


    private void setStateFalse()
    {
        animator.SetBool("Walk", false);
        animator.SetBool("Run", false);
        animator.SetBool("Jump", false);
    }


    // Start is called before the first frame update
    void Start()
    {
        soldier = this.transform.gameObject;
        role = GameObject.FindWithTag("Player");
        animator = soldier.GetComponent<Animator>();
        SoldierState = 0;
        setStateFalse();
        animator.SetBool("Walk", true);
    }

    // Update is called once per frame
    void Update()
    {
        if (SoldierState == 0)
        {
            float translationZ = 2f;
            translationZ *= Time.deltaTime;
            transform.Translate(0, 0, translationZ);
        }
        else if(SoldierState == 1)
        {
            Vector3 rolepos = role.transform.position;
            float offX = (-rolepos.x + transform.position.x) * Time.deltaTime;
            float offZ = (-rolepos.z + transform.position.z) * Time.deltaTime;
            transform.Translate(offX, 0, offZ);
            if(offZ != 0)
            {
                float angle = Mathf.Atan(Mathf.Abs(offX / offZ))*Time.deltaTime;
                transform.Rotate(new Vector3(0, angle, 0));
            }
        }
    }

    private void OnTriggerEnter(Collider other)
    {

    }

    // 当角色停留在一个monster的触发区域内时,monster追逐角色
    private void OnTriggerStay(Collider other)
    {
        if (other.gameObject.tag == "Player" && SoldierState == 0)
        {
            Debug.Log("stay");
            setStateFalse();
            animator.SetBool("Run", true);
            SoldierState = 1;
        }
    }

    // 当角色离开一个monster的触发区域内时,monster恢复巡逻状态
    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player" && SoldierState == 1)
        {
            Debug.Log("stay");
            setStateFalse();
            animator.SetBool("Walk", true);
            SoldierState = 0;
        }
    }

    public void TurnRight()
    {
        transform.Rotate(0, 90, 0);
    }

    private void OnCollisionEnter(Collision collision)
    {

        if (collision.gameObject.tag == "Wall")
        {
            Debug.Log("WALL");
            TurnRight();
        }
        else if (collision.gameObject.tag == "Player")
        {
            Debug.Log("player");
            setStateFalse();
            animator.SetBool("Jump", true);
            SoldierState = 2;
            //soldier.GetComponent<Rigidbody>().isKinematic = true;
            role.GetComponent<Rigidbody>().isKinematic = true;
            Singleton<MyGUI>.Instance.state = 1;
        }
    }



}

工厂模式

这里采用单例工厂生产巡逻兵。在FirstSceneController中加入单例工厂,然后使用该工厂生产的巡逻兵。

        this.gameObject.AddComponent<SoldierFactory>();

兵工厂用来负责巡逻兵的生产和销毁。

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

public class SoldierFactory : MonoBehaviour
{
    // Start is called before the first frame update
    private List<GameObject> used = new List<GameObject>();
    private List<GameObject> free = new List<GameObject>();

    // 巡逻兵的“出生”位置
    Vector3 []MonsterPos = {
        new Vector3(7, 0, 17) , new Vector3(-7, 0, 17),
        new Vector3(-7, 0, 34) , new Vector3(12, 0, 36)
    };

    public GameObject GetSoldier(int index)
    {
        GameObject newSoldier = null;
        if (free.Count > 0)
        {
            newSoldier = free[0];
            free.Remove(free[0]);
        }
        else
        {
            newSoldier = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Monster"));
        }
        newSoldier.transform.position = MonsterPos[index];
        used.Add(newSoldier);
        newSoldier.name = newSoldier.GetInstanceID().ToString();
        newSoldier.SetActive(true);
        return newSoldier;
    }

    public void FreeSoldier(GameObject soldier)
    {
        if (soldier != null)
        {
            used.Remove(soldier);
            soldier.SetActive(false);
            Destroy(soldier);
        }
    }
}

观察者模式

我们采用观察者模式(订阅与发布模式)完成如下操作:

  • 当角色成功在一个方形区域内甩掉巡逻兵后,会进行记分;

在这里,发布者是不同区域间的“门洞”,观察者是记分系统。

发布者(Subject)是SceneTrigger


public class SceneTrigger : MonoBehaviour
{
    protected List<Observer> observers = new List<Observer>();
    public void attach(Observer o)
    {
        observers.Add(o);
    }
    public void detach(Observer o)
    {
        observers.Remove(o);
    }
    public void notify()
    {
        foreach (Observer o in observers)
        {
            o.update();
        }
    }

    // 当角色离开一个方块区域时,加一分
    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player" )
        {
            notify();
        }
    }
}

观察者们要实现统一的观察者接口。

public interface Observer
{
    void update();
}

计分器是观察者。

public class ScoreController : Observer
{
    public int score;

    // Start is called before the first frame update
    void Start()
    {
        score = 0;
    }


    public void reset()
    {
        score = 0;
    }

    public void update()
    {
        score++;
    }

    public int getScore()
    {
        return score;
    }

}

游戏效果

在这里插入图片描述

游戏心得

本次作业做了三天的时间,耗费了不少精力,但是感觉本人对设计模式的理解和掌握依旧不够,有很多代码冗余,希望下一次能够更进一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值