在 Nez 中实现碰撞检测
在 Nez 中实现碰撞检测
在游戏开发中,碰撞检测是实现玩家互动和环境交互的重要功能。本文将介绍如何在 Nez 框架中使用碰撞检测功能,包括几种常见的碰撞器实现及其扩展。
一、Nez 中的碰撞器实现
Nez 提供了三种内置碰撞器:BoxCollider(矩形碰撞器)、CircleCollider(圆形碰撞器)和 PolygonCollider(多边形碰撞器)。这些碰撞器能够检测不同形状的碰撞区域。
为了实现碰撞进入和离开事件,我们对这些碰撞器进行了扩展:
internal class CollisionStateManager
{
#region 私有字段
private Dictionary<Collider, bool> _collisionMap;
private List<Collider> _removeList;
#endregion
#region 事件
public event EventHandler<Collider> OnCollisionEnter;
public event EventHandler<Collider> OnCollisionExit;
#endregion
#region 构造函数
public CollisionStateManager()
{
_collisionMap = new Dictionary<Collider, bool>();
_removeList = new List<Collider>();
}
#endregion
#region 方法
public void ResetState()
{
var keys = _collisionMap.Keys.ToImmutableArray();
foreach (var collider in keys)
{
_collisionMap[collider] = false;
}
}
public void Add(Collider obj)
{
if (_collisionMap.ContainsKey(obj))
{
_collisionMap[obj] = true;
}
else
{
_collisionMap.Add(obj, true);
OnCollisionEnter?.Invoke(this, obj);
}
}
public void Cleanup()
{
if (_collisionMap.Count == 0) return;
_removeList.Clear();
foreach (var key in _collisionMap.Keys)
{
if (_collisionMap[key] == false)
_removeList.Add(key);
}
foreach (var key in _removeList)
{
_collisionMap.Remove(key);
OnCollisionExit?.Invoke(this, key);
}
}
#endregion
}
public interface ICollisionNotifier
{
event EventHandler<Collider> OnCollisionEnter;
event EventHandler<Collider> OnCollisionExit;
}
public class BoxCollisionHandler : BoxCollider, IUpdatable, ICollisionNotifier
{
#region 私有字段
private List<CollisionResult> _results = new List<CollisionResult>();
private CollisionStateManager _stateManager = new CollisionStateManager();
#endregion
#region 事件
public event EventHandler<Collider> OnCollisionEnter;
public event EventHandler<Collider> OnCollisionExit;
#endregion
#region 构造函数
public BoxCollisionHandler()
{
_stateManager.OnCollisionEnter += HandleCollisionEnter;
_stateManager.OnCollisionExit += HandleCollisionExit;
}
#endregion
#region 事件处理
private void HandleCollisionExit(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionExit?.Invoke(this, collider);
}
private void HandleCollisionEnter(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionEnter?.Invoke(this, collider);
}
#endregion
#region 更新逻辑
public void Update()
{
if (!Enabled) return;
_stateManager.ResetState();
if (CollidesWithAll(ref _results))
{
foreach (var result in _results)
{
_stateManager.Add(result.Collider);
}
}
_stateManager.Cleanup();
}
#endregion
}
public class CircleCollisionHandler : CircleCollider, IUpdatable, ICollisionNotifier
{
#region 私有字段
private List<CollisionResult> _results = new List<CollisionResult>();
private CollisionStateManager _stateManager = new CollisionStateManager();
#endregion
#region 事件
public event EventHandler<Collider> OnCollisionEnter;
public event EventHandler<Collider> OnCollisionExit;
#endregion
#region 构造函数
public CircleCollisionHandler()
{
_stateManager.OnCollisionEnter += HandleCollisionEnter;
_stateManager.OnCollisionExit += HandleCollisionExit;
}
#endregion
#region 事件处理
private void HandleCollisionExit(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionExit?.Invoke(this, collider);
}
private void HandleCollisionEnter(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionEnter?.Invoke(this, collider);
}
#endregion
#region 更新逻辑
public void Update()
{
if (!Enabled) return;
_stateManager.ResetState();
if (CollidesWithAll(ref _results))
{
foreach (var result in _results)
{
_stateManager.Add(result.Collider);
}
}
_stateManager.Cleanup();
}
#endregion
}
public class PolygonCollisionHandler : PolygonCollider, IUpdatable, ICollisionNotifier
{
#region 私有字段
private List<CollisionResult> _results = new List<CollisionResult>();
private CollisionStateManager _stateManager = new CollisionStateManager();
#endregion
#region 事件
public event EventHandler<Collider> OnCollisionEnter;
public event EventHandler<Collider> OnCollisionExit;
#endregion
#region 构造函数
public PolygonCollisionHandler()
{
_stateManager.OnCollisionEnter += HandleCollisionEnter;
_stateManager.OnCollisionExit += HandleCollisionExit;
}
#endregion
#region 事件处理
private void HandleCollisionExit(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionExit?.Invoke(this, collider);
}
private void HandleCollisionEnter(object? sender, Collider e)
{
if (e is Collider collider)
OnCollisionEnter?.Invoke(this, collider);
}
#endregion
#region 更新逻辑
public void Update()
{
if (!Enabled) return;
_stateManager.ResetState();
if (CollidesWithAll(ref _results))
{
foreach (var result in _results)
{
_stateManager.Add(result.Collider);
}
}
_stateManager.Cleanup();
}
#endregion
}
二、如何使用碰撞检测
使用 Nez 的碰撞检测功能需要以下条件:
- 项目中需要包含 XNA 框架(如 FNA 或 MonoGame)的相关依赖项。
- 确保项目中引用了 Nez 的正确版本。
下面是一个简单的示例代码,展示了如何创建一个跟随鼠标移动的圆形物体和一个矩形碰撞区域:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Nez;
namespace CollisionTest
{
public class GameScene : Scene
{
private Entity CreateSphere()
{
var sphere = CreateEntity("sphere");
var collider = new CircleCollisionHandler();
collider.Radius = 30;
sphere.AddComponent(collider);
return sphere;
}
private Entity CreateRectArea()
{
var rectArea = CreateEntity("rect_area");
var collider = new BoxCollisionHandler();
collider.SetSize(400, 400);
rectArea.AddComponent(collider);
collider.OnCollisionEnter += Collider_OnCollisionEnter;
collider.OnCollisionExit += Collider_OnCollisionExit;
rectArea.SetPosition(new Vector2(250, 250));
return rectArea;
}
private void Collider_OnCollisionEnter(object? sender, Collider e)
{
Console.WriteLine($"{e.Entity.Name} entered collision area!");
}
private void Collider_OnCollisionExit(object? sender, Collider e)
{
Console.WriteLine($"{e.Entity.Name} exited collision area!");
}
public override void Initialize()
{
base.Initialize();
ClearColor = Color.Black;
Core.DebugRenderEnabled = true;
CreateSphere();
CreateRectArea();
}
public override void Update()
{
base.Update();
var mouseState = Mouse.GetState();
var sphere = Entities.FindEntity("sphere");
if (sphere is not null)
{
sphere.Position = new Vector2(mouseState.X, mouseState.Y);
}
}
}
public class GameCore : Core
{
protected override void Initialize()
{
base.Initialize();
Scene = new GameScene();
}
static void Main(string[] args)
{
using (var game = new GameCore())
{
game.Run();
}
}
}
}
三、碰撞检测的应用
通过上述代码,你可以看到当小球进入矩形区域时,控制台会输出相应的日志信息。这种机制可以应用于许多场景,比如触发攻击范围、检测视野区域或实现 UI 热区响应。
总结来说,Nez 提供了强大的碰撞检测功能,能够帮助开发者更高效地实现游戏中的各种交互逻辑。