png支持
This commit is contained in:
+26
-1
@@ -5,6 +5,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
add_library(boundard STATIC
|
||||
src/boundard.cpp
|
||||
src/game.cpp
|
||||
src/scene.cpp
|
||||
src/render.cpp
|
||||
src/config.cpp
|
||||
src/engine.cpp
|
||||
@@ -14,9 +15,33 @@ target_compile_features(boundard PUBLIC cxx_std_17)
|
||||
|
||||
find_package(glfw3 REQUIRED)
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp)
|
||||
target_link_libraries(boundard PUBLIC glfw Vulkan::Vulkan PkgConfig::JSONCPP)
|
||||
find_program(GLSLC_EXECUTABLE glslc REQUIRED)
|
||||
|
||||
set(BOUNDARD_SHADER_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders")
|
||||
set(BOUNDARD_SHADER_SOURCES
|
||||
shaders/textured_quad.vert
|
||||
shaders/textured_quad.frag
|
||||
)
|
||||
set(BOUNDARD_SHADER_BINARIES)
|
||||
foreach(shader IN LISTS BOUNDARD_SHADER_SOURCES)
|
||||
get_filename_component(shader_name "${shader}" NAME)
|
||||
set(shader_output "${BOUNDARD_SHADER_DIR}/${shader_name}.spv")
|
||||
add_custom_command(
|
||||
OUTPUT "${shader_output}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${BOUNDARD_SHADER_DIR}"
|
||||
COMMAND ${GLSLC_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" -o "${shader_output}"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${shader}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND BOUNDARD_SHADER_BINARIES "${shader_output}")
|
||||
endforeach()
|
||||
add_custom_target(boundard_shaders DEPENDS ${BOUNDARD_SHADER_BINARIES})
|
||||
add_dependencies(boundard boundard_shaders)
|
||||
target_compile_definitions(boundard PRIVATE BOUNDARD_SHADER_DIR="${BOUNDARD_SHADER_DIR}")
|
||||
target_link_libraries(boundard PUBLIC glfw Vulkan::Vulkan PNG::PNG PkgConfig::JSONCPP)
|
||||
|
||||
add_executable(square_example examples/square/main.cpp)
|
||||
target_link_libraries(square_example PRIVATE boundard)
|
||||
|
||||
+3
-4
@@ -2,10 +2,9 @@
|
||||
"version": 8,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "Clang",
|
||||
"displayName": "使用工具链文件配置预设",
|
||||
"description": "设置 Ninja 生成器、版本和安装目录",
|
||||
"generator": "Ninja",
|
||||
"name": "Boundard",
|
||||
"displayName": "Boundard",
|
||||
"description": "使用工具链文件配置预设",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
|
||||
@@ -1 +1 @@
|
||||
out/build/Clang/compile_commands.json
|
||||
out/build/Boundard/compile_commands.json
|
||||
+7
-15
@@ -2,25 +2,17 @@
|
||||
"window": {
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "OpenCGE - config.ceg"
|
||||
"title": "Boundard - config.ceg"
|
||||
},
|
||||
|
||||
"input": [
|
||||
{
|
||||
"action": "quit",
|
||||
"keys": ["Escape"]
|
||||
}
|
||||
{ "action": "quit", "keys": ["Escape"] }
|
||||
],
|
||||
|
||||
"actors": [
|
||||
{
|
||||
"name": "square",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"facing": { "x": 1, "y": 0 },
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"color": { "r": 0.5, "g": 0.5, "b": 0.5, "a": 1.0 }
|
||||
}
|
||||
}
|
||||
"start_scene": "main",
|
||||
|
||||
"scenes": [
|
||||
{ "name": "main", "path": "scenes/main.json" },
|
||||
{ "name": "second", "path": "scenes/second.json" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,29 +2,22 @@
|
||||
"window": {
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "OpenCGE - Square Example"
|
||||
"title": "Boundard - Square Example"
|
||||
},
|
||||
|
||||
"input": [
|
||||
{ "action": "quit", "keys": ["Escape"] },
|
||||
{ "action": "reset", "mouse_buttons": ["Left"] },
|
||||
{ "action": "switch_scene", "keys": ["Tab"] },
|
||||
{ "action": "move_left", "keys": ["Left"] },
|
||||
{ "action": "move_right", "keys": ["Right"] },
|
||||
{ "action": "move_up", "keys": ["Up"] },
|
||||
{ "action": "move_down", "keys": ["Down"] }
|
||||
],
|
||||
|
||||
"actors": [
|
||||
{
|
||||
"name": "square",
|
||||
"init": "square_init",
|
||||
"tick": "square_tick",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"facing": { "x": 1, "y": 0 },
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"color": { "r": 0.2, "g": 0.8, "b": 0.9, "a": 1.0 }
|
||||
}
|
||||
}
|
||||
"start_scene": "square",
|
||||
|
||||
"scenes": [
|
||||
{ "name": "square", "path": "scenes/square.json" },
|
||||
{ "name": "square_alt", "path": "scenes/square_alt.json" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,6 +38,47 @@ void register_square_behaviors() {
|
||||
position[0] += input.get_axis("move_left", "move_right") * move_speed * dt;
|
||||
position[1] += input.get_axis("move_up", "move_down") * move_speed * dt;
|
||||
actor.set_position(position);
|
||||
|
||||
if (input.is_action_just_pressed("switch_scene")) {
|
||||
boundard::Game::getInstance().switch_scene("square_alt");
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.register_init("square_alt_init", [](boundard::Actor& actor) {
|
||||
actor.set_position({0.0F, 0.0F});
|
||||
actor.set_facing({0.0F, 1.0F});
|
||||
if (actor.mesh() != nullptr) {
|
||||
actor.mesh()->set_color({0.9F, 0.3F, 0.2F, 1.0F});
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.register_tick(
|
||||
"square_alt_tick",
|
||||
[](boundard::Actor& actor, float dt, const boundard::render::InputMap& input) {
|
||||
const float t = actor.age();
|
||||
actor.set_facing({
|
||||
std::sin(t * 1.3F),
|
||||
std::cos(t * 1.3F),
|
||||
});
|
||||
|
||||
if (actor.mesh() != nullptr) {
|
||||
actor.mesh()->set_color({
|
||||
0.5F + 0.5F * std::sin(t * 2.0F + 4.189F),
|
||||
0.5F + 0.5F * std::sin(t * 2.0F),
|
||||
0.5F + 0.5F * std::sin(t * 2.0F + 2.094F),
|
||||
1.0F,
|
||||
});
|
||||
}
|
||||
|
||||
auto position = actor.position();
|
||||
const float move_speed = 0.7F;
|
||||
position[0] += input.get_axis("move_left", "move_right") * move_speed * dt;
|
||||
position[1] += input.get_axis("move_up", "move_down") * move_speed * dt;
|
||||
actor.set_position(position);
|
||||
|
||||
if (input.is_action_just_pressed("switch_scene")) {
|
||||
boundard::Game::getInstance().switch_scene("square");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"actors": [
|
||||
{
|
||||
"name": "square",
|
||||
"init": "square_init",
|
||||
"tick": "square_tick",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"facing": {
|
||||
"x": 1,
|
||||
"y": 0
|
||||
},
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"texture": "Dank.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"actors": [
|
||||
{
|
||||
"name": "square_alt",
|
||||
"init": "square_alt_init",
|
||||
"tick": "square_alt_tick",
|
||||
"position": { "x": 0, "y": 0 },
|
||||
"facing": { "x": 0, "y": 1 },
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"color": { "r": 0.9, "g": 0.3, "b": 0.2, "a": 1.0 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,4 +6,5 @@
|
||||
#include <behavior.hpp>
|
||||
#include <render.hpp>
|
||||
#include <config.hpp>
|
||||
#include <scene.hpp>
|
||||
#include <engine.hpp>
|
||||
|
||||
@@ -15,6 +15,7 @@ struct WindowConfig {
|
||||
|
||||
struct MeshConfig {
|
||||
std::string shape = "quad";
|
||||
std::string texture;
|
||||
std::array<float, 4> color{0.5F, 0.5F, 0.5F, 1.0F};
|
||||
};
|
||||
|
||||
@@ -33,9 +34,19 @@ struct InputBindingConfig {
|
||||
std::vector<int> mouse_buttons;
|
||||
};
|
||||
|
||||
struct SceneConfig {
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::vector<ActorConfig> actors;
|
||||
};
|
||||
|
||||
struct GameConfig {
|
||||
WindowConfig window;
|
||||
std::vector<InputBindingConfig> input_bindings;
|
||||
std::vector<SceneConfig> scenes;
|
||||
std::string start_scene;
|
||||
|
||||
// 兼容旧的单场景写法:直接写在游戏配置里的 actors。
|
||||
std::vector<ActorConfig> actors;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace boundard {
|
||||
|
||||
class Scene;
|
||||
|
||||
class Game {
|
||||
private:
|
||||
size_t id_cnt;
|
||||
std::string current_scene_name_;
|
||||
std::string pending_scene_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Scene>> scenes_;
|
||||
std::shared_ptr<Scene> current_scene_;
|
||||
|
||||
public:
|
||||
Game();
|
||||
@@ -17,5 +28,20 @@ class Game {
|
||||
static Game& getInstance();
|
||||
void launch();
|
||||
size_t new_id();
|
||||
|
||||
// 场景注册与查询。
|
||||
void add_scene(std::shared_ptr<Scene> scene);
|
||||
std::shared_ptr<Scene> scene(const std::string& name) const;
|
||||
std::shared_ptr<Scene> current_scene() const;
|
||||
const std::string& current_scene_name() const;
|
||||
bool has_scene(const std::string& name) const;
|
||||
|
||||
// 引擎在安全时机调用,把目标场景设为当前场景。
|
||||
void set_current_scene(std::shared_ptr<Scene> scene);
|
||||
|
||||
// 供行为函数调用的切换 API。场景会在主循环的安全时机切换。
|
||||
bool switch_scene(const std::string& name);
|
||||
void request_scene(const std::string& name);
|
||||
std::string take_scene_request();
|
||||
};
|
||||
} // namespace boundard
|
||||
|
||||
+7
-6
@@ -9,17 +9,18 @@
|
||||
|
||||
namespace boundard {
|
||||
|
||||
// 渲染组件:描述 Actor 的几何形状、颜色和纹理占位。
|
||||
// 渲染组件:描述 Actor 的几何形状、颜色和可选 PNG 纹理。
|
||||
class Mesh {
|
||||
public:
|
||||
struct Vertex {
|
||||
std::array<float, 2> position;
|
||||
std::array<float, 4> color;
|
||||
std::array<float, 2> uv;
|
||||
};
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
std::string texture; // 纹理路径占位,暂未采样。
|
||||
std::string texture; // PNG 纹理路径;为空时使用白色纹理。
|
||||
|
||||
std::array<float, 4> color() const {
|
||||
std::lock_guard<std::mutex> lock(*color_mutex_);
|
||||
@@ -34,10 +35,10 @@ class Mesh {
|
||||
static Mesh quad() {
|
||||
Mesh mesh;
|
||||
mesh.vertices = {
|
||||
{{-1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}},
|
||||
{{1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}},
|
||||
{{1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}},
|
||||
{{-1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}},
|
||||
{{-1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {0.0F, 0.0F}},
|
||||
{{1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {1.0F, 0.0F}},
|
||||
{{1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {1.0F, 1.0F}},
|
||||
{{-1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {0.0F, 1.0F}},
|
||||
};
|
||||
mesh.indices = {0, 1, 2, 2, 3, 0};
|
||||
return mesh;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace boundard::render {
|
||||
class InputMap;
|
||||
}
|
||||
|
||||
namespace boundard {
|
||||
|
||||
class Actor;
|
||||
|
||||
// 运行时场景:持有场景名和其中的 Actor,并统一驱动 Actor 的 init/tick。
|
||||
class Scene {
|
||||
public:
|
||||
explicit Scene(std::string name = {});
|
||||
~Scene();
|
||||
|
||||
Scene(const Scene&) = delete;
|
||||
Scene& operator=(const Scene&) = delete;
|
||||
Scene(Scene&&) noexcept = delete;
|
||||
Scene& operator=(Scene&&) noexcept = delete;
|
||||
|
||||
const std::string& name() const;
|
||||
void set_name(std::string name);
|
||||
|
||||
void add_actor(std::shared_ptr<Actor> actor);
|
||||
void set_actors(std::vector<std::shared_ptr<Actor>> actors);
|
||||
void clear();
|
||||
|
||||
const std::vector<Actor*>& actors() const;
|
||||
std::vector<Actor*>& actors();
|
||||
|
||||
void initialize();
|
||||
void process(float dt, const render::InputMap& input);
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::vector<std::shared_ptr<Actor>> actor_storage_;
|
||||
std::vector<Actor*> actors_;
|
||||
};
|
||||
|
||||
} // namespace boundard
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"actors": [
|
||||
{
|
||||
"name": "square",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"facing": {
|
||||
"x": 1,
|
||||
"y": 0
|
||||
},
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"texture": "Dank.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"actors": [
|
||||
{
|
||||
"name": "square",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"facing": {
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"mesh": {
|
||||
"shape": "quad",
|
||||
"texture": "assets/player.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(binding = 0) uniform sampler2D u_texture;
|
||||
|
||||
layout(location = 0) in vec4 v_color;
|
||||
layout(location = 1) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = texture(u_texture, v_uv) * v_color;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#version 450
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 model;
|
||||
vec4 color;
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 a_position;
|
||||
layout(location = 1) in vec4 a_color;
|
||||
layout(location = 2) in vec2 a_uv;
|
||||
|
||||
layout(location = 0) out vec4 v_color;
|
||||
layout(location = 1) out vec2 v_uv;
|
||||
|
||||
void main() {
|
||||
gl_Position = pc.model * vec4(a_position, 0.0, 1.0);
|
||||
v_color = a_color * pc.color;
|
||||
v_uv = a_uv;
|
||||
}
|
||||
+101
-8
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -154,6 +155,20 @@ void add_binding_code(const Json::Value& value, int type, std::vector<int>& outp
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_json_file(const std::string& path, Json::Value& root, std::string& error) {
|
||||
Json::CharReaderBuilder builder;
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
error = "无法打开 JSON 文件: " + path;
|
||||
return false;
|
||||
}
|
||||
if (!Json::parseFromStream(builder, file, &root, &error)) {
|
||||
error = "无法解析 JSON 文件: " + path + " - " + error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void parse_window(const Json::Value& root, WindowConfig& window) {
|
||||
if (!root.isMember("window") || !root["window"].isObject()) {
|
||||
return;
|
||||
@@ -252,15 +267,21 @@ ActorConfig parse_actor(const Json::Value& value) {
|
||||
if (mesh.isMember("shape")) {
|
||||
actor.mesh.shape = json_string(mesh["shape"], actor.mesh.shape);
|
||||
}
|
||||
if (mesh.isMember("texture")) {
|
||||
actor.mesh.texture = json_string(mesh["texture"], actor.mesh.texture);
|
||||
}
|
||||
parse_color(mesh, actor.mesh);
|
||||
}
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
void parse_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
|
||||
void extract_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
|
||||
const Json::Value* list = nullptr;
|
||||
|
||||
if (root.isArray()) {
|
||||
list = &root;
|
||||
} else if (root.isObject()) {
|
||||
if (root.isMember("actors") && root["actors"].isArray()) {
|
||||
list = &root["actors"];
|
||||
} else if (root.isMember("scene")) {
|
||||
@@ -271,6 +292,7 @@ void parse_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
|
||||
list = &scene["actors"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (list == nullptr) {
|
||||
return;
|
||||
@@ -287,16 +309,65 @@ void parse_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
|
||||
}
|
||||
}
|
||||
|
||||
void resolve_texture_paths(std::vector<ActorConfig>& actors,
|
||||
const std::filesystem::path& base_dir) {
|
||||
for (ActorConfig& actor : actors) {
|
||||
if (actor.mesh.texture.empty()) continue;
|
||||
std::filesystem::path texture_path(actor.mesh.texture);
|
||||
if (texture_path.is_relative()) {
|
||||
actor.mesh.texture = (base_dir / texture_path).lexically_normal().string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_scene_entry(const Json::Value& item, SceneConfig& scene,
|
||||
const std::filesystem::path& base_dir, std::string& error) {
|
||||
if (!item.isObject()) {
|
||||
error = "scenes 数组项必须是对象";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.isMember("name")) {
|
||||
scene.name = json_string(item["name"], "");
|
||||
}
|
||||
if (scene.name.empty()) {
|
||||
error = "场景缺少 name";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (item.isMember("path")) {
|
||||
path = json_string(item["path"], "");
|
||||
} else if (item.isMember("file")) {
|
||||
path = json_string(item["file"], "");
|
||||
}
|
||||
|
||||
if (!path.empty()) {
|
||||
std::filesystem::path scene_path(path);
|
||||
if (scene_path.is_relative()) {
|
||||
scene_path = base_dir / scene_path;
|
||||
}
|
||||
scene.path = scene_path.string();
|
||||
Json::Value root;
|
||||
if (!parse_json_file(scene_path.string(), root, error)) {
|
||||
return false;
|
||||
}
|
||||
extract_actors(root, scene.actors);
|
||||
resolve_texture_paths(scene.actors, scene_path.parent_path());
|
||||
} else {
|
||||
// 允许场景直接内联在游戏配置中。
|
||||
extract_actors(item, scene.actors);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConfigLoader::load(const std::string& path) {
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder builder;
|
||||
std::ifstream file(path);
|
||||
std::string errors;
|
||||
|
||||
if (!Json::parseFromStream(builder, file, &root, &errors)) {
|
||||
last_error_ = "无法解析 JSON 配置文件: " + path + " - " + errors;
|
||||
std::string error;
|
||||
if (!parse_json_file(path, root, error)) {
|
||||
last_error_ = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -306,9 +377,31 @@ bool ConfigLoader::load(const std::string& path) {
|
||||
}
|
||||
|
||||
config_ = GameConfig{};
|
||||
const std::filesystem::path config_path(path);
|
||||
const std::filesystem::path base_dir = config_path.parent_path();
|
||||
|
||||
parse_window(root, config_.window);
|
||||
parse_input(root, config_.input_bindings);
|
||||
parse_actors(root, config_.actors);
|
||||
|
||||
// 兼容旧式配置:根节点直接写 actors。
|
||||
extract_actors(root, config_.actors);
|
||||
resolve_texture_paths(config_.actors, base_dir);
|
||||
|
||||
if (root.isMember("start_scene")) {
|
||||
config_.start_scene = json_string(root["start_scene"], "");
|
||||
}
|
||||
|
||||
if (root.isMember("scenes") && root["scenes"].isArray()) {
|
||||
for (const Json::Value& item : root["scenes"]) {
|
||||
SceneConfig scene;
|
||||
if (!parse_scene_entry(item, scene, base_dir, error)) {
|
||||
last_error_ = error;
|
||||
return false;
|
||||
}
|
||||
config_.scenes.push_back(std::move(scene));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+94
-27
@@ -10,27 +10,10 @@
|
||||
#include <boundard.hpp>
|
||||
|
||||
namespace boundard {
|
||||
namespace {
|
||||
|
||||
int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
std::string config_path = default_config_path != nullptr ? default_config_path : "config.ceg";
|
||||
if (argc > 1) {
|
||||
config_path = argv[1];
|
||||
}
|
||||
|
||||
ConfigLoader loader;
|
||||
if (!loader.load(config_path)) {
|
||||
std::fprintf(stderr, "%s\n", loader.last_error().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const GameConfig& config = loader.config();
|
||||
Game& game = Game::getInstance();
|
||||
BehaviorRegistry& behaviors = BehaviorRegistry::getInstance();
|
||||
|
||||
std::vector<std::shared_ptr<Actor>> actor_storage;
|
||||
std::vector<Actor*> actors;
|
||||
|
||||
for (const ActorConfig& actor_config : config.actors) {
|
||||
std::shared_ptr<Actor> create_actor(const ActorConfig& actor_config, Game& game,
|
||||
BehaviorRegistry& behaviors) {
|
||||
auto mesh = std::make_shared<Mesh>();
|
||||
if (actor_config.mesh.shape == "quad") {
|
||||
*mesh = Mesh::quad();
|
||||
@@ -39,6 +22,7 @@ int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
*mesh = Mesh::quad();
|
||||
}
|
||||
mesh->set_color(actor_config.mesh.color);
|
||||
mesh->texture = actor_config.mesh.texture;
|
||||
|
||||
auto actor = std::make_shared<Actor>(actor_config.name, game, mesh);
|
||||
actor->set_position(actor_config.position);
|
||||
@@ -64,12 +48,74 @@ int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
}
|
||||
}
|
||||
|
||||
actors.push_back(actor.get());
|
||||
actor_storage.push_back(std::move(actor));
|
||||
return actor;
|
||||
}
|
||||
|
||||
for (auto& actor : actor_storage) {
|
||||
actor->initialize();
|
||||
std::shared_ptr<Scene> build_scene(const SceneConfig& scene, Game& game,
|
||||
BehaviorRegistry& behaviors) {
|
||||
auto runtime_scene = std::make_shared<Scene>(scene.name);
|
||||
|
||||
for (const ActorConfig& actor_config : scene.actors) {
|
||||
auto actor = create_actor(actor_config, game, behaviors);
|
||||
runtime_scene->add_actor(std::move(actor));
|
||||
}
|
||||
|
||||
return runtime_scene;
|
||||
}
|
||||
|
||||
const SceneConfig* find_scene(const std::vector<SceneConfig>& scenes,
|
||||
const std::string& name) {
|
||||
for (const SceneConfig& scene : scenes) {
|
||||
if (scene.name == name) {
|
||||
return &scene;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
std::string config_path = default_config_path != nullptr ? default_config_path : "config.ceg";
|
||||
if (argc > 1) {
|
||||
config_path = argv[1];
|
||||
}
|
||||
|
||||
ConfigLoader loader;
|
||||
if (!loader.load(config_path)) {
|
||||
std::fprintf(stderr, "%s\n", loader.last_error().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
const GameConfig& config = loader.config();
|
||||
Game& game = Game::getInstance();
|
||||
BehaviorRegistry& behaviors = BehaviorRegistry::getInstance();
|
||||
|
||||
std::vector<SceneConfig> scenes = config.scenes;
|
||||
if (scenes.empty() && !config.actors.empty()) {
|
||||
SceneConfig legacy;
|
||||
legacy.name = "default";
|
||||
legacy.path = config_path;
|
||||
legacy.actors = config.actors;
|
||||
scenes.push_back(std::move(legacy));
|
||||
}
|
||||
|
||||
std::string start_scene = config.start_scene;
|
||||
if (start_scene.empty() && !scenes.empty()) {
|
||||
start_scene = scenes.front().name;
|
||||
}
|
||||
|
||||
const SceneConfig* active_scene = find_scene(scenes, start_scene);
|
||||
if (active_scene == nullptr && !scenes.empty()) {
|
||||
active_scene = &scenes.front();
|
||||
std::fprintf(stderr, "未找到起始场景 %s,已回退到 %s\n", start_scene.c_str(),
|
||||
active_scene->name.c_str());
|
||||
}
|
||||
|
||||
// 把配置文件中的场景包装为运行时 Scene,并注册到 Game。
|
||||
for (const SceneConfig& scene_config : scenes) {
|
||||
std::shared_ptr<Scene> runtime_scene = build_scene(scene_config, game, behaviors);
|
||||
game.add_scene(std::move(runtime_scene));
|
||||
}
|
||||
|
||||
render::RenderLoop render_loop;
|
||||
@@ -87,7 +133,22 @@ int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
render_loop.input().add_action("quit");
|
||||
render_loop.input().bind_key("quit", 256);
|
||||
|
||||
render_loop.set_actors(std::move(actors));
|
||||
auto apply_scene = [&](const std::string& name) {
|
||||
std::shared_ptr<Scene> scene = game.scene(name);
|
||||
if (scene == nullptr) {
|
||||
std::fprintf(stderr, "未找到场景: %s\n", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
scene->initialize();
|
||||
game.set_current_scene(scene);
|
||||
render_loop.set_actors(scene->actors());
|
||||
};
|
||||
|
||||
if (active_scene != nullptr) {
|
||||
apply_scene(active_scene->name);
|
||||
}
|
||||
|
||||
render_loop.start(config.window.width, config.window.height,
|
||||
config.window.title.c_str());
|
||||
|
||||
@@ -98,12 +159,18 @@ int run_engine(int argc, char** argv, const char* default_config_path) {
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string requested_scene = game.take_scene_request();
|
||||
if (!requested_scene.empty() && requested_scene != game.current_scene_name()) {
|
||||
apply_scene(requested_scene);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const float dt = std::chrono::duration<float>(now - last).count();
|
||||
last = now;
|
||||
|
||||
for (auto& actor : actor_storage) {
|
||||
actor->process(dt, render_loop.input());
|
||||
std::shared_ptr<Scene> scene = game.current_scene();
|
||||
if (scene != nullptr) {
|
||||
scene->process(dt, render_loop.input());
|
||||
}
|
||||
|
||||
render_loop.input().end_frame();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include <game.hpp>
|
||||
|
||||
#include <scene.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace boundard {
|
||||
|
||||
Game::Game() { id_cnt = 0; }
|
||||
@@ -14,4 +18,52 @@ void Game::launch() {}
|
||||
|
||||
size_t Game::new_id() { return id_cnt++; }
|
||||
|
||||
void Game::add_scene(std::shared_ptr<Scene> scene) {
|
||||
if (scene == nullptr) {
|
||||
return;
|
||||
}
|
||||
scenes_[scene->name()] = std::move(scene);
|
||||
}
|
||||
|
||||
std::shared_ptr<Scene> Game::scene(const std::string& name) const {
|
||||
const auto it = scenes_.find(name);
|
||||
if (it == scenes_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<Scene> Game::current_scene() const {
|
||||
return current_scene_;
|
||||
}
|
||||
|
||||
const std::string& Game::current_scene_name() const {
|
||||
return current_scene_name_;
|
||||
}
|
||||
|
||||
bool Game::has_scene(const std::string& name) const {
|
||||
return scenes_.find(name) != scenes_.end();
|
||||
}
|
||||
|
||||
void Game::set_current_scene(std::shared_ptr<Scene> scene) {
|
||||
current_scene_ = std::move(scene);
|
||||
current_scene_name_ = current_scene_ != nullptr ? current_scene_->name() : "";
|
||||
}
|
||||
|
||||
bool Game::switch_scene(const std::string& name) {
|
||||
const bool exists = has_scene(name);
|
||||
pending_scene_ = name;
|
||||
return exists;
|
||||
}
|
||||
|
||||
void Game::request_scene(const std::string& name) {
|
||||
switch_scene(name);
|
||||
}
|
||||
|
||||
std::string Game::take_scene_request() {
|
||||
std::string request = std::move(pending_scene_);
|
||||
pending_scene_.clear();
|
||||
return request;
|
||||
}
|
||||
|
||||
} // namespace boundard
|
||||
|
||||
+253
-132
@@ -9,7 +9,10 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <png.h>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace boundard::render {
|
||||
@@ -133,6 +136,41 @@ VkShaderModule create_shader_module(VkDevice device, const uint32_t* code, size_
|
||||
return module;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> load_shader(const char* name) {
|
||||
const std::string path = std::string(BOUNDARD_SHADER_DIR) + "/" + name;
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("failed to open shader: " + path);
|
||||
}
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0 || size % static_cast<std::streamsize>(sizeof(uint32_t)) != 0) {
|
||||
throw std::runtime_error("invalid SPIR-V shader: " + path);
|
||||
}
|
||||
std::vector<uint32_t> code(static_cast<size_t>(size) / sizeof(uint32_t));
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char*>(code.data()), size);
|
||||
if (!file) {
|
||||
throw std::runtime_error("failed to read shader: " + path);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
bool load_png_rgba(const std::string& path, uint32_t& width, uint32_t& height,
|
||||
std::vector<unsigned char>& pixels) {
|
||||
png_image image{};
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
if (!png_image_begin_read_from_file(&image, path.c_str())) {
|
||||
return false;
|
||||
}
|
||||
image.format = PNG_FORMAT_RGBA;
|
||||
width = image.width;
|
||||
height = image.height;
|
||||
pixels.resize(PNG_IMAGE_SIZE(image));
|
||||
const bool loaded = png_image_finish_read(&image, nullptr, pixels.data(), 0, nullptr) != 0;
|
||||
png_image_free(&image);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Renderer::Impl {
|
||||
@@ -157,7 +195,6 @@ struct Renderer::Impl {
|
||||
VkRenderPass render_pass = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
|
||||
@@ -165,18 +202,19 @@ struct Renderer::Impl {
|
||||
std::vector<FrameData> frames;
|
||||
std::vector<VkFence> images_in_flight;
|
||||
|
||||
struct MeshResource {
|
||||
VkBuffer vertex_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE;
|
||||
VkBuffer index_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE;
|
||||
uint32_t cached_index_count = 0;
|
||||
uint32_t cached_vertex_count = 0;
|
||||
const Mesh* cached_mesh = nullptr;
|
||||
bool mesh_uploaded = false;
|
||||
VkBuffer uniform_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory uniform_buffer_memory = VK_NULL_HANDLE;
|
||||
VkDeviceSize uniform_buffer_size = sizeof(float) * 16;
|
||||
void* uniform_mapped = nullptr;
|
||||
uint32_t index_count = 0;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory image_memory = VK_NULL_HANDLE;
|
||||
VkImageView image_view = VK_NULL_HANDLE;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
||||
};
|
||||
std::unordered_map<const Mesh*, MeshResource> mesh_resources;
|
||||
|
||||
size_t current_frame = 0;
|
||||
bool initialized = false;
|
||||
@@ -185,6 +223,9 @@ struct Renderer::Impl {
|
||||
void init(int width, int height, const char* title);
|
||||
void draw(const Actor* const* actors, size_t actor_count);
|
||||
void upload_mesh(const Mesh& mesh);
|
||||
void create_texture(MeshResource& resource, const std::string& path);
|
||||
VkCommandBuffer begin_transfer();
|
||||
void end_transfer(VkCommandBuffer command_buffer);
|
||||
void destroy();
|
||||
};
|
||||
|
||||
@@ -856,9 +897,9 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
|
||||
VkDescriptorSetLayoutBinding layout_binding{};
|
||||
layout_binding.binding = 0;
|
||||
layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
layout_binding.descriptorCount = 1;
|
||||
layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo descriptor_layout_info{};
|
||||
descriptor_layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
@@ -871,7 +912,7 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
VkPushConstantRange push_constant_range{};
|
||||
push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
push_constant_range.offset = 0;
|
||||
push_constant_range.size = sizeof(float) * 4;
|
||||
push_constant_range.size = sizeof(float) * 20;
|
||||
|
||||
VkPipelineLayoutCreateInfo pipeline_layout_info{};
|
||||
pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
@@ -883,50 +924,23 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
"vkCreatePipelineLayout");
|
||||
|
||||
VkDescriptorPoolSize pool_size{};
|
||||
pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
pool_size.descriptorCount = 1;
|
||||
pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
pool_size.descriptorCount = 1024;
|
||||
|
||||
VkDescriptorPoolCreateInfo pool_info{};
|
||||
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
pool_info.maxSets = 1;
|
||||
pool_info.maxSets = 1024;
|
||||
pool_info.poolSizeCount = 1;
|
||||
pool_info.pPoolSizes = &pool_size;
|
||||
check(vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptor_pool),
|
||||
"vkCreateDescriptorPool");
|
||||
|
||||
VkDescriptorSetAllocateInfo set_allocate_info{};
|
||||
set_allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
set_allocate_info.descriptorPool = descriptor_pool;
|
||||
set_allocate_info.descriptorSetCount = 1;
|
||||
set_allocate_info.pSetLayouts = &descriptor_set_layout;
|
||||
check(vkAllocateDescriptorSets(device, &set_allocate_info, &descriptor_set),
|
||||
"vkAllocateDescriptorSets");
|
||||
|
||||
create_buffer(physical_device, device, uniform_buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
uniform_buffer, uniform_buffer_memory);
|
||||
check(vkMapMemory(device, uniform_buffer_memory, 0, uniform_buffer_size, 0, &uniform_mapped),
|
||||
"vkMapMemory");
|
||||
|
||||
VkDescriptorBufferInfo buffer_info{};
|
||||
buffer_info.buffer = uniform_buffer;
|
||||
buffer_info.offset = 0;
|
||||
buffer_info.range = uniform_buffer_size;
|
||||
|
||||
VkWriteDescriptorSet descriptor_write{};
|
||||
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptor_write.dstSet = descriptor_set;
|
||||
descriptor_write.dstBinding = 0;
|
||||
descriptor_write.dstArrayElement = 0;
|
||||
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
descriptor_write.descriptorCount = 1;
|
||||
descriptor_write.pBufferInfo = &buffer_info;
|
||||
vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr);
|
||||
|
||||
const std::vector<uint32_t> vertex_shader = load_shader("textured_quad.vert.spv");
|
||||
const std::vector<uint32_t> fragment_shader = load_shader("textured_quad.frag.spv");
|
||||
VkShaderModule vertex_module = create_shader_module(
|
||||
device, kQuadVertShader.data(), kQuadVertShader.size() * sizeof(kQuadVertShader[0]));
|
||||
device, vertex_shader.data(), vertex_shader.size() * sizeof(uint32_t));
|
||||
VkShaderModule fragment_module = create_shader_module(
|
||||
device, kQuadFragShader.data(), kQuadFragShader.size() * sizeof(kQuadFragShader[0]));
|
||||
device, fragment_shader.data(), fragment_shader.size() * sizeof(uint32_t));
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertex_stage{};
|
||||
vertex_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
@@ -947,7 +961,7 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
vertex_binding.stride = sizeof(Mesh::Vertex);
|
||||
vertex_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
|
||||
std::array<VkVertexInputAttributeDescription, 2> vertex_attributes{};
|
||||
std::array<VkVertexInputAttributeDescription, 3> vertex_attributes{};
|
||||
vertex_attributes[0].binding = 0;
|
||||
vertex_attributes[0].location = 0;
|
||||
vertex_attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
|
||||
@@ -956,6 +970,10 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
vertex_attributes[1].location = 1;
|
||||
vertex_attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
vertex_attributes[1].offset = offsetof(Mesh::Vertex, color);
|
||||
vertex_attributes[2].binding = 0;
|
||||
vertex_attributes[2].location = 2;
|
||||
vertex_attributes[2].format = VK_FORMAT_R32G32_SFLOAT;
|
||||
vertex_attributes[2].offset = offsetof(Mesh::Vertex, uv);
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input{};
|
||||
vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
@@ -1005,7 +1023,13 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
multisampling.sampleShadingEnable = VK_FALSE;
|
||||
|
||||
VkPipelineColorBlendAttachmentState color_blend_attachment{};
|
||||
color_blend_attachment.blendEnable = VK_FALSE;
|
||||
color_blend_attachment.blendEnable = VK_TRUE;
|
||||
color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
color_blend_attachment.colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
|
||||
VK_COLOR_COMPONENT_A_BIT;
|
||||
@@ -1040,58 +1064,172 @@ void Renderer::Impl::init(int width, int height, const char* title) {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
VkCommandBuffer Renderer::Impl::begin_transfer() {
|
||||
VkCommandBufferAllocateInfo allocate_info{};
|
||||
allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocate_info.commandPool = command_pool;
|
||||
allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocate_info.commandBufferCount = 1;
|
||||
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
|
||||
check(vkAllocateCommandBuffers(device, &allocate_info, &command_buffer), "vkAllocateCommandBuffers");
|
||||
VkCommandBufferBeginInfo begin_info{};
|
||||
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
check(vkBeginCommandBuffer(command_buffer, &begin_info), "vkBeginCommandBuffer");
|
||||
return command_buffer;
|
||||
}
|
||||
|
||||
void Renderer::Impl::end_transfer(VkCommandBuffer command_buffer) {
|
||||
check(vkEndCommandBuffer(command_buffer), "vkEndCommandBuffer");
|
||||
VkSubmitInfo submit_info{};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &command_buffer;
|
||||
check(vkQueueSubmit(graphics_queue, 1, &submit_info, VK_NULL_HANDLE), "vkQueueSubmit");
|
||||
check(vkQueueWaitIdle(graphics_queue), "vkQueueWaitIdle");
|
||||
vkFreeCommandBuffers(device, command_pool, 1, &command_buffer);
|
||||
}
|
||||
|
||||
void Renderer::Impl::create_texture(MeshResource& resource, const std::string& path) {
|
||||
uint32_t width = 1;
|
||||
uint32_t height = 1;
|
||||
std::vector<unsigned char> pixels = {255, 255, 255, 255};
|
||||
if (!path.empty() && !load_png_rgba(path, width, height, pixels)) {
|
||||
std::fprintf(stderr, "无法加载 PNG 纹理 %s,已使用白色纹理\n", path.c_str());
|
||||
width = 1;
|
||||
height = 1;
|
||||
pixels = {255, 255, 255, 255};
|
||||
}
|
||||
|
||||
VkBuffer staging_buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory staging_memory = VK_NULL_HANDLE;
|
||||
const VkDeviceSize size = static_cast<VkDeviceSize>(pixels.size());
|
||||
create_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
staging_buffer, staging_memory);
|
||||
void* mapped = nullptr;
|
||||
check(vkMapMemory(device, staging_memory, 0, size, 0, &mapped), "vkMapMemory");
|
||||
std::memcpy(mapped, pixels.data(), pixels.size());
|
||||
vkUnmapMemory(device, staging_memory);
|
||||
|
||||
VkImageCreateInfo image_info{};
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.extent = {width, height, 1};
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
check(vkCreateImage(device, &image_info, nullptr, &resource.image), "vkCreateImage");
|
||||
VkMemoryRequirements requirements{};
|
||||
vkGetImageMemoryRequirements(device, resource.image, &requirements);
|
||||
VkMemoryAllocateInfo alloc_info{};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc_info.allocationSize = requirements.size;
|
||||
alloc_info.memoryTypeIndex = find_memory_type(physical_device, requirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
check(vkAllocateMemory(device, &alloc_info, nullptr, &resource.image_memory), "vkAllocateMemory");
|
||||
check(vkBindImageMemory(device, resource.image, resource.image_memory, 0), "vkBindImageMemory");
|
||||
|
||||
VkCommandBuffer command_buffer = begin_transfer();
|
||||
VkImageMemoryBarrier barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = resource.image;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
VkBufferImageCopy region{};
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageExtent = {width, height, 1};
|
||||
vkCmdCopyBufferToImage(command_buffer, staging_buffer, resource.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
end_transfer(command_buffer);
|
||||
vkDestroyBuffer(device, staging_buffer, nullptr);
|
||||
vkFreeMemory(device, staging_memory, nullptr);
|
||||
|
||||
VkImageViewCreateInfo view_info{};
|
||||
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
view_info.image = resource.image;
|
||||
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
view_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
view_info.subresourceRange.levelCount = 1;
|
||||
view_info.subresourceRange.layerCount = 1;
|
||||
check(vkCreateImageView(device, &view_info, nullptr, &resource.image_view), "vkCreateImageView");
|
||||
VkSamplerCreateInfo sampler_info{};
|
||||
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
sampler_info.magFilter = VK_FILTER_LINEAR;
|
||||
sampler_info.minFilter = VK_FILTER_LINEAR;
|
||||
sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
sampler_info.maxLod = 0.0F;
|
||||
check(vkCreateSampler(device, &sampler_info, nullptr, &resource.sampler), "vkCreateSampler");
|
||||
|
||||
VkDescriptorSetAllocateInfo set_info{};
|
||||
set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
set_info.descriptorPool = descriptor_pool;
|
||||
set_info.descriptorSetCount = 1;
|
||||
set_info.pSetLayouts = &descriptor_set_layout;
|
||||
check(vkAllocateDescriptorSets(device, &set_info, &resource.descriptor_set), "vkAllocateDescriptorSets");
|
||||
VkDescriptorImageInfo image_descriptor{};
|
||||
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
image_descriptor.imageView = resource.image_view;
|
||||
image_descriptor.sampler = resource.sampler;
|
||||
VkWriteDescriptorSet write{};
|
||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
write.dstSet = resource.descriptor_set;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = &image_descriptor;
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
|
||||
void Renderer::Impl::upload_mesh(const Mesh& mesh) {
|
||||
if (cached_mesh == &mesh && mesh_uploaded) {
|
||||
return;
|
||||
}
|
||||
if (mesh_resources.find(&mesh) != mesh_resources.end()) return;
|
||||
MeshResource& resource = mesh_resources[&mesh];
|
||||
resource.index_count = static_cast<uint32_t>(mesh.indices.size());
|
||||
if (mesh.vertices.empty() || resource.index_count == 0) return;
|
||||
|
||||
if (vertex_buffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, vertex_buffer, nullptr);
|
||||
vertex_buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
if (vertex_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, vertex_buffer_memory, nullptr);
|
||||
vertex_buffer_memory = VK_NULL_HANDLE;
|
||||
}
|
||||
if (index_buffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, index_buffer, nullptr);
|
||||
index_buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
if (index_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, index_buffer_memory, nullptr);
|
||||
index_buffer_memory = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
cached_vertex_count = static_cast<uint32_t>(mesh.vertices.size());
|
||||
cached_index_count = static_cast<uint32_t>(mesh.indices.size());
|
||||
cached_mesh = &mesh;
|
||||
mesh_uploaded = false;
|
||||
|
||||
if (cached_vertex_count == 0 || cached_index_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VkDeviceSize vertex_size = sizeof(Mesh::Vertex) * cached_vertex_count;
|
||||
const VkDeviceSize vertex_size = sizeof(Mesh::Vertex) * mesh.vertices.size();
|
||||
create_buffer(physical_device, device, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
vertex_buffer, vertex_buffer_memory);
|
||||
resource.vertex_buffer, resource.vertex_buffer_memory);
|
||||
void* vertex_mapped = nullptr;
|
||||
check(vkMapMemory(device, vertex_buffer_memory, 0, vertex_size, 0, &vertex_mapped),
|
||||
check(vkMapMemory(device, resource.vertex_buffer_memory, 0, vertex_size, 0, &vertex_mapped),
|
||||
"vkMapMemory");
|
||||
std::memcpy(vertex_mapped, mesh.vertices.data(), vertex_size);
|
||||
vkUnmapMemory(device, vertex_buffer_memory);
|
||||
vkUnmapMemory(device, resource.vertex_buffer_memory);
|
||||
|
||||
const VkDeviceSize index_size = sizeof(uint32_t) * cached_index_count;
|
||||
const VkDeviceSize index_size = sizeof(uint32_t) * resource.index_count;
|
||||
create_buffer(physical_device, device, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||
index_buffer, index_buffer_memory);
|
||||
resource.index_buffer, resource.index_buffer_memory);
|
||||
void* index_mapped = nullptr;
|
||||
check(vkMapMemory(device, index_buffer_memory, 0, index_size, 0, &index_mapped),
|
||||
check(vkMapMemory(device, resource.index_buffer_memory, 0, index_size, 0, &index_mapped),
|
||||
"vkMapMemory");
|
||||
std::memcpy(index_mapped, mesh.indices.data(), index_size);
|
||||
vkUnmapMemory(device, index_buffer_memory);
|
||||
|
||||
mesh_uploaded = true;
|
||||
vkUnmapMemory(device, resource.index_buffer_memory);
|
||||
create_texture(resource, mesh.texture);
|
||||
}
|
||||
|
||||
void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
|
||||
@@ -1120,7 +1258,6 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
|
||||
images_in_flight[image_index] = frame.in_flight;
|
||||
check(vkResetFences(device, 1, &frame.in_flight), "vkResetFences");
|
||||
|
||||
// 示例渲染器一次上传一个 Mesh;最后一个 Actor 的网格用于绑定。
|
||||
for (size_t i = 0; i < actor_count; ++i) {
|
||||
const Actor& actor = *actors[i];
|
||||
const auto& mesh = actor.mesh();
|
||||
@@ -1149,23 +1286,20 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
|
||||
|
||||
vkCmdBeginRenderPass(frame.command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vkCmdBindPipeline(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
|
||||
0, 1, &descriptor_set, 0, nullptr);
|
||||
|
||||
if (vertex_buffer == VK_NULL_HANDLE || index_buffer == VK_NULL_HANDLE ||
|
||||
cached_index_count == 0 || cached_vertex_count == 0) {
|
||||
vkCmdEndRenderPass(frame.command_buffer);
|
||||
check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer");
|
||||
} else {
|
||||
VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(frame.command_buffer, 0, 1, &vertex_buffer, &offset);
|
||||
vkCmdBindIndexBuffer(frame.command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
|
||||
for (size_t i = 0; i < actor_count; ++i) {
|
||||
const Actor& actor = *actors[i];
|
||||
if (actor.mesh() == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (actor.mesh() == nullptr) continue;
|
||||
const auto resource_it = mesh_resources.find(actor.mesh().get());
|
||||
if (resource_it == mesh_resources.end()) continue;
|
||||
const MeshResource& resource = resource_it->second;
|
||||
if (resource.vertex_buffer == VK_NULL_HANDLE || resource.index_buffer == VK_NULL_HANDLE ||
|
||||
resource.descriptor_set == VK_NULL_HANDLE || resource.index_count == 0) continue;
|
||||
|
||||
VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(frame.command_buffer, 0, 1, &resource.vertex_buffer, &offset);
|
||||
vkCmdBindIndexBuffer(frame.command_buffer, resource.index_buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
|
||||
0, 1, &resource.descriptor_set, 0, nullptr);
|
||||
|
||||
const auto position = actor.position();
|
||||
const auto facing = actor.facing();
|
||||
@@ -1181,24 +1315,22 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
|
||||
facing_y = 0.0F;
|
||||
}
|
||||
|
||||
// 朝向向量作为局部 X 轴,旋转 + 平移,把单位四边形放到 Actor 的屏幕坐标。
|
||||
std::array<float, 16> model = {{
|
||||
std::array<float, 20> constants = {{
|
||||
scale * facing_x, scale * facing_y, 0.0F, 0.0F,
|
||||
-scale * facing_y, scale * facing_x, 0.0F, 0.0F,
|
||||
0.0F, 0.0F, 1.0F, 0.0F,
|
||||
position[0], position[1], 0.0F, 1.0F,
|
||||
0.0F, 0.0F, 0.0F, 0.0F,
|
||||
}};
|
||||
std::memcpy(uniform_mapped, model.data(), uniform_buffer_size);
|
||||
|
||||
const std::array<float, 4> color = actor.mesh()->color();
|
||||
std::copy(color.begin(), color.end(), constants.begin() + 16);
|
||||
vkCmdPushConstants(frame.command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||
sizeof(float) * 4, color.data());
|
||||
vkCmdDrawIndexed(frame.command_buffer, cached_index_count, 1, 0, 0, 0);
|
||||
sizeof(constants), constants.data());
|
||||
vkCmdDrawIndexed(frame.command_buffer, resource.index_count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
vkCmdEndRenderPass(frame.command_buffer);
|
||||
check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer");
|
||||
}
|
||||
|
||||
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
VkSubmitInfo submit_info{};
|
||||
@@ -1208,6 +1340,7 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) {
|
||||
submit_info.pWaitDstStageMask = &wait_stage;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &frame.command_buffer;
|
||||
// Present 不提供 fence;同一交换链图像再次被获取前,不能重用其等待的信号量。
|
||||
FrameData& image_frame = frames[image_index];
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &image_frame.render_finished;
|
||||
@@ -1238,11 +1371,6 @@ void Renderer::Impl::destroy() {
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
if (uniform_mapped != nullptr && uniform_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkUnmapMemory(device, uniform_buffer_memory);
|
||||
uniform_mapped = nullptr;
|
||||
}
|
||||
|
||||
for (FrameData& frame : frames) {
|
||||
if (frame.in_flight != VK_NULL_HANDLE) {
|
||||
vkDestroyFence(device, frame.in_flight, nullptr);
|
||||
@@ -1258,24 +1386,17 @@ void Renderer::Impl::destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
if (uniform_buffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, uniform_buffer, nullptr);
|
||||
}
|
||||
if (uniform_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, uniform_buffer_memory, nullptr);
|
||||
}
|
||||
if (vertex_buffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, vertex_buffer, nullptr);
|
||||
}
|
||||
if (vertex_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, vertex_buffer_memory, nullptr);
|
||||
}
|
||||
if (index_buffer != VK_NULL_HANDLE) {
|
||||
vkDestroyBuffer(device, index_buffer, nullptr);
|
||||
}
|
||||
if (index_buffer_memory != VK_NULL_HANDLE) {
|
||||
vkFreeMemory(device, index_buffer_memory, nullptr);
|
||||
for (auto& [mesh, resource] : mesh_resources) {
|
||||
if (resource.sampler != VK_NULL_HANDLE) vkDestroySampler(device, resource.sampler, nullptr);
|
||||
if (resource.image_view != VK_NULL_HANDLE) vkDestroyImageView(device, resource.image_view, nullptr);
|
||||
if (resource.image != VK_NULL_HANDLE) vkDestroyImage(device, resource.image, nullptr);
|
||||
if (resource.image_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.image_memory, nullptr);
|
||||
if (resource.vertex_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.vertex_buffer, nullptr);
|
||||
if (resource.vertex_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.vertex_buffer_memory, nullptr);
|
||||
if (resource.index_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.index_buffer, nullptr);
|
||||
if (resource.index_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.index_buffer_memory, nullptr);
|
||||
}
|
||||
mesh_resources.clear();
|
||||
|
||||
if (descriptor_pool != VK_NULL_HANDLE) {
|
||||
vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <scene.hpp>
|
||||
|
||||
#include <actor.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace boundard {
|
||||
|
||||
Scene::Scene(std::string name) : name_(std::move(name)) {}
|
||||
|
||||
Scene::~Scene() = default;
|
||||
|
||||
const std::string& Scene::name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
void Scene::set_name(std::string name) {
|
||||
name_ = std::move(name);
|
||||
}
|
||||
|
||||
void Scene::add_actor(std::shared_ptr<Actor> actor) {
|
||||
if (actor == nullptr) {
|
||||
return;
|
||||
}
|
||||
actor_storage_.push_back(actor);
|
||||
actors_.push_back(actor.get());
|
||||
}
|
||||
|
||||
void Scene::set_actors(std::vector<std::shared_ptr<Actor>> actors) {
|
||||
clear();
|
||||
for (std::shared_ptr<Actor>& actor : actors) {
|
||||
add_actor(std::move(actor));
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::clear() {
|
||||
actors_.clear();
|
||||
actor_storage_.clear();
|
||||
}
|
||||
|
||||
const std::vector<Actor*>& Scene::actors() const {
|
||||
return actors_;
|
||||
}
|
||||
|
||||
std::vector<Actor*>& Scene::actors() {
|
||||
return actors_;
|
||||
}
|
||||
|
||||
void Scene::initialize() {
|
||||
for (Actor* actor : actors_) {
|
||||
actor->initialize();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::process(float dt, const render::InputMap& input) {
|
||||
for (Actor* actor : actors_) {
|
||||
actor->process(dt, input);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace boundard
|
||||
Reference in New Issue
Block a user