mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-11 12:14:30 +08:00
- 修复 InputBindingStore 只读查询会污染导出快照的问题 - 修复 Godot 输入绑定导入时未清理残留动作绑定的问题 - 补充输入运行时与 Godot backend 的 XML 契约说明和 README 入口 - 更新 ai-plan 跟踪并补充针对 PR #346 的回归测试
149 lines
4.4 KiB
C#
149 lines
4.4 KiB
C#
// Copyright (c) 2025-2026 GeWuYou
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
using GFramework.Game.Abstractions.Input;
|
|
using GFramework.Game.Input;
|
|
|
|
namespace GFramework.Godot.Input;
|
|
|
|
/// <summary>
|
|
/// 提供基于 Godot `InputMap` 的输入绑定存储实现。
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 该类型把 Godot 原生 `InputEvent` / `InputMap` 适配到 `GFramework.Game.Abstractions.Input` 契约。
|
|
/// 项目可以直接用它做重绑定、动作快照导出导入,以及“当前活跃设备”识别。
|
|
/// </remarks>
|
|
public sealed class GodotInputBindingStore : IInputBindingStore, IInputDeviceTracker
|
|
{
|
|
private readonly IGodotInputMapBackend _backend;
|
|
private readonly InputBindingStore _state;
|
|
private readonly InputDeviceTracker _deviceTracker;
|
|
|
|
/// <summary>
|
|
/// 初始化一个基于全局 `InputMap` 的输入绑定存储。
|
|
/// </summary>
|
|
public GodotInputBindingStore()
|
|
: this(new GodotInputMapBackend())
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化一个可测试的输入绑定存储。
|
|
/// </summary>
|
|
/// <param name="backend">要使用的 `InputMap` 后端。</param>
|
|
internal GodotInputBindingStore(IGodotInputMapBackend backend)
|
|
{
|
|
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
|
|
_state = new InputBindingStore(CaptureSnapshotFromBackend());
|
|
_deviceTracker = new InputDeviceTracker();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public InputDeviceContext CurrentDevice => _deviceTracker.CurrentDevice;
|
|
|
|
/// <inheritdoc />
|
|
public InputActionBinding GetBindings(string actionName)
|
|
{
|
|
ReloadFromBackend();
|
|
return _state.GetBindings(actionName);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public InputBindingSnapshot ExportSnapshot()
|
|
{
|
|
ReloadFromBackend();
|
|
return _state.ExportSnapshot();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ImportSnapshot(InputBindingSnapshot snapshot)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(snapshot);
|
|
|
|
ReloadFromBackend();
|
|
var snapshotActionNames = snapshot.Actions
|
|
.Select(static action => action.ActionName)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
var removedActionNames = _state.ExportSnapshot().Actions
|
|
.Select(static action => action.ActionName)
|
|
.Where(actionName => !snapshotActionNames.Contains(actionName))
|
|
.ToArray();
|
|
|
|
foreach (var actionName in removedActionNames)
|
|
{
|
|
_backend.SetBindings(actionName, Array.Empty<InputBindingDescriptor>());
|
|
}
|
|
|
|
foreach (var action in snapshot.Actions)
|
|
{
|
|
ApplyActionBindings(action);
|
|
}
|
|
|
|
ReloadFromBackend();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void SetPrimaryBinding(string actionName, InputBindingDescriptor binding, bool swapIfTaken = true)
|
|
{
|
|
ReloadFromBackend();
|
|
_state.SetPrimaryBinding(actionName, binding, swapIfTaken);
|
|
ApplySnapshot(_state.ExportSnapshot());
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ResetAction(string actionName)
|
|
{
|
|
_backend.ResetAction(actionName);
|
|
ReloadFromBackend();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ResetAll()
|
|
{
|
|
_backend.ResetAll();
|
|
ReloadFromBackend();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 使用 Godot 原生输入事件更新当前活跃设备上下文。
|
|
/// </summary>
|
|
/// <param name="inputEvent">原生输入事件。</param>
|
|
public void UpdateDeviceFromInput(InputEvent inputEvent)
|
|
{
|
|
var context = GodotInputBindingCodec.GetDeviceContext(inputEvent);
|
|
_deviceTracker.Update(context);
|
|
}
|
|
|
|
private void ApplyActionBindings(InputActionBinding actionBinding)
|
|
{
|
|
_backend.SetBindings(actionBinding.ActionName, actionBinding.Bindings);
|
|
}
|
|
|
|
private void ApplySnapshot(InputBindingSnapshot snapshot)
|
|
{
|
|
foreach (var actionBinding in snapshot.Actions)
|
|
{
|
|
ApplyActionBindings(actionBinding);
|
|
}
|
|
}
|
|
|
|
private InputBindingSnapshot CaptureSnapshotFromBackend()
|
|
{
|
|
var actions = _backend.GetActionNames()
|
|
.Select(CreateActionBinding)
|
|
.ToArray();
|
|
|
|
return new InputBindingSnapshot(actions);
|
|
}
|
|
|
|
private InputActionBinding CreateActionBinding(string actionName)
|
|
{
|
|
return new InputActionBinding(actionName, _backend.GetBindings(actionName).ToArray());
|
|
}
|
|
|
|
private void ReloadFromBackend()
|
|
{
|
|
_state.ImportSnapshot(CaptureSnapshotFromBackend());
|
|
}
|
|
}
|