栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

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

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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
{
    /// 
    /// 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.
    /// 
	[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;
        
        /// 
        /// On Init we grab our character
        /// 
        public override void Initialization()
        {
            _character = this.gameObject.GetComponentInParent();
            _gizmosColor.a = 0.25f;
            _init = true;
        }

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

        /// 
        /// Returns true if a target is found by the ray
        /// 
        /// 
        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.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.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;
        }
        
        /// 
        /// Draws ray gizmos
        /// 
        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.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.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的基础上进行更改得来的,目前在白模中能良好运行,具体实装在游戏中的运行效果还需等一段时间才能测试。如果有大佬在此,请轻点喷!如果有更好的实现方法也欢迎留言(我在网上没怎么找到。。。)!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/906485.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号