Checkpoint 2: integrate native Editor, MCP, gameplay builds and standalone export

This commit is contained in:
Emil
2026-09-18 03:40:15 +03:00
parent 5c6b24d34d
commit 999686a896
125 changed files with 16086 additions and 1714 deletions
+70
View File
@@ -0,0 +1,70 @@
#include "Gameplay.hpp"
#include <map>
#include <tuple>
namespace faset::gameplay {
void registerGameplay(runtime::Runtime& world) {
runtime::Behavior spawner;
spawner.onStart = [](runtime::Runtime& game, runtime::EntityHandle self, double) {
const auto settings = game.fields(self, "tutorial.spawn_once");
const auto id = settings.at("spawned_id").get<std::string>();
// This queues a runtime object; it does not change the saved scene.
game.spawn(
{{"id", id},
{"name", "Temporary box"},
{"parent", nullptr},
{"components", nlohmann::json::array(
{{{"id", id + "/transform"},
{"type", "faset.transform"},
{"version", 1},
{"fields", {{"position", {0, 0, 0}}}}},
{{"id", id + "/sprite"},
{"type", "faset.sprite"},
{"version", 1},
{"fields", {{"color", {0.9, 0.6, 0.2, 1}}, {"size", {1, 1}}}}},
{{"id", id + "/lifetime"},
{"type", "tutorial.timed_despawn"},
{"version", 1},
{"fields", {{"seconds", settings.value("lifetime", 1.0)}}}}})}});
};
world.registerBehavior("tutorial.spawn_once", std::move(spawner));
// Ordinary C++ state belongs to the gameplay module. Every handle part matters.
using Key = std::tuple<std::uint64_t, std::uint32_t, std::uint64_t>;
auto ages = std::make_shared<std::map<Key, double>>();
auto key = [](runtime::EntityHandle h) { return Key{h.session, h.slot, h.generation}; };
runtime::Behavior lifetime;
lifetime.onStart = [ages, key](runtime::Runtime&, runtime::EntityHandle self, double) {
(*ages)[key(self)] = 0.0;
};
lifetime.fixedUpdate = [ages, key](runtime::Runtime& game, runtime::EntityHandle self,
double delta) {
auto& age = ages->at(key(self));
age += delta;
if (age >= game.fields(self, "tutorial.timed_despawn").value("seconds", 1.0))
game.destroy(self); // Still valid until the next fixed-tick barrier.
};
lifetime.onDestroy = [ages, key](runtime::Runtime&, runtime::EntityHandle self, double) {
ages->erase(key(self));
};
world.registerBehavior("tutorial.timed_despawn", std::move(lifetime));
}
nlohmann::json schema() {
return nlohmann::json::array(
{{{"id", "tutorial.spawn_once"},
{"version", 1},
{"name", "Spawn once"},
{"fields",
{{"spawned_id",
{{"id", "spawned_id"}, {"type", "string"}, {"default", "temporary-box"}}},
{"lifetime",
{{"id", "lifetime"}, {"type", "number"}, {"default", 1.0}, {"min", 0.0}}}}}},
{{"id", "tutorial.timed_despawn"},
{"version", 1},
{"name", "Timed despawn"},
{"fields",
{{"seconds",
{{"id", "seconds"}, {"type", "number"}, {"default", 1.0}, {"min", 0.0}}}}}}});
}
} // namespace faset::gameplay
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <faset/runtime/Runtime.hpp>
namespace faset::gameplay {
void registerGameplay(runtime::Runtime& world);
nlohmann::json schema();
} // namespace faset::gameplay
+58
View File
@@ -0,0 +1,58 @@
{
"format": "faset.scene",
"version": 1,
"id": "tutorial-spawning",
"name": "Spawning tutorial",
"dimension": 2,
"simulation": {
"fixed_delta": 0.016666666666666666,
"max_catch_up_ticks": 4,
"physics_substeps": 4,
"gravity": [
0,
-9.81,
0
]
},
"entities": [
{
"id": "spawner",
"name": "spawner",
"parent": null,
"components": [
{
"id": "spawner-faset.transform",
"type": "faset.transform",
"version": 1,
"fields": {
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
}
},
{
"id": "spawner-tutorial.spawn_once",
"type": "tutorial.spawn_once",
"version": 1,
"fields": {
"spawned_id": "temporary-box",
"lifetime": 1.0
}
}
]
}
],
"instances": []
}