Nez 中使用 Collider 检测碰撞交叠

Nez 中使用 Collider 检测碰撞交叠

在 Unity 和 Godot 中都有检测碰撞的方式,这里提到的碰撞是指对象交叠,并不是单纯意义上的物理碰撞,是和物理引擎关系不大的。在进行范围内触发任务,攻击范围视野探测等等会有一定相关,但是和物理碰撞确实关系不大。

一、Nez 中自带的 Collider 及其拓展

Nez 中有提供以下三种已经封装好的碰撞器,分别是:BoxCollider、CircleCollider 和 PolygonCollider,它们可以实现对方形、圆心和多边形区域的指定。

但是自带的碰撞器没有碰撞进入事件,所以我对此进行了一些拓展:

    internal class ActivateHashSet
    {
        #region fields
        private Dictionary<Collider, bool> _colliderMap;
        private List<Collider> _deleteArrayList;
        #endregion

        #region events
        public event EventHandler<Collider> OnEnter;
        public event EventHandler<Collider> OnLeave;
        #endregion

        #region ctors
        public ActivateHashSet()
        {
            _colliderMap = new Dictionary<Collider, bool>();
            _deleteArrayList = new List<Collider>();
        }
        #endregion

        #region methods
        public void Begin()
        {
            var colliders = _colliderMap.Keys.ToImmutableArray();

            foreach (var collider in colliders)
            {
                _colliderMap[collider] = false;
            }
        }

        public void Add(Collider obj)
        {
            if (_colliderMap.ContainsKey(obj))
            {
                _colliderMap[obj] = true;
            }
            else
            {
                _colliderMap.Add(obj, true);
                OnEnter?.Invoke(this, obj);
            }

        }

        public void End()
        {
            if (_colliderMap.Keys.Count == 0) return;
            _deleteArrayList.Clear();
            foreach (var key in _colliderMap.Keys)
            {
                if (_colliderMap[key] is bool bvalue)
                {
                    if (bvalue != true)
                        _deleteArrayList.Add(key);
                }
            }

            foreach (var key in _deleteArrayList)
            {
                _colliderMap.Remove(key, out var value);
                OnLeave?.Invoke(this, key);
            }
        }
        #endregion
    }

    public interface IColliderAdvanced
    {
        event EventHandler<Collider> OnEnter;
        event EventHandler<Collider> OnLeave;
    }

    public class BoxColliderAdvanced : BoxCollider, IUpdatable, IColliderAdvanced
    {
        #region fields
        private IList<CollisionResult> _collisionResults = new List<CollisionResult>();
        private ActivateHashSet _activateHashSet = new ActivateHashSet();
        #endregion

        #region events
        public event EventHandler<Collider> OnEnter;
        public event EventHandler<Collider> OnLeave;
        #endregion

        #region ctors
        public BoxColliderAdvanced()
        {
            _activateHashSet.OnEnter += _activateHashSet_OnEnter;
            _activateHashSet.OnLeave += _activateHashSet_OnLeave;
        }
        #endregion

        #region events subscription
        private void _activateHashSet_OnLeave(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnLeave?.Invoke(this, collider);
        }

        private void _activateHashSet_OnEnter(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnEnter?.Invoke(this, collider);
        }
        #endregion

        #region methods
        public void Update()
        {
            if (Enabled == false) return;
            _activateHashSet.Begin();

            if (this.CollidesWithAll(ref _collisionResults))
            {
                foreach (var collisionResult in _collisionResults)
                {
                    _activateHashSet.Add(collisionResult.Collider);
                }
            }

            _activateHashSet.End();
        }
        #endregion
    }

    public class CircleColliderAdvanced : CircleCollider, IUpdatable, IColliderAdvanced
    {
        #region fields
        private IList<CollisionResult> _collisionResults = new List<CollisionResult>();
        private ActivateHashSet _activateHashSet = new ActivateHashSet();
        #endregion

        #region events
        public event EventHandler<Collider> OnEnter;
        public event EventHandler<Collider> OnLeave;
        #endregion

        #region ctors
        public CircleColliderAdvanced()
        {
            _activateHashSet.OnEnter += _activateHashSet_OnEnter;
            _activateHashSet.OnLeave += _activateHashSet_OnLeave;
        }
        #endregion

        #region events subscription
        private void _activateHashSet_OnLeave(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnLeave?.Invoke(this, collider);
        }

        private void _activateHashSet_OnEnter(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnEnter?.Invoke(this, collider);
        }
        #endregion

        #region methods
        public void Update()
        {
            if (Enabled == false) return;
            _activateHashSet.Begin();

            if (this.CollidesWithAll(ref _collisionResults))
            {
                foreach (var collisionResult in _collisionResults)
                {
                    _activateHashSet.Add(collisionResult.Collider);
                }
            }

            _activateHashSet.End();
        }
        #endregion

    }

    public class PolygonColliderAdvanced : PolygonCollider, IUpdatable, IColliderAdvanced
    {
        #region fields
        private IList<CollisionResult> _collisionResults = new List<CollisionResult>();
        private ActivateHashSet _activateHashSet = new ActivateHashSet();
        #endregion

        #region events
        public event EventHandler<Collider> OnEnter;
        public event EventHandler<Collider> OnLeave;
        #endregion

        #region ctors
        public PolygonColliderAdvanced()
        {
            _activateHashSet.OnEnter += _activateHashSet_OnEnter;
            _activateHashSet.OnLeave += _activateHashSet_OnLeave;
        }
        #endregion

        #region events subscription
        private void _activateHashSet_OnLeave(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnLeave?.Invoke(this, collider);
        }

        private void _activateHashSet_OnEnter(object? sender, Collider e)
        {
            if (e is Collider collider)
                OnEnter?.Invoke(this, collider);
        }
        #endregion

        #region methods
        public void Update()
        {
            if (Enabled == false) return;
            _activateHashSet.Begin();

            if (this.CollidesWithAll(ref _collisionResults))
            {
                foreach (var collisionResult in _collisionResults)
                {
                    _activateHashSet.Add(collisionResult.Collider);
                }
            }

            _activateHashSet.End();
        }
        #endregion
    }

总而言之,你可以使用 BoxColliderAdvancedCircleColliderAdvancedPolygonColliderAdvanced获得碰撞进入的功能。

二、如何使用碰撞器?

你需要有 XNA 系(FNA 或者 MonoGame)的一些使用经验,并且在项目中有它们的依赖和源码。

其次你需要有 Nez 和对应的版本。

我们提供了以下的示例,小球会跟随鼠标的移动而移动。

运行下面的代码吧!

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Nez;

namespace HelloFNA
{
    public class MainScene : Scene
    {

        private Entity CreateBall()
        {
            var ball = CreateEntity("ball");
            var collider = new CircleColliderAdvanced();
            collider.Radius = 30;
            ball.AddComponent(collider);
            return ball;
        }

        private Entity CreateRectArea()
        {
            var rectArea = CreateEntity("rect_area");
            var collider = new BoxColliderAdvanced();
            collider.SetSize(400, 400);
            rectArea.AddComponent(collider);
            collider.OnEnter += Collider_OnEnter;
            collider.OnLeave += Collider_OnLeave;
            rectArea.SetPosition(new Vector2(250, 250));
            return rectArea;
        }

        private void Collider_OnEnter(object? sender, Collider e)
        {
            Console.WriteLine($"{e.Entity.Name} entered!");
        }
        private void Collider_OnLeave(object? sender, Collider e)
        {
            Console.WriteLine($"{e.Entity.Name} leaved!");
        }
        public override void Initialize()
        {
            base.Initialize();
            ClearColor = Color.Black;
            Core.DebugRenderEnabled = true;  // 调试模式开启,可以看到碰撞器轮廓

            CreateBall();
            CreateRectArea();
        }

        public override void Update()
        {
            base.Update();
            var mouseState = Mouse.GetState();
            var ball = Entities.FindEntity("ball");
            if (ball is not null)
            {
                ball.Position = new Vector2(mouseState.X, mouseState.Y);
            }
        }

    }

    public class MainGame : Core
    {
        protected override void Initialize()
        {
            base.Initialize();
            Scene = new MainScene();
        }

        static void Main(string[] args)
        {
            using (var game = new MainGame())
            {
                game.Run();
            }
        }
    }


}

在边缘的边界反复摩擦试探:

三、碰撞事件的用处

相信你可以成功跑出来了,有了碰撞交叠,游戏之旅会方便很多,有一说一,还是别用 XNA 系的框架了……

posted @ 2025-06-23 21:07  fanbal  阅读(24)  评论(0)    收藏  举报