映射优化
This commit is contained in:
parent
204ece778f
commit
2626070af7
@ -10,6 +10,8 @@
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h"
|
||||
#include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||
@ -77,6 +79,7 @@ MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
|
||||
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
|
||||
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
|
||||
document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime";
|
||||
document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime";
|
||||
return document;
|
||||
}
|
||||
|
||||
@ -345,6 +348,7 @@ int main(int argc, char* argv[]) {
|
||||
viewportRenderer.SetProjectRootPath(projectRoot);
|
||||
MetaCore::MetaCoreTypeRegistry typeRegistry;
|
||||
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
|
||||
MetaCore::MetaCoreRegisterRuntimeInputGeneratedTypes(typeRegistry);
|
||||
const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument(
|
||||
deliveryDocument ? packageRoot / deliveryDocument->RuntimeConfig : projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
|
||||
typeRegistry
|
||||
@ -412,12 +416,22 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
if (deliveryDocument && missingRequiredScript) return 2;
|
||||
const auto loadedInputMap = runtimeProjectDocument.InputMapPath.empty() ? std::optional<MetaCore::MetaCoreInputMapDocument>{} :
|
||||
MetaCore::MetaCoreReadInputMap((projectRoot / runtimeProjectDocument.InputMapPath).lexically_normal(), typeRegistry);
|
||||
MetaCore::MetaCoreRuntimeInput runtimeInput;
|
||||
runtimeInput.SetInputMap(loadedInputMap.value_or(MetaCore::MetaCoreInputMapDocument{}));
|
||||
const auto projectDocument = MetaCore::MetaCoreReadProjectFile(projectPath);
|
||||
const std::string inputProjectName = projectDocument ? projectDocument->Name : projectRoot.filename().string();
|
||||
const auto inputOverridePath = MetaCore::MetaCoreGetInputOverridePath(inputProjectName);
|
||||
(void)runtimeInput.LoadOverrides(inputOverridePath);
|
||||
for (const auto& warning : runtimeInput.GetWarnings()) errors << "MetaCorePlayer Input: " << warning << '\n';
|
||||
|
||||
MetaCore::MetaCoreScriptRuntime scriptRuntime(
|
||||
scene, scriptRegistry,
|
||||
[&output, &errors](std::uint32_t level, std::string_view category, std::string_view message) {
|
||||
std::ostream& stream = level >= 2U ? errors : output;
|
||||
stream << "MetaCorePlayer Script[" << category << "]: " << message << '\n';
|
||||
}
|
||||
}, &runtimeInput
|
||||
);
|
||||
scriptRuntime.Start();
|
||||
|
||||
@ -493,12 +507,42 @@ int main(int argc, char* argv[]) {
|
||||
? runtimeAdapter->GetStatus().State
|
||||
: MetaCore::MetaCoreRuntimeDataSourceState::Disconnected;
|
||||
std::unordered_map<std::string, std::string> lastReportedBindingIssueKeys;
|
||||
MetaCore::MetaCoreRuntimeInteraction runtimeInteraction;
|
||||
const std::uint64_t inputSubscription = runtimeInput.Subscribe([&scriptRuntime](const MetaCore::MetaCoreInputActionEvent& event) {
|
||||
MetaCoreScriptValueV1 phase{}; phase.Type = MetaCoreScriptAbiValue_Int; phase.IntValue = static_cast<std::int64_t>(event.Phase);
|
||||
scriptRuntime.Publish("MetaCore.Input.Action/" + event.ActionId, {phase});
|
||||
});
|
||||
runtimeUi.SetEventCallback([&scriptRuntime](const MetaCore::MetaCoreRuntimeUiEvent& event) {
|
||||
const auto textValue = [](const std::string& text) {
|
||||
MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_String;
|
||||
std::snprintf(value.Text, sizeof(value.Text), "%s", text.c_str()); return value;
|
||||
};
|
||||
scriptRuntime.Publish("MetaCore.UI", {
|
||||
textValue(event.DocumentId), textValue(event.ElementId), textValue(event.Action),
|
||||
textValue(event.Type), textValue(event.Value)
|
||||
});
|
||||
});
|
||||
runtimeInteraction.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePointerEvent& event) {
|
||||
MetaCoreScriptValueV1 type{}; type.Type = MetaCoreScriptAbiValue_Int; type.IntValue = static_cast<std::int64_t>(event.Type);
|
||||
MetaCoreScriptValueV1 button{}; button.Type = MetaCoreScriptAbiValue_Int; button.IntValue = event.Button;
|
||||
MetaCoreScriptValueV1 screen{}; screen.Type = MetaCoreScriptAbiValue_Vec3; screen.Vec3[0] = event.ScreenPosition.x; screen.Vec3[1] = event.ScreenPosition.y;
|
||||
MetaCoreScriptValueV1 delta{}; delta.Type = MetaCoreScriptAbiValue_Vec3; delta.Vec3[0] = event.ScreenDelta.x; delta.Vec3[1] = event.ScreenDelta.y;
|
||||
MetaCoreScriptValueV1 world{}; world.Type = MetaCoreScriptAbiValue_Vec3; world.Vec3[0] = event.WorldPosition.x; world.Vec3[1] = event.WorldPosition.y; world.Vec3[2] = event.WorldPosition.z;
|
||||
MetaCoreScriptValueV1 target{}; target.Type = MetaCoreScriptAbiValue_GameObjectRef;
|
||||
target.ObjectValue = {scriptRuntime.GetWorldGeneration(), event.TargetObjectId};
|
||||
scriptRuntime.PublishToObject(event.TargetObjectId, "MetaCore.Interaction.Pointer", {type, button, screen, delta, world, target});
|
||||
});
|
||||
|
||||
std::uint64_t diagnosticsWriteFrame = 0;
|
||||
std::uint64_t renderedFrames = 0U;
|
||||
while (!window.ShouldClose()) {
|
||||
window.BeginFrame();
|
||||
const double frameDeltaSeconds = window.GetDeltaSeconds();
|
||||
const MetaCore::MetaCoreInputConsumption uiConsumption = runtimeUi.ProcessInput();
|
||||
runtimeInput.Update(window.GetInput(), uiConsumption);
|
||||
const auto [inputWidth, inputHeight] = window.GetWindowSize();
|
||||
const MetaCore::MetaCoreViewportRect inputViewport{0.0F, 0.0F, static_cast<float>(inputWidth), static_cast<float>(inputHeight)};
|
||||
runtimeInteraction.Update(scene, viewportRenderer, window.GetInput(), inputViewport, uiConsumption.Mouse);
|
||||
scriptRuntime.FixedUpdate(1.0 / 60.0);
|
||||
scriptRuntime.Update(frameDeltaSeconds);
|
||||
const auto [windowWidth, windowHeight] = window.GetWindowSize();
|
||||
@ -568,6 +612,8 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
scriptRuntime.Stop();
|
||||
runtimeInput.Unsubscribe(inputSubscription);
|
||||
(void)runtimeInput.SaveOverrides(inputOverridePath);
|
||||
runtimeUi.Shutdown();
|
||||
viewportRenderer.Shutdown();
|
||||
renderDevice.Shutdown();
|
||||
|
||||
@ -427,12 +427,36 @@ if(METACORE_ENABLE_PLATFORM)
|
||||
target_link_libraries(MetaCorePlatform PUBLIC glfw)
|
||||
endif()
|
||||
|
||||
target_compile_options(MetaCorePlatform PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
target_compile_options(MetaCorePlatform PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
if(WIN32)
|
||||
target_compile_definitions(MetaCorePlatform PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
|
||||
target_link_libraries(MetaCorePlatform PRIVATE xinput9_1_0)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(METACORE_RUNTIME_INPUT_HEADERS
|
||||
Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreInputMap.h
|
||||
Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInput.h
|
||||
)
|
||||
set(METACORE_RUNTIME_INPUT_SOURCES
|
||||
Source/MetaCoreRuntimeInput/Private/MetaCoreInputMap.cpp
|
||||
Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInput.cpp
|
||||
)
|
||||
metacore_generate_reflection(
|
||||
RuntimeInput
|
||||
MetaCoreRegisterRuntimeInputGeneratedTypes
|
||||
METACORE_RUNTIME_INPUT_GENERATED_SOURCE
|
||||
Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreInputMap.h
|
||||
)
|
||||
add_library(MetaCoreRuntimeInput STATIC
|
||||
${METACORE_RUNTIME_INPUT_HEADERS}
|
||||
${METACORE_RUNTIME_INPUT_SOURCES}
|
||||
${METACORE_RUNTIME_INPUT_GENERATED_SOURCE}
|
||||
)
|
||||
target_include_directories(MetaCoreRuntimeInput PUBLIC Source/MetaCoreRuntimeInput/Public PRIVATE third_party)
|
||||
target_link_libraries(MetaCoreRuntimeInput PUBLIC MetaCoreFoundation MetaCorePlatform)
|
||||
target_compile_options(MetaCoreRuntimeInput PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
|
||||
set(METACORE_SCENE_HEADERS
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h
|
||||
Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h
|
||||
@ -487,7 +511,7 @@ target_include_directories(MetaCoreScripting
|
||||
PUBLIC Source/MetaCoreScripting/Public
|
||||
PRIVATE third_party
|
||||
)
|
||||
target_link_libraries(MetaCoreScripting PUBLIC MetaCoreFoundation MetaCoreScene glm::glm)
|
||||
target_link_libraries(MetaCoreScripting PUBLIC MetaCoreFoundation MetaCoreScene MetaCoreRuntimeInput glm::glm)
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(MetaCoreScripting PRIVATE dl)
|
||||
endif()
|
||||
@ -504,7 +528,7 @@ target_include_directories(MetaCoreDelivery
|
||||
PRIVATE third_party
|
||||
)
|
||||
target_link_libraries(MetaCoreDelivery
|
||||
PUBLIC MetaCoreFoundation MetaCoreScene MetaCoreScripting
|
||||
PUBLIC MetaCoreFoundation MetaCoreScene MetaCoreScripting MetaCoreRuntimeInput MetaCoreRuntimeData
|
||||
)
|
||||
target_compile_options(MetaCoreDelivery PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
if(crashpad_FOUND)
|
||||
@ -566,6 +590,14 @@ metacore_use_filament(MetaCoreRender)
|
||||
|
||||
|
||||
target_compile_options(MetaCoreRender PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
|
||||
add_library(MetaCoreRuntimeInteraction STATIC
|
||||
Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h
|
||||
Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp
|
||||
)
|
||||
target_include_directories(MetaCoreRuntimeInteraction PUBLIC Source/MetaCoreRuntimeInput/Public)
|
||||
target_link_libraries(MetaCoreRuntimeInteraction PUBLIC MetaCoreRuntimeInput MetaCoreRender MetaCoreScene)
|
||||
target_compile_options(MetaCoreRuntimeInteraction PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
endif()
|
||||
|
||||
if(METACORE_ENABLE_RUNTIME_DATA)
|
||||
@ -618,6 +650,7 @@ target_link_libraries(MetaCoreRuntimeConfigTool
|
||||
PRIVATE
|
||||
MetaCoreFoundation
|
||||
MetaCoreRuntimeData
|
||||
MetaCoreRuntimeInput
|
||||
MetaCoreScene
|
||||
MetaCoreDelivery
|
||||
)
|
||||
@ -714,7 +747,10 @@ target_link_libraries(MetaCoreEditor
|
||||
MetaCoreFoundation
|
||||
MetaCorePlatform
|
||||
MetaCoreRender
|
||||
MetaCoreRuntimeInteraction
|
||||
MetaCoreRuntimeData
|
||||
MetaCoreRuntimeInput
|
||||
MetaCoreRuntimeUi
|
||||
MetaCoreScene
|
||||
MetaCoreScripting
|
||||
MetaCoreDelivery
|
||||
@ -784,6 +820,7 @@ target_link_libraries(MetaCoreRuntimeUi
|
||||
MetaCorePlatform
|
||||
MetaCoreRender
|
||||
MetaCoreRuntimeData
|
||||
MetaCoreRuntimeInput
|
||||
RmlUi::RmlUi
|
||||
PNG::PNG
|
||||
unofficial::brotli::brotlidec
|
||||
@ -802,6 +839,7 @@ target_link_libraries(MetaCorePlayer
|
||||
MetaCoreFoundation
|
||||
MetaCorePlatform
|
||||
MetaCoreRender
|
||||
MetaCoreRuntimeInteraction
|
||||
MetaCoreRuntimeData
|
||||
MetaCoreScene
|
||||
MetaCoreRuntimeUi
|
||||
@ -833,6 +871,11 @@ if(METACORE_BUILD_TESTS)
|
||||
target_compile_definitions(MetaCoreScriptingTests PRIVATE METACORE_SOURCE_ROOT="${CMAKE_SOURCE_DIR}")
|
||||
add_test(NAME MetaCoreScriptingTests COMMAND MetaCoreScriptingTests)
|
||||
|
||||
add_executable(MetaCoreRuntimeInputTests tests/MetaCoreRuntimeInputTests.cpp Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp)
|
||||
target_link_libraries(MetaCoreRuntimeInputTests PRIVATE MetaCoreRuntimeInput MetaCoreRuntimeInteraction MetaCorePlatform MetaCoreScene)
|
||||
target_compile_options(MetaCoreRuntimeInputTests PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
add_test(NAME MetaCoreRuntimeInputTests COMMAND MetaCoreRuntimeInputTests)
|
||||
|
||||
add_executable(MetaCoreSmokeTests
|
||||
tests/MetaCoreSmokeTests.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp
|
||||
|
||||
BIN
SandboxProject/Runtime/Input.mcruntime
Normal file
BIN
SandboxProject/Runtime/Input.mcruntime
Normal file
Binary file not shown.
@ -3,6 +3,9 @@
|
||||
#include "MetaCoreFoundation/MetaCoreHash.h"
|
||||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreScene/MetaCoreScenePackage.h"
|
||||
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
|
||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||
@ -613,6 +616,20 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque
|
||||
if (error) return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to create staging directory", staging);
|
||||
|
||||
report(MetaCoreBuildStage::Cook, "Cooking startup scene and project content");
|
||||
MetaCoreTypeRegistry inputRegistry;
|
||||
MetaCoreRegisterRuntimeDataGeneratedTypes(inputRegistry);
|
||||
MetaCoreRegisterRuntimeInputGeneratedTypes(inputRegistry);
|
||||
const auto runtimeProjectPath = request.ProjectRoot / project->RuntimeDirectory / "ProjectRuntime.mcruntimecfg";
|
||||
const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(runtimeProjectPath, inputRegistry);
|
||||
if (!runtimeProject) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime project configuration is missing or unreadable", runtimeProjectPath);
|
||||
if (runtimeProject->InputMapPath.empty() || !IsSafeRelativePath(runtimeProject->InputMapPath))
|
||||
return fail(MetaCoreBuildErrorCode::UnsafePath, "Runtime input map path is empty or unsafe", runtimeProjectPath);
|
||||
const auto inputMapPath = request.ProjectRoot / runtimeProject->InputMapPath;
|
||||
const auto inputMap = MetaCoreReadInputMap(inputMapPath, inputRegistry);
|
||||
if (!inputMap) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime input map is missing or unreadable", inputMapPath);
|
||||
const auto inputIssues = MetaCoreValidateInputMap(*inputMap);
|
||||
if (std::any_of(inputIssues.begin(), inputIssues.end(), [](const auto& issue) { return issue.Error; }))
|
||||
return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime input map validation failed", inputMapPath);
|
||||
std::filesystem::path startupSource = profile->StartupScene.empty() ? project->StartupScenePath : profile->StartupScene;
|
||||
if (!IsSafeRelativePath(startupSource)) return fail(MetaCoreBuildErrorCode::UnsafePath, "Startup scene path is unsafe", startupSource);
|
||||
const auto absoluteStartup = request.ProjectRoot / startupSource;
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
||||
#include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
|
||||
#include "MetaCoreFoundation/MetaCoreRuntimeAssetRegistry.h"
|
||||
@ -3580,6 +3586,20 @@ void MetaCoreDrawUiRendererComponentInspector(MetaCoreEditorContext& editorConte
|
||||
ImGui::TextDisabled("2D 使用屏幕空间层级;3D 使用对象位置/旋转、世界尺寸和每单位像素。");
|
||||
}
|
||||
|
||||
void MetaCoreDrawInteractableComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreInteractableComponent>()) return;
|
||||
auto& component = gameObject.GetComponent<MetaCoreInteractableComponent>();
|
||||
bool changed = ImGui::Checkbox("启用", &component.Enabled);
|
||||
bool hover = (component.EventMask & MetaCoreInteraction_Hover) != 0;
|
||||
bool click = (component.EventMask & MetaCoreInteraction_Click) != 0;
|
||||
bool drag = (component.EventMask & MetaCoreInteraction_Drag) != 0;
|
||||
if (ImGui::Checkbox("悬停事件", &hover)) { component.EventMask = hover ? component.EventMask | MetaCoreInteraction_Hover : component.EventMask & ~MetaCoreInteraction_Hover; changed = true; }
|
||||
if (ImGui::Checkbox("点击事件", &click)) { component.EventMask = click ? component.EventMask | MetaCoreInteraction_Click : component.EventMask & ~MetaCoreInteraction_Click; changed = true; }
|
||||
if (ImGui::Checkbox("拖拽事件", &drag)) { component.EventMask = drag ? component.EventMask | MetaCoreInteraction_Drag : component.EventMask & ~MetaCoreInteraction_Drag; changed = true; }
|
||||
changed |= ImGui::DragFloat("拖拽阈值(像素)", &component.DragThresholdPixels, 0.25F, 0.0F, 100.0F);
|
||||
if (changed) editorContext.GetScene().IncrementRevision();
|
||||
}
|
||||
|
||||
void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
|
||||
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
|
||||
return;
|
||||
@ -6388,6 +6408,14 @@ bool MetaCoreBuiltinAssetDatabaseService::CreateProject(const std::filesystem::p
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaCoreTypeRegistry registry;
|
||||
MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
|
||||
const std::filesystem::path inputMapPath = *normalizedRoot / "Runtime" / "Input.mcruntime";
|
||||
if (!std::filesystem::exists(inputMapPath) &&
|
||||
!MetaCoreWriteInputMap(inputMapPath, MetaCoreBuildDefaultInputMap(), registry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return OpenProject(*normalizedRoot);
|
||||
}
|
||||
|
||||
@ -9986,6 +10014,16 @@ public:
|
||||
},
|
||||
MetaCoreDrawMeshRendererComponentInspector
|
||||
});
|
||||
Descriptors_.push_back(MetaCoreComponentDescriptor{
|
||||
"Interactable", "可交互对象",
|
||||
[](const MetaCoreGameObject& object) { return object.HasComponent<MetaCoreInteractableComponent>(); },
|
||||
[](MetaCoreGameObject& object) { if (object.HasComponent<MetaCoreInteractableComponent>()) return false; object.AddComponent<MetaCoreInteractableComponent>(MetaCoreInteractableComponent{}); return true; },
|
||||
[](MetaCoreGameObject& object) { if (!object.HasComponent<MetaCoreInteractableComponent>()) return false; object.RemoveComponent<MetaCoreInteractableComponent>(); return true; },
|
||||
[](MetaCoreGameObject& object) { if (!object.HasComponent<MetaCoreInteractableComponent>()) return false; object.GetComponent<MetaCoreInteractableComponent>() = {}; return true; },
|
||||
[](const MetaCoreGameObject& object, const MetaCoreTypeRegistry& registry) -> std::optional<std::vector<std::byte>> { if (!object.HasComponent<MetaCoreInteractableComponent>()) return std::nullopt; return MetaCoreSerializeComponentValue(object.GetComponent<MetaCoreInteractableComponent>(), registry); },
|
||||
[](MetaCoreGameObject& object, std::span<const std::byte> payload, const MetaCoreTypeRegistry& registry) { MetaCoreInteractableComponent value{}; if (!MetaCoreDeserializeComponentValue(payload, value, registry)) return false; if (object.HasComponent<MetaCoreInteractableComponent>()) object.GetComponent<MetaCoreInteractableComponent>() = value; else object.AddComponent<MetaCoreInteractableComponent>(value); return true; },
|
||||
MetaCoreDrawInteractableComponentInspector
|
||||
});
|
||||
}
|
||||
|
||||
void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
@ -10341,8 +10379,60 @@ private:
|
||||
std::filesystem::path ActiveProjectRoot_{};
|
||||
};
|
||||
|
||||
class MetaCoreInputPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem {
|
||||
public:
|
||||
[[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Input"; }
|
||||
void OnPlayStart(MetaCoreEditorContext& editorContext) override {
|
||||
MetaCoreTypeRegistry registry; MetaCoreRegisterRuntimeDataGeneratedTypes(registry); MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
|
||||
const auto assets = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
const auto root = assets && assets->HasProject() ? assets->GetProjectDescriptor().RootPath : std::filesystem::current_path();
|
||||
const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(root / "Runtime" / "ProjectRuntime.mcruntimecfg", registry)
|
||||
.value_or(MetaCoreRuntimeProjectDocument{});
|
||||
const auto path = root / (runtimeProject.InputMapPath.empty() ? std::filesystem::path("Runtime/Input.mcruntime") : std::filesystem::path(runtimeProject.InputMapPath));
|
||||
Runtime_.SetInputMap(MetaCoreReadInputMap(path, registry).value_or(MetaCoreInputMapDocument{}));
|
||||
OverridePath_ = MetaCoreGetInputOverridePath(assets && assets->HasProject() ? assets->GetProjectDescriptor().Name : root.filename().string());
|
||||
(void)Runtime_.LoadOverrides(OverridePath_);
|
||||
for (const auto& warning : Runtime_.GetWarnings()) editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Input", warning);
|
||||
const auto manifestPath = root / (runtimeProject.UiManifestPath.empty() ? std::filesystem::path("Runtime/Ui.mcruntime") : std::filesystem::path(runtimeProject.UiManifestPath));
|
||||
if (const auto manifest = MetaCoreReadRuntimeUiManifest(manifestPath, registry); manifest.has_value() &&
|
||||
RuntimeUi_.Initialize(editorContext.GetWindow(), root, root / "Ui", *manifest)) {
|
||||
RuntimeUi_.AttachToViewportRenderer(editorContext.GetViewportRenderer());
|
||||
RuntimeUiActive_ = true;
|
||||
}
|
||||
}
|
||||
void Update(MetaCoreEditorContext& editorContext, double deltaSeconds) override {
|
||||
const auto& viewport = editorContext.GetSceneViewportState();
|
||||
const bool focused = editorContext.GetWorkspacePage() == MetaCoreEditorWorkspacePage::Game &&
|
||||
editorContext.HasGameViewInputFocus() && editorContext.GetInput().IsWindowFocused();
|
||||
if (focused) {
|
||||
const MetaCoreInputConsumption consumption = RuntimeUiActive_ ? RuntimeUi_.ProcessInput() : MetaCoreInputConsumption{};
|
||||
Runtime_.Update(editorContext.GetInput(), consumption);
|
||||
Interaction_.Update(editorContext.GetScene(), editorContext.GetViewportRenderer(), editorContext.GetInput(),
|
||||
MetaCoreViewportRect{viewport.Left, viewport.Top, viewport.Width, viewport.Height}, consumption.Mouse);
|
||||
if (RuntimeUiActive_) RuntimeUi_.Update(static_cast<float>(deltaSeconds));
|
||||
} else {
|
||||
Runtime_.CancelAll();
|
||||
Interaction_.Cancel();
|
||||
}
|
||||
}
|
||||
void OnPlayStop(MetaCoreEditorContext& editorContext) override {
|
||||
Runtime_.CancelAll(); Interaction_.Cancel(); if (!OverridePath_.empty()) (void)Runtime_.SaveOverrides(OverridePath_); RuntimeUi_.Shutdown(); RuntimeUiActive_ = false;
|
||||
editorContext.SetGameViewInputFocus(false);
|
||||
}
|
||||
[[nodiscard]] MetaCoreRuntimeInput& GetRuntime() { return Runtime_; }
|
||||
[[nodiscard]] MetaCoreRuntimeInteraction& GetInteraction() { return Interaction_; }
|
||||
[[nodiscard]] MetaCoreRuntimeUiSystem& GetRuntimeUi() { return RuntimeUi_; }
|
||||
private:
|
||||
MetaCoreRuntimeInput Runtime_{};
|
||||
MetaCoreRuntimeInteraction Interaction_{};
|
||||
MetaCoreRuntimeUiSystem RuntimeUi_{};
|
||||
bool RuntimeUiActive_ = false;
|
||||
std::filesystem::path OverridePath_{};
|
||||
};
|
||||
|
||||
class MetaCoreScriptPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem {
|
||||
public:
|
||||
explicit MetaCoreScriptPlayModeComponentSystem(std::shared_ptr<MetaCoreInputPlayModeComponentSystem> input) : Input_(std::move(input)) {}
|
||||
[[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Script"; }
|
||||
|
||||
void OnPlayStart(MetaCoreEditorContext& editorContext) override {
|
||||
@ -10353,9 +10443,35 @@ public:
|
||||
[&editorContext](std::uint32_t level, std::string_view category, std::string_view message) {
|
||||
const MetaCoreLogLevel mapped = level >= 2U ? MetaCoreLogLevel::Error : level == 1U ? MetaCoreLogLevel::Warning : MetaCoreLogLevel::Info;
|
||||
editorContext.AddConsoleMessage(mapped, std::string(category), std::string(message));
|
||||
}
|
||||
}, Input_ ? &Input_->GetRuntime() : nullptr
|
||||
);
|
||||
Runtime_->Start();
|
||||
if (Input_) {
|
||||
InputSubscription_ = Input_->GetRuntime().Subscribe([this](const MetaCoreInputActionEvent& event) {
|
||||
if (!Runtime_) return;
|
||||
MetaCoreScriptValueV1 phase{}; phase.Type = MetaCoreScriptAbiValue_Int; phase.IntValue = static_cast<std::int64_t>(event.Phase);
|
||||
Runtime_->Publish("MetaCore.Input.Action/" + event.ActionId, {phase});
|
||||
});
|
||||
Input_->GetInteraction().SetEventCallback([this](const MetaCorePointerEvent& event) {
|
||||
if (!Runtime_) return;
|
||||
MetaCoreScriptValueV1 type{}; type.Type = MetaCoreScriptAbiValue_Int; type.IntValue = static_cast<std::int64_t>(event.Type);
|
||||
MetaCoreScriptValueV1 button{}; button.Type = MetaCoreScriptAbiValue_Int; button.IntValue = event.Button;
|
||||
MetaCoreScriptValueV1 screen{}; screen.Type = MetaCoreScriptAbiValue_Vec3; screen.Vec3[0] = event.ScreenPosition.x; screen.Vec3[1] = event.ScreenPosition.y;
|
||||
MetaCoreScriptValueV1 delta{}; delta.Type = MetaCoreScriptAbiValue_Vec3; delta.Vec3[0] = event.ScreenDelta.x; delta.Vec3[1] = event.ScreenDelta.y;
|
||||
MetaCoreScriptValueV1 world{}; world.Type = MetaCoreScriptAbiValue_Vec3; world.Vec3[0] = event.WorldPosition.x; world.Vec3[1] = event.WorldPosition.y; world.Vec3[2] = event.WorldPosition.z;
|
||||
MetaCoreScriptValueV1 target{}; target.Type = MetaCoreScriptAbiValue_GameObjectRef; target.ObjectValue = {Runtime_->GetWorldGeneration(), event.TargetObjectId};
|
||||
Runtime_->PublishToObject(event.TargetObjectId, "MetaCore.Interaction.Pointer", {type, button, screen, delta, world, target});
|
||||
});
|
||||
Input_->GetRuntimeUi().SetEventCallback([this](const MetaCoreRuntimeUiEvent& event) {
|
||||
if (!Runtime_) return;
|
||||
const auto textValue = [](const std::string& text) {
|
||||
MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_String;
|
||||
std::snprintf(value.Text, sizeof(value.Text), "%s", text.c_str()); return value;
|
||||
};
|
||||
Runtime_->Publish("MetaCore.UI", {textValue(event.DocumentId), textValue(event.ElementId),
|
||||
textValue(event.Action), textValue(event.Type), textValue(event.Value)});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FixedUpdate(MetaCoreEditorContext&, double fixedDeltaSeconds) override {
|
||||
@ -10371,12 +10487,20 @@ public:
|
||||
}
|
||||
|
||||
void OnPlayStop(MetaCoreEditorContext&) override {
|
||||
if (Input_) {
|
||||
Input_->GetRuntime().Unsubscribe(InputSubscription_);
|
||||
Input_->GetInteraction().SetEventCallback({});
|
||||
Input_->GetRuntimeUi().SetEventCallback({});
|
||||
InputSubscription_ = 0;
|
||||
}
|
||||
if (Runtime_ != nullptr) Runtime_->Stop();
|
||||
Runtime_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MetaCoreInputPlayModeComponentSystem> Input_{};
|
||||
std::unique_ptr<MetaCoreScriptRuntime> Runtime_{};
|
||||
std::uint64_t InputSubscription_ = 0;
|
||||
};
|
||||
|
||||
class MetaCoreRotatorPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem {
|
||||
@ -10497,9 +10621,11 @@ public:
|
||||
void Startup(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
(void)moduleRegistry;
|
||||
Systems_.clear();
|
||||
auto input = std::make_shared<MetaCoreInputPlayModeComponentSystem>();
|
||||
RegisterSystem(input);
|
||||
RegisterSystem(std::make_shared<MetaCoreAnimationPlayModeComponentSystem>());
|
||||
RegisterSystem(std::make_shared<MetaCoreRotatorPlayModeComponentSystem>());
|
||||
RegisterSystem(std::make_shared<MetaCoreScriptPlayModeComponentSystem>());
|
||||
RegisterSystem(std::make_shared<MetaCoreScriptPlayModeComponentSystem>(std::move(input)));
|
||||
}
|
||||
|
||||
void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
|
||||
#include "MetaCorePlatform/MetaCoreWindow.h"
|
||||
#include "MetaCoreFoundation/MetaCoreAssetRegistry.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
@ -109,6 +110,7 @@ static std::unordered_map<std::string, MetaCoreModelAnimationPreviewState> Model
|
||||
MetaCoreTypeRegistry registry;
|
||||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
MetaCoreRegisterRuntimeDataGeneratedTypes(registry);
|
||||
MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
|
||||
return registry;
|
||||
}
|
||||
|
||||
@ -3421,6 +3423,61 @@ private:
|
||||
int AddRmlIndex_ = 0;
|
||||
};
|
||||
|
||||
class MetaCoreInputMapPanelProvider final : public MetaCoreIEditorPanelProvider {
|
||||
public:
|
||||
std::string GetPanelId() const override { return "InputMap"; }
|
||||
std::string GetPanelTitle() const override { return "输入映射"; }
|
||||
bool IsOpenByDefault() const override { return false; }
|
||||
|
||||
void DrawPanel(MetaCoreEditorContext& editorContext) override {
|
||||
const auto assets = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (!assets || !assets->HasProject()) { ImGui::TextDisabled("没有打开项目。"); return; }
|
||||
const auto root = assets->GetProjectDescriptor().RootPath;
|
||||
if (LoadedRoot_ != root) { MetaCoreTypeRegistry registry = MetaCoreBuildRuntimePanelTypeRegistry(); const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(root / "Runtime" / "ProjectRuntime.mcruntimecfg", registry).value_or(MetaCoreRuntimeProjectDocument{}); MapPath_ = root / (runtimeProject.InputMapPath.empty() ? std::filesystem::path("Runtime/Input.mcruntime") : std::filesystem::path(runtimeProject.InputMapPath)); Map_ = MetaCoreReadInputMap(MapPath_, registry).value_or(MetaCoreBuildDefaultInputMap()); LoadedRoot_ = root; Dirty_ = false; }
|
||||
const auto& path = MapPath_;
|
||||
if (!ListeningBinding_.empty()) {
|
||||
for (const auto& event : editorContext.GetInput().GetEvents()) if (event.Type == MetaCoreInputEventType::Control && std::abs(event.Value) > 0.5F) {
|
||||
const auto it = std::find_if(Map_.Bindings.begin(), Map_.Bindings.end(), [&](const auto& b){ return b.BindingId == ListeningBinding_; });
|
||||
if (it != Map_.Bindings.end()) { it->ControlPath = event.ControlPath; Dirty_ = true; }
|
||||
ListeningBinding_.clear(); break;
|
||||
}
|
||||
}
|
||||
if (ImGui::Button("保存")) {
|
||||
const auto issues = MetaCoreValidateInputMap(Map_);
|
||||
const bool invalid = std::any_of(issues.begin(), issues.end(), [](const auto& value){ return value.Error; });
|
||||
if (!invalid) { MetaCoreTypeRegistry registry = MetaCoreBuildRuntimePanelTypeRegistry(); Dirty_ = !MetaCoreWriteInputMap(path, Map_, registry); }
|
||||
for (const auto& issue : issues) editorContext.AddConsoleMessage(issue.Error ? MetaCoreLogLevel::Error : MetaCoreLogLevel::Warning, "InputMap", issue.Scope + ": " + issue.Message);
|
||||
}
|
||||
ImGui::SameLine(); if (ImGui::Button("恢复默认")) { Map_ = MetaCoreBuildDefaultInputMap(); Dirty_ = true; }
|
||||
ImGui::SameLine(); ImGui::TextDisabled("%s", Dirty_ ? "未保存" : "已保存");
|
||||
auto editString = [&](const char* label, std::string& value) { std::array<char, 256> buffer{}; std::snprintf(buffer.data(), buffer.size(), "%s", value.c_str()); if (ImGui::InputText(label, buffer.data(), buffer.size())) { value = buffer.data(); Dirty_ = true; } };
|
||||
if (ImGui::CollapsingHeader("Actions", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (std::size_t i = 0; i < Map_.Actions.size();) { ImGui::PushID(static_cast<int>(i)); editString("ID", Map_.Actions[i].Id); editString("显示名", Map_.Actions[i].DisplayName); ImGui::SameLine(); if (ImGui::SmallButton("删除")) { Map_.Actions.erase(Map_.Actions.begin() + static_cast<std::ptrdiff_t>(i)); Dirty_ = true; ImGui::PopID(); continue; } ImGui::PopID(); ++i; }
|
||||
if (ImGui::Button("添加 Action")) { const auto id = "Action" + std::to_string(Map_.Actions.size() + 1); Map_.Actions.push_back({id, id}); Dirty_ = true; }
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Axes", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (std::size_t i = 0; i < Map_.Axes.size();) { auto& axis = Map_.Axes[i]; ImGui::PushID(1000 + static_cast<int>(i)); editString("ID", axis.Id); Dirty_ |= ImGui::DragFloat("死区", &axis.DeadZone, 0.01F, 0.0F, 0.99F); Dirty_ |= ImGui::DragFloat("灵敏度", &axis.Sensitivity, 0.05F, 0.0F, 10.0F); float range[2]{axis.Minimum, axis.Maximum}; if (ImGui::DragFloat2("范围", range, 0.05F, -100.0F, 100.0F)) { axis.Minimum = range[0]; axis.Maximum = range[1]; Dirty_ = true; } ImGui::SameLine(); if (ImGui::SmallButton("删除")) { Map_.Axes.erase(Map_.Axes.begin() + static_cast<std::ptrdiff_t>(i)); Dirty_ = true; ImGui::PopID(); continue; } ImGui::PopID(); ++i; }
|
||||
if (ImGui::Button("添加 Axis")) { const auto id = "Axis" + std::to_string(Map_.Axes.size() + 1); Map_.Axes.push_back({id, id, 0.0F, 1.0F, -1.0F, 1.0F}); Dirty_ = true; }
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Bindings", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (std::size_t i = 0; i < Map_.Bindings.size();) { auto& binding = Map_.Bindings[i]; ImGui::PushID(2000 + static_cast<int>(i)); editString("Binding ID", binding.BindingId); int targetKind = static_cast<int>(binding.Target); if (ImGui::Combo("目标类型", &targetKind, "Action\0Axis\0")) { binding.Target = static_cast<MetaCoreInputBindingTarget>(targetKind); Dirty_ = true; } editString("目标", binding.TargetId); editString("控制路径", binding.ControlPath); Dirty_ |= ImGui::DragFloat("倍率", &binding.Scale, 0.05F, -10.0F, 10.0F); std::string modifiers; for (const auto& modifier : binding.Modifiers) { if (!modifiers.empty()) modifiers += ","; modifiers += modifier; } std::array<char, 512> modifierBuffer{}; std::snprintf(modifierBuffer.data(), modifierBuffer.size(), "%s", modifiers.c_str()); if (ImGui::InputText("修饰键(逗号分隔)", modifierBuffer.data(), modifierBuffer.size())) { binding.Modifiers.clear(); std::stringstream stream(modifierBuffer.data()); for (std::string item; std::getline(stream, item, ',');) if (!item.empty()) binding.Modifiers.push_back(item); Dirty_ = true; } if (ImGui::Button(ListeningBinding_ == binding.BindingId ? "等待输入..." : "监听下一个控制")) ListeningBinding_ = binding.BindingId; ImGui::SameLine(); if (ImGui::SmallButton("删除")) { const std::string id = binding.BindingId; Map_.Bindings.erase(Map_.Bindings.begin() + static_cast<std::ptrdiff_t>(i)); for (auto& context : Map_.Contexts) std::erase(context.BindingIds, id); Dirty_ = true; ImGui::PopID(); continue; } ImGui::Separator(); ImGui::PopID(); ++i; }
|
||||
if (ImGui::Button("添加 Binding")) { const auto id = "binding." + std::to_string(Map_.Bindings.size() + 1); const std::string target = Map_.Actions.empty() ? std::string{} : Map_.Actions.front().Id; Map_.Bindings.push_back({id, target, MetaCoreInputBindingTarget::Action, "<Keyboard>/Space", 1.0F, {}}); if (!Map_.Contexts.empty()) Map_.Contexts.front().BindingIds.push_back(id); Dirty_ = true; }
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Contexts", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (std::size_t i = 0; i < Map_.Contexts.size();) { auto& context = Map_.Contexts[i]; ImGui::PushID(3000 + static_cast<int>(i)); editString("ID", context.Id); Dirty_ |= ImGui::InputInt("优先级", &context.Priority); Dirty_ |= ImGui::Checkbox("默认启用", &context.EnabledByDefault); Dirty_ |= ImGui::Checkbox("消费低优先级", &context.ConsumeLowerPriority); if (ImGui::TreeNode("Bindings")) { for (const auto& binding : Map_.Bindings) { bool included = std::find(context.BindingIds.begin(), context.BindingIds.end(), binding.BindingId) != context.BindingIds.end(); if (ImGui::Checkbox(binding.BindingId.c_str(), &included)) { if (included) context.BindingIds.push_back(binding.BindingId); else std::erase(context.BindingIds, binding.BindingId); Dirty_ = true; } } ImGui::TreePop(); } if (ImGui::SmallButton("删除 Context")) { Map_.Contexts.erase(Map_.Contexts.begin() + static_cast<std::ptrdiff_t>(i)); Dirty_ = true; ImGui::PopID(); continue; } ImGui::Separator(); ImGui::PopID(); ++i; }
|
||||
if (ImGui::Button("添加 Context")) { const auto id = "Context" + std::to_string(Map_.Contexts.size() + 1); Map_.Contexts.push_back({id, 0, true, false, {}}); Dirty_ = true; }
|
||||
}
|
||||
for (std::size_t i = 0; i < Map_.Bindings.size(); ++i) for (std::size_t j = i + 1; j < Map_.Bindings.size(); ++j) if (Map_.Bindings[i].ControlPath == Map_.Bindings[j].ControlPath && Map_.Bindings[i].Modifiers == Map_.Bindings[j].Modifiers) { ImGui::TextColored(ImVec4(1.0F, 0.65F, 0.2F, 1.0F), "冲突: %s / %s", Map_.Bindings[i].BindingId.c_str(), Map_.Bindings[j].BindingId.c_str()); }
|
||||
ImGui::Separator(); ImGui::Text("鼠标增量: %.1f, %.1f 手柄: %s", editorContext.GetInput().GetCursorDelta().x, editorContext.GetInput().GetCursorDelta().y, editorContext.GetInput().GetGamepadState().Connected ? "已连接" : "未连接");
|
||||
}
|
||||
private:
|
||||
std::filesystem::path LoadedRoot_{};
|
||||
std::filesystem::path MapPath_{};
|
||||
MetaCoreInputMapDocument Map_{};
|
||||
std::string ListeningBinding_{};
|
||||
bool Dirty_ = false;
|
||||
};
|
||||
|
||||
class MetaCoreRmlUiVisualEditorPanelProvider final : public MetaCoreIEditorPanelProvider {
|
||||
public:
|
||||
std::string GetPanelId() const override { return "RmlUiVisualEditor"; }
|
||||
@ -9878,6 +9935,7 @@ public:
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreHierarchyPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreScenePanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRuntimeUiPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreInputMapPanelProvider>());
|
||||
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRmlUiVisualEditorPanelProvider>());
|
||||
moduleRegistry.RegisterService<MetaCoreIUiDesignerService>(std::make_shared<MetaCoreNativeUiDesignerService>());
|
||||
// The central Scene / Game viewport is not a dockable provider, but it keeps
|
||||
|
||||
@ -2764,8 +2764,16 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
mousePosition.x <= viewportState.Left + viewportState.Width &&
|
||||
mousePosition.y >= viewportState.Top &&
|
||||
mousePosition.y <= viewportState.Top + viewportState.Height;
|
||||
viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse;
|
||||
const bool isGameView = activeWorkspacePage == MetaCoreEditorWorkspacePage::Game;
|
||||
if (!isGameView || !EditorContext_->GetInput().IsWindowFocused() ||
|
||||
EditorContext_->GetInput().WasControlPressed("<Keyboard>/Escape")) {
|
||||
EditorContext_->SetGameViewInputFocus(false);
|
||||
} else if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
EditorContext_->SetGameViewInputFocus(viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse);
|
||||
}
|
||||
viewportState.Focused = isGameView
|
||||
? EditorContext_->HasGameViewInputFocus()
|
||||
: viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse;
|
||||
const MetaCoreSceneView preInteractionSceneView = EditorContext_->GetCameraController().BuildSceneView();
|
||||
const bool uiPreviewHoveredBeforeCamera =
|
||||
!isGameView &&
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "MetaCoreEditorCameraController.h"
|
||||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
|
||||
#include "MetaCorePlatform/MetaCoreWindow.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||||
@ -249,6 +250,7 @@ private:
|
||||
MetaCoreTypeRegistry registry;
|
||||
MetaCoreRegisterFoundationGeneratedTypes(registry);
|
||||
MetaCoreRegisterRuntimeDataGeneratedTypes(registry);
|
||||
MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
|
||||
return registry;
|
||||
}
|
||||
|
||||
@ -259,6 +261,7 @@ private:
|
||||
document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime";
|
||||
document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate";
|
||||
document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime";
|
||||
document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime";
|
||||
return document;
|
||||
}
|
||||
|
||||
@ -296,6 +299,8 @@ MetaCoreEditorModuleRegistry& MetaCoreEditorContext::GetModuleRegistry() { retur
|
||||
const MetaCoreEditorModuleRegistry& MetaCoreEditorContext::GetModuleRegistry() const { return ModuleRegistry_; }
|
||||
MetaCoreSceneViewportState& MetaCoreEditorContext::GetSceneViewportState() { return SceneViewportState_; }
|
||||
const MetaCoreSceneViewportState& MetaCoreEditorContext::GetSceneViewportState() const { return SceneViewportState_; }
|
||||
bool MetaCoreEditorContext::HasGameViewInputFocus() const { return GameViewInputFocused_; }
|
||||
void MetaCoreEditorContext::SetGameViewInputFocus(bool focused) { GameViewInputFocused_ = focused; }
|
||||
MetaCoreEditorCameraController& MetaCoreEditorContext::GetCameraController() { return *CameraController_; }
|
||||
const MetaCoreEditorCameraController& MetaCoreEditorContext::GetCameraController() const { return *CameraController_; }
|
||||
|
||||
@ -694,8 +699,10 @@ bool MetaCoreEditorContext::SaveRuntimeDataConfig() {
|
||||
RuntimeUiManifestDocument_,
|
||||
runtimeRegistry
|
||||
);
|
||||
const std::filesystem::path inputMapPath = runtimeDirectory / "Input.mcruntime";
|
||||
const bool wroteInputMap = std::filesystem::exists(inputMapPath) || MetaCoreWriteInputMap(inputMapPath, MetaCoreBuildDefaultInputMap(), runtimeRegistry);
|
||||
|
||||
if (!wroteProjectRuntime || !wroteSources || !wroteBindings || !wroteUiManifest) {
|
||||
if (!wroteProjectRuntime || !wroteSources || !wroteBindings || !wroteUiManifest || !wroteInputMap) {
|
||||
AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", "保存 Runtime 配置失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -195,6 +195,8 @@ public:
|
||||
[[nodiscard]] bool HasDockLayoutBuilt() const;
|
||||
[[nodiscard]] MetaCoreEditorWorkspacePage GetWorkspacePage() const;
|
||||
void SetWorkspacePage(MetaCoreEditorWorkspacePage page);
|
||||
[[nodiscard]] bool HasGameViewInputFocus() const;
|
||||
void SetGameViewInputFocus(bool focused);
|
||||
|
||||
private:
|
||||
void NormalizeSelection();
|
||||
@ -228,6 +230,7 @@ private:
|
||||
MetaCoreRuntimeUiManifest RuntimeUiManifestDocument_{};
|
||||
MetaCoreSelectedAssetState SelectedAsset_{};
|
||||
std::filesystem::path SelectedProjectDirectory_{"Assets"};
|
||||
bool GameViewInputFocused_ = false;
|
||||
std::string SelectedAssetSubId_{};
|
||||
MetaCoreEditorWorkspacePage WorkspacePage_ = MetaCoreEditorWorkspacePage::Scene;
|
||||
};
|
||||
|
||||
@ -8,5 +8,6 @@ void MetaCoreRegisterFoundationGeneratedTypes(MetaCoreTypeRegistry& registry);
|
||||
void MetaCoreRegisterSceneGeneratedTypes(MetaCoreTypeRegistry& registry);
|
||||
void MetaCoreRegisterEditorGeneratedTypes(MetaCoreTypeRegistry& registry);
|
||||
void MetaCoreRegisterRuntimeDataGeneratedTypes(MetaCoreTypeRegistry& registry);
|
||||
void MetaCoreRegisterRuntimeInputGeneratedTypes(MetaCoreTypeRegistry& registry);
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
void MetaCoreInput::BeginFrame() {
|
||||
@ -10,6 +12,7 @@ void MetaCoreInput::BeginFrame() {
|
||||
MouseWheelDelta_ = 0.0F;
|
||||
KeyEvents_.clear();
|
||||
TextInput_.clear();
|
||||
Events_.clear();
|
||||
}
|
||||
|
||||
void MetaCoreInput::SetKeyState(MetaCoreInputKey key, bool isDown) {
|
||||
@ -35,8 +38,24 @@ void MetaCoreInput::AddKeyEvent(MetaCoreKeyEvent event) {
|
||||
|
||||
void MetaCoreInput::AddTextInput(char32_t codePoint) {
|
||||
TextInput_.push_back(codePoint);
|
||||
Events_.push_back({MetaCoreInputEventType::Text, {}, 0.0F, codePoint});
|
||||
}
|
||||
|
||||
void MetaCoreInput::SetControlValue(std::string controlPath, float value) {
|
||||
const auto iterator = ControlValues_.find(controlPath);
|
||||
const float previous = iterator == ControlValues_.end() ? 0.0F : iterator->second;
|
||||
ControlValues_[controlPath] = value;
|
||||
if (previous != value) Events_.push_back({MetaCoreInputEventType::Control, std::move(controlPath), value, 0});
|
||||
}
|
||||
|
||||
void MetaCoreInput::SetWindowFocused(bool focused) {
|
||||
if (WindowFocused_ == focused) return;
|
||||
WindowFocused_ = focused;
|
||||
Events_.push_back({MetaCoreInputEventType::Focus, {}, focused ? 1.0F : 0.0F, 0});
|
||||
}
|
||||
|
||||
void MetaCoreInput::SetGamepadState(const MetaCoreGamepadState& state) { Gamepad_ = state; }
|
||||
|
||||
bool MetaCoreInput::IsKeyDown(MetaCoreInputKey key) const {
|
||||
return CurrentKeyStates_[static_cast<std::size_t>(key)];
|
||||
}
|
||||
@ -84,4 +103,24 @@ std::vector<char32_t> MetaCoreInput::ConsumeTextInput() {
|
||||
return text;
|
||||
}
|
||||
|
||||
const std::vector<MetaCoreKeyEvent>& MetaCoreInput::GetKeyEvents() const { return KeyEvents_; }
|
||||
const std::vector<char32_t>& MetaCoreInput::GetTextInput() const { return TextInput_; }
|
||||
const std::vector<MetaCoreInputEvent>& MetaCoreInput::GetEvents() const { return Events_; }
|
||||
float MetaCoreInput::GetControlValue(std::string_view controlPath) const {
|
||||
const auto iterator = ControlValues_.find(std::string(controlPath));
|
||||
return iterator == ControlValues_.end() ? 0.0F : iterator->second;
|
||||
}
|
||||
bool MetaCoreInput::WasControlPressed(std::string_view controlPath) const {
|
||||
return std::any_of(Events_.begin(), Events_.end(), [controlPath](const MetaCoreInputEvent& event) {
|
||||
return event.Type == MetaCoreInputEventType::Control && event.ControlPath == controlPath && event.Value > 0.5F;
|
||||
});
|
||||
}
|
||||
bool MetaCoreInput::WasControlReleased(std::string_view controlPath) const {
|
||||
return std::any_of(Events_.begin(), Events_.end(), [controlPath](const MetaCoreInputEvent& event) {
|
||||
return event.Type == MetaCoreInputEventType::Control && event.ControlPath == controlPath && event.Value <= 0.5F;
|
||||
});
|
||||
}
|
||||
bool MetaCoreInput::IsWindowFocused() const { return WindowFocused_; }
|
||||
const MetaCoreGamepadState& MetaCoreInput::GetGamepadState() const { return Gamepad_; }
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <xinput.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
@ -18,6 +19,7 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
@ -158,6 +160,30 @@ int MetaCoreMapVirtualKeyToRuntimeKey(WPARAM virtualKey) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string MetaCoreWin32KeyControlPath(WPARAM key) {
|
||||
if (key >= 'A' && key <= 'Z') return "<Keyboard>/" + std::string(1, static_cast<char>(key));
|
||||
if (key >= '0' && key <= '9') return "<Keyboard>/" + std::string(1, static_cast<char>(key));
|
||||
if (key >= VK_F1 && key <= VK_F24) return "<Keyboard>/F" + std::to_string(key - VK_F1 + 1);
|
||||
if (key >= VK_NUMPAD0 && key <= VK_NUMPAD9) return "<Keyboard>/Numpad" + std::to_string(key - VK_NUMPAD0);
|
||||
switch (key) {
|
||||
case VK_SPACE: return "<Keyboard>/Space"; case VK_ESCAPE: return "<Keyboard>/Escape";
|
||||
case VK_RETURN: return "<Keyboard>/Enter"; case VK_TAB: return "<Keyboard>/Tab";
|
||||
case VK_LEFT: return "<Keyboard>/Left"; case VK_RIGHT: return "<Keyboard>/Right";
|
||||
case VK_UP: return "<Keyboard>/Up"; case VK_DOWN: return "<Keyboard>/Down";
|
||||
case VK_LSHIFT: return "<Keyboard>/LeftShift"; case VK_RSHIFT: return "<Keyboard>/RightShift";
|
||||
case VK_LCONTROL: return "<Keyboard>/LeftControl"; case VK_RCONTROL: return "<Keyboard>/RightControl";
|
||||
case VK_LMENU: return "<Keyboard>/LeftAlt"; case VK_RMENU: return "<Keyboard>/RightAlt";
|
||||
case VK_BACK: return "<Keyboard>/Backspace"; case VK_DELETE: return "<Keyboard>/Delete";
|
||||
case VK_INSERT: return "<Keyboard>/Insert"; case VK_HOME: return "<Keyboard>/Home"; case VK_END: return "<Keyboard>/End";
|
||||
case VK_PRIOR: return "<Keyboard>/PageUp"; case VK_NEXT: return "<Keyboard>/PageDown"; case VK_CAPITAL: return "<Keyboard>/CapsLock";
|
||||
case VK_OEM_MINUS: return "<Keyboard>/Minus"; case VK_OEM_PLUS: return "<Keyboard>/Equal";
|
||||
case VK_OEM_4: return "<Keyboard>/LeftBracket"; case VK_OEM_6: return "<Keyboard>/RightBracket"; case VK_OEM_5: return "<Keyboard>/Backslash";
|
||||
case VK_OEM_1: return "<Keyboard>/Semicolon"; case VK_OEM_7: return "<Keyboard>/Quote";
|
||||
case VK_OEM_COMMA: return "<Keyboard>/Comma"; case VK_OEM_PERIOD: return "<Keyboard>/Period"; case VK_OEM_2: return "<Keyboard>/Slash"; case VK_OEM_3: return "<Keyboard>/Backquote";
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<HWND, class MetaCoreWindow::MetaCoreWindowImpl*>& MetaCoreGetWindowMap() {
|
||||
static std::unordered_map<HWND, class MetaCoreWindow::MetaCoreWindowImpl*> windowMap;
|
||||
return windowMap;
|
||||
@ -213,6 +239,8 @@ LRESULT CALLBACK MetaCoreWindowSubclassProc(HWND hwnd, UINT msg, WPARAM wparam,
|
||||
msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN,
|
||||
MetaCoreGetWin32InputModifiers()
|
||||
});
|
||||
const std::string path = MetaCoreWin32KeyControlPath(wparam);
|
||||
if (!path.empty()) owner.Input.SetControlValue(path, (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ? 1.0F : 0.0F);
|
||||
}
|
||||
} else if (msg == WM_CHAR) {
|
||||
if (wparam >= 0x20 && wparam != 0x7F) {
|
||||
@ -390,6 +418,28 @@ void MetaCoreWindow::BeginFrame() {
|
||||
if (GetCursorPos(&cursorPosition) != FALSE && ScreenToClient(Impl_->NativeHandle, &cursorPosition) != FALSE) {
|
||||
Impl_->Input.SetCursorPosition(glm::vec2(static_cast<float>(cursorPosition.x), static_cast<float>(cursorPosition.y)));
|
||||
}
|
||||
Impl_->Input.SetControlValue("<Mouse>/delta/x", Impl_->Input.GetCursorDelta().x);
|
||||
Impl_->Input.SetControlValue("<Mouse>/delta/y", Impl_->Input.GetCursorDelta().y);
|
||||
Impl_->Input.SetControlValue("<Mouse>/wheel", Impl_->Input.GetMouseWheelDelta());
|
||||
Impl_->Input.SetControlValue("<Mouse>/leftButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Left) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetControlValue("<Mouse>/rightButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Right) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetControlValue("<Mouse>/middleButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Middle) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetWindowFocused(GetForegroundWindow() == Impl_->NativeHandle);
|
||||
|
||||
XINPUT_STATE xinput{};
|
||||
MetaCoreGamepadState gamepad;
|
||||
gamepad.Connected = XInputGetState(0, &xinput) == ERROR_SUCCESS;
|
||||
if (gamepad.Connected) {
|
||||
const auto normalizeStick = [](SHORT value) { return value < 0 ? static_cast<float>(value) / 32768.0F : static_cast<float>(value) / 32767.0F; };
|
||||
gamepad.Axes = {normalizeStick(xinput.Gamepad.sThumbLX), normalizeStick(xinput.Gamepad.sThumbLY), normalizeStick(xinput.Gamepad.sThumbRX), normalizeStick(xinput.Gamepad.sThumbRY), static_cast<float>(xinput.Gamepad.bLeftTrigger) / 255.0F, static_cast<float>(xinput.Gamepad.bRightTrigger) / 255.0F};
|
||||
static constexpr std::array<WORD, 15> masks{XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER, XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, 0, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_RIGHT, XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_LEFT};
|
||||
for (std::size_t i = 0; i < masks.size(); ++i) gamepad.Buttons[i] = masks[i] != 0 && (xinput.Gamepad.wButtons & masks[i]) != 0;
|
||||
}
|
||||
Impl_->Input.SetGamepadState(gamepad);
|
||||
static constexpr std::array<const char*, 6> axisPaths{"leftStick/x", "leftStick/y", "rightStick/x", "rightStick/y", "leftTrigger", "rightTrigger"};
|
||||
for (std::size_t i = 0; i < axisPaths.size(); ++i) Impl_->Input.SetControlValue(std::string("<Gamepad>/") + axisPaths[i], gamepad.Connected ? gamepad.Axes[i] : 0.0F);
|
||||
static constexpr std::array<const char*, 15> buttonPaths{"buttonSouth", "buttonEast", "buttonWest", "buttonNorth", "leftBumper", "rightBumper", "Back", "Start", "Guide", "leftStickPress", "rightStickPress", "dpadUp", "dpadRight", "dpadDown", "dpadLeft"};
|
||||
for (std::size_t i = 0; i < buttonPaths.size(); ++i) Impl_->Input.SetControlValue(std::string("<Gamepad>/") + buttonPaths[i], gamepad.Connected && gamepad.Buttons[i] ? 1.0F : 0.0F);
|
||||
}
|
||||
|
||||
void MetaCoreWindow::EndFrame() {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
@ -30,6 +31,49 @@ bool MetaCoreIsGlfwMouseButtonDown(GLFWwindow* window, int button) {
|
||||
return glfwGetMouseButton(window, button) == GLFW_PRESS;
|
||||
}
|
||||
|
||||
std::string MetaCoreGlfwKeyControlPath(int key) {
|
||||
if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z) return "<Keyboard>/" + std::string(1, static_cast<char>('A' + key - GLFW_KEY_A));
|
||||
if (key >= GLFW_KEY_0 && key <= GLFW_KEY_9) return "<Keyboard>/" + std::string(1, static_cast<char>('0' + key - GLFW_KEY_0));
|
||||
if (key >= GLFW_KEY_F1 && key <= GLFW_KEY_F25) return "<Keyboard>/F" + std::to_string(key - GLFW_KEY_F1 + 1);
|
||||
if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_9) return "<Keyboard>/Numpad" + std::to_string(key - GLFW_KEY_KP_0);
|
||||
switch (key) {
|
||||
case GLFW_KEY_SPACE: return "<Keyboard>/Space";
|
||||
case GLFW_KEY_ESCAPE: return "<Keyboard>/Escape";
|
||||
case GLFW_KEY_ENTER: return "<Keyboard>/Enter";
|
||||
case GLFW_KEY_TAB: return "<Keyboard>/Tab";
|
||||
case GLFW_KEY_LEFT: return "<Keyboard>/Left";
|
||||
case GLFW_KEY_RIGHT: return "<Keyboard>/Right";
|
||||
case GLFW_KEY_UP: return "<Keyboard>/Up";
|
||||
case GLFW_KEY_DOWN: return "<Keyboard>/Down";
|
||||
case GLFW_KEY_LEFT_SHIFT: return "<Keyboard>/LeftShift";
|
||||
case GLFW_KEY_RIGHT_SHIFT: return "<Keyboard>/RightShift";
|
||||
case GLFW_KEY_LEFT_CONTROL: return "<Keyboard>/LeftControl";
|
||||
case GLFW_KEY_RIGHT_CONTROL: return "<Keyboard>/RightControl";
|
||||
case GLFW_KEY_LEFT_ALT: return "<Keyboard>/LeftAlt";
|
||||
case GLFW_KEY_RIGHT_ALT: return "<Keyboard>/RightAlt";
|
||||
case GLFW_KEY_BACKSPACE: return "<Keyboard>/Backspace";
|
||||
case GLFW_KEY_DELETE: return "<Keyboard>/Delete";
|
||||
case GLFW_KEY_INSERT: return "<Keyboard>/Insert";
|
||||
case GLFW_KEY_HOME: return "<Keyboard>/Home";
|
||||
case GLFW_KEY_END: return "<Keyboard>/End";
|
||||
case GLFW_KEY_PAGE_UP: return "<Keyboard>/PageUp";
|
||||
case GLFW_KEY_PAGE_DOWN: return "<Keyboard>/PageDown";
|
||||
case GLFW_KEY_CAPS_LOCK: return "<Keyboard>/CapsLock";
|
||||
case GLFW_KEY_MINUS: return "<Keyboard>/Minus";
|
||||
case GLFW_KEY_EQUAL: return "<Keyboard>/Equal";
|
||||
case GLFW_KEY_LEFT_BRACKET: return "<Keyboard>/LeftBracket";
|
||||
case GLFW_KEY_RIGHT_BRACKET: return "<Keyboard>/RightBracket";
|
||||
case GLFW_KEY_BACKSLASH: return "<Keyboard>/Backslash";
|
||||
case GLFW_KEY_SEMICOLON: return "<Keyboard>/Semicolon";
|
||||
case GLFW_KEY_APOSTROPHE: return "<Keyboard>/Quote";
|
||||
case GLFW_KEY_COMMA: return "<Keyboard>/Comma";
|
||||
case GLFW_KEY_PERIOD: return "<Keyboard>/Period";
|
||||
case GLFW_KEY_SLASH: return "<Keyboard>/Slash";
|
||||
case GLFW_KEY_GRAVE_ACCENT: return "<Keyboard>/Backquote";
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
class MetaCoreGlfwContext {
|
||||
public:
|
||||
static bool Retain() {
|
||||
@ -101,6 +145,8 @@ void MetaCoreGlfwKeyCallback(GLFWwindow* window, int key, int, int action, int m
|
||||
if (auto* owner = MetaCoreGetWindowImpl(window); owner != nullptr) {
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT || action == GLFW_RELEASE) {
|
||||
owner->Input.AddKeyEvent({key, action != GLFW_RELEASE, modifiers});
|
||||
const std::string path = MetaCoreGlfwKeyControlPath(key);
|
||||
if (!path.empty()) owner->Input.SetControlValue(path, action == GLFW_RELEASE ? 0.0F : 1.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,6 +280,27 @@ void MetaCoreWindow::BeginFrame() {
|
||||
double cursorY = 0.0;
|
||||
glfwGetCursorPos(window, &cursorX, &cursorY);
|
||||
Impl_->Input.SetCursorPosition(glm::vec2(static_cast<float>(cursorX), static_cast<float>(cursorY)));
|
||||
Impl_->Input.SetControlValue("<Mouse>/delta/x", Impl_->Input.GetCursorDelta().x);
|
||||
Impl_->Input.SetControlValue("<Mouse>/delta/y", Impl_->Input.GetCursorDelta().y);
|
||||
Impl_->Input.SetControlValue("<Mouse>/wheel", Impl_->Input.GetMouseWheelDelta());
|
||||
Impl_->Input.SetControlValue("<Mouse>/leftButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Left) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetControlValue("<Mouse>/rightButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Right) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetControlValue("<Mouse>/middleButton", Impl_->Input.IsMouseButtonDown(MetaCoreMouseButton::Middle) ? 1.0F : 0.0F);
|
||||
Impl_->Input.SetWindowFocused(glfwGetWindowAttrib(window, GLFW_FOCUSED) == GLFW_TRUE);
|
||||
|
||||
MetaCoreGamepadState gamepad;
|
||||
GLFWgamepadstate glfwGamepad{};
|
||||
gamepad.Connected = glfwJoystickIsGamepad(GLFW_JOYSTICK_1) == GLFW_TRUE &&
|
||||
glfwGetGamepadState(GLFW_JOYSTICK_1, &glfwGamepad) == GLFW_TRUE;
|
||||
if (gamepad.Connected) {
|
||||
for (std::size_t i = 0; i < gamepad.Axes.size(); ++i) gamepad.Axes[i] = glfwGamepad.axes[i];
|
||||
for (std::size_t i = 0; i < gamepad.Buttons.size(); ++i) gamepad.Buttons[i] = glfwGamepad.buttons[i] == GLFW_PRESS;
|
||||
}
|
||||
Impl_->Input.SetGamepadState(gamepad);
|
||||
static constexpr std::array<const char*, 6> axisPaths{"leftStick/x", "leftStick/y", "rightStick/x", "rightStick/y", "leftTrigger", "rightTrigger"};
|
||||
for (std::size_t i = 0; i < axisPaths.size(); ++i) Impl_->Input.SetControlValue(std::string("<Gamepad>/") + axisPaths[i], gamepad.Connected ? gamepad.Axes[i] : 0.0F);
|
||||
static constexpr std::array<const char*, 15> buttonPaths{"buttonSouth", "buttonEast", "buttonWest", "buttonNorth", "leftBumper", "rightBumper", "Back", "Start", "Guide", "leftStickPress", "rightStickPress", "dpadUp", "dpadRight", "dpadDown", "dpadLeft"};
|
||||
for (std::size_t i = 0; i < buttonPaths.size(); ++i) Impl_->Input.SetControlValue(std::string("<Gamepad>/") + buttonPaths[i], gamepad.Connected && gamepad.Buttons[i] ? 1.0F : 0.0F);
|
||||
|
||||
Impl_->CloseRequested = Impl_->CloseRequested || glfwWindowShouldClose(window) == GLFW_TRUE;
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace MetaCore {
|
||||
@ -38,6 +40,25 @@ struct MetaCoreKeyEvent {
|
||||
int Modifiers = 0;
|
||||
};
|
||||
|
||||
enum class MetaCoreInputEventType : std::uint8_t {
|
||||
Control,
|
||||
Text,
|
||||
Focus
|
||||
};
|
||||
|
||||
struct MetaCoreInputEvent {
|
||||
MetaCoreInputEventType Type = MetaCoreInputEventType::Control;
|
||||
std::string ControlPath{};
|
||||
float Value = 0.0F;
|
||||
char32_t CodePoint = 0;
|
||||
};
|
||||
|
||||
struct MetaCoreGamepadState {
|
||||
bool Connected = false;
|
||||
std::array<float, 6> Axes{};
|
||||
std::array<bool, 15> Buttons{};
|
||||
};
|
||||
|
||||
class MetaCoreInput {
|
||||
public:
|
||||
void BeginFrame();
|
||||
@ -47,6 +68,9 @@ public:
|
||||
void AddMouseWheelDelta(float delta);
|
||||
void AddKeyEvent(MetaCoreKeyEvent event);
|
||||
void AddTextInput(char32_t codePoint);
|
||||
void SetControlValue(std::string controlPath, float value);
|
||||
void SetWindowFocused(bool focused);
|
||||
void SetGamepadState(const MetaCoreGamepadState& state);
|
||||
|
||||
[[nodiscard]] bool IsKeyDown(MetaCoreInputKey key) const;
|
||||
[[nodiscard]] bool WasKeyPressed(MetaCoreInputKey key) const;
|
||||
@ -58,6 +82,14 @@ public:
|
||||
[[nodiscard]] const glm::vec2& GetCursorDelta() const;
|
||||
[[nodiscard]] std::vector<MetaCoreKeyEvent> ConsumeKeyEvents();
|
||||
[[nodiscard]] std::vector<char32_t> ConsumeTextInput();
|
||||
[[nodiscard]] const std::vector<MetaCoreKeyEvent>& GetKeyEvents() const;
|
||||
[[nodiscard]] const std::vector<char32_t>& GetTextInput() const;
|
||||
[[nodiscard]] const std::vector<MetaCoreInputEvent>& GetEvents() const;
|
||||
[[nodiscard]] float GetControlValue(std::string_view controlPath) const;
|
||||
[[nodiscard]] bool WasControlPressed(std::string_view controlPath) const;
|
||||
[[nodiscard]] bool WasControlReleased(std::string_view controlPath) const;
|
||||
[[nodiscard]] bool IsWindowFocused() const;
|
||||
[[nodiscard]] const MetaCoreGamepadState& GetGamepadState() const;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t KeyCount_ = static_cast<std::size_t>(MetaCoreInputKey::Count);
|
||||
@ -73,6 +105,10 @@ private:
|
||||
float MouseWheelDelta_ = 0.0F;
|
||||
std::vector<MetaCoreKeyEvent> KeyEvents_{};
|
||||
std::vector<char32_t> TextInput_{};
|
||||
std::vector<MetaCoreInputEvent> Events_{};
|
||||
std::unordered_map<std::string, float> ControlValues_{};
|
||||
bool WindowFocused_ = true;
|
||||
MetaCoreGamepadState Gamepad_{};
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -122,6 +122,10 @@ void MetaCoreEditorViewportRenderer::RenderAll() {
|
||||
FilamentSceneBridge_.RenderAll();
|
||||
}
|
||||
|
||||
bool MetaCoreEditorViewportRenderer::RequestPick(std::uint32_t x, std::uint32_t y, MetaCoreFilamentSceneBridge::PickCallback callback) {
|
||||
return FilamentSceneBridge_.RequestPick(x, y, std::move(callback));
|
||||
}
|
||||
|
||||
|
||||
void MetaCoreEditorViewportRenderer::SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||||
FilamentSceneBridge_.SetProjectRootPath(projectRootPath);
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/EntityManager.h>
|
||||
#include <utils/NameComponentManager.h>
|
||||
#include <math/mat4.h>
|
||||
#include "MetaCoreScene/MetaCoreTransformUtils.h"
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
@ -621,7 +622,10 @@ public:
|
||||
GLTextureId_ = 0;
|
||||
}
|
||||
// 销毁 View
|
||||
Engine_->flushAndWait();
|
||||
PickCallbacks_.clear();
|
||||
Engine_->destroy(View_);
|
||||
View_ = nullptr;
|
||||
if (EnvironmentView_) {
|
||||
Engine_->destroy(EnvironmentView_);
|
||||
EnvironmentView_ = nullptr;
|
||||
@ -1898,9 +1902,11 @@ public:
|
||||
|
||||
if (mappedAncestor) {
|
||||
shouldBeInScene = isObjectVisible(entityToObjectId[mappedAncestor]);
|
||||
entityToObjectId[entity] = entityToObjectId[mappedAncestor];
|
||||
} else {
|
||||
// 回退保护:如果没有找到,则与该资产对应的顶级根宿主保持状态一致
|
||||
shouldBeInScene = isObjectVisible(hostRootId);
|
||||
entityToObjectId[entity] = hostRootId;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1918,6 +1924,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
EntityToObjectId_ = std::move(entityToObjectId);
|
||||
|
||||
// 3. 控制 ObjectToFilamentEntity_ 中所有非资产实体(如 pivotEntity 辅助实体)的可见性
|
||||
for (const auto& [objectId, assetEntity] : ObjectToFilamentEntity_) {
|
||||
@ -2429,6 +2436,43 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestPick(std::uint32_t x, std::uint32_t y, MetaCoreFilamentSceneBridge::PickCallback callback) {
|
||||
if (View_ == nullptr || Camera_ == nullptr || !callback) return false;
|
||||
const auto viewport = View_->getViewport();
|
||||
if (x >= viewport.width || y >= viewport.height) return false;
|
||||
View_->setTransparentPickingEnabled(true);
|
||||
const std::uint64_t callbackId = NextPickCallbackId_++;
|
||||
PickCallbacks_.emplace(callbackId, PendingPick{
|
||||
std::move(callback), Camera_->getProjectionMatrix(), Camera_->getModelMatrix(), viewport.width, viewport.height
|
||||
});
|
||||
View_->pick(x, y, [this, callbackId](const filament::View::PickingQueryResult& result) {
|
||||
const auto callbackIterator = PickCallbacks_.find(callbackId);
|
||||
if (callbackIterator == PickCallbacks_.end()) return;
|
||||
auto pending = std::move(callbackIterator->second);
|
||||
PickCallbacks_.erase(callbackIterator);
|
||||
MetaCoreFilamentSceneBridge::PickResult converted;
|
||||
const auto iterator = EntityToObjectId_.find(result.renderable);
|
||||
if (iterator != EntityToObjectId_.end()) converted.ObjectId = iterator->second;
|
||||
converted.Depth = result.depth;
|
||||
converted.FragmentCoordinates = {result.fragCoords.x, result.fragCoords.y, result.fragCoords.z};
|
||||
const filament::math::double4 clip{
|
||||
(static_cast<double>(result.fragCoords.x) / static_cast<double>(pending.ViewportWidth)) * 2.0 - 1.0,
|
||||
(static_cast<double>(result.fragCoords.y) / static_cast<double>(pending.ViewportHeight)) * 2.0 - 1.0,
|
||||
static_cast<double>(result.fragCoords.z) * 2.0 - 1.0,
|
||||
1.0
|
||||
};
|
||||
filament::math::double4 viewPosition = inverse(pending.Projection) * clip;
|
||||
if (std::abs(viewPosition.w) > std::numeric_limits<double>::epsilon()) viewPosition /= viewPosition.w;
|
||||
filament::math::double4 worldPosition = pending.Model * viewPosition;
|
||||
if (std::abs(worldPosition.w) > std::numeric_limits<double>::epsilon()) worldPosition /= worldPosition.w;
|
||||
converted.WorldPosition = {
|
||||
static_cast<float>(worldPosition.x), static_cast<float>(worldPosition.y), static_cast<float>(worldPosition.z)
|
||||
};
|
||||
pending.Callback(converted);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t GetGLTextureId() const {
|
||||
return GLTextureId_;
|
||||
}
|
||||
@ -3524,6 +3568,16 @@ private:
|
||||
std::unordered_map<MetaCoreId, std::string> LastMaterialSyncDebugByObject_{};
|
||||
std::unordered_map<MetaCoreId, glm::mat4> ObjectWorldMatrices_{};
|
||||
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
|
||||
std::unordered_map<utils::Entity, MetaCoreId, utils::Entity::Hasher> EntityToObjectId_;
|
||||
struct PendingPick {
|
||||
MetaCoreFilamentSceneBridge::PickCallback Callback{};
|
||||
filament::math::mat4 Projection{};
|
||||
filament::math::mat4 Model{};
|
||||
std::uint32_t ViewportWidth = 1;
|
||||
std::uint32_t ViewportHeight = 1;
|
||||
};
|
||||
std::unordered_map<std::uint64_t, PendingPick> PickCallbacks_;
|
||||
std::uint64_t NextPickCallbackId_ = 1;
|
||||
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
|
||||
std::unordered_map<MetaCoreId, PendingResourceLoad> PendingResourceLoads_;
|
||||
std::unordered_map<MetaCoreId, MetaCoreAssetGuid> HostRootSourceModelGuids_;
|
||||
@ -3609,6 +3663,10 @@ void MetaCoreFilamentSceneBridge::RenderAll() {
|
||||
Impl_->RenderAll();
|
||||
}
|
||||
|
||||
bool MetaCoreFilamentSceneBridge::RequestPick(std::uint32_t x, std::uint32_t y, PickCallback callback) {
|
||||
return Impl_->RequestPick(x, y, std::move(callback));
|
||||
}
|
||||
|
||||
uint32_t MetaCoreFilamentSceneBridge::GetGLTextureId() const {
|
||||
return Impl_->GetGLTextureId();
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ public:
|
||||
void RenderSceneToViewport(MetaCoreScene& scene, const MetaCoreSceneView& sceneView, bool useScenePrimaryCamera = false);
|
||||
void RenderSnapshotToViewport(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene, const MetaCoreSceneView& sceneView);
|
||||
void RenderAll();
|
||||
[[nodiscard]] bool RequestPick(std::uint32_t x, std::uint32_t y, MetaCoreFilamentSceneBridge::PickCallback callback);
|
||||
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
|
||||
void SetEditorGridVisible(bool visible);
|
||||
void SetRuntimeOverlayRenderCallback(MetaCoreFilamentSceneBridge::RuntimeOverlayRenderCallback callback);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
@ -27,6 +28,13 @@ struct MetaCoreSceneRenderSyncSnapshot;
|
||||
*/
|
||||
class MetaCoreFilamentSceneBridge {
|
||||
public:
|
||||
struct PickResult {
|
||||
MetaCoreId ObjectId = 0;
|
||||
float Depth = 0.0F;
|
||||
glm::vec3 FragmentCoordinates{0.0F};
|
||||
glm::vec3 WorldPosition{0.0F};
|
||||
};
|
||||
using PickCallback = std::function<void(const PickResult&)>;
|
||||
using RuntimeOverlayRenderCallback = std::function<void(
|
||||
filament::Engine& engine,
|
||||
filament::Renderer& renderer,
|
||||
@ -47,6 +55,7 @@ public:
|
||||
void SetEditorGridVisible(bool visible);
|
||||
void SetRuntimeOverlayRenderCallback(RuntimeOverlayRenderCallback callback);
|
||||
void RenderAll();
|
||||
[[nodiscard]] bool RequestPick(std::uint32_t x, std::uint32_t y, PickCallback callback);
|
||||
[[nodiscard]] uint32_t GetGLTextureId() const;
|
||||
[[nodiscard]] void* GetFilamentTexturePointer() const;
|
||||
|
||||
|
||||
@ -119,6 +119,9 @@ struct MetaCoreRuntimeProjectDocument {
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path UiManifestPath{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path InputMapPath{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
|
||||
78
Source/MetaCoreRuntimeInput/Private/MetaCoreInputMap.cpp
Normal file
78
Source/MetaCoreRuntimeInput/Private/MetaCoreInputMap.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
std::vector<MetaCoreInputMapIssue> MetaCoreValidateInputMap(const MetaCoreInputMapDocument& document) {
|
||||
std::vector<MetaCoreInputMapIssue> issues;
|
||||
std::unordered_set<std::string> actionIds, axisIds, bindingIds, contextIds;
|
||||
for (const auto& value : document.Actions) {
|
||||
if (value.Id.empty() || !actionIds.insert(value.Id).second) issues.push_back({true, value.Id, "Action ID 为空或重复"});
|
||||
}
|
||||
for (const auto& value : document.Axes) {
|
||||
if (value.Id.empty() || !axisIds.insert(value.Id).second) issues.push_back({true, value.Id, "Axis ID 为空或重复"});
|
||||
if (value.DeadZone < 0.0F || value.DeadZone >= 1.0F || value.Sensitivity < 0.0F || value.Minimum >= value.Maximum)
|
||||
issues.push_back({true, value.Id, "Axis 死区、灵敏度或取值范围无效"});
|
||||
}
|
||||
for (const auto& value : document.Bindings) {
|
||||
if (value.BindingId.empty() || !bindingIds.insert(value.BindingId).second) issues.push_back({true, value.BindingId, "Binding ID 为空或重复"});
|
||||
if (value.ControlPath.empty() || value.ControlPath.front() != '<' || value.ControlPath.find("/", 1) == std::string::npos) issues.push_back({true, value.BindingId, "控制路径无效"});
|
||||
const bool targetExists = value.Target == MetaCoreInputBindingTarget::Action ? actionIds.contains(value.TargetId) : axisIds.contains(value.TargetId);
|
||||
if (!targetExists) issues.push_back({true, value.BindingId, "Binding 目标不存在"});
|
||||
}
|
||||
for (const auto& value : document.Contexts) {
|
||||
if (value.Id.empty() || !contextIds.insert(value.Id).second) issues.push_back({true, value.Id, "Context ID 为空或重复"});
|
||||
for (const auto& binding : value.BindingIds) if (!bindingIds.contains(binding)) issues.push_back({true, value.Id, "Context 引用了不存在的 Binding: " + binding});
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
bool MetaCoreWriteInputMap(const std::filesystem::path& path, const MetaCoreInputMapDocument& document, const MetaCoreTypeRegistry& registry) {
|
||||
const auto bytes = MetaCoreSerializeToBytes(document, registry);
|
||||
if (!bytes) return false;
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(path.parent_path(), error);
|
||||
std::ofstream output(path, std::ios::binary | std::ios::trunc);
|
||||
if (!output) return false;
|
||||
output.write(reinterpret_cast<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size()));
|
||||
return output.good();
|
||||
}
|
||||
|
||||
std::optional<MetaCoreInputMapDocument> MetaCoreReadInputMap(const std::filesystem::path& path, const MetaCoreTypeRegistry& registry) {
|
||||
std::ifstream input(path, std::ios::binary | std::ios::ate);
|
||||
if (!input) return std::nullopt;
|
||||
const auto size = static_cast<std::size_t>(input.tellg()); input.seekg(0);
|
||||
std::vector<std::byte> bytes(size);
|
||||
if (size && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size))) return std::nullopt;
|
||||
MetaCoreInputMapDocument result;
|
||||
if (!MetaCoreDeserializeFromBytes(std::span<const std::byte>(bytes.data(), bytes.size()), result, registry)) return std::nullopt;
|
||||
return result;
|
||||
}
|
||||
|
||||
MetaCoreInputMapDocument MetaCoreBuildDefaultInputMap() {
|
||||
MetaCoreInputMapDocument map;
|
||||
map.Actions = {{"Interact", "Interact"}, {"Cancel", "Cancel"}};
|
||||
map.Axes = {{"MoveX", "Move X", 0.15F, 1.0F, -1.0F, 1.0F}, {"MoveY", "Move Y", 0.15F, 1.0F, -1.0F, 1.0F}, {"LookX", "Look X", 0.0F, 1.0F, -1.0F, 1.0F}, {"LookY", "Look Y", 0.0F, 1.0F, -1.0F, 1.0F}};
|
||||
map.Bindings = {
|
||||
{"game.interact.mouse", "Interact", MetaCoreInputBindingTarget::Action, "<Mouse>/leftButton", 1.0F, {}},
|
||||
{"game.cancel.escape", "Cancel", MetaCoreInputBindingTarget::Action, "<Keyboard>/Escape", 1.0F, {}},
|
||||
{"game.move.left", "MoveX", MetaCoreInputBindingTarget::Axis, "<Keyboard>/A", -1.0F, {}},
|
||||
{"game.move.right", "MoveX", MetaCoreInputBindingTarget::Axis, "<Keyboard>/D", 1.0F, {}},
|
||||
{"game.move.back", "MoveY", MetaCoreInputBindingTarget::Axis, "<Keyboard>/S", -1.0F, {}},
|
||||
{"game.move.forward", "MoveY", MetaCoreInputBindingTarget::Axis, "<Keyboard>/W", 1.0F, {}},
|
||||
{"game.look.x", "LookX", MetaCoreInputBindingTarget::Axis, "<Mouse>/delta/x", 1.0F, {}},
|
||||
{"game.look.y", "LookY", MetaCoreInputBindingTarget::Axis, "<Mouse>/delta/y", 1.0F, {}},
|
||||
{"game.pad.move.x", "MoveX", MetaCoreInputBindingTarget::Axis, "<Gamepad>/leftStick/x", 1.0F, {}},
|
||||
{"game.pad.move.y", "MoveY", MetaCoreInputBindingTarget::Axis, "<Gamepad>/leftStick/y", 1.0F, {}}
|
||||
};
|
||||
MetaCoreInputContextDefinition gameplay{"Gameplay", 0, true, false, {}};
|
||||
for (const auto& binding : map.Bindings) gameplay.BindingIds.push_back(binding.BindingId);
|
||||
map.Contexts.push_back(std::move(gameplay));
|
||||
return map;
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
124
Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInput.cpp
Normal file
124
Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInput.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreRuntimeInput::Impl {
|
||||
public:
|
||||
MetaCoreInputMapDocument Map{};
|
||||
std::unordered_map<std::string, MetaCoreInputActionState> Actions{};
|
||||
std::unordered_map<std::string, float> Axes{};
|
||||
std::unordered_map<std::string, bool> Contexts{};
|
||||
std::vector<std::string> ContextStack{};
|
||||
std::unordered_map<std::string, std::string> Overrides{};
|
||||
std::unordered_map<std::uint64_t, ActionCallback> Subscribers{};
|
||||
std::vector<std::string> Warnings{};
|
||||
std::uint64_t NextSubscription = 1;
|
||||
|
||||
void Emit(std::string id, MetaCoreInputActionPhase phase) const {
|
||||
MetaCoreInputActionEvent event{std::move(id), phase};
|
||||
for (const auto& [_, callback] : Subscribers) if (callback) callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
MetaCoreRuntimeInput::MetaCoreRuntimeInput() : Impl_(std::make_unique<Impl>()) {}
|
||||
MetaCoreRuntimeInput::~MetaCoreRuntimeInput() = default;
|
||||
void MetaCoreRuntimeInput::SetInputMap(MetaCoreInputMapDocument document) {
|
||||
Impl_->Map = std::move(document); Impl_->Actions.clear(); Impl_->Axes.clear(); Impl_->Contexts.clear();
|
||||
for (const auto& action : Impl_->Map.Actions) Impl_->Actions[action.Id] = {};
|
||||
for (const auto& axis : Impl_->Map.Axes) Impl_->Axes[axis.Id] = 0.0F;
|
||||
for (const auto& context : Impl_->Map.Contexts) Impl_->Contexts[context.Id] = context.EnabledByDefault;
|
||||
}
|
||||
const MetaCoreInputMapDocument& MetaCoreRuntimeInput::GetInputMap() const { return Impl_->Map; }
|
||||
|
||||
void MetaCoreRuntimeInput::Update(const MetaCoreInput& input, const MetaCoreInputConsumption& consumed) {
|
||||
if (!input.IsWindowFocused()) { CancelAll(); return; }
|
||||
std::unordered_map<std::string, bool> nextActions;
|
||||
std::unordered_map<std::string, float> nextAxes;
|
||||
std::unordered_set<std::string> consumedPaths;
|
||||
std::vector<const MetaCoreInputContextDefinition*> contexts;
|
||||
for (const auto& context : Impl_->Map.Contexts) if (Impl_->Contexts[context.Id]) contexts.push_back(&context);
|
||||
std::stable_sort(contexts.begin(), contexts.end(), [](auto* a, auto* b) { return a->Priority > b->Priority; });
|
||||
const auto bindingById = [&](std::string_view id) -> const MetaCoreInputBindingDefinition* {
|
||||
const auto it = std::find_if(Impl_->Map.Bindings.begin(), Impl_->Map.Bindings.end(), [&](const auto& value) { return value.BindingId == id; });
|
||||
return it == Impl_->Map.Bindings.end() ? nullptr : &*it;
|
||||
};
|
||||
for (const auto* context : contexts) {
|
||||
for (const auto& bindingId : context->BindingIds) {
|
||||
const auto* binding = bindingById(bindingId); if (!binding) continue;
|
||||
const auto overrideIt = Impl_->Overrides.find(binding->BindingId);
|
||||
const std::string& path = overrideIt == Impl_->Overrides.end() ? binding->ControlPath : overrideIt->second;
|
||||
if (consumedPaths.contains(path)) continue;
|
||||
if ((consumed.Keyboard && path.starts_with("<Keyboard>")) || (consumed.Mouse && path.starts_with("<Mouse>")) || (consumed.Gamepad && path.starts_with("<Gamepad>"))) continue;
|
||||
bool modifiersHeld = true;
|
||||
for (const auto& modifier : binding->Modifiers) modifiersHeld = modifiersHeld && std::abs(input.GetControlValue(modifier)) > 0.5F;
|
||||
const float value = modifiersHeld ? input.GetControlValue(path) * binding->Scale : 0.0F;
|
||||
if (binding->Target == MetaCoreInputBindingTarget::Action) nextActions[binding->TargetId] = nextActions[binding->TargetId] || std::abs(value) > 0.5F;
|
||||
else nextAxes[binding->TargetId] += value;
|
||||
if (context->ConsumeLowerPriority && std::abs(value) > 0.0001F) consumedPaths.insert(path);
|
||||
}
|
||||
}
|
||||
for (const auto& definition : Impl_->Map.Actions) {
|
||||
auto& state = Impl_->Actions[definition.Id]; const bool wasHeld = state.Held; const bool held = nextActions[definition.Id];
|
||||
state = {!wasHeld && held, held, wasHeld && !held};
|
||||
if (state.Pressed) Impl_->Emit(definition.Id, MetaCoreInputActionPhase::Started);
|
||||
if (held) Impl_->Emit(definition.Id, MetaCoreInputActionPhase::Performed);
|
||||
if (state.Released) Impl_->Emit(definition.Id, MetaCoreInputActionPhase::Canceled);
|
||||
}
|
||||
for (const auto& definition : Impl_->Map.Axes) {
|
||||
float value = std::clamp(nextAxes[definition.Id], -1.0F, 1.0F);
|
||||
if (std::abs(value) < definition.DeadZone) value = 0.0F;
|
||||
Impl_->Axes[definition.Id] = std::clamp(value * definition.Sensitivity, definition.Minimum, definition.Maximum);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeInput::CancelAll() {
|
||||
for (auto& [id, state] : Impl_->Actions) { if (state.Held) Impl_->Emit(id, MetaCoreInputActionPhase::Canceled); state = {false, false, state.Held}; }
|
||||
for (auto& [_, value] : Impl_->Axes) value = 0.0F;
|
||||
}
|
||||
MetaCoreInputActionState MetaCoreRuntimeInput::GetAction(std::string_view id) const { const auto it = Impl_->Actions.find(std::string(id)); return it == Impl_->Actions.end() ? MetaCoreInputActionState{} : it->second; }
|
||||
float MetaCoreRuntimeInput::GetAxis(std::string_view id) const { const auto it = Impl_->Axes.find(std::string(id)); return it == Impl_->Axes.end() ? 0.0F : it->second; }
|
||||
bool MetaCoreRuntimeInput::SetContextEnabled(std::string_view id, bool enabled) { auto it = Impl_->Contexts.find(std::string(id)); if (it == Impl_->Contexts.end()) return false; it->second = enabled; return true; }
|
||||
bool MetaCoreRuntimeInput::PushContext(std::string_view id) { if (!SetContextEnabled(id, true)) return false; Impl_->ContextStack.emplace_back(id); return true; }
|
||||
bool MetaCoreRuntimeInput::PopContext(std::string_view id) { auto it = std::find(Impl_->ContextStack.rbegin(), Impl_->ContextStack.rend(), id); if (it == Impl_->ContextStack.rend()) return false; Impl_->ContextStack.erase(std::next(it).base()); return SetContextEnabled(id, false); }
|
||||
std::uint64_t MetaCoreRuntimeInput::Subscribe(ActionCallback callback) { const auto id = Impl_->NextSubscription++; Impl_->Subscribers.emplace(id, std::move(callback)); return id; }
|
||||
void MetaCoreRuntimeInput::Unsubscribe(std::uint64_t id) { Impl_->Subscribers.erase(id); }
|
||||
bool MetaCoreRuntimeInput::Rebind(std::string_view id, std::string path) { if (path.empty() || path.front() != '<') return false; const auto it = std::find_if(Impl_->Map.Bindings.begin(), Impl_->Map.Bindings.end(), [&](const auto& b){ return b.BindingId == id; }); if (it == Impl_->Map.Bindings.end()) return false; Impl_->Overrides[std::string(id)] = std::move(path); return true; }
|
||||
bool MetaCoreRuntimeInput::ResetBinding(std::string_view id) { return Impl_->Overrides.erase(std::string(id)) > 0; }
|
||||
void MetaCoreRuntimeInput::ResetAllBindings() { Impl_->Overrides.clear(); }
|
||||
bool MetaCoreRuntimeInput::LoadOverrides(const std::filesystem::path& path) {
|
||||
Impl_->Warnings.clear(); std::ifstream input(path); if (!input) return true;
|
||||
try { nlohmann::json json; input >> json; for (auto it = json.begin(); it != json.end(); ++it) if (!Rebind(it.key(), it.value().get<std::string>())) Impl_->Warnings.push_back("忽略无效输入覆盖: " + it.key()); }
|
||||
catch (const std::exception& error) { Impl_->Warnings.push_back(std::string("输入覆盖损坏,已使用默认绑定: ") + error.what()); Impl_->Overrides.clear(); return false; }
|
||||
return true;
|
||||
}
|
||||
bool MetaCoreRuntimeInput::SaveOverrides(const std::filesystem::path& path) const {
|
||||
std::error_code error; std::filesystem::create_directories(path.parent_path(), error); const auto temporary = path.string() + ".tmp";
|
||||
nlohmann::json json = Impl_->Overrides; { std::ofstream output(temporary, std::ios::trunc); if (!output) return false; output << json.dump(2); if (!output.good()) return false; }
|
||||
std::filesystem::rename(temporary, path, error); if (!error) return true;
|
||||
std::filesystem::remove(path, error); error.clear(); std::filesystem::rename(temporary, path, error); return !error;
|
||||
}
|
||||
const std::vector<std::string>& MetaCoreRuntimeInput::GetWarnings() const { return Impl_->Warnings; }
|
||||
|
||||
std::filesystem::path MetaCoreGetInputOverridePath(std::string_view projectName) {
|
||||
std::string safe(projectName); for (char& value : safe) if (!std::isalnum(static_cast<unsigned char>(value)) && value != '-' && value != '_') value = '_';
|
||||
#if defined(_WIN32)
|
||||
const char* root = std::getenv("LOCALAPPDATA");
|
||||
return std::filesystem::path(root ? root : ".") / "MetaCore" / safe / "InputBindings.json";
|
||||
#else
|
||||
const char* xdg = std::getenv("XDG_DATA_HOME"); const char* home = std::getenv("HOME");
|
||||
const std::filesystem::path root = xdg ? xdg : (home ? std::filesystem::path(home) / ".local" / "share" : std::filesystem::path("."));
|
||||
return root / "MetaCore" / safe / "InputBindings.json";
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,140 @@
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
||||
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreRuntimeInteraction::Impl {
|
||||
public:
|
||||
enum class QueryKind { Hover, Down, Up };
|
||||
struct Completed { std::uint64_t Sequence; QueryKind Kind; glm::vec2 Position; MetaCoreId Object; glm::vec3 World; std::uint64_t SceneRevision; std::uint64_t InteractionRevision; };
|
||||
struct AsyncState {
|
||||
std::mutex Mutex{};
|
||||
std::vector<Completed> CompletedQueries{};
|
||||
bool Alive = true;
|
||||
};
|
||||
EventCallback Callback{};
|
||||
std::shared_ptr<AsyncState> Async = std::make_shared<AsyncState>();
|
||||
std::uint64_t NextSequence = 1;
|
||||
std::uint64_t LatestHoverSequence = 0;
|
||||
std::uint64_t InteractionRevision = 1;
|
||||
std::deque<std::uint64_t> PointerSequenceOrder{};
|
||||
std::unordered_map<std::uint64_t, Completed> PointerResults{};
|
||||
MetaCoreId Hovered = 0;
|
||||
MetaCoreId Captured = 0;
|
||||
glm::vec2 PressPosition{0.0F};
|
||||
glm::vec2 PreviousPosition{0.0F};
|
||||
bool Dragging = false;
|
||||
|
||||
void Emit(MetaCorePointerEventType type, MetaCoreId target, glm::vec2 position, glm::vec3 world = {}, std::uint32_t button = 0) {
|
||||
if (Callback && target != 0) Callback({type, target, button, position, position - PreviousPosition, world});
|
||||
}
|
||||
bool IsInteractive(MetaCoreScene& scene, MetaCoreId id, std::uint32_t mask) const {
|
||||
auto object = scene.FindGameObject(id);
|
||||
return object && object.HasComponent<MetaCoreInteractableComponent>() && object.GetComponent<MetaCoreInteractableComponent>().Enabled &&
|
||||
object.HasComponent<MetaCoreMeshRendererComponent>() && object.GetComponent<MetaCoreMeshRendererComponent>().Visible &&
|
||||
(object.GetComponent<MetaCoreInteractableComponent>().EventMask & mask) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
MetaCoreRuntimeInteraction::MetaCoreRuntimeInteraction() : Impl_(std::make_unique<Impl>()) {}
|
||||
MetaCoreRuntimeInteraction::~MetaCoreRuntimeInteraction() {
|
||||
std::scoped_lock lock(Impl_->Async->Mutex);
|
||||
Impl_->Async->Alive = false;
|
||||
Impl_->Async->CompletedQueries.clear();
|
||||
}
|
||||
void MetaCoreRuntimeInteraction::SetEventCallback(EventCallback callback) { Impl_->Callback = std::move(callback); }
|
||||
void MetaCoreRuntimeInteraction::Cancel() {
|
||||
if (Impl_->Captured) Impl_->Emit(MetaCorePointerEventType::Cancel, Impl_->Captured, Impl_->PreviousPosition);
|
||||
if (Impl_->Hovered) Impl_->Emit(MetaCorePointerEventType::Leave, Impl_->Hovered, Impl_->PreviousPosition);
|
||||
Impl_->Captured = Impl_->Hovered = 0; Impl_->Dragging = false;
|
||||
++Impl_->InteractionRevision;
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) {
|
||||
Update(scene, [&renderer](std::uint32_t x, std::uint32_t y, PickCallback callback) {
|
||||
return renderer.RequestPick(x, y, [callback = std::move(callback)](const MetaCoreFilamentSceneBridge::PickResult& result) {
|
||||
callback({result.ObjectId, result.WorldPosition});
|
||||
});
|
||||
}, input, viewport, mouseConsumed);
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) {
|
||||
std::vector<Impl::Completed> completed;
|
||||
{ std::scoped_lock lock(Impl_->Async->Mutex); completed.swap(Impl_->Async->CompletedQueries); }
|
||||
std::sort(completed.begin(), completed.end(), [](const auto& a, const auto& b){ return a.Sequence < b.Sequence; });
|
||||
const auto processResult = [&](const Impl::Completed& result) {
|
||||
if (result.SceneRevision != scene.GetRevision() || result.InteractionRevision != Impl_->InteractionRevision) return;
|
||||
if (result.Kind == Impl::QueryKind::Hover) {
|
||||
if (result.Sequence != Impl_->LatestHoverSequence) return;
|
||||
const MetaCoreId next = Impl_->IsInteractive(scene, result.Object, MetaCoreInteraction_Hover) ? result.Object : 0;
|
||||
if (next != Impl_->Hovered) { if (Impl_->Hovered) Impl_->Emit(MetaCorePointerEventType::Leave, Impl_->Hovered, result.Position); Impl_->Hovered = next; if (next) Impl_->Emit(MetaCorePointerEventType::Enter, next, result.Position, result.World); }
|
||||
else if (next) Impl_->Emit(MetaCorePointerEventType::Move, next, result.Position, result.World);
|
||||
} else if (result.Kind == Impl::QueryKind::Down) {
|
||||
if (Impl_->IsInteractive(scene, result.Object, MetaCoreInteraction_Click | MetaCoreInteraction_Drag)) {
|
||||
Impl_->Captured = result.Object; Impl_->PressPosition = result.Position; Impl_->Dragging = false;
|
||||
Impl_->Emit(MetaCorePointerEventType::Down, result.Object, result.Position, result.World);
|
||||
}
|
||||
} else if (result.Kind == Impl::QueryKind::Up && Impl_->Captured) {
|
||||
const MetaCoreId target = Impl_->Captured;
|
||||
Impl_->Emit(MetaCorePointerEventType::Up, target, result.Position, result.World);
|
||||
if (Impl_->Dragging) Impl_->Emit(MetaCorePointerEventType::DragEnd, target, result.Position, result.World);
|
||||
else if (result.Object == target && Impl_->IsInteractive(scene, target, MetaCoreInteraction_Click)) Impl_->Emit(MetaCorePointerEventType::Click, target, result.Position, result.World);
|
||||
Impl_->Captured = 0; Impl_->Dragging = false;
|
||||
}
|
||||
};
|
||||
for (const auto& result : completed) {
|
||||
if (result.Kind == Impl::QueryKind::Hover) processResult(result);
|
||||
else Impl_->PointerResults.insert_or_assign(result.Sequence, result);
|
||||
}
|
||||
while (!Impl_->PointerSequenceOrder.empty()) {
|
||||
const auto iterator = Impl_->PointerResults.find(Impl_->PointerSequenceOrder.front());
|
||||
if (iterator == Impl_->PointerResults.end()) break;
|
||||
processResult(iterator->second);
|
||||
Impl_->PointerResults.erase(iterator);
|
||||
Impl_->PointerSequenceOrder.pop_front();
|
||||
}
|
||||
const glm::vec2 cursor = input.GetCursorPosition();
|
||||
if (Impl_->Captured && !Impl_->IsInteractive(scene, Impl_->Captured, MetaCoreInteraction_Click | MetaCoreInteraction_Drag)) Cancel();
|
||||
if (!input.IsWindowFocused() || mouseConsumed || cursor.x < viewport.Left || cursor.y < viewport.Top || cursor.x >= viewport.Left + viewport.Width || cursor.y >= viewport.Top + viewport.Height) { Cancel(); Impl_->PreviousPosition = cursor; return; }
|
||||
if (Impl_->Captured && input.IsMouseButtonDown(MetaCoreMouseButton::Left)) {
|
||||
auto object = scene.FindGameObject(Impl_->Captured);
|
||||
const float threshold = object && object.HasComponent<MetaCoreInteractableComponent>() ? object.GetComponent<MetaCoreInteractableComponent>().DragThresholdPixels : 5.0F;
|
||||
if (!Impl_->Dragging && glm::length(cursor - Impl_->PressPosition) >= threshold && Impl_->IsInteractive(scene, Impl_->Captured, MetaCoreInteraction_Drag)) { Impl_->Dragging = true; Impl_->Emit(MetaCorePointerEventType::DragStart, Impl_->Captured, cursor); }
|
||||
if (Impl_->Dragging) Impl_->Emit(MetaCorePointerEventType::Drag, Impl_->Captured, cursor);
|
||||
}
|
||||
const auto submit = [&](Impl::QueryKind kind) {
|
||||
const std::uint64_t sequence = Impl_->NextSequence++; const std::uint64_t revision = scene.GetRevision();
|
||||
if (kind == Impl::QueryKind::Hover) Impl_->LatestHoverSequence = sequence;
|
||||
else Impl_->PointerSequenceOrder.push_back(sequence);
|
||||
const std::uint64_t interactionRevision = Impl_->InteractionRevision;
|
||||
const auto localX = static_cast<std::uint32_t>(std::clamp(cursor.x - viewport.Left, 0.0F, viewport.Width - 1.0F));
|
||||
const auto localY = static_cast<std::uint32_t>(std::clamp(viewport.Height - 1.0F - (cursor.y - viewport.Top), 0.0F, viewport.Height - 1.0F));
|
||||
const std::weak_ptr<Impl::AsyncState> async = Impl_->Async;
|
||||
const bool submitted = requestPick(localX, localY, [async, sequence, kind, cursor, revision, interactionRevision](const MetaCoreInteractionPickResult& result) {
|
||||
const auto state = async.lock();
|
||||
if (!state) return;
|
||||
std::scoped_lock lock(state->Mutex);
|
||||
if (state->Alive) state->CompletedQueries.push_back({sequence, kind, cursor, result.ObjectId, result.WorldPosition, revision, interactionRevision});
|
||||
});
|
||||
if (!submitted && kind != Impl::QueryKind::Hover && !Impl_->PointerSequenceOrder.empty()) Impl_->PointerSequenceOrder.pop_back();
|
||||
};
|
||||
submit(Impl::QueryKind::Hover);
|
||||
if (input.WasMouseButtonPressed(MetaCoreMouseButton::Left)) submit(Impl::QueryKind::Down);
|
||||
if (input.WasMouseButtonReleased(MetaCoreMouseButton::Left)) submit(Impl::QueryKind::Up);
|
||||
Impl_->PreviousPosition = cursor;
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreReflection.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
enum class MetaCoreInputBindingTarget : std::uint32_t { Action = 0, Axis = 1 };
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInputActionDefinition {
|
||||
MC_GENERATED_BODY()
|
||||
MC_PROPERTY()
|
||||
std::string Id{};
|
||||
MC_PROPERTY()
|
||||
std::string DisplayName{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInputAxisDefinition {
|
||||
MC_GENERATED_BODY()
|
||||
MC_PROPERTY()
|
||||
std::string Id{};
|
||||
MC_PROPERTY()
|
||||
std::string DisplayName{};
|
||||
MC_PROPERTY()
|
||||
float DeadZone = 0.0F;
|
||||
MC_PROPERTY()
|
||||
float Sensitivity = 1.0F;
|
||||
MC_PROPERTY()
|
||||
float Minimum = -1.0F;
|
||||
MC_PROPERTY()
|
||||
float Maximum = 1.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInputBindingDefinition {
|
||||
MC_GENERATED_BODY()
|
||||
MC_PROPERTY()
|
||||
std::string BindingId{};
|
||||
MC_PROPERTY()
|
||||
std::string TargetId{};
|
||||
MC_PROPERTY()
|
||||
MetaCoreInputBindingTarget Target = MetaCoreInputBindingTarget::Action;
|
||||
MC_PROPERTY()
|
||||
std::string ControlPath{};
|
||||
MC_PROPERTY()
|
||||
float Scale = 1.0F;
|
||||
MC_PROPERTY()
|
||||
std::vector<std::string> Modifiers{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInputContextDefinition {
|
||||
MC_GENERATED_BODY()
|
||||
MC_PROPERTY()
|
||||
std::string Id{};
|
||||
MC_PROPERTY()
|
||||
std::int32_t Priority = 0;
|
||||
MC_PROPERTY()
|
||||
bool EnabledByDefault = true;
|
||||
MC_PROPERTY()
|
||||
bool ConsumeLowerPriority = false;
|
||||
MC_PROPERTY()
|
||||
std::vector<std::string> BindingIds{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInputMapDocument {
|
||||
MC_GENERATED_BODY()
|
||||
MC_PROPERTY()
|
||||
std::uint32_t Version = 1;
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreInputActionDefinition> Actions{};
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreInputAxisDefinition> Axes{};
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreInputBindingDefinition> Bindings{};
|
||||
MC_PROPERTY()
|
||||
std::vector<MetaCoreInputContextDefinition> Contexts{};
|
||||
};
|
||||
|
||||
struct MetaCoreInputMapIssue {
|
||||
bool Error = false;
|
||||
std::string Scope{};
|
||||
std::string Message{};
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<MetaCoreInputMapIssue> MetaCoreValidateInputMap(const MetaCoreInputMapDocument& document);
|
||||
[[nodiscard]] bool MetaCoreWriteInputMap(const std::filesystem::path& path, const MetaCoreInputMapDocument& document, const MetaCoreTypeRegistry& registry);
|
||||
[[nodiscard]] std::optional<MetaCoreInputMapDocument> MetaCoreReadInputMap(const std::filesystem::path& path, const MetaCoreTypeRegistry& registry);
|
||||
[[nodiscard]] MetaCoreInputMapDocument MetaCoreBuildDefaultInputMap();
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreRuntimeInput/MetaCoreInputMap.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreInput;
|
||||
|
||||
enum class MetaCoreInputActionPhase : std::uint8_t { Started, Performed, Canceled };
|
||||
|
||||
struct MetaCoreInputActionState {
|
||||
bool Pressed = false;
|
||||
bool Held = false;
|
||||
bool Released = false;
|
||||
};
|
||||
|
||||
struct MetaCoreInputActionEvent {
|
||||
std::string ActionId{};
|
||||
MetaCoreInputActionPhase Phase = MetaCoreInputActionPhase::Performed;
|
||||
};
|
||||
|
||||
struct MetaCoreInputConsumption {
|
||||
bool Keyboard = false;
|
||||
bool Mouse = false;
|
||||
bool Gamepad = false;
|
||||
};
|
||||
|
||||
class MetaCoreRuntimeInput {
|
||||
public:
|
||||
using ActionCallback = std::function<void(const MetaCoreInputActionEvent&)>;
|
||||
|
||||
MetaCoreRuntimeInput();
|
||||
~MetaCoreRuntimeInput();
|
||||
void SetInputMap(MetaCoreInputMapDocument document);
|
||||
[[nodiscard]] const MetaCoreInputMapDocument& GetInputMap() const;
|
||||
void Update(const MetaCoreInput& input, const MetaCoreInputConsumption& consumed = {});
|
||||
void CancelAll();
|
||||
[[nodiscard]] MetaCoreInputActionState GetAction(std::string_view actionId) const;
|
||||
[[nodiscard]] float GetAxis(std::string_view axisId) const;
|
||||
[[nodiscard]] bool SetContextEnabled(std::string_view contextId, bool enabled);
|
||||
[[nodiscard]] bool PushContext(std::string_view contextId);
|
||||
[[nodiscard]] bool PopContext(std::string_view contextId);
|
||||
[[nodiscard]] std::uint64_t Subscribe(ActionCallback callback);
|
||||
void Unsubscribe(std::uint64_t subscriptionId);
|
||||
[[nodiscard]] bool Rebind(std::string_view bindingId, std::string controlPath);
|
||||
[[nodiscard]] bool ResetBinding(std::string_view bindingId);
|
||||
void ResetAllBindings();
|
||||
[[nodiscard]] bool LoadOverrides(const std::filesystem::path& path);
|
||||
[[nodiscard]] bool SaveOverrides(const std::filesystem::path& path) const;
|
||||
[[nodiscard]] const std::vector<std::string>& GetWarnings() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> Impl_;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::filesystem::path MetaCoreGetInputOverridePath(std::string_view projectName);
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreInput;
|
||||
class MetaCoreScene;
|
||||
class MetaCoreEditorViewportRenderer;
|
||||
struct MetaCoreViewportRect;
|
||||
|
||||
enum class MetaCorePointerEventType : std::uint8_t { Enter, Move, Leave, Down, Up, Click, DragStart, Drag, DragEnd, Cancel };
|
||||
struct MetaCorePointerEvent {
|
||||
MetaCorePointerEventType Type = MetaCorePointerEventType::Move;
|
||||
MetaCoreId TargetObjectId = 0;
|
||||
std::uint32_t Button = 0;
|
||||
glm::vec2 ScreenPosition{0.0F};
|
||||
glm::vec2 ScreenDelta{0.0F};
|
||||
glm::vec3 WorldPosition{0.0F};
|
||||
};
|
||||
struct MetaCoreInteractionPickResult {
|
||||
MetaCoreId ObjectId = 0;
|
||||
glm::vec3 WorldPosition{0.0F};
|
||||
};
|
||||
|
||||
class MetaCoreRuntimeInteraction {
|
||||
public:
|
||||
using EventCallback = std::function<void(const MetaCorePointerEvent&)>;
|
||||
using PickCallback = std::function<void(const MetaCoreInteractionPickResult&)>;
|
||||
using PickRequest = std::function<bool(std::uint32_t, std::uint32_t, PickCallback)>;
|
||||
MetaCoreRuntimeInteraction();
|
||||
~MetaCoreRuntimeInteraction();
|
||||
void SetEventCallback(EventCallback callback);
|
||||
void Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed);
|
||||
void Update(MetaCoreScene& scene, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed);
|
||||
void Cancel();
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> Impl_;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -667,34 +667,33 @@ void MetaCoreRuntimeUiSystem::Update(float) {
|
||||
if (!Impl_->Initialized_ || Impl_->Context_ == nullptr || Impl_->Window_ == nullptr) return;
|
||||
const auto [width, height] = Impl_->Window_->GetFramebufferSize();
|
||||
Impl_->Context_->SetDimensions({width, height});
|
||||
MetaCoreInput& input = Impl_->Window_->GetInput();
|
||||
if (Impl_->HasInputEnabledDocument()) {
|
||||
const glm::vec2 cursor = input.GetCursorPosition();
|
||||
Impl_->Context_->ProcessMouseMove(static_cast<int>(cursor.x), static_cast<int>(cursor.y), 0);
|
||||
for (int button = 0; button < 3; ++button) {
|
||||
const auto mouseButton = static_cast<MetaCoreMouseButton>(button);
|
||||
if (input.WasMouseButtonPressed(mouseButton)) Impl_->Context_->ProcessMouseButtonDown(button, 0);
|
||||
if (input.WasMouseButtonReleased(mouseButton)) Impl_->Context_->ProcessMouseButtonUp(button, 0);
|
||||
}
|
||||
if (std::abs(input.GetMouseWheelDelta()) > 0.001F) Impl_->Context_->ProcessMouseWheel(input.GetMouseWheelDelta(), 0);
|
||||
for (const MetaCoreKeyEvent& event : input.ConsumeKeyEvents()) {
|
||||
const Rml::Input::KeyIdentifier key = MetaCoreMapGlfwKeyToRml(event.Key);
|
||||
const int modifiers = MetaCoreMapGlfwModifiersToRml(event.Modifiers);
|
||||
if (event.Pressed) Impl_->Context_->ProcessKeyDown(key, modifiers);
|
||||
else Impl_->Context_->ProcessKeyUp(key, modifiers);
|
||||
}
|
||||
for (const char32_t codePoint : input.ConsumeTextInput()) {
|
||||
Impl_->Context_->ProcessTextInput(static_cast<Rml::Character>(codePoint));
|
||||
}
|
||||
} else {
|
||||
(void)input.ConsumeKeyEvents();
|
||||
(void)input.ConsumeTextInput();
|
||||
}
|
||||
Impl_->RenderInterface_.BeginFrame();
|
||||
Impl_->Context_->Update();
|
||||
Impl_->Context_->Render();
|
||||
}
|
||||
|
||||
MetaCoreInputConsumption MetaCoreRuntimeUiSystem::ProcessInput() {
|
||||
MetaCoreInputConsumption consumed;
|
||||
if (!Impl_->Initialized_ || Impl_->Context_ == nullptr || Impl_->Window_ == nullptr || !Impl_->HasInputEnabledDocument()) return consumed;
|
||||
MetaCoreInput& input = Impl_->Window_->GetInput();
|
||||
const glm::vec2 cursor = input.GetCursorPosition();
|
||||
consumed.Mouse = !Impl_->Context_->ProcessMouseMove(static_cast<int>(cursor.x), static_cast<int>(cursor.y), 0);
|
||||
for (int button = 0; button < 3; ++button) {
|
||||
const auto mouseButton = static_cast<MetaCoreMouseButton>(button);
|
||||
if (input.WasMouseButtonPressed(mouseButton)) consumed.Mouse = !Impl_->Context_->ProcessMouseButtonDown(button, 0) || consumed.Mouse;
|
||||
if (input.WasMouseButtonReleased(mouseButton)) consumed.Mouse = !Impl_->Context_->ProcessMouseButtonUp(button, 0) || consumed.Mouse;
|
||||
}
|
||||
if (std::abs(input.GetMouseWheelDelta()) > 0.001F) consumed.Mouse = !Impl_->Context_->ProcessMouseWheel(input.GetMouseWheelDelta(), 0) || consumed.Mouse;
|
||||
for (const MetaCoreKeyEvent& event : input.GetKeyEvents()) {
|
||||
const Rml::Input::KeyIdentifier key = MetaCoreMapGlfwKeyToRml(event.Key);
|
||||
const int modifiers = MetaCoreMapGlfwModifiersToRml(event.Modifiers);
|
||||
const bool propagated = event.Pressed ? Impl_->Context_->ProcessKeyDown(key, modifiers) : Impl_->Context_->ProcessKeyUp(key, modifiers);
|
||||
consumed.Keyboard = !propagated || consumed.Keyboard;
|
||||
}
|
||||
for (const char32_t codePoint : input.GetTextInput()) consumed.Keyboard = !Impl_->Context_->ProcessTextInput(static_cast<Rml::Character>(codePoint)) || consumed.Keyboard;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void MetaCoreRuntimeUiSystem::ApplyDataUpdates(const std::vector<MetaCoreRuntimeDataUpdate>& updates) {
|
||||
if (!Impl_->Initialized_) return;
|
||||
for (const MetaCoreRuntimeDataUpdate& update : updates) Impl_->Values_[update.DataPointId] = update.Value;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
||||
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
@ -40,6 +41,7 @@ public:
|
||||
void AttachToViewportRenderer(MetaCoreEditorViewportRenderer& viewportRenderer);
|
||||
|
||||
void Update(float deltaSeconds);
|
||||
[[nodiscard]] MetaCoreInputConsumption ProcessInput();
|
||||
void ApplyDataUpdates(const std::vector<MetaCoreRuntimeDataUpdate>& updates);
|
||||
[[nodiscard]] bool SetDocumentVisible(const std::string& documentId, bool visible);
|
||||
[[nodiscard]] std::vector<MetaCoreRuntimeUiEvent> ConsumeEvents();
|
||||
|
||||
@ -293,6 +293,8 @@ std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<Me
|
||||
|
||||
if (sourceObject.HasComponent<MetaCoreUiRendererComponent>())
|
||||
clonedObject.AddComponent<MetaCoreUiRendererComponent>(sourceObject.GetComponent<MetaCoreUiRendererComponent>());
|
||||
if (sourceObject.HasComponent<MetaCoreInteractableComponent>())
|
||||
clonedObject.AddComponent<MetaCoreInteractableComponent>(sourceObject.GetComponent<MetaCoreInteractableComponent>());
|
||||
|
||||
if (sourceObject.HasComponent<MetaCorePrefabInstanceMetadata>())
|
||||
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(sourceObject.GetComponent<MetaCorePrefabInstanceMetadata>());
|
||||
@ -459,6 +461,8 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
|
||||
data.Script = obj.GetComponent<MetaCoreScriptComponent>();
|
||||
if (obj.HasComponent<MetaCoreUiRendererComponent>())
|
||||
data.UiRenderer = obj.GetComponent<MetaCoreUiRendererComponent>();
|
||||
if (obj.HasComponent<MetaCoreInteractableComponent>())
|
||||
data.Interactable = obj.GetComponent<MetaCoreInteractableComponent>();
|
||||
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
|
||||
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
|
||||
if (obj.HasComponent<MetaCoreModelRootTag>())
|
||||
@ -494,6 +498,8 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
|
||||
obj.AddComponent<MetaCoreScriptComponent>(data.Script.value());
|
||||
if (data.UiRenderer.has_value())
|
||||
obj.AddComponent<MetaCoreUiRendererComponent>(data.UiRenderer.value());
|
||||
if (data.Interactable.has_value())
|
||||
obj.AddComponent<MetaCoreInteractableComponent>(data.Interactable.value());
|
||||
if (data.PrefabInstance.has_value())
|
||||
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
|
||||
if (data.ModelRootTag.has_value())
|
||||
|
||||
@ -193,6 +193,12 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
|
||||
}
|
||||
objJson["UiRenderer"] = std::move(uiJson);
|
||||
}
|
||||
} else if (objField.Name == "Interactable" && obj.Interactable.has_value()) {
|
||||
objJson["Interactable"] = {
|
||||
{"Enabled", obj.Interactable->Enabled},
|
||||
{"EventMask", obj.Interactable->EventMask},
|
||||
{"DragThresholdPixels", obj.Interactable->DragThresholdPixels}
|
||||
};
|
||||
} else if (objField.Name == "Camera" && obj.Camera.has_value()) {
|
||||
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
|
||||
if (camDesc) {
|
||||
@ -379,6 +385,13 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
|
||||
}
|
||||
obj.UiRenderer = ui;
|
||||
}
|
||||
} else if (objField.Name == "Interactable" && objJson.contains("Interactable")) {
|
||||
const auto& value = objJson["Interactable"];
|
||||
MetaCoreInteractableComponent interactable;
|
||||
if (value.contains("Enabled")) interactable.Enabled = value["Enabled"].get<bool>();
|
||||
if (value.contains("EventMask")) interactable.EventMask = value["EventMask"].get<std::uint32_t>();
|
||||
if (value.contains("DragThresholdPixels")) interactable.DragThresholdPixels = value["DragThresholdPixels"].get<float>();
|
||||
obj.Interactable = interactable;
|
||||
} else if (objField.Name == "Camera" && objJson.contains("Camera")) {
|
||||
const auto& camJson = objJson["Camera"];
|
||||
const MetaCoreStructDescriptor* camDesc = registry.FindStruct<MetaCoreCameraComponent>();
|
||||
|
||||
@ -245,6 +245,25 @@ struct MetaCoreUiRendererComponent {
|
||||
glm::vec3 WorldSize{1.92F, 1.08F, 0.0F};
|
||||
};
|
||||
|
||||
enum MetaCoreInteractionEventMask : std::uint32_t {
|
||||
MetaCoreInteraction_Hover = 1U << 0U,
|
||||
MetaCoreInteraction_Click = 1U << 1U,
|
||||
MetaCoreInteraction_Drag = 1U << 2U,
|
||||
MetaCoreInteraction_All = MetaCoreInteraction_Hover | MetaCoreInteraction_Click | MetaCoreInteraction_Drag
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreInteractableComponent {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
MC_PROPERTY()
|
||||
bool Enabled = true;
|
||||
MC_PROPERTY()
|
||||
std::uint32_t EventMask = MetaCoreInteraction_All;
|
||||
MC_PROPERTY()
|
||||
float DragThresholdPixels = 5.0F;
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
struct MetaCoreModelRootTag {
|
||||
MC_GENERATED_BODY()
|
||||
|
||||
@ -137,6 +137,8 @@ struct MetaCoreGameObjectData {
|
||||
std::optional<MetaCoreScriptComponent> Script;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreUiRendererComponent> UiRenderer;
|
||||
MC_PROPERTY()
|
||||
std::optional<MetaCoreInteractableComponent> Interactable;
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "MetaCoreScripting/MetaCoreScripting.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
#include "MetaCoreFoundation/MetaCoreHash.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@ -432,7 +433,7 @@ std::optional<MetaCoreLoadedScriptModule> MetaCoreScriptModuleLoader::LoadCandid
|
||||
|
||||
struct MetaCoreScriptRuntime::Impl {
|
||||
struct Entry { MetaCoreId ObjectId = 0; std::uint64_t InstanceId = 0; std::string TypeId; std::unique_ptr<MetaCoreIScriptBehaviour> Behaviour; bool Started = false; bool Faulted = false; };
|
||||
struct Event { std::string Topic; std::vector<MetaCoreScriptValueV1> Fields; };
|
||||
struct Event { MetaCoreId TargetObjectId = 0; std::string Topic; std::vector<MetaCoreScriptValueV1> Fields; };
|
||||
struct Subscription { std::uint64_t Id; MetaCoreId ObjectId; std::uint64_t InstanceId; std::string Topic; EventHandler Handler; };
|
||||
struct Timer { std::uint64_t Id; MetaCoreId ObjectId; std::uint64_t InstanceId; double Remaining; double Interval; bool Unscaled; std::function<void()> Callback; };
|
||||
|
||||
@ -447,9 +448,10 @@ struct MetaCoreScriptRuntime::Impl {
|
||||
std::uint64_t NextSubscription = 1;
|
||||
std::uint64_t NextTimer = 1;
|
||||
bool Running = false;
|
||||
MetaCoreRuntimeInput* Input = nullptr;
|
||||
|
||||
Impl(MetaCoreScriptRuntime& owner, MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger)
|
||||
: Owner(owner), Scene(scene), Registry(registry), Logger(std::move(logger)) {}
|
||||
Impl(MetaCoreScriptRuntime& owner, MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, MetaCoreRuntimeInput* input)
|
||||
: Owner(owner), Scene(scene), Registry(registry), Logger(std::move(logger)), Input(input) {}
|
||||
|
||||
void Log(std::uint32_t level, std::string_view category, std::string_view text) const { if (Logger) Logger(level, category, text); }
|
||||
Entry* FindEntry(MetaCoreId object, std::uint64_t instance) {
|
||||
@ -529,7 +531,7 @@ struct MetaCoreScriptRuntime::Impl {
|
||||
for (const auto& event : current) {
|
||||
const auto subscriptions = Subscriptions;
|
||||
for (const auto& subscription : subscriptions) {
|
||||
if (subscription.Topic == event.Topic && FindInstance(Scene.FindGameObject(subscription.ObjectId), subscription.InstanceId)) {
|
||||
if (subscription.Topic == event.Topic && (event.TargetObjectId == 0 || event.TargetObjectId == subscription.ObjectId) && FindInstance(Scene.FindGameObject(subscription.ObjectId), subscription.InstanceId)) {
|
||||
try { subscription.Handler(event.Topic, event.Fields); }
|
||||
catch (...) { Log(2, "Script.Event", "Event handler threw an exception"); }
|
||||
}
|
||||
@ -554,8 +556,8 @@ struct MetaCoreScriptRuntime::Impl {
|
||||
}
|
||||
};
|
||||
|
||||
MetaCoreScriptRuntime::MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger)
|
||||
: Impl_(std::make_unique<Impl>(*this, scene, registry, std::move(logger))) {}
|
||||
MetaCoreScriptRuntime::MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, MetaCoreRuntimeInput* input)
|
||||
: Impl_(std::make_unique<Impl>(*this, scene, registry, std::move(logger), input)) {}
|
||||
MetaCoreScriptRuntime::~MetaCoreScriptRuntime() { Stop(); }
|
||||
void MetaCoreScriptRuntime::Start() { if (Impl_->Running) return; Impl_->Running = true; ++WorldGeneration_; for (auto o : Impl_->Scene.GetGameObjects()) if (o.HasComponent<MetaCoreScriptComponent>()) for (auto& i : o.GetComponent<MetaCoreScriptComponent>().Instances) if (i.Enabled) Impl_->Ensure(o, i); }
|
||||
void MetaCoreScriptRuntime::FixedUpdate(double delta) { if (!Impl_->Running) return; Time_.FixedDeltaSeconds = std::max(0.0, delta); Impl_->Dispatch(Impl::Phase::Fixed, Time_.FixedDeltaSeconds); }
|
||||
@ -575,7 +577,9 @@ void MetaCoreScriptRuntime::SetTimeScale(double scale) { Time_.TimeScale = std::
|
||||
std::size_t MetaCoreScriptRuntime::GetFaultedInstanceCount() const { return std::count_if(Impl_->Entries.begin(), Impl_->Entries.end(), [](const auto& e) { return e.Faulted; }); }
|
||||
std::uint64_t MetaCoreScriptRuntime::Subscribe(MetaCoreId object, std::uint64_t instance, std::string topic, EventHandler handler) { const auto id = Impl_->NextSubscription++; Impl_->Subscriptions.push_back({id, object, instance, std::move(topic), std::move(handler)}); return id; }
|
||||
void MetaCoreScriptRuntime::Unsubscribe(std::uint64_t id) { std::erase_if(Impl_->Subscriptions, [id](const auto& s) { return s.Id == id; }); }
|
||||
void MetaCoreScriptRuntime::Publish(std::string topic, std::vector<MetaCoreScriptValueV1> fields) { Impl_->Events.push_back({std::move(topic), std::move(fields)}); }
|
||||
void MetaCoreScriptRuntime::Publish(std::string topic, std::vector<MetaCoreScriptValueV1> fields) { Impl_->Events.push_back({0, std::move(topic), std::move(fields)}); }
|
||||
void MetaCoreScriptRuntime::PublishToObject(MetaCoreId object, std::string topic, std::vector<MetaCoreScriptValueV1> fields) { Impl_->Events.push_back({object, std::move(topic), std::move(fields)}); }
|
||||
MetaCoreRuntimeInput* MetaCoreScriptRuntime::GetRuntimeInput() const { return Impl_->Input; }
|
||||
std::uint64_t MetaCoreScriptRuntime::ScheduleTimer(MetaCoreId object, std::uint64_t instance, double delay, double interval, bool unscaled, std::function<void()> callback) { const auto id = Impl_->NextTimer++; Impl_->Timers.push_back({id, object, instance, std::max(0.0, delay), std::max(0.0, interval), unscaled, std::move(callback)}); return id; }
|
||||
void MetaCoreScriptRuntime::CancelTimer(std::uint64_t id) { std::erase_if(Impl_->Timers, [id](const auto& t) { return t.Id == id; }); }
|
||||
bool MetaCoreScriptRuntime::IsHandleValid(MetaCoreScriptObjectHandleV1 handle) const { return handle.WorldGeneration == WorldGeneration_ && handle.ObjectId != 0 && static_cast<bool>(Impl_->Scene.FindGameObject(handle.ObjectId)); }
|
||||
@ -638,7 +642,12 @@ std::uint64_t HSubscribe(void* c, MetaCoreScriptObjectHandleV1 h, std::uint64_t
|
||||
void HUnsubscribe(void* c, std::uint64_t id) noexcept { try { if (c) static_cast<MetaCoreScriptRuntime*>(c)->Unsubscribe(id); } catch (...) {} }
|
||||
std::uint64_t HSchedule(void* c, MetaCoreScriptObjectHandleV1 h, std::uint64_t instance, double delay, double interval, bool unscaled, MetaCoreScriptTimerCallbackV1 cb, void* user) noexcept { try { if (!c || !cb) return 0; auto* runtime = static_cast<MetaCoreScriptRuntime*>(c); return runtime->ScheduleTimer(h.ObjectId, instance, delay, interval, unscaled, [runtime, h, instance, cb, user] { MetaCoreScriptExecutionContextV1 context{&HostApi(), runtime, h, instance, "", "{}", {}}; if (const char* e = cb(user, &context); e) runtime->Log(2, "Script.Timer", e); }); } catch (...) { return 0; } }
|
||||
void HCancel(void* c, std::uint64_t id) noexcept { try { if (c) static_cast<MetaCoreScriptRuntime*>(c)->CancelTimer(id); } catch (...) {} }
|
||||
const MetaCoreScriptHostApiV1& HostApi() { static const MetaCoreScriptHostApiV1 api{METACORE_SCRIPT_ABI_MAJOR, METACORE_SCRIPT_ABI_MINOR, METACORE_SCRIPT_HOST_CAPABILITIES, HValid, HHas, HGet, HSet, HGetField, HSetField, HLog, HPublish, HSubscribe, HUnsubscribe, HSchedule, HCancel}; return api; }
|
||||
bool HInputAction(void* c, const char* id, MetaCoreScriptInputActionStateV1* out) noexcept { try { if (!c || !id || !out) return false; auto* input = static_cast<MetaCoreScriptRuntime*>(c)->GetRuntimeInput(); if (!input) return false; const auto state = input->GetAction(id); *out = {state.Pressed, state.Held, state.Released}; return true; } catch (...) { return false; } }
|
||||
bool HInputAxis(void* c, const char* id, float* out) noexcept { try { if (!c || !id || !out) return false; auto* input = static_cast<MetaCoreScriptRuntime*>(c)->GetRuntimeInput(); if (!input) return false; *out = input->GetAxis(id); return true; } catch (...) { return false; } }
|
||||
bool HInputContext(void* c, const char* id, bool enabled) noexcept { try { auto* input = c ? static_cast<MetaCoreScriptRuntime*>(c)->GetRuntimeInput() : nullptr; return input && id && input->SetContextEnabled(id, enabled); } catch (...) { return false; } }
|
||||
bool HInputRebind(void* c, const char* id, const char* path) noexcept { try { auto* input = c ? static_cast<MetaCoreScriptRuntime*>(c)->GetRuntimeInput() : nullptr; return input && id && path && input->Rebind(id, path); } catch (...) { return false; } }
|
||||
bool HInputReset(void* c, const char* id) noexcept { try { auto* input = c ? static_cast<MetaCoreScriptRuntime*>(c)->GetRuntimeInput() : nullptr; return input && id && input->ResetBinding(id); } catch (...) { return false; } }
|
||||
const MetaCoreScriptHostApiV1& HostApi() { static const MetaCoreScriptHostApiV1 api{METACORE_SCRIPT_ABI_MAJOR, METACORE_SCRIPT_ABI_MINOR, METACORE_SCRIPT_HOST_CAPABILITIES, HValid, HHas, HGet, HSet, HGetField, HSetField, HLog, HPublish, HSubscribe, HUnsubscribe, HSchedule, HCancel, HInputAction, HInputAxis, HInputContext, HInputRebind, HInputReset}; return api; }
|
||||
} // namespace
|
||||
|
||||
bool MetaCoreGenerateScriptProject(const std::filesystem::path& root, std::string_view projectName, const std::filesystem::path& sdkRoot, std::string* error) {
|
||||
@ -650,7 +659,24 @@ bool MetaCoreGenerateScriptProject(const std::filesystem::path& root, std::strin
|
||||
if (ec) { if (error) *error = ec.message(); return false; }
|
||||
const std::string id = SafeIdentifier(projectName);
|
||||
const std::string cmake = "cmake_minimum_required(VERSION 3.20)\nproject(" + id + "Scripts LANGUAGES CXX)\nif(NOT CMAKE_SYSTEM_NAME STREQUAL \"Linux\" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)\n message(FATAL_ERROR \"MetaCore script v1 requires Linux x86_64\")\nendif()\nif(NOT CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n message(FATAL_ERROR \"MetaCore script v1 requires Clang and libc++\")\nendif()\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\nfile(GLOB SCRIPT_SOURCES CONFIGURE_DEPENDS Source/*.cpp)\nadd_library(" + id + "Scripts SHARED ${SCRIPT_SOURCES})\ntarget_include_directories(" + id + "Scripts PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/SDK)\ntarget_compile_definitions(" + id + "Scripts PRIVATE METACORE_SCRIPT_MODULE_ID=\\\"" + id + "\\\" METACORE_SCRIPT_BUILD_ID=\\\"${METACORE_SCRIPT_BUILD_ID}\\\")\nset_target_properties(" + id + "Scripts PROPERTIES PREFIX \"\" OUTPUT_NAME \"" + id + "Scripts_${METACORE_SCRIPT_BUILD_ID}\" LIBRARY_OUTPUT_DIRECTORY \"${METACORE_SCRIPT_OUTPUT_DIR}\")\n";
|
||||
const std::string sdk = "#pragma once\n#include <MetaCoreScripting/MetaCoreScriptAbi.h>\n#include <exception>\n#include <string>\n#include <vector>\nnamespace MetaCoreScriptSdk { inline std::vector<const MetaCoreScriptBehaviourDescriptorV1*>& Registry(){ static std::vector<const MetaCoreScriptBehaviourDescriptorV1*> value; return value; } struct Registrar { explicit Registrar(const MetaCoreScriptBehaviourDescriptorV1* value){ Registry().push_back(value); } }; template<class F> const char* Guard(F&& function) noexcept { try { function(); return nullptr; } catch(const std::exception& e) { static thread_local std::string error; error=e.what(); return error.c_str(); } catch(...) { return \"unknown C++ exception\"; } } }\n#define MC_REGISTER_SCRIPT(Name, Descriptor) static MetaCoreScriptSdk::Registrar Name##Registrar(&(Descriptor))\n";
|
||||
const std::string sdk = R"SDK(#pragma once
|
||||
#include <MetaCoreScripting/MetaCoreScriptAbi.h>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace MetaCoreScriptSdk {
|
||||
inline std::vector<const MetaCoreScriptBehaviourDescriptorV1*>& Registry(){ static std::vector<const MetaCoreScriptBehaviourDescriptorV1*> value; return value; }
|
||||
struct Registrar { explicit Registrar(const MetaCoreScriptBehaviourDescriptorV1* value){ Registry().push_back(value); } };
|
||||
template<class F> const char* Guard(F&& function) noexcept { try { function(); return nullptr; } catch(const std::exception& e) { static thread_local std::string error; error=e.what(); return error.c_str(); } catch(...) { return "unknown C++ exception"; } }
|
||||
inline bool HasInput(const MetaCoreScriptExecutionContextV1& context) { return context.Host && (context.Host->Capabilities & METACORE_SCRIPT_CAP_INPUT) != 0; }
|
||||
inline MetaCoreScriptInputActionStateV1 GetAction(const MetaCoreScriptExecutionContextV1& context, const char* actionId) { MetaCoreScriptInputActionStateV1 state{}; if(HasInput(context) && context.Host->GetInputAction) context.Host->GetInputAction(context.HostContext, actionId, &state); return state; }
|
||||
inline float GetAxis(const MetaCoreScriptExecutionContextV1& context, const char* axisId) { float value=0.0f; if(HasInput(context) && context.Host->GetInputAxis) context.Host->GetInputAxis(context.HostContext, axisId, &value); return value; }
|
||||
inline bool SetContextEnabled(const MetaCoreScriptExecutionContextV1& context, const char* contextId, bool enabled) { return HasInput(context) && context.Host->SetInputContextEnabled && context.Host->SetInputContextEnabled(context.HostContext, contextId, enabled); }
|
||||
inline bool Rebind(const MetaCoreScriptExecutionContextV1& context, const char* bindingId, const char* controlPath) { return HasInput(context) && context.Host->RebindInput && context.Host->RebindInput(context.HostContext, bindingId, controlPath); }
|
||||
inline bool ResetBinding(const MetaCoreScriptExecutionContextV1& context, const char* bindingId) { return HasInput(context) && context.Host->ResetInputBinding && context.Host->ResetInputBinding(context.HostContext, bindingId); }
|
||||
}
|
||||
#define MC_REGISTER_SCRIPT(Name, Descriptor) static MetaCoreScriptSdk::Registrar Name##Registrar(&(Descriptor))
|
||||
)SDK";
|
||||
const std::string module = "#include <MetaCoreScripting/MetaCoreScriptSdk.h>\n#include <vector>\nextern \"C\" METACORE_SCRIPT_EXPORT bool MetaCoreLoadScriptModuleV1(const MetaCoreScriptHostApiV1* host, MetaCoreScriptModuleApiV1* out) noexcept { if(!host||!out||host->AbiMajor!=METACORE_SCRIPT_ABI_MAJOR) return false; static std::vector<MetaCoreScriptBehaviourDescriptorV1> descriptors; if(descriptors.empty()) for(const auto* descriptor:MetaCoreScriptSdk::Registry()) descriptors.push_back(*descriptor); *out={METACORE_SCRIPT_ABI_MAJOR,METACORE_SCRIPT_ABI_MINOR,METACORE_SCRIPT_CAP_PROPERTIES|METACORE_SCRIPT_CAP_EVENTS|METACORE_SCRIPT_CAP_TIMERS,METACORE_SCRIPT_MODULE_ID,METACORE_SCRIPT_BUILD_ID,descriptors.size(),descriptors.data(),nullptr}; return true; }\n";
|
||||
const std::string launch = "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\"name\": \"Attach MetaCore Editor (LLDB)\", \"type\": \"lldb\", \"request\": \"attach\", \"pid\": \"${command:pickMyProcess}\"},\n {\"name\": \"Attach MetaCore Player (LLDB)\", \"type\": \"lldb\", \"request\": \"attach\", \"pid\": \"${command:pickMyProcess}\"}\n ]\n}\n";
|
||||
if (!WriteIfMissing(scripts / "CMakeLists.txt", cmake) ||
|
||||
|
||||
@ -12,12 +12,13 @@
|
||||
extern "C" {
|
||||
|
||||
inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MAJOR = 1U;
|
||||
inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MINOR = 0U;
|
||||
inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MINOR = 1U;
|
||||
inline constexpr std::uint64_t METACORE_SCRIPT_CAP_PROPERTIES = 1ULL << 0U;
|
||||
inline constexpr std::uint64_t METACORE_SCRIPT_CAP_EVENTS = 1ULL << 1U;
|
||||
inline constexpr std::uint64_t METACORE_SCRIPT_CAP_TIMERS = 1ULL << 2U;
|
||||
inline constexpr std::uint64_t METACORE_SCRIPT_CAP_INPUT = 1ULL << 3U;
|
||||
inline constexpr std::uint64_t METACORE_SCRIPT_HOST_CAPABILITIES =
|
||||
METACORE_SCRIPT_CAP_PROPERTIES | METACORE_SCRIPT_CAP_EVENTS | METACORE_SCRIPT_CAP_TIMERS;
|
||||
METACORE_SCRIPT_CAP_PROPERTIES | METACORE_SCRIPT_CAP_EVENTS | METACORE_SCRIPT_CAP_TIMERS | METACORE_SCRIPT_CAP_INPUT;
|
||||
|
||||
enum MetaCoreScriptAbiValueType : std::uint32_t {
|
||||
MetaCoreScriptAbiValue_None = 0,
|
||||
@ -54,6 +55,8 @@ struct MetaCoreScriptTimeV1 {
|
||||
double TimeScale;
|
||||
};
|
||||
|
||||
struct MetaCoreScriptInputActionStateV1 { bool Pressed; bool Held; bool Released; };
|
||||
|
||||
struct MetaCoreScriptExecutionContextV1;
|
||||
using MetaCoreScriptTimerCallbackV1 = const char* (*)(void*, const MetaCoreScriptExecutionContextV1*) noexcept;
|
||||
using MetaCoreScriptEventCallbackV1 = const char* (*)(void*, const MetaCoreScriptExecutionContextV1*, const char*, const MetaCoreScriptValueV1*, std::size_t) noexcept;
|
||||
@ -74,6 +77,11 @@ struct MetaCoreScriptHostApiV1 {
|
||||
void (*UnsubscribeEvent)(void*, std::uint64_t) noexcept;
|
||||
std::uint64_t (*ScheduleTimer)(void*, MetaCoreScriptObjectHandleV1, std::uint64_t, double, double, bool, MetaCoreScriptTimerCallbackV1, void*) noexcept;
|
||||
void (*CancelTimer)(void*, std::uint64_t) noexcept;
|
||||
bool (*GetInputAction)(void*, const char*, MetaCoreScriptInputActionStateV1*) noexcept;
|
||||
bool (*GetInputAxis)(void*, const char*, float*) noexcept;
|
||||
bool (*SetInputContextEnabled)(void*, const char*, bool) noexcept;
|
||||
bool (*RebindInput)(void*, const char*, const char*) noexcept;
|
||||
bool (*ResetInputBinding)(void*, const char*) noexcept;
|
||||
};
|
||||
|
||||
struct MetaCoreScriptExecutionContextV1 {
|
||||
@ -127,4 +135,3 @@ struct MetaCoreScriptModuleApiV1 {
|
||||
using MetaCoreLoadScriptModuleV1Fn = bool (*)(const MetaCoreScriptHostApiV1*, MetaCoreScriptModuleApiV1*) noexcept;
|
||||
|
||||
} // extern "C"
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ struct MetaCoreScriptTime {
|
||||
};
|
||||
|
||||
class MetaCoreScriptRuntime;
|
||||
class MetaCoreRuntimeInput;
|
||||
|
||||
struct MetaCoreScriptExecutionContext {
|
||||
MetaCoreScene& Scene;
|
||||
@ -115,7 +116,7 @@ public:
|
||||
|
||||
class MetaCoreScriptRuntime {
|
||||
public:
|
||||
MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger = {});
|
||||
MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger = {}, MetaCoreRuntimeInput* input = nullptr);
|
||||
~MetaCoreScriptRuntime();
|
||||
|
||||
void Start();
|
||||
@ -132,6 +133,8 @@ public:
|
||||
std::uint64_t Subscribe(MetaCoreId objectId, std::uint64_t instanceId, std::string topic, EventHandler handler);
|
||||
void Unsubscribe(std::uint64_t subscriptionId);
|
||||
void Publish(std::string topic, std::vector<MetaCoreScriptValueV1> fields = {});
|
||||
void PublishToObject(MetaCoreId objectId, std::string topic, std::vector<MetaCoreScriptValueV1> fields = {});
|
||||
[[nodiscard]] MetaCoreRuntimeInput* GetRuntimeInput() const;
|
||||
std::uint64_t ScheduleTimer(MetaCoreId objectId, std::uint64_t instanceId, double delay, double interval, bool unscaled, std::function<void()> callback);
|
||||
void CancelTimer(std::uint64_t timerId);
|
||||
|
||||
|
||||
@ -178,12 +178,14 @@ MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备:
|
||||
|
||||
#### 需要做的事
|
||||
|
||||
- [ ] 定义 Input Action、Axis、Binding 和 Input Context 数据模型。
|
||||
- [ ] 支持键盘、鼠标和基础手柄映射。
|
||||
- [ ] 建立编辑器输入、Game View 输入和 Player 输入的焦点规则。
|
||||
- [ ] 统一 RmlUi 与 3D 场景输入路由和事件消费顺序。
|
||||
- [ ] 为运行时对象提供点击、悬停、拖拽和射线命中事件。
|
||||
- [ ] 将输入配置纳入项目、Cook 和 Player 加载链路。
|
||||
- [x] 定义 Input Action、Axis、Binding 和 Input Context 数据模型。
|
||||
- [x] 支持键盘、鼠标和基础手柄映射。
|
||||
- [x] 建立编辑器输入、Game View 输入和 Player 输入的焦点规则。
|
||||
- [x] 统一 RmlUi 与 3D 场景输入路由和事件消费顺序。
|
||||
- [x] 为运行时对象提供点击、悬停、拖拽和射线命中事件。
|
||||
- [x] 将输入配置纳入项目、Cook 和 Player 加载链路。
|
||||
|
||||
实施结果:已新增 `MetaCoreRuntimeInput`/`MetaCoreRuntimeInteraction`,由平台逐帧输入快照经 RmlUi 消费后求值 Input Map,并通过 Filament GPU Picking 生成定向 3D 交互事件。Editor Play Mode 与独立 Player 共用 `Runtime/Input.mcruntime`、脚本输入 ABI 和用户重绑定覆盖;项目创建、Cook/Stage 与正式打包校验均已接入。模块测试覆盖输入生命周期、Context 消费、序列化/损坏回退、异步 Picking 乱序与 Click 路由,相关 Scripting、Delivery 和端到端 Smoke Tests 同步通过。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
|
||||
113
tests/MetaCoreRuntimeInputTests.cpp
Normal file
113
tests/MetaCoreRuntimeInputTests.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
|
||||
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
|
||||
#include "MetaCorePlatform/MetaCoreInput.h"
|
||||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
#include "MetaCoreScene/MetaCoreComponents.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
void Expect(bool value, const char* message) { if (!value) { std::cerr << message << '\n'; std::exit(1); } }
|
||||
}
|
||||
|
||||
int main() {
|
||||
MetaCore::MetaCoreRuntimeInput runtime;
|
||||
runtime.SetInputMap(MetaCore::MetaCoreBuildDefaultInputMap());
|
||||
MetaCore::MetaCoreInput input;
|
||||
input.BeginFrame(); input.SetControlValue("<Keyboard>/W", 1.0F);
|
||||
runtime.Update(input);
|
||||
Expect(std::abs(runtime.GetAxis("MoveY") - 1.0F) < 0.001F, "W should drive MoveY");
|
||||
input.BeginFrame(); input.SetControlValue("<Mouse>/leftButton", 1.0F); runtime.Update(input);
|
||||
Expect(runtime.GetAction("Interact").Pressed && runtime.GetAction("Interact").Held, "action should start");
|
||||
input.BeginFrame(); runtime.Update(input); Expect(!runtime.GetAction("Interact").Pressed && runtime.GetAction("Interact").Held, "action should remain held");
|
||||
input.BeginFrame(); input.SetControlValue("<Mouse>/leftButton", 0.0F); runtime.Update(input);
|
||||
Expect(runtime.GetAction("Interact").Released, "action should cancel");
|
||||
Expect(runtime.Rebind("game.move.forward", "<Keyboard>/Up"), "rebind should succeed");
|
||||
input.BeginFrame(); input.SetControlValue("<Keyboard>/W", 0.0F); input.SetControlValue("<Keyboard>/Up", 1.0F); runtime.Update(input);
|
||||
Expect(runtime.GetAxis("MoveY") > 0.9F, "override should be evaluated");
|
||||
runtime.CancelAll(); Expect(!runtime.GetAction("Interact").Held && runtime.GetAxis("MoveY") == 0.0F, "cancel should clear state");
|
||||
MetaCore::MetaCoreTypeRegistry registry; MetaCore::MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
|
||||
const auto mapPath = std::filesystem::temp_directory_path() / "MetaCoreInputMap.test";
|
||||
Expect(MetaCore::MetaCoreWriteInputMap(mapPath, MetaCore::MetaCoreBuildDefaultInputMap(), registry), "input map should serialize");
|
||||
const auto loaded = MetaCore::MetaCoreReadInputMap(mapPath, registry);
|
||||
Expect(loaded && loaded->Bindings.size() == 10 && loaded->Contexts.size() == 1, "input map should round trip");
|
||||
const auto overridePath = std::filesystem::temp_directory_path() / "MetaCoreInputOverrides.test.json";
|
||||
Expect(runtime.SaveOverrides(overridePath), "overrides should save atomically");
|
||||
MetaCore::MetaCoreRuntimeInput loadedRuntime; loadedRuntime.SetInputMap(MetaCore::MetaCoreBuildDefaultInputMap());
|
||||
Expect(loadedRuntime.LoadOverrides(overridePath), "overrides should load");
|
||||
auto invalidMap = MetaCore::MetaCoreBuildDefaultInputMap();
|
||||
invalidMap.Bindings.push_back(invalidMap.Bindings.front());
|
||||
invalidMap.Bindings.back().ControlPath = "invalid";
|
||||
const auto invalidIssues = MetaCore::MetaCoreValidateInputMap(invalidMap);
|
||||
Expect(std::count_if(invalidIssues.begin(), invalidIssues.end(), [](const auto& issue){ return issue.Error; }) >= 2,
|
||||
"duplicate binding IDs and invalid paths should fail validation");
|
||||
{ std::ofstream corrupted(overridePath, std::ios::trunc); corrupted << "{broken"; }
|
||||
Expect(!loadedRuntime.LoadOverrides(overridePath) && !loadedRuntime.GetWarnings().empty(), "corrupt overrides should fall back with diagnostics");
|
||||
|
||||
MetaCore::MetaCoreInputMapDocument priorityMap;
|
||||
priorityMap.Actions = {{"High", "High"}, {"Low", "Low"}};
|
||||
priorityMap.Bindings = {{"high", "High", MetaCore::MetaCoreInputBindingTarget::Action, "<Keyboard>/Space", 1.0F, {}},
|
||||
{"low", "Low", MetaCore::MetaCoreInputBindingTarget::Action, "<Keyboard>/Space", 1.0F, {}}};
|
||||
priorityMap.Contexts = {{"HighContext", 10, true, true, {"high"}}, {"LowContext", 0, true, false, {"low"}}};
|
||||
MetaCore::MetaCoreRuntimeInput priorityRuntime; priorityRuntime.SetInputMap(priorityMap);
|
||||
MetaCore::MetaCoreInput priorityInput; priorityInput.BeginFrame(); priorityInput.SetControlValue("<Keyboard>/Space", 1.0F); priorityRuntime.Update(priorityInput);
|
||||
Expect(priorityRuntime.GetAction("High").Held && !priorityRuntime.GetAction("Low").Held, "higher context should consume the physical control");
|
||||
priorityInput.SetWindowFocused(false); priorityRuntime.Update(priorityInput);
|
||||
Expect(priorityRuntime.GetAction("High").Released, "focus loss should cancel held actions");
|
||||
|
||||
MetaCore::MetaCoreScene scene;
|
||||
auto interactiveObject = scene.CreateGameObject("Interactive");
|
||||
interactiveObject.AddComponent<MetaCore::MetaCoreMeshRendererComponent>();
|
||||
interactiveObject.AddComponent<MetaCore::MetaCoreInteractableComponent>();
|
||||
MetaCore::MetaCoreRuntimeInteraction interaction;
|
||||
std::vector<MetaCore::MetaCorePointerEvent> pointerEvents;
|
||||
std::vector<MetaCore::MetaCoreRuntimeInteraction::PickCallback> pickCallbacks;
|
||||
interaction.SetEventCallback([&](const auto& event) { pointerEvents.push_back(event); });
|
||||
const MetaCore::MetaCoreRuntimeInteraction::PickRequest pickBackend = [&](std::uint32_t, std::uint32_t, auto callback) {
|
||||
pickCallbacks.push_back(std::move(callback)); return true;
|
||||
};
|
||||
const MetaCore::MetaCoreViewportRect viewport{0.0F, 0.0F, 100.0F, 100.0F};
|
||||
MetaCore::MetaCoreInput pointerInput;
|
||||
pointerInput.BeginFrame(); pointerInput.SetCursorPosition({20.0F, 20.0F});
|
||||
interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(pickCallbacks.size() == 1, "hover should submit one pick");
|
||||
auto firstHover = std::move(pickCallbacks.back()); pickCallbacks.clear();
|
||||
pointerInput.BeginFrame(); pointerInput.SetCursorPosition({21.0F, 20.0F});
|
||||
interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
auto latestHover = std::move(pickCallbacks.back()); pickCallbacks.clear();
|
||||
latestHover({interactiveObject.GetId(), {1.0F, 2.0F, 3.0F}});
|
||||
firstHover({0, {}});
|
||||
pointerInput.BeginFrame(); interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(!pointerEvents.empty() && pointerEvents.back().Type == MetaCore::MetaCorePointerEventType::Enter,
|
||||
"out-of-order hover should accept only the latest result");
|
||||
|
||||
pickCallbacks.clear(); pointerEvents.clear();
|
||||
pointerInput.BeginFrame(); pointerInput.SetMouseButtonState(MetaCore::MetaCoreMouseButton::Left, true);
|
||||
interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(pickCallbacks.size() == 2, "press frame should preserve hover and down picks");
|
||||
auto down = std::move(pickCallbacks[1]); pickCallbacks.clear(); down({interactiveObject.GetId(), {}});
|
||||
pointerInput.BeginFrame(); interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(std::any_of(pointerEvents.begin(), pointerEvents.end(), [](const auto& e){ return e.Type == MetaCore::MetaCorePointerEventType::Down; }), "press result should capture target");
|
||||
pickCallbacks.clear();
|
||||
pointerInput.BeginFrame(); pointerInput.SetMouseButtonState(MetaCore::MetaCoreMouseButton::Left, false);
|
||||
interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(pickCallbacks.size() == 2, "release frame should preserve hover and up picks");
|
||||
auto up = std::move(pickCallbacks[1]); pickCallbacks.clear(); up({interactiveObject.GetId(), {}});
|
||||
pointerInput.BeginFrame(); interaction.Update(scene, pickBackend, pointerInput, viewport, false);
|
||||
Expect(std::any_of(pointerEvents.begin(), pointerEvents.end(), [](const auto& e){ return e.Type == MetaCore::MetaCorePointerEventType::Click; }), "same-target release should click");
|
||||
pickCallbacks.clear();
|
||||
pointerInput.BeginFrame(); interaction.Update(scene, pickBackend, pointerInput, viewport, true);
|
||||
Expect(pickCallbacks.empty(), "UI-consumed mouse should not submit picking");
|
||||
|
||||
std::filesystem::remove(mapPath); std::filesystem::remove(overridePath);
|
||||
std::cout << "MetaCoreRuntimeInputTests passed\n";
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user