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
}
总而言之,你可以使用 BoxColliderAdvanced、CircleColliderAdvanced和PolygonColliderAdvanced获得碰撞进入的功能。
二、如何使用碰撞器?
你需要有 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 系的框架了……

浙公网安备 33010602011771号