[Unity]创建攻击Slot系统

一、背景

在游戏中,如果有多个敌人攻击玩家,那么就需要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。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值