Unity 3D 巡逻兵设计

游戏设计要求:

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

项目

github地址

gif图(图片较大加载较慢),若无法加载,可以到此链接查看
这里写图片描述

游戏实现

订阅/发布模式的使用

相比与c++,java,js等语言,C#有一个提供订阅/发布模式的语句,分别为delegate和event,一个使用的例子如下:
在巡逻兵的脚本中,可以定义下面的事件,当巡逻兵捕捉到玩家的时候,可以通过CatchHeroEvent()发布消息

public delegate void CatchHero();
public static event CatchHero CatchHeroEvent;

而在接收者中,可以使用一种很简单的方式接收到发布的消息:

PatrolScript.CatchHeroEvent += patrolCatchHero;

巡逻兵的巡逻方法

我们在挂载到巡逻兵的脚本上设计以下方法:当巡逻兵应该去抓捕玩家(shouldChase())且仍然未抓到(!hasCatch)时,把追捕的目标设置成玩家(hero)所在的位置;如果玩家走出区域,会通过EscapeEvent发送一条事件告知gamecontrol可以加分,并获得下一个随机的位置

void Update () {
    if (shouldChase() && !hasCatch) {
        target = hero.gameobject.transform.position;
        isChasing = true;
    } else if(isChasing) { // 停止抓捕
        isChasing = false;
        EscapeEvent();
        target = getRandomPosition();
    } else if((target - gameObject.transform.position).magnitude < 3) {
        target = getRandomPosition();
    }
    move();
}

巡逻兵的碰撞检测

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

void OnCollisionStay(Collision e) {
    string name = e.gameObject.name;
    if(name.Contains("Hero")) {
        CatchHeroEvent();
        hasCatch = true;
        target = getRandomTarget();
    } else if(name.Contains("wall") || name.Contains("Patrol")) {
        target = getRandomTarget();
    }
}

当捕捉到玩家时,发出一条捕捉到的消息;当碰撞到其他巡逻兵或墙的时候,自动寻找下一个目标

UML图

这里写图片描述

其他

关于多边形巡逻的实现

事实上,我们在游戏中没有采用让小兵进行多边形巡逻的设计:所有小兵按同样的速率,按相似的多边形巡逻,让游戏的视觉体验不太好。不过为了达到这个目标,这次我们在多设计了两个Action函数:CycleAction和RotateToAction。实现多边形巡逻的方法为:
1. 巡逻兵走到多边形的起点
2. 巡逻兵走到下一个点
3. 转向,面向需要走到的下一个点
4. 循环回到第二步

循环动作

这个动作的设计思路是让游戏对象进行完一组前导动作之后,进入循环的动作中

// 循环动作
public class CycleAction : Action, Callback {
    public List<Action> _begin;
    public List<Action> _cycle;
    private int i = 0;
    private bool inCycle = false;

    public static Action getAction(List<Action> begin, List<Action> cycle) {
        CycleAction action = ScriptableObject.CreateInstance<CycleAction>();
        action._begin = begin;
        action._cycle = cycle;
        return action;
    }

    public override void Start() {
        inCycle = false;
        foreach (Action ac in _cycle) {
            ac.callback = this;
        }
        foreach (Action ac in _begin) {
            ac.callback = this;
        }
        if (_begin.Count != 0) {
            _begin[0].Start();
        } else if (_cycle.Count != 0) {
            inCycle = true;
            _cycle[0].Start();
        } else {
            if (callback != null) {
                callback.call();
            }
            destroy = true;
        }
    }
    public override void Update() {
        if(inCycle) {
            if(_cycle.Count != 0) {
                _cycle[i].Update();
            } else {
                this.destroy = true;
            }
        } else {
            _begin[i].Update();
        }
    }
    public override void OnGUI() {
        if (inCycle) {
            _cycle[i].OnGUI();
        } else {
            _begin[i].OnGUI();
        }
    }
    // 子动作的回调函数
    public void call() {
        if(inCycle) {
            i = (i + 1) % _cycle.Count;
            _cycle[i].Start();
        } else {
            _begin[i].destroy = true;
            if (++i < _begin.Count) {
                // 开始操作还没结束
                _begin[i].Start();
            } else if (_cycle.Count != 0) {
                // 开始操作已结束,进入循环
                i = 0;
                _cycle[0].Start();
                inCycle = true;
            } else {
                // 循环队列无动作
                this.destroy = true;
            }
        }
    }
}

转体动作

这个Action的作用在于让指定对象在给定时间或角速度下转向,面向某个方向

// 转体动作
public class RotateToAction : Action {
    private Vector3 targetPos;
    private Quaternion dist;
    private Vector3 Del;
    private float during;

    private float time = 0;

    public static Action getAction(GameObject gameobject, Vector3 dist, float during) {
        RotateToAction ac = ScriptableObject.CreateInstance<RotateToAction>();
        ac.targetPos = dist;
        ac.gameobject = gameobject;
        ac.during = during;
        return ac;
    }
    public static Action getActionByVelocity(GameObject gameobject, Vector3 dist, float velocity) {
        Vector3 e = Quaternion.LookRotation(dist).eulerAngles - gameobject.transform.rotation.eulerAngles;
        float during = e.magnitude / velocity;
        return getAction(gameobject, dist, during);
    }

    public override void Start() {
        time = 0;
        dist = Quaternion.LookRotation(targetPos - gameobject.transform.position);
        Del = dist.eulerAngles - gameobject.transform.rotation.eulerAngles;
    }
    public override void Update() {
        if (time < during) {
            gameobject.gameObject.transform.rotation *= Quaternion.Euler(Del * Time.deltaTime / during);
            time += Time.deltaTime;
        } else {
            gameobject.transform.rotation = dist;
            destroy = true;
            if (callback != null) {
                callback.call();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Mrfive555/article/details/80287821
今日推荐