[Unity]创建攻击Slot系统

来源:互联网 发布:淘宝达人后台在哪里 编辑:程序博客网 时间:2024/06/07 23:49

一、背景

在游戏中,如果有多个敌人攻击玩家,那么就需要slot系统。在这个小教程中,我们将创建一个waypoint pathing系统,系统中的每个小攻击者都是个AI。

什么是Attack Slots:在这个系统中,我们给每一个攻击者分配一个位置,这个位置可以在攻击者周围的任何地方。如果没有Attack Slots,攻击者AI可能会挤在玩家正面。Attack Slots系统使得AI更聪明一点。

二、创建场景

1. 创建场景物体

首先我们来创建我们的场景。
添加一个Plane作为地面,一些Cube、Cylinder作为障碍物。然后添加一个Capsule作为玩家,另一个Capsule作为敌人。

基本场景

2. 创建NavMesh

在windows-Navigation里打开Navigation窗口。
选择地面、障碍物(注意不要选择Player和Enemy),在Navigation-Object子窗口里勾选Navigation Static

Navigation_1

然后可以在Bake子窗口里点击“Bake”按钮进行烘培了。烘培出结果如下(可适当调整Bake窗口里的参数)。

Navigation_2

3. 为Player和Enemy添加组件

1) 为Player和Enemy添加Nav Mesh Agent组件

Nav Mesh Agent

2) 为Player添加一个控制脚本PlayerController,来控制Player在场景中的移动。

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class PlayerController : MonoBehaviour {    // Use this for initialization    void Start () {    }    // Update is called once per frame    void Update () {        if(Input.GetMouseButtonDown(0))        {            Vector3 mpos  = Input.mousePosition;            mpos.z = 10;            Ray ray = Camera.main.ScreenPointToRay(mpos);            RaycastHit hit;            if(Physics.Raycast(ray, out hit))            {                this.GetComponent<NavMeshAgent>().destination = hit.point;            }        }    }}

这个脚本的作用就是,当鼠标点击某物体时,Player移动到鼠标点击的位置。

3)为Enemy添加脚本EnemyController

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class EnemyController : MonoBehaviour {    public GameObject target = null;  // 攻击目标    float pathTime = 0f;    // Use this for initialization    void Start () {    }    // Update is called once per frame    void Update () {        pathTime += Time.deltaTime;        if(pathTime > 0.5f)        {            pathTime = 0f;            Vector3 targetPos = target.transform.position;  // 攻击目标的世界坐标            Vector3 offset = (this.transform.position - targetPos).normalized * 1.5f;            this.GetComponent<NavMeshAgent>().destination = targetPos + offset;        }    }}

首先,我们把Player设为EnemyController的target,而后每0.5f获取target的位置。targetPos+offset就是Enemy的目标位置。这就是一个基本的没有Attack Slot的系统。但这样的问题是,当有很多个Enemy时,这些Enemy AI就会朝着同样的位置移动,它们会挤在一起。

Without AttackSlot

四、创建Slot Manager

1) 创建SlotManager脚本

using System.Collections;using System.Collections.Generic;using UnityEngine;public class SlotManager : MonoBehaviour {    List<GameObject> slots;    public int count = 6;    public float distance = 2f;    // Use this for initialization    void Start () {        slots = new List<GameObject>();        for(int i = 0; i < count; i++)        {            slots.Add(null);        }    }    // Update is called once per frame    void Update () {    }}

在SlotManager中维护一个GameObject列表,上述代码对slots进行初始化,初始化后包含了6个空物体。

2) 在slots中,每个列表元素(即一个slot)代表一个Enemy,我们需要获取每个slot的坐标,使得slot围绕着攻击目标,而不是挤在一起。因此在上述脚本中添加一个函数GetSlotPosition,以获得Player周围一圈的坐标。

    public Vector3 GetSlotPosition(int index)    {        float degrresPerIndex = 360f / count;        Vector3 pos = transform.position;        Vector3 offset = new Vector3(0, 0, distance);        return pos + (Quaternion.Euler(new Vector3(0, degrresPerIndex * index, 0f)) * offset);    }

3) 在SlotManager.cs里添加Reserve函数,为某个Enemy挑选一个最佳slot

    // 为攻击者设置一个slot    public int Reserve(GameObject attacker)    {        Vector3 bestPosition = transform.position;        Vector3 offset = (attacker.transform.position - bestPosition).normalized;        bestPosition += offset*distance;  // 不用slot时的位置        int bestSlot = -1;        float bestDist = 9999f;        for(int i=0;i<slots.Count; i++)        {            if (slots[i] != null)                continue;            float dist = (GetSlotPosition(i) - bestPosition).sqrMagnitude;            if(dist < bestDist) // 在所有slots里找出离bestPosition最近的那个空闲slot            {                bestSlot = i;                bestDist = dist;            }        }        if (bestSlot != -1)            slots[bestSlot] = attacker;        return bestSlot;    }

4) 释放slot

    public void Release(int slot)    {        slots[slot] = null;    }

5) 添加一些可视化效果,方便Debug
Gizmos: Gizmos用于可视化调试,所有的Gizmos绘制都必须在脚本下的OnDrawGizmos或OnDrawGizmosSelected函数中完成。OnDrawGizmos在每一帧都被调用,所有在OnDrawGizmos内部渲染的Gizmos都是可见的。OnDrawGizmosSelected仅在脚本所附加的物体被选中时调用。

    void OnDrawGizmosSelected()    {        for(int i=0;i<count;i++)        {            if (slots == null || slots.Count <= i || slots[i] == null)                Gizmos.color = Color.black;            else                Gizmos.color = Color.red;            Gizmos.DrawWireSphere(GetSlotPosition(i), 0.5f);        }    }

当某个slot空闲时,该slot的位置上会画一个黑色的球。当某个slot被一个enemy占据时,该位置会画上一个红色的球。

五、修改EnemyController

EnemyController中控制Enemy的移动,现在要把Enemy移动的目标位置修改为slot的位置。修改后的EnemyController.cs如下

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class EnemyController : MonoBehaviour {    public GameObject target = null;  // 攻击目标    float pathTime = 0f;    int slot = -1;    // Use this for initialization    void Start () {    }    // Update is called once per frame    void Update () {        pathTime += Time.deltaTime;        if(pathTime > 0.5f)        {            pathTime = 0f;            /*            Vector3 targetPos = target.transform.position;  // 攻击目标的世界坐标            Vector3 offset = (this.transform.position - targetPos).normalized * keepGap;            this.GetComponent<NavMeshAgent>().destination = targetPos + offset;            */            SlotManager slotManager = target.GetComponent<SlotManager>();            if(slotManager != null)            {                if (slot == -1)                    slot = slotManager.Reserve(gameObject);                if (slot == -1)                    return;                NavMeshAgent agent = this.GetComponent<NavMeshAgent>();                if (agent == null)                    return;                agent.destination = slotManager.GetSlotPosition(slot);            }        }    }}

修改为以上的移动方式后,对于某个enemy,会为其保留一个slot,而后每0.5s更新enemy的位置,向这个slot移动。

六、为Player添加Component

把SlotManager.cs添加给Player

with_slot

七、可改进的地方

  1. slot数量可根据游戏情况自动增加,或者根据不同的攻击范围来添加多个slot。
原创粉丝点击