Unity2D // 基于RayCast的检测系统:可用于BOSS对玩家所处位置的检测以便进行更好的行为处理

最近在使用Unity商城里的Corgi Engine2D进行BOSS行为制作时,我发现原先写的检测机制不太合理,只能检测到BOSS的前方是否有玩家,而无法达到分距离检测,即判断玩家在BOSS的远处还是近处。于是准备重写检测逻辑,同时也希望能做到检测玩家是否在BOSS身后这一事件。

注意:使用本代码需要Unity商城中的Corgi Engine为基础,但逻辑都相通。

刚开始准备用RayCast自带的距离检测进行判断,但思考过后发现只用距离无法判断角色是否在BOSS身后。于是另想了一个方法,就是一次性使用两个RayCast判断一个区域,光说不明白,直接上图:

大概就是用一长一短两个RayCast,通过(长的检测玩家 && 短的检测不到玩家)判断角色是否在区间内,进而判断并返回角色位置。

AIDecision检测脚本代码:

using MoreMountains.Tools;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MoreMountains.CorgiEngine
{
    /// <summary>
    /// This Decision will return true if any object on its TargetLayer layermask enters its line of sight. It will also set the Brain's Target to that object. You can choose to have it in ray mode, in which case its line of sight will be an actual line (a raycast), or have it be wider (in which case it'll use a spherecast). You can also specify an offset for the ray's origin, and an obstacle layer mask that will block it.
    /// </summary>
	[AddComponentMenu("Corgi Engine/Character/AI/Decisions/AI Decision Detect Target Line IN STATE")]
    // [RequireComponent(typeof(Character))]
    public class AIDecisionDetectTargetLineINSTATE : AIDecision
    {
        /// the possible detection methods
        public enum DetectMethods { Ray, WideRay }
        /// the possible detection directions
        public enum DetectionDirections { Front, Back}
        /// the detection method
        [Tooltip("the detection method (ray casts a single ray, wideray casts a boxcast")]
        public DetectMethods DetectMethod = DetectMethods.Ray;
        /// the detection direction
        [Tooltip("the detection direction (front, back, or both)")]
        public DetectionDirections DetectionDirection = DetectionDirections.Front;
        /// the width of the ray to cast (if we're in WideRay mode only
        [Tooltip("the width of the ray to cast (if we're in WideRay mode only")]
        public float RayWidth = 1f;
        /// the distance up to which we'll cast our rays
        [Tooltip("the distance up to which we'll cast our rays")]
        public float DetectionDistance = 10f;
        public float DetectionDistanceClose = 8f;
        /// the offset to apply to the ray(s)
        [Tooltip("the offset to apply to the ray(s)")]
        public Vector3 DetectionOriginOffset = new Vector3(0,0,0);
        /// the layer(s) on which we want to search a target on
        [Tooltip("the layer(s) on which we want to search a target on")]
        public LayerMask TargetLayer = LayerManager.PlayerLayerMask;
        // public LayerMask TargetLayerLeft = LayerManager.CharacterLeftLayerMask;
        // public LayerMask TargetLayerRight = LayerManager.CharacterRightLayerMask;

        /// the layer(s) on which obstacles are set. Obstacles will block the ray
        [Tooltip("the layer(s) on which obstacles are set. Obstacles will block the ray")]
        public LayerMask ObstaclesLayer = LayerManager.ObstaclesLayerMask;

        protected Vector2 _direction;
        protected Vector2 _facingDirection;
        protected float _distanceToTarget;
        protected Vector2 _raycastOrigin;
        protected Character _character;
        protected bool _drawLeftGizmo = false;
        protected bool _drawRightGizmo = false;
        protected Color _gizmosColor = Color.yellow;
        // protected Color __gizmosColorClose = Color.red;
        protected Vector3 _gizmoCenter;
        protected Vector3 _gizmoCenterClose;
        protected Vector3 _gizmoSize;
        protected Vector3 _gizmoSizeClose;
        protected bool _init = false;
        protected Vector2 _boxcastSize = Vector2.zero;
        protected Vector2 _boxcastSizeClose = Vector2.zero;
        
        /// <summary>
        /// On Init we grab our character
        /// </summary>
        public override void Initialization()
        {
            _character = this.gameObject.GetComponentInParent<Character>();
            _gizmosColor.a = 0.25f;
            _init = true;
        }

        /// <summary>
        /// On Decide we look for a target
        /// </summary>
        /// <returns></returns>
        public override bool Decide()
        {
            return DetectTarget();
        }

        /// <summary>
        /// Returns true if a target is found by the ray
        /// </summary>
        /// <returns></returns>
        protected virtual bool DetectTarget()
        {
            bool hit = false;
            _distanceToTarget = 0;
            Transform target = _brain.Target;
            RaycastHit2D raycastFar;
            RaycastHit2D raycastClose;
            _drawLeftGizmo = false;
            _drawRightGizmo = false;

            _boxcastSize.x = DetectionDistance / 5f;
            _boxcastSize.y = RayWidth;
            _boxcastSizeClose.x = DetectionDistanceClose / 5f;
            _boxcastSizeClose.y = RayWidth;

            _facingDirection = _character.IsFacingRight ? Vector2.right : Vector2.left;
            // we cast a ray to the left of the agent to check for a Player
            _raycastOrigin.x = transform.position.x + _facingDirection.x * DetectionOriginOffset.x / 2;
            _raycastOrigin.y = transform.position.y + DetectionOriginOffset.y;

            // we cast it to the left	
            if (/*(DetectionDirection == DetectionDirections.Both)
                || */((DetectionDirection == DetectionDirections.Front) && (!_character.IsFacingRight))
                || ((DetectionDirection == DetectionDirections.Back) && (_character.IsFacingRight)))
            {
                if (DetectMethod == DetectMethods.Ray)
                {
                    raycastFar = MMDebug.RayCast(_raycastOrigin, Vector2.left, DetectionDistance, TargetLayer, MMColors.Gold, true);
                    raycastClose = MMDebug.RayCast(_raycastOrigin, Vector2.left, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                }
                else
                {
                    raycastFar = Physics2D.BoxCast(_raycastOrigin + Vector2.right * _boxcastSize.x / 2f, _boxcastSize, 0f, Vector2.left, DetectionDistance, TargetLayer);
                    raycastClose = Physics2D.BoxCast(_raycastOrigin + Vector2.right * _boxcastSizeClose.x / 2f, _boxcastSizeClose, 0f, Vector2.left, DetectionDistanceClose, TargetLayer);
                    MMDebug.RayCast(_raycastOrigin + Vector2.up * RayWidth/2f, Vector2.left, DetectionDistance, TargetLayer, MMColors.Gold, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f, Vector2.left, DetectionDistance, TargetLayer, MMColors.Gold, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f + Vector2.left * DetectionDistance, Vector2.up, RayWidth, TargetLayer, MMColors.Gold, true);
                    MMDebug.RayCast(_raycastOrigin + Vector2.up * RayWidth/2f, Vector2.left, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f, Vector2.left, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f + Vector2.left * DetectionDistanceClose, Vector2.up, RayWidth, TargetLayer, MMColors.Red, true);
                    _drawLeftGizmo = true;
                }
                
                // if we see a player
                if (raycastFar && (!raycastClose))
                {
                    hit = true;
                    _direction = Vector2.left;
                    _distanceToTarget = Vector2.Distance(_raycastOrigin, raycastFar.point);
                    target = raycastFar.collider.gameObject.transform;
                }
            }

            // we cast a ray to the right of the agent to check for a Player	
            if (/*(DetectionDirection == DetectionDirections.Both)
                || */((DetectionDirection == DetectionDirections.Front) && (_character.IsFacingRight))
               || ((DetectionDirection == DetectionDirections.Back) && (!_character.IsFacingRight)))
            {
                if (DetectMethod == DetectMethods.Ray)
                {
                    raycastFar = MMDebug.RayCast(_raycastOrigin, Vector2.right, DetectionDistance, TargetLayer, MMColors.Yellow, true);
                    raycastClose = MMDebug.RayCast(_raycastOrigin, Vector2.right, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                }
                else
                {
                    raycastFar = Physics2D.BoxCast(_raycastOrigin - Vector2.right * _boxcastSize.x / 2f, _boxcastSize, 0f, Vector2.right, DetectionDistance, TargetLayer);
                    raycastClose = Physics2D.BoxCast(_raycastOrigin - Vector2.right * _boxcastSizeClose.x / 2f, _boxcastSizeClose, 0f, Vector2.right, DetectionDistanceClose, TargetLayer);
                    MMDebug.RayCast(_raycastOrigin + Vector2.up * RayWidth / 2f, Vector2.right, DetectionDistance, TargetLayer, MMColors.Yellow, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f, Vector2.right, DetectionDistance, TargetLayer, MMColors.Yellow, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f + Vector2.right * DetectionDistance, Vector2.up, RayWidth, TargetLayer, MMColors.Yellow, true);
                    MMDebug.RayCast(_raycastOrigin + Vector2.up * RayWidth/2f, Vector2.right, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f, Vector2.right, DetectionDistanceClose, TargetLayer, MMColors.Red, true);
                    MMDebug.RayCast(_raycastOrigin - Vector2.up * RayWidth / 2f + Vector2.right * DetectionDistanceClose, Vector2.up, RayWidth, TargetLayer, MMColors.Red, true);
                    _drawLeftGizmo = true;
                }
                
                if (raycastFar && (!raycastClose))
                {
                    hit = true;
                    _direction = Vector2.right;
                    _distanceToTarget = Vector2.Distance(_raycastOrigin, raycastFar.point);
                    target = raycastFar.collider.gameObject.transform;
                }
            }

            if (hit)
            {
                // we make sure there isn't an obstacle in between
                float distance = Vector2.Distance((Vector2)target.transform.position, _raycastOrigin);
                RaycastHit2D raycastObstacle = MMDebug.RayCast(_raycastOrigin, ((Vector2)target.transform.position - _raycastOrigin).normalized, distance, ObstaclesLayer, Color.gray, true);
                
                if (raycastObstacle && _distanceToTarget > raycastObstacle.distance)
                {
                    _brain.Target = null;
                    return false;
                }
                else
                {
                    // if there's no obstacle, we store our target and return true
                    _brain.Target = target;
                    return true;
                }
            }
            _brain.Target = null;
            return false;
        }
        
        /// <summary>
        /// Draws ray gizmos
        /// </summary>
        protected virtual void OnDrawGizmos()
        {
            if ((DetectMethod != DetectMethods.WideRay) || !_init)
            {
                return;
            }

            Gizmos.color = _gizmosColor;

            _raycastOrigin.x = transform.position.x + _facingDirection.x * DetectionOriginOffset.x / 2;
            _raycastOrigin.y = transform.position.y + DetectionOriginOffset.y;

            if (/*(DetectionDirection == DetectionDirections.Both)
                || */((DetectionDirection == DetectionDirections.Front) && (!_character.IsFacingRight))
                || ((DetectionDirection == DetectionDirections.Back) && (_character.IsFacingRight)))
            {
                _gizmoCenter = (Vector3)_raycastOrigin + Vector3.left * DetectionDistance / 2f;
                _gizmoSize.x = DetectionDistance;
                _gizmoSize.y = RayWidth;
                _gizmoSize.z = 1f;
                _gizmoCenterClose = (Vector3)_raycastOrigin + Vector3.left * DetectionDistanceClose / 2f;
                _gizmoSizeClose.x = DetectionDistanceClose;
                _gizmoSizeClose.y = RayWidth;
                _gizmoSizeClose.z = 1f;
                Gizmos.DrawCube(_gizmoCenter, _gizmoSize);
                Gizmos.DrawCube(_gizmoCenterClose, _gizmoSizeClose);
            }

            if (/*(DetectionDirection == DetectionDirections.Both)
                || */((DetectionDirection == DetectionDirections.Front) && (_character.IsFacingRight))
               || ((DetectionDirection == DetectionDirections.Back) && (!_character.IsFacingRight)))
            {
                _gizmoCenter = (Vector3)_raycastOrigin + Vector3.right * DetectionDistance / 2f;
                _gizmoSize.x = DetectionDistance;
                _gizmoSize.y = RayWidth;
                _gizmoSize.z = 1f;
                _gizmoCenterClose = (Vector3)_raycastOrigin + Vector3.right * DetectionDistanceClose / 2f;
                _gizmoSizeClose.x = DetectionDistanceClose;
                _gizmoSizeClose.y = RayWidth;
                _gizmoSizeClose.z = 1f;
                Gizmos.DrawCube(_gizmoCenter, _gizmoSize);
                Gizmos.DrawCube(_gizmoCenterClose, _gizmoSizeClose);
            }
        }
    }
}

 注意:本段代码原本为Corgi Engine内的AIDecisionDetectTargetLine,我将其更改后重命名为AIDecisionDetectTargetLineINSTATE。如读者有需要,只需新建此脚本并复制上面内容即可,无需在源代码上进行更改(源代码可用于逻辑较简单的小怪)。

使用时,在Inspector窗口内设置,Detection Distance Far是距离BOSS较远的A点的长度,Detection Distance Close是距离BOSS较近的B点的长度需要注意的是我删除了原本代码Both选项(向两边同时发射射线的功能)而只用Front和Back(单独向前向后发射射线),因为使用Both会有Bug导致玩家同时被两条射线都检测到。

如果希望实现后方检测,在后方射线的代码中勾选上Back即可,还应注意在进行BOSS前方近距离的设置时,要将Detection Distance Close距离调得比0大一些,按照逻辑应大出角色宽度,使这一段检测不到。以防BOSS自动转身,始终用正面对着玩家。

具体设置:

本人目前只是大一计算机学生,Unity也没学多久,这个代码也只是在Corgi Enigne的基础上进行更改得来的,目前在白模中能良好运行,具体实装在游戏中的运行效果还需等一段时间才能测试。如果有大佬在此,请轻点喷!如果有更好的实现方法也欢迎留言(我在网上没怎么找到。。。)!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值