You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# MonoGame多可点击对象的鼠标输入处理优化问询

在C# MonoGame中处理多可点击GUI对象的鼠标输入规范方式

一、你的方案可行性分析

完全可行,这是GUI系统中常用的事件委托+拾取检测模式,比每个对象自行轮询输入的方式更高效、解耦,能解决大量对象时订阅繁琐的问题。

二、具体实现步骤

1. 完善自定义Mouse类

让Mouse类专注于跟踪输入状态,对外暴露输入事件(点击、移动),不关心具体交互对象:

public class CustomMouse
{
    public Point Position { get; private set; }
    private bool _leftButtonDown;

    // 定义事件供管理器订阅
    public event Action<Point, MouseButton> OnMouseClick;
    public event Action<Point> OnMouseMove;

    public void Update(GameTime gameTime)
    {
        var currentState = Mouse.GetState();
        Position = currentState.Position;

        // 检测左键完整点击周期(按下后释放)
        if (!_leftButtonDown && currentState.LeftButton == ButtonState.Pressed)
        {
            _leftButtonDown = true;
        }
        else if (_leftButtonDown && currentState.LeftButton == ButtonState.Released)
        {
            _leftButtonDown = false;
            OnMouseClick?.Invoke(Position, MouseButton.Left);
        }

        // 触发鼠标移动事件,用于悬停检测
        OnMouseMove?.Invoke(Position);
    }
}

public enum MouseButton
{
    Left, Right, Middle
}

2. 实现GUI对象管理器

管理器维护所有可交互对象列表,订阅Mouse事件,触发时做拾取检测并分发事件:

public class GuiManager
{
    private List<IGuiInteractable> _interactables = new List<IGuiInteractable>();
    private IGuiInteractable _currentHovered;

    public GuiManager(CustomMouse mouse)
    {
        mouse.OnMouseClick += HandleClick;
        mouse.OnMouseMove += HandleMouseMove;
    }

    // 注册可交互对象
    public void Register(IGuiInteractable interactable)
    {
        _interactables.Add(interactable);
    }

    // 处理点击:倒序遍历保证上层对象优先响应
    private void HandleClick(Point pos, MouseButton button)
    {
        for (int i = _interactables.Count - 1; i >= 0; i--)
        {
            var obj = _interactables[i];
            if (obj.Bounds.Contains(pos))
            {
                obj.OnClick(button);
                break; // 只触发最上层对象
            }
        }
    }

    // 处理悬停状态变化
    private void HandleMouseMove(Point pos)
    {
        IGuiInteractable newHovered = null;
        // 倒序检测上层对象
        for (int i = _interactables.Count - 1; i >= 0; i--)
        {
            var obj = _interactables[i];
            if (obj.Bounds.Contains(pos))
            {
                newHovered = obj;
                break;
            }
        }

        // 更新悬停状态并触发对应事件
        if (_currentHovered != newHovered)
        {
            _currentHovered?.OnHoverExit();
            newHovered?.OnHoverEnter();
            _currentHovered = newHovered;
        }

        // 持续触发悬停中事件
        _currentHovered?.OnHoverStay();
    }
}

// 定义可交互对象接口
public interface IGuiInteractable
{
    Rectangle Bounds { get; }
    void OnClick(MouseButton button);
    void OnHoverEnter();
    void OnHoverExit();
    void OnHoverStay();
}

3. 实现具体GUI按钮类

按钮实现接口,专注自身业务逻辑:

public class Button : IGuiInteractable
{
    public Rectangle Bounds { get; private set; }
    private Action _onClick;
    private Color _normalColor = Color.Gray;
    private Color _hoverColor = Color.LightGray;
    public Color CurrentColor { get; private set; }

    public Button(Rectangle bounds, Action onClick)
    {
        Bounds = bounds;
        _onClick = onClick;
        CurrentColor = _normalColor;
    }

    public void OnClick(MouseButton button)
    {
        if (button == MouseButton.Left)
            _onClick?.Invoke();
    }

    public void OnHoverEnter()
    {
        CurrentColor = _hoverColor;
    }

    public void OnHoverExit()
    {
        CurrentColor = _normalColor;
    }

    public void OnHoverStay()
    {
        // 可添加tooltip显示、动画等逻辑
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(/* 按钮纹理或纯色填充 */, Bounds, CurrentColor);
    }
}

4. 在Game类中整合使用

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private CustomMouse _mouse;
    private GuiManager _guiManager;
    private List<Button> _buttons = new List<Button>();

    protected override void Initialize()
    {
        _mouse = new CustomMouse();
        _guiManager = new GuiManager(_mouse);

        // 创建按钮并注册到管理器
        var startBtn = new Button(new Rectangle(100, 100, 200, 50), () => Console.WriteLine("游戏启动"));
        var settingsBtn = new Button(new Rectangle(100, 180, 200, 50), () => Console.WriteLine("打开设置"));
        _guiManager.Register(startBtn);
        _guiManager.Register(settingsBtn);
        _buttons.AddRange(new[] { startBtn, settingsBtn });

        base.Initialize();
    }

    protected override void Update(GameTime gameTime)
    {
        _mouse.Update(gameTime);
        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        _spriteBatch.Begin();
        foreach (var btn in _buttons)
            btn.Draw(_spriteBatch);
        _spriteBatch.End();
        base.Draw(gameTime);
    }
}

三、方案优势

  • 解耦:Mouse类只管输入,GUI对象只管自身逻辑,管理器负责调度,符合单一职责原则。
  • 高效:避免每个对象重复检测鼠标状态,仅在事件触发时做一次拾取。
  • 灵活:轻松处理GUI层级(倒序遍历保证上层优先),可扩展右键点击、拖拽等功能。
  • 易维护:新增GUI对象只需实现接口并注册,无需修改核心逻辑。

四、悬停功能实现说明

上述代码已完整实现悬停逻辑:

  1. 鼠标移动时检测当前坐标下的最上层可交互对象。
  2. 对比上一帧的悬停对象,触发进入/离开事件。
  3. 对当前悬停对象持续触发OnHoverStay,用于tooltip、动画等需求。
    逻辑与点击复用同一套拾取机制,统一且高效。

内容的提问来源于stack exchange,提问作者Peter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 18:35:28