模型与动画——作业与练习

本文介绍了一个游戏设计项目,其中涉及到智能巡逻兵的创建和动画。巡逻兵在3-5边的凸多边形路径上移动,遇到障碍物会改变目标,检测到玩家则会追击。游戏采用订阅发布模式进行通信,使用工厂模式生成巡逻兵,并通过不同的控制器管理行为。此外,还提供了项目结构和关键模块的详细说明。
摘要由CSDN通过智能技术生成

智能巡逻兵

提交要求:

游戏设计要求:

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

程序设计要求:

  • 必须使用订阅与发布模式传消息

  • subject:OnLostGoal

  • Publisher: ?

  • Subscriber: ?
    工厂模式生产巡逻兵

  • 友善提示1:生成 3~5个边的凸多边型
    随机生成矩形

  • 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
    5 ?

  • 友善提示2:参考以前博客,给出自己新玩法

参考UML类图:
订阅与发布模式

在“发布者-订阅者”模式中,称为发布者的消息发送者不会将消息编程为直接发送给称为订阅者的特定接收者。这意味着发布者和订阅者不知道彼此的存在。存在第三个组件,称为代理或消息代理或事件总线,它由发布者和订阅者都知道,它过滤所有传入的消息并相应地分发它们。换句话说,它是用于在不同系统组件之间传递消息的模式,而这些组件不知道关于彼此身份的任何信息。


项目结构:

主要预制体及设置:
Hero:

Patrol:

关键模块

Actions
实现动作的管理,与之前几次的作业类似。

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

public class SSAction : ScriptableObject
{
    public bool enable = true;
    public bool destroy = false;

    public GameObject gameObject { get; set; }
    public Transform transform { get; set; }
    public ISSActionCallback callBack { get; set; }

    protected SSAction() { }

    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

public enum SSActionEventType : int { Started, Completed }
public enum SSActionTargetType : int { Normal, Catching }   

public interface ISSActionCallback
{
    void SSActionEvent(SSAction source,
        SSActionEventType eventType = SSActionEventType.Completed,
        SSActionTargetType intParam = SSActionTargetType.Normal,     
        string strParam = null,
        Object objParam = null);
}

public class CCSequeneActions : SSAction, ISSActionCallback
{
    public List<SSAction> actionList;
    public int repeatTimes = -1;         
    public int subActionIndex = 0;        

    public static CCSequeneActions CreateSSAction(List<SSAction> _actionList, int _repeatTimes = 0)
    {
        CCSequeneActions action = ScriptableObject.CreateInstance<CCSequeneActions>();
        action.repeatTimes = _repeatTimes;
        action.actionList = _actionList;
        return action;
    }

    public override void Start()
    {
        foreach (SSAction action in actionList)
        {
            action.gameObject = this.gameObject;
            action.transform = this.transform;
            action.callBack = this;
            action.Start();
        }
    }

    public override void Update()
    {
        if (actionList.Count == 0)
            return;
        else if (subActionIndex < actionList.Count)
        {
            actionList[subActionIndex].Update();
        }
    }

    public void SSActionEvent(SSAction source, 
        SSActionEventType eventType = SSActionEventType.Completed, 
        SSActionTargetType intParam = SSActionTargetType.Normal, 
        string strParam = null, Object objParam = null)
    {
        source.destroy = false;
        this.subActionIndex++;
        if (this.subActionIndex >= actionList.Count)
        {
            this.subActionIndex = 0;
            if (repeatTimes > 0)
                repeatTimes--;
            if (repeatTimes == 0)
            {
                this.destroy = true;
                this.callBack.SSActionEvent(this);
            }
        }
    }
    void OnDestroy()
    {

    }


}


public class CCMoveToAction : SSAction
{
    public Vector3 target;
    public float speed;
    public bool isCatching;    

    public static CCMoveToAction CreateSSAction(Vector3 _target, float _speed, bool _isCatching)
    {
        CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
        action.target = _target;
        action.speed = _speed;
        action.isCatching = _isCatching;
        return action;
    }

    public override void Update()
    {
        this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed);
        if (this.transform.position == target)
        {
            this.destroy = true;
            if (!isCatching)    
                this.callBack.SSActionEvent(this);
            else
                this.callBack.SSActionEvent(this, SSActionEventType.Completed, SSActionTargetType.Catching);
        }
    }
}



public class SSActionManager : MonoBehaviour
{

    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    private List<SSAction> waitingAdd = new List<SSAction>();
    private List<int> waitingDelete = new List<int>();

    protected void Update()
    {
        foreach (SSAction ac in waitingAdd)
        {
            actions[ac.GetInstanceID()] = ac;
        }
        waitingAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
                waitingDelete.Add(kv.Key);
            else if (ac.enable)
                ac.Update();
        }

        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        waitingDelete.Clear();
    }

    public void runAction(GameObject gameObj, SSAction action, ISSActionCallback manager)
    {
        
        for (int i = 0; i < waitingAdd.Count; i++) //destroy actions
        {
            if (waitingAdd[i].gameObject.Equals(gameObj))
            {
                SSAction ac = waitingAdd[i];
                waitingAdd.RemoveAt(i);
                i--;
                DestroyObject(ac);
            }
        }
        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.gameObject.Equals(gameObj))
            {
                ac.destroy = true;
            }
        }

        action.gameObject = gameObj;
        action.transform = gameObj.transform;
        action.callBack = manager;
        waitingAdd.Add(action);
        action.Start();
    }
}



PartolFactory
巡逻兵工厂,负责生产巡逻兵,初始化位置等属性。

public class PatrolFactory : System.Object {
        private static PatrolFactory instance;
        private GameObject PatrolItem;

        private Vector3[] PatrolPosSet = new Vector3[] { new Vector3(-6, 0, 16), new Vector3(-1, 0, 19), //set patrol's position
            new Vector3(6, 0, 16), new Vector3(-5, 0, 7), new Vector3(0, 0, 7), new Vector3(6, 0, 7)};

        public static PatrolFactory getInstance() {
            if (instance == null)
                instance = new PatrolFactory();
            return instance;
        }

        public void initItem(GameObject _PatrolItem) {
            PatrolItem = _PatrolItem;
        }

        public GameObject getPatrol() {
            GameObject newPatrol = Camera.Instantiate(PatrolItem);
            return newPatrol;
        }

        public Vector3[] getPosSet() {
            return PatrolPosSet;
        }
    }

PatrolController
实现对巡逻兵行为的控制,检测附近的Hero位置,对碰撞事件做出相应的响应。当碰撞对象是巡逻兵或围栏时不进行抓捕,当对象为Hero时进行抓捕。

  private IAddAction addAction;
    private IStatusOptions op;

    public int ownIndex;
    public bool isCatching;    //if sense the hero

    private float range = 3.0f;

    void Start () {
        addAction = SceneController.getInstance() as IAddAction;
        op = SceneController.getInstance() as IStatusOptions;
        ownIndex = getOwnIndex();
        isCatching = false;
    }
	
	void Update () {
        checkNearByHero();
	}

    int getOwnIndex() { //get self index
        string name = this.gameObject.name;
        char cindex = name[name.Length - 1];
        int result = cindex - '0';
        return result;
    }

 
    void checkNearByHero () { //check if the hero is into it's range
        if (op.getHeroArea() == ownIndex) {   //step into the range 
            if (!isCatching) {
                isCatching = true;
                addAction.addDirectMovement(this.gameObject);
            }
        }
        else {
            if (isCatching) {   //stop catching
                op.heroScore();
                isCatching = false;
                addAction.addRandomMovement(this.gameObject, false);
            }
        }
    }

    void OnCollisionStay(Collision e) {
        if (e.gameObject.name.Contains("Patrol") || e.gameObject.name.Contains("fence") || e.gameObject.tag.Contains("FenceAround")) {
            isCatching = false;
            addAction.addRandomMovement(this.gameObject, false);
        }

        if (e.gameObject.name.Contains("Hero")) {
            op.caughtHero();
        }
    }

HeroController
主要实现了对Hero所在区域的判断。通过对比Hero位置与栏杆位置来判断Hero所在区域,并以此来触发巡逻兵的捕捉事件。

  void modifyStandOnArea() {
        float posX = this.gameObject.transform.position.x;
        float posZ = this.gameObject.transform.position.z;
        if (posZ >= RangeLimit.horiLimit) {
            if (posX < RangeLimit.leftLimit)
                standOnArea = 0;
            else if (posX > RangeLimit.rightLimit)
                standOnArea = 2;
            else
                standOnArea = 1;
        }
        else {
            if (posX < RangeLimit.leftLimit)
                standOnArea = 3;
            else if (posX > RangeLimit.rightLimit)
                standOnArea = 5;
            else
                standOnArea = 4;
        }
    }

Interface
系统需要用到的一些接口。

public interface IUserAction
{
    void heroMove(int dir);
}

public interface IAddAction
{
    void addRandomMovement(GameObject sourceObj, bool isActive);
    void addDirectMovement(GameObject sourceObj);
}

public interface IStatusOptions
{
    int getHeroArea();
    void heroScore();
    void caughtHero();
}

SceneController
实现对场景的控制。主要实现上面的接口函数。并实现几个getter和setter函数,与GameController做链接。

 public class Diretion {
        public const int UP = 0;
        public const int DOWN = 2;
        public const int LEFT = -1;
        public const int RIGHT = 1;
    }

    public class RangeLimit {
        public const float horiLimit = 12.42f;
        public const float leftLimit = -3.0f;
        public const float rightLimit = 3.0f;
    }

    public class SceneController : System.Object, IUserAction, IAddAction, IStatusOptions {
        private static SceneController instance;
        private GameModel myGameModel;
        private GameController myGameController;

        public void heroMove(int dir) { myGameModel.heroMove(dir); }

        public void addRandomMovement(GameObject sourceObj, bool isActive) { myGameModel.addRandomMovement(sourceObj, isActive); }

        public void addDirectMovement(GameObject sourceObj) { myGameModel.addDirectMovement(sourceObj); }

        public int getHeroArea() { return myGameModel.getHeroArea(); }

        public void heroScore() { myGameController.heroScore(); }

        public void caughtHero() { myGameController.caughtHero(); }

        public static SceneController getInstance() {
            if (instance == null)
                instance = new SceneController();
            return instance;
        }

        internal void setGameModel(GameModel _myGameModel) {
            if (myGameModel == null) {
                myGameModel = _myGameModel;
            }
        }

        internal void setGameController(GameController _myGameController) {
            if (myGameController == null) {
                myGameController = _myGameController;
            }
        }


    }

GamController
加载场景,通过调用接口函数实现Hero的得分和Hero的被捕事件。

    public delegate void GameScoreAction();
    public static event GameScoreAction myGameScoreAction;
    public delegate void GameOverAction();
    public static event GameOverAction myGameOverAction;
    private SceneController scene;

    void Start () {
        scene = SceneController.getInstance();
        scene.setGameController(this);
    }
	
    public void heroScore() {
        if (myGameScoreAction != null)
            myGameScoreAction();
    }

    public void caughtHero() {
        if (myGameOverAction != null)
            myGameOverAction();
    }

GameModel
实现对游戏对象的加载和动作实现。用list来存放所有的巡逻兵。定义了Hero的四方向基本移动操作以及组合移动操作,同时对巡逻兵巡逻范围进行了判断,当超出巡逻范围则不再抓捕。

  private List<GameObject> patrolList;
    private List<int> patrolRemainList;

    private const float normalSpeed = 0.03f;
    private const float catchSpeed = 0.05f;
    public GameObject PatrolItem, HeroItem, sceneModelItem, canvasItem;

    private SceneController scene;
    private GameObject myHero, sceneModel, canvasAndText;

    public void SSActionEvent(SSAction source, //ssActionCallBack interface
    SSActionEventType eventType = SSActionEventType.Completed,
    SSActionTargetType intParam = SSActionTargetType.Normal,
    string strParam = null, Object objParam = null)
    {
        if (intParam == SSActionTargetType.Normal)
            addRandomMovement(source.gameObject, true);
        else
            addDirectMovement(source.gameObject);
    }


    void Awake() {
        PatrolFactory.getInstance().initItem(PatrolItem);
    }

    protected new void Start () {
        scene = SceneController.getInstance();
        scene.setGameModel(this);
        genHero();
        genPatrols();
        sceneModel = Instantiate(sceneModelItem);
        canvasAndText = Instantiate(canvasItem);
    }

    protected new void Update() {
        base.Update();
    }

  
    void genHero() { //get hero
        myHero = Instantiate(HeroItem);
    }

    void genPatrols() { //get patrols
        patrolList = new List<GameObject>(6);
        patrolRemainList = new List<int>(6);
        Vector3[] posSet = PatrolFactory.getInstance().getPosSet();
        for (int i = 0; i < 6; i++) {
            GameObject newPatrol = PatrolFactory.getInstance().getPatrol();
            newPatrol.transform.position = posSet[i];
            newPatrol.name = "Patrol" + i;
            patrolRemainList.Add(-2);
            patrolList.Add(newPatrol);
            addRandomMovement(newPatrol, true);
        }
    }

    public void heroMove(int dir) { //hero movement
        myHero.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
        switch (dir) {
            case Diretion.UP:
                myHero.transform.position += new Vector3(0, 0, 0.1f);
                break;
            case Diretion.DOWN:
                myHero.transform.position += new Vector3(0, 0, -0.1f);
                break;
            case Diretion.LEFT:
                myHero.transform.position += new Vector3(-0.1f, 0, 0);
                break;
            case Diretion.RIGHT:
                myHero.transform.position += new Vector3(0.1f, 0, 0);
                break;
        }
    }


    public void addRandomMovement(GameObject sourceObj, bool isActive) {
        int index = getIndex(sourceObj);
        int dir = getdirection(index, isActive);
        patrolRemainList[index] = dir;

        sourceObj.transform.rotation = Quaternion.Euler(new Vector3(0, dir * 90, 0));
        Vector3 target = sourceObj.transform.position;
        switch (dir) {
            case Diretion.UP:
                target += new Vector3(0, 0, 1);
                break;
            case Diretion.DOWN:
                target += new Vector3(0, 0, -1);
                break;
            case Diretion.LEFT:
                target += new Vector3(-1, 0, 0);
                break;
            case Diretion.RIGHT:
                target += new Vector3(1, 0, 0);
                break;
        }
        addSingleMoving(sourceObj, target, normalSpeed, false);
    }

    int getIndex(GameObject sourceObj) {
        string name = sourceObj.name;
        char cindex = name[name.Length - 1];
        int result = cindex - '0';
        return result;
    }

    int getdirection(int index, bool isActive) {
        int dir = Random.Range(-1, 3);
        if (!isActive) {    //when hit
            while (patrolRemainList[index] == dir || isOutOfRange(index, dir)) {
                dir = Random.Range(-1, 3);
            }
        }
        else {              //when not hit
            while (patrolRemainList[index] == 0 && dir == 2 
                || patrolRemainList[index] == 2 && dir == 0
                || patrolRemainList[index] == 1 && dir == -1
                || patrolRemainList[index] == -1 && dir == 1
                || isOutOfRange(index, dir)) {
                dir = Random.Range(-1, 3);
            }
        }
        return dir;
    }
    

    //judge if patrol out range
    bool isOutOfRange(int index, int dir) {
        Vector3 patrolPos = patrolList[index].transform.position;
        float posX = patrolPos.x;
        float posZ = patrolPos.z;
        switch (index) {
            case 0:
                if (dir == 1 && posX + 1 > RangeLimit.leftLimit
                    || dir == 2 && posZ - 1 < RangeLimit.horiLimit)
                    return true;
                break;
            case 1:
                if (dir == 1 && posX + 1 > RangeLimit.rightLimit
                    || dir == -1 && posX - 1 < RangeLimit.leftLimit
                    || dir == 2 && posZ - 1 < RangeLimit.horiLimit)
                    return true;
                break;
            case 2:
                if (dir == -1 && posX - 1 < RangeLimit.rightLimit
                    || dir == 2 && posZ - 1 < RangeLimit.horiLimit)
                    return true;
                break;
            case 3:
                if (dir == 1 && posX + 1 > RangeLimit.leftLimit
                    || dir == 0 && posZ + 1 > RangeLimit.horiLimit)
                    return true;
                break;
            case 4:
                if (dir == 1 && posX + 1 > RangeLimit.rightLimit
                    || dir == -1 && posX - 1 < RangeLimit.leftLimit
                    || dir == 0 && posZ + 1 > RangeLimit.horiLimit)
                    return true;
                break;
            case 5:
                if (dir == -1 && posX - 1 < RangeLimit.rightLimit
                    || dir == 0 && posZ + 1 > RangeLimit.horiLimit)
                    return true;
                break;
        }
        return false;
    }

    public void addDirectMovement(GameObject sourceObj) { //catch movement
        int index = getIndex(sourceObj);
        patrolRemainList[index] = -2;
        sourceObj.transform.LookAt(sourceObj.transform);
        Vector3 oriTarget = myHero.transform.position - sourceObj.transform.position; //get original target
        Vector3 target = new Vector3(oriTarget.x / 4.0f, 0, oriTarget.z / 4.0f);
        target += sourceObj.transform.position;
        addSingleMoving(sourceObj, target, catchSpeed, true);
    }

    void addSingleMoving(GameObject sourceObj, Vector3 target, float speed, bool isCatching) {
        this.runAction(sourceObj, CCMoveToAction.CreateSSAction(target, speed, isCatching), this);
    }

    void addCombinedMoving(GameObject sourceObj, Vector3[] target, float[] speed, bool isCatching) {
        List<SSAction> acList = new List<SSAction>();
        for (int i = 0; i < target.Length; i++) {
            acList.Add(CCMoveToAction.CreateSSAction(target[i], speed[i], isCatching));
        }
        CCSequeneActions MoveSeq = CCSequeneActions.CreateSSAction(acList);
        this.runAction(sourceObj, MoveSeq, this);
    }

    
    public int getHeroArea() { //get hero area
        return myHero.GetComponent<HeroController>().standOnArea;
    }

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值