aa
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 100
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AlignTrailingComments: true
|
||||
@@ -0,0 +1,4 @@
|
||||
out
|
||||
.vscode
|
||||
.cache
|
||||
.idea
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "third_party/ConsoleLib"]
|
||||
path = third_party/ConsoleLib
|
||||
url = https://github.com/ZeroOSProject/ConsoleLib.git
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.10.0)
|
||||
project(opencge VERSION 0.1.0 LANGUAGES C CXX)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
add_library(opencge STATIC
|
||||
src/opencge.cpp
|
||||
src/game.cpp
|
||||
src/render.cpp
|
||||
src/config.cpp
|
||||
src/engine.cpp
|
||||
)
|
||||
target_include_directories(opencge PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_compile_features(opencge PUBLIC cxx_std_17)
|
||||
|
||||
find_package(glfw3 REQUIRED)
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp)
|
||||
target_link_libraries(opencge PUBLIC glfw Vulkan::Vulkan PkgConfig::JSONCPP)
|
||||
|
||||
add_executable(square_example examples/square/main.cpp)
|
||||
target_link_libraries(square_example PRIVATE opencge)
|
||||
|
||||
add_executable(opencge_engine src/engine_main.cpp)
|
||||
target_link_libraries(opencge_engine PRIVATE opencge)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 8,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "Clang",
|
||||
"displayName": "使用工具链文件配置预设",
|
||||
"description": "设置 Ninja 生成器、版本和安装目录",
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_TOOLCHAIN_FILE": "",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
out/build/Clang/compile_commands.json
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"window": {
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "OpenCGE - config.ceg"
|
||||
},
|
||||
|
||||
"input": [
|
||||
{
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"window": {
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "OpenCGE - Square Example"
|
||||
},
|
||||
|
||||
"input": [
|
||||
{ "action": "quit", "keys": ["Escape"] },
|
||||
{ "action": "reset", "mouse_buttons": ["Left"] },
|
||||
{ "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 }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <openceg.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
void register_square_behaviors() {
|
||||
auto& behaviors = openceg::BehaviorRegistry::getInstance();
|
||||
|
||||
behaviors.register_init("square_init", [](openceg::Actor& actor) {
|
||||
actor.set_position({0.0F, 0.0F});
|
||||
actor.set_facing({1.0F, 0.0F});
|
||||
if (actor.mesh() != nullptr) {
|
||||
actor.mesh()->set_color({0.2F, 0.8F, 0.9F, 1.0F});
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.register_tick(
|
||||
"square_tick",
|
||||
[](openceg::Actor& actor, float dt, const openceg::render::InputMap& input) {
|
||||
const float t = actor.age();
|
||||
actor.set_facing({
|
||||
std::cos(t * 1.5F),
|
||||
std::sin(t * 1.5F),
|
||||
});
|
||||
|
||||
if (actor.mesh() != nullptr) {
|
||||
actor.mesh()->set_color({
|
||||
0.5F + 0.5F * std::sin(t * 2.0F),
|
||||
0.5F + 0.5F * std::sin(t * 2.0F + 2.094F),
|
||||
0.5F + 0.5F * std::sin(t * 2.0F + 4.189F),
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
register_square_behaviors();
|
||||
return openceg::run_engine(argc, argv, "examples/square/config.ceg");
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <game.hpp>
|
||||
#include <mesh.hpp>
|
||||
|
||||
namespace openceg::render {
|
||||
class InputMap;
|
||||
}
|
||||
|
||||
namespace openceg {
|
||||
|
||||
class Actor {
|
||||
private:
|
||||
size_t id;
|
||||
std::string nickName_;
|
||||
mutable std::mutex transform_mutex_;
|
||||
std::array<float, 2> position_{0.0F, 0.0F};
|
||||
std::array<float, 2> facing_{1.0F, 0.0F};
|
||||
std::shared_ptr<Mesh> mesh_;
|
||||
|
||||
float age_ = 0.0F;
|
||||
bool initialized_ = false;
|
||||
std::function<void(Actor&)> init_handler_;
|
||||
std::function<void(Actor&, float, const render::InputMap&)> tick_handler_;
|
||||
|
||||
public:
|
||||
Actor() noexcept = default;
|
||||
virtual ~Actor() = default;
|
||||
|
||||
Actor(const Actor&) = delete;
|
||||
Actor& operator=(const Actor&) = delete;
|
||||
Actor(Actor&&) noexcept = delete;
|
||||
Actor& operator=(Actor&&) noexcept = delete;
|
||||
|
||||
Actor(std::string nickName, Game& game) {
|
||||
id = game.new_id();
|
||||
nickName_ = std::move(nickName);
|
||||
}
|
||||
|
||||
Actor(std::string nickName, Game& game, std::shared_ptr<Mesh> mesh)
|
||||
: Actor(std::move(nickName), game) {
|
||||
mesh_ = std::move(mesh);
|
||||
}
|
||||
|
||||
static Actor& getInstance() {
|
||||
static Actor instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
const std::string& name() const { return nickName_; }
|
||||
|
||||
float age() const { return age_; }
|
||||
|
||||
void set_init_handler(std::function<void(Actor&)> handler) {
|
||||
init_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void set_tick_handler(
|
||||
std::function<void(Actor&, float, const render::InputMap&)> handler) {
|
||||
tick_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
// 类似 Godot 的 _ready()。
|
||||
void initialize() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
initialized_ = true;
|
||||
if (init_handler_) {
|
||||
init_handler_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// 类似 Godot 的 _process(delta)。
|
||||
void process(float dt, const render::InputMap& input) {
|
||||
age_ += dt;
|
||||
if (tick_handler_) {
|
||||
tick_handler_(*this, dt, input);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<float, 2> position() const {
|
||||
std::lock_guard<std::mutex> lock(transform_mutex_);
|
||||
return position_;
|
||||
}
|
||||
|
||||
void set_position(std::array<float, 2> position) {
|
||||
std::lock_guard<std::mutex> lock(transform_mutex_);
|
||||
position_ = position;
|
||||
}
|
||||
|
||||
std::array<float, 2> facing() const {
|
||||
std::lock_guard<std::mutex> lock(transform_mutex_);
|
||||
return facing_;
|
||||
}
|
||||
|
||||
void set_facing(std::array<float, 2> facing) {
|
||||
std::lock_guard<std::mutex> lock(transform_mutex_);
|
||||
facing_ = facing;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Mesh>& mesh() const { return mesh_; }
|
||||
void set_mesh(std::shared_ptr<Mesh> mesh) { mesh_ = std::move(mesh); }
|
||||
};
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <actor.hpp>
|
||||
|
||||
namespace openceg::render {
|
||||
class InputMap;
|
||||
}
|
||||
|
||||
namespace openceg {
|
||||
|
||||
using ActorInitHandler = std::function<void(Actor&)>;
|
||||
using ActorTickHandler = std::function<void(Actor&, float, const render::InputMap&)>;
|
||||
|
||||
// 全局行为函数注册表。config.ceg 中的 init/tick 名字在这里解析为 C++ 处理函数。
|
||||
class BehaviorRegistry {
|
||||
public:
|
||||
static BehaviorRegistry& getInstance() {
|
||||
static BehaviorRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
BehaviorRegistry(const BehaviorRegistry&) = delete;
|
||||
BehaviorRegistry& operator=(const BehaviorRegistry&) = delete;
|
||||
|
||||
void register_init(const std::string& name, ActorInitHandler handler) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
init_handlers_[name] = std::move(handler);
|
||||
}
|
||||
|
||||
void register_tick(const std::string& name, ActorTickHandler handler) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
tick_handlers_[name] = std::move(handler);
|
||||
}
|
||||
|
||||
ActorInitHandler init(const std::string& name) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const auto it = init_handlers_.find(name);
|
||||
if (it == init_handlers_.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ActorTickHandler tick(const std::string& name) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const auto it = tick_handlers_.find(name);
|
||||
if (it == tick_handlers_.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
BehaviorRegistry() = default;
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, ActorInitHandler> init_handlers_;
|
||||
std::unordered_map<std::string, ActorTickHandler> tick_handlers_;
|
||||
};
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openceg {
|
||||
|
||||
struct WindowConfig {
|
||||
int width = 800;
|
||||
int height = 600;
|
||||
std::string title = "OpenCGE";
|
||||
};
|
||||
|
||||
struct MeshConfig {
|
||||
std::string shape = "quad";
|
||||
std::array<float, 4> color{0.5F, 0.5F, 0.5F, 1.0F};
|
||||
};
|
||||
|
||||
struct ActorConfig {
|
||||
std::string name;
|
||||
std::string init_function;
|
||||
std::string tick_function;
|
||||
std::array<float, 2> position{0.0F, 0.0F};
|
||||
std::array<float, 2> facing{1.0F, 0.0F};
|
||||
MeshConfig mesh;
|
||||
};
|
||||
|
||||
struct InputBindingConfig {
|
||||
std::string action;
|
||||
std::vector<int> keys;
|
||||
std::vector<int> mouse_buttons;
|
||||
};
|
||||
|
||||
struct GameConfig {
|
||||
WindowConfig window;
|
||||
std::vector<InputBindingConfig> input_bindings;
|
||||
std::vector<ActorConfig> actors;
|
||||
};
|
||||
|
||||
// 从 JSON 加载游戏场景配置(类似 Godot 的场景描述)。
|
||||
class ConfigLoader {
|
||||
public:
|
||||
bool load(const std::string& path);
|
||||
const GameConfig& config() const;
|
||||
const std::string& last_error() const;
|
||||
|
||||
private:
|
||||
GameConfig config_;
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace openceg {
|
||||
|
||||
// 通用引擎入口:读取 config.ceg,实例化场景并驱动 Actor 的 init/tick。
|
||||
// 具体游戏行为通过 BehaviorRegistry 注册,因此引擎本身不内置任何游戏逻辑。
|
||||
int run_engine(int argc, char** argv, const char* default_config_path = "config.ceg");
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
|
||||
namespace openceg {
|
||||
class Game {
|
||||
private:
|
||||
size_t id_cnt;
|
||||
|
||||
public:
|
||||
Game();
|
||||
~Game();
|
||||
Game(const Game&) = delete;
|
||||
Game& operator=(const Game&) = delete;
|
||||
Game(Game&&) noexcept = delete;
|
||||
Game& operator=(Game&&) noexcept = delete;
|
||||
|
||||
static Game& getInstance();
|
||||
void launch();
|
||||
size_t new_id();
|
||||
};
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openceg {
|
||||
|
||||
// 渲染组件:描述 Actor 的几何形状、颜色和纹理占位。
|
||||
class Mesh {
|
||||
public:
|
||||
struct Vertex {
|
||||
std::array<float, 2> position;
|
||||
std::array<float, 4> color;
|
||||
};
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
std::string texture; // 纹理路径占位,暂未采样。
|
||||
|
||||
std::array<float, 4> color() const {
|
||||
std::lock_guard<std::mutex> lock(*color_mutex_);
|
||||
return color_;
|
||||
}
|
||||
|
||||
void set_color(std::array<float, 4> color) {
|
||||
std::lock_guard<std::mutex> lock(*color_mutex_);
|
||||
color_ = color;
|
||||
}
|
||||
|
||||
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}},
|
||||
};
|
||||
mesh.indices = {0, 1, 2, 2, 3, 0};
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::mutex> color_mutex_ = std::make_unique<std::mutex>();
|
||||
std::array<float, 4> color_{1.0F, 1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <game.hpp>
|
||||
#include <mesh.hpp>
|
||||
#include <actor.hpp>
|
||||
#include <behavior.hpp>
|
||||
#include <render.hpp>
|
||||
#include <config.hpp>
|
||||
#include <engine.hpp>
|
||||
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <actor.hpp>
|
||||
|
||||
namespace openceg::render {
|
||||
|
||||
struct InputEvent {
|
||||
enum class Type {
|
||||
Key,
|
||||
MouseButton,
|
||||
CursorMove,
|
||||
Scroll,
|
||||
};
|
||||
|
||||
enum class Action {
|
||||
Release = 0,
|
||||
Press = 1,
|
||||
Repeat = 2,
|
||||
};
|
||||
|
||||
Type type = Type::Key;
|
||||
Action action = Action::Press;
|
||||
int key = -1;
|
||||
int scancode = -1;
|
||||
int mods = 0;
|
||||
int button = -1;
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
};
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer();
|
||||
~Renderer();
|
||||
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
Renderer(Renderer&&) = delete;
|
||||
Renderer& operator=(Renderer&&) = delete;
|
||||
|
||||
void init(int width, int height, const char* title);
|
||||
void draw(const Actor* const* actors, size_t actor_count);
|
||||
bool should_close() const;
|
||||
void poll_events() const;
|
||||
|
||||
using InputCallback = std::function<void(const InputEvent&)>;
|
||||
void set_input_callback(InputCallback callback);
|
||||
|
||||
bool is_key_down(int key) const;
|
||||
bool is_mouse_button_down(int button) const;
|
||||
std::array<double, 2> cursor_position() const;
|
||||
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
Impl* impl_;
|
||||
};
|
||||
|
||||
// 类似 Godot InputMap 的按键动作绑定。
|
||||
class InputMap {
|
||||
public:
|
||||
void add_action(const std::string& action);
|
||||
void bind_key(const std::string& action, int key);
|
||||
void unbind_key(const std::string& action, int key);
|
||||
void bind_mouse_button(const std::string& action, int button);
|
||||
void unbind_mouse_button(const std::string& action, int button);
|
||||
|
||||
void handle_event(const InputEvent& event);
|
||||
void end_frame();
|
||||
|
||||
bool is_action_pressed(const std::string& action) const;
|
||||
bool is_action_just_pressed(const std::string& action) const;
|
||||
bool is_action_just_released(const std::string& action) const;
|
||||
float get_action_strength(const std::string& action) const;
|
||||
float get_axis(const std::string& negative_action,
|
||||
const std::string& positive_action) const;
|
||||
|
||||
private:
|
||||
struct ActionState {
|
||||
std::unordered_set<int> keys;
|
||||
std::unordered_set<int> mouse_buttons;
|
||||
std::unordered_map<int, bool> held_keys;
|
||||
std::unordered_map<int, bool> held_buttons;
|
||||
size_t held_key_count = 0;
|
||||
size_t held_button_count = 0;
|
||||
bool pressed = false;
|
||||
bool just_pressed = false;
|
||||
bool just_released = false;
|
||||
};
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, ActionState> actions_;
|
||||
};
|
||||
|
||||
// 渲染线程包装:主线程更新 Actor,渲染线程持续读取并提交绘制。
|
||||
class RenderLoop {
|
||||
public:
|
||||
RenderLoop() = default;
|
||||
~RenderLoop();
|
||||
|
||||
RenderLoop(const RenderLoop&) = delete;
|
||||
RenderLoop& operator=(const RenderLoop&) = delete;
|
||||
RenderLoop(RenderLoop&&) = delete;
|
||||
RenderLoop& operator=(RenderLoop&&) = delete;
|
||||
|
||||
void start(int width, int height, const char* title);
|
||||
void stop();
|
||||
|
||||
void set_actors(std::vector<Actor*> actors);
|
||||
void poll_events() const;
|
||||
bool should_close() const;
|
||||
|
||||
InputMap& input();
|
||||
const InputMap& input() const;
|
||||
|
||||
private:
|
||||
void run();
|
||||
|
||||
Renderer renderer_;
|
||||
InputMap input_map_;
|
||||
std::thread thread_;
|
||||
std::atomic<bool> running_{false};
|
||||
mutable std::mutex actors_mutex_;
|
||||
std::vector<Actor*> actors_;
|
||||
};
|
||||
|
||||
} // namespace openceg::render
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
#include <config.hpp>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openceg {
|
||||
namespace {
|
||||
|
||||
std::string lowercase(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
int parse_key_name(const std::string& name) {
|
||||
const std::string key = lowercase(name);
|
||||
if (key == "space") return 32;
|
||||
if (key == "apostrophe" || key == "'") return 39;
|
||||
if (key == "comma" || key == ",") return 44;
|
||||
if (key == "minus" || key == "-") return 45;
|
||||
if (key == "period" || key == ".") return 46;
|
||||
if (key == "slash" || key == "/") return 47;
|
||||
if (key.size() == 1 && key[0] >= '0' && key[0] <= '9') return key[0];
|
||||
if (key.size() == 1 && key[0] >= 'a' && key[0] <= 'z') return key[0] - 'a' + 'A';
|
||||
if (key == "semicolon" || key == ";") return 59;
|
||||
if (key == "equal" || key == "=") return 61;
|
||||
if (key == "leftbracket" || key == "[") return 91;
|
||||
if (key == "backslash" || key == "\\") return 92;
|
||||
if (key == "rightbracket" || key == "]") return 93;
|
||||
if (key == "graveaccent" || key == "`") return 96;
|
||||
if (key == "escape" || key == "esc") return 256;
|
||||
if (key == "enter" || key == "return") return 257;
|
||||
if (key == "tab") return 258;
|
||||
if (key == "backspace") return 259;
|
||||
if (key == "insert") return 260;
|
||||
if (key == "delete" || key == "del") return 261;
|
||||
if (key == "right") return 262;
|
||||
if (key == "left") return 263;
|
||||
if (key == "down") return 264;
|
||||
if (key == "up") return 265;
|
||||
if (key == "pageup" || key == "pgup") return 266;
|
||||
if (key == "pagedown" || key == "pgdown" || key == "pgdn") return 267;
|
||||
if (key == "home") return 268;
|
||||
if (key == "end") return 269;
|
||||
if (key == "capslock" || key == "caps") return 280;
|
||||
if (key == "scrolllock") return 281;
|
||||
if (key == "numlock") return 282;
|
||||
if (key == "printscreen" || key == "prtscr") return 283;
|
||||
if (key == "pause") return 284;
|
||||
if (key.size() > 1 && key[0] == 'f') {
|
||||
try {
|
||||
int number = std::stoi(key.substr(1));
|
||||
if (number >= 1 && number <= 25) return 290 + number - 1;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
if (key == "kp0" || key == "keypad0") return 320;
|
||||
if (key == "kp1" || key == "keypad1") return 321;
|
||||
if (key == "kp2" || key == "keypad2") return 322;
|
||||
if (key == "kp3" || key == "keypad3") return 323;
|
||||
if (key == "kp4" || key == "keypad4") return 324;
|
||||
if (key == "kp5" || key == "keypad5") return 325;
|
||||
if (key == "kp6" || key == "keypad6") return 326;
|
||||
if (key == "kp7" || key == "keypad7") return 327;
|
||||
if (key == "kp8" || key == "keypad8") return 328;
|
||||
if (key == "kp9" || key == "keypad9") return 329;
|
||||
if (key == "leftshift" || key == "lshift") return 340;
|
||||
if (key == "rightshift" || key == "rshift") return 344;
|
||||
if (key == "leftcontrol" || key == "leftctrl" || key == "lctrl") return 341;
|
||||
if (key == "rightcontrol" || key == "rightctrl" || key == "rctrl") return 345;
|
||||
if (key == "leftalt" || key == "lalt") return 342;
|
||||
if (key == "rightalt" || key == "ralt") return 346;
|
||||
if (key == "leftsuper" || key == "lsuper") return 343;
|
||||
if (key == "rightsuper" || key == "rsuper") return 347;
|
||||
|
||||
try {
|
||||
return std::stoi(name);
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int parse_mouse_button(const std::string& name) {
|
||||
const std::string button = lowercase(name);
|
||||
if (button == "left") return 0;
|
||||
if (button == "right") return 1;
|
||||
if (button == "middle") return 2;
|
||||
try {
|
||||
return std::stoi(name);
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int json_int(const Json::Value& value, int fallback) {
|
||||
if (value.isNumeric()) {
|
||||
return value.asInt();
|
||||
}
|
||||
if (value.isString()) {
|
||||
try {
|
||||
return std::stoi(value.asString());
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
float json_float(const Json::Value& value, float fallback) {
|
||||
if (value.isNumeric()) {
|
||||
return value.asFloat();
|
||||
}
|
||||
if (value.isString()) {
|
||||
try {
|
||||
return std::stof(value.asString());
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
std::string json_string(const Json::Value& value, const std::string& fallback) {
|
||||
if (value.isString()) {
|
||||
return value.asString();
|
||||
}
|
||||
if (value.isNumeric()) {
|
||||
return std::to_string(value.asInt());
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void add_binding_code(const Json::Value& value, int type, std::vector<int>& output) {
|
||||
if (value.isArray()) {
|
||||
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
|
||||
add_binding_code(value[i], type, output);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int code = -1;
|
||||
if (value.isNumeric()) {
|
||||
code = value.asInt();
|
||||
} else if (value.isString()) {
|
||||
code = type == 0 ? parse_key_name(value.asString())
|
||||
: parse_mouse_button(value.asString());
|
||||
}
|
||||
if (code >= 0) {
|
||||
output.push_back(code);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_window(const Json::Value& root, WindowConfig& window) {
|
||||
if (!root.isMember("window") || !root["window"].isObject()) {
|
||||
return;
|
||||
}
|
||||
const Json::Value& value = root["window"];
|
||||
if (value.isMember("width")) {
|
||||
window.width = json_int(value["width"], window.width);
|
||||
}
|
||||
if (value.isMember("height")) {
|
||||
window.height = json_int(value["height"], window.height);
|
||||
}
|
||||
if (value.isMember("title")) {
|
||||
window.title = json_string(value["title"], window.title);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_input(const Json::Value& root, std::vector<InputBindingConfig>& bindings) {
|
||||
if (!root.isMember("input") || !root["input"].isArray()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Json::Value& item : root["input"]) {
|
||||
if (!item.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputBindingConfig binding;
|
||||
binding.action = item.isMember("action")
|
||||
? json_string(item["action"], "")
|
||||
: json_string(item["name"], "");
|
||||
|
||||
if (item.isMember("key")) {
|
||||
add_binding_code(item["key"], 0, binding.keys);
|
||||
}
|
||||
if (item.isMember("keys")) {
|
||||
add_binding_code(item["keys"], 0, binding.keys);
|
||||
}
|
||||
if (item.isMember("mouse")) {
|
||||
add_binding_code(item["mouse"], 1, binding.mouse_buttons);
|
||||
}
|
||||
if (item.isMember("mouse_buttons")) {
|
||||
add_binding_code(item["mouse_buttons"], 1, binding.mouse_buttons);
|
||||
}
|
||||
|
||||
if (!binding.action.empty()) {
|
||||
bindings.push_back(std::move(binding));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_color(const Json::Value& mesh, MeshConfig& mesh_config) {
|
||||
if (!mesh.isObject() || !mesh.isMember("color")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Json::Value& color = mesh["color"];
|
||||
if (color.isObject()) {
|
||||
if (color.isMember("r")) mesh_config.color[0] = json_float(color["r"], mesh_config.color[0]);
|
||||
if (color.isMember("g")) mesh_config.color[1] = json_float(color["g"], mesh_config.color[1]);
|
||||
if (color.isMember("b")) mesh_config.color[2] = json_float(color["b"], mesh_config.color[2]);
|
||||
if (color.isMember("a")) mesh_config.color[3] = json_float(color["a"], mesh_config.color[3]);
|
||||
} else if (color.isArray()) {
|
||||
for (Json::ArrayIndex i = 0; i < color.size() && i < 4; ++i) {
|
||||
mesh_config.color[i] = json_float(color[i], mesh_config.color[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActorConfig parse_actor(const Json::Value& value) {
|
||||
ActorConfig actor;
|
||||
|
||||
if (value.isMember("name")) {
|
||||
actor.name = json_string(value["name"], "");
|
||||
}
|
||||
if (value.isMember("init")) {
|
||||
actor.init_function = json_string(value["init"], "");
|
||||
}
|
||||
if (value.isMember("tick")) {
|
||||
actor.tick_function = json_string(value["tick"], "");
|
||||
}
|
||||
|
||||
if (value.isMember("position") && value["position"].isObject()) {
|
||||
const Json::Value& position = value["position"];
|
||||
actor.position[0] = json_float(position["x"], actor.position[0]);
|
||||
actor.position[1] = json_float(position["y"], actor.position[1]);
|
||||
}
|
||||
|
||||
if (value.isMember("facing") && value["facing"].isObject()) {
|
||||
const Json::Value& facing = value["facing"];
|
||||
actor.facing[0] = json_float(facing["x"], actor.facing[0]);
|
||||
actor.facing[1] = json_float(facing["y"], actor.facing[1]);
|
||||
}
|
||||
|
||||
if (value.isMember("mesh") && value["mesh"].isObject()) {
|
||||
const Json::Value& mesh = value["mesh"];
|
||||
if (mesh.isMember("shape")) {
|
||||
actor.mesh.shape = json_string(mesh["shape"], actor.mesh.shape);
|
||||
}
|
||||
parse_color(mesh, actor.mesh);
|
||||
}
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
void parse_actors(const Json::Value& root, std::vector<ActorConfig>& actors) {
|
||||
const Json::Value* list = nullptr;
|
||||
|
||||
if (root.isMember("actors") && root["actors"].isArray()) {
|
||||
list = &root["actors"];
|
||||
} else if (root.isMember("scene")) {
|
||||
const Json::Value& scene = root["scene"];
|
||||
if (scene.isArray()) {
|
||||
list = &scene;
|
||||
} else if (scene.isObject() && scene.isMember("actors") && scene["actors"].isArray()) {
|
||||
list = &scene["actors"];
|
||||
}
|
||||
}
|
||||
|
||||
if (list == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Json::Value& item : *list) {
|
||||
if (!item.isObject()) {
|
||||
continue;
|
||||
}
|
||||
ActorConfig actor = parse_actor(item);
|
||||
if (!actor.name.empty()) {
|
||||
actors.push_back(std::move(actor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // 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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!root.isObject()) {
|
||||
last_error_ = "JSON 根节点必须是对象: " + path;
|
||||
return false;
|
||||
}
|
||||
|
||||
config_ = GameConfig{};
|
||||
parse_window(root, config_.window);
|
||||
parse_input(root, config_.input_bindings);
|
||||
parse_actors(root, config_.actors);
|
||||
return true;
|
||||
}
|
||||
|
||||
const GameConfig& ConfigLoader::config() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
const std::string& ConfigLoader::last_error() const {
|
||||
return last_error_;
|
||||
}
|
||||
|
||||
} // namespace openceg
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
#include <engine.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <openceg.hpp>
|
||||
|
||||
namespace openceg {
|
||||
|
||||
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) {
|
||||
auto mesh = std::make_shared<Mesh>();
|
||||
if (actor_config.mesh.shape == "quad") {
|
||||
*mesh = Mesh::quad();
|
||||
} else {
|
||||
// 当前渲染器只提供 quad;未知形状先按 quad 创建,便于后续扩展。
|
||||
*mesh = Mesh::quad();
|
||||
}
|
||||
mesh->set_color(actor_config.mesh.color);
|
||||
|
||||
auto actor = std::make_shared<Actor>(actor_config.name, game, mesh);
|
||||
actor->set_position(actor_config.position);
|
||||
actor->set_facing(actor_config.facing);
|
||||
|
||||
if (!actor_config.init_function.empty()) {
|
||||
auto handler = behaviors.init(actor_config.init_function);
|
||||
if (handler) {
|
||||
actor->set_init_handler(std::move(handler));
|
||||
} else {
|
||||
std::fprintf(stderr, "未找到 init 处理函数: %s\n",
|
||||
actor_config.init_function.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!actor_config.tick_function.empty()) {
|
||||
auto handler = behaviors.tick(actor_config.tick_function);
|
||||
if (handler) {
|
||||
actor->set_tick_handler(std::move(handler));
|
||||
} else {
|
||||
std::fprintf(stderr, "未找到 tick 处理函数: %s\n",
|
||||
actor_config.tick_function.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
actors.push_back(actor.get());
|
||||
actor_storage.push_back(std::move(actor));
|
||||
}
|
||||
|
||||
for (auto& actor : actor_storage) {
|
||||
actor->initialize();
|
||||
}
|
||||
|
||||
render::RenderLoop render_loop;
|
||||
for (const InputBindingConfig& binding : config.input_bindings) {
|
||||
render_loop.input().add_action(binding.action);
|
||||
for (int key : binding.keys) {
|
||||
render_loop.input().bind_key(binding.action, key);
|
||||
}
|
||||
for (int button : binding.mouse_buttons) {
|
||||
render_loop.input().bind_mouse_button(binding.action, button);
|
||||
}
|
||||
}
|
||||
|
||||
// 引擎级默认退出动作,不包含具体游戏逻辑。
|
||||
render_loop.input().add_action("quit");
|
||||
render_loop.input().bind_key("quit", 256);
|
||||
|
||||
render_loop.set_actors(std::move(actors));
|
||||
render_loop.start(config.window.width, config.window.height,
|
||||
config.window.title.c_str());
|
||||
|
||||
auto last = std::chrono::steady_clock::now();
|
||||
while (!render_loop.should_close()) {
|
||||
render_loop.poll_events();
|
||||
if (render_loop.input().is_action_just_pressed("quit")) {
|
||||
break;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
render_loop.input().end_frame();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
}
|
||||
|
||||
render_loop.stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace openceg
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <engine.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return openceg::run_engine(argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <game.hpp>
|
||||
|
||||
namespace openceg {
|
||||
|
||||
Game::Game() { id_cnt = 0; }
|
||||
Game::~Game() = default;
|
||||
|
||||
Game& Game::getInstance() {
|
||||
static Game instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Game::launch() {}
|
||||
|
||||
size_t Game::new_id() { return id_cnt++; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
// Minimal GLFW-compatible Win32 backend for cross-compiling the example.
|
||||
// It implements only the subset used by src/render.cpp.
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#define GLFW_INCLUDE_VULKAN
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vulkan_win32.h>
|
||||
|
||||
struct GLFWwindow {
|
||||
HWND handle = nullptr;
|
||||
void* user_pointer = nullptr;
|
||||
bool should_close = false;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
GLFWkeyfun key_callback = nullptr;
|
||||
GLFWmousebuttonfun mouse_button_callback = nullptr;
|
||||
GLFWcursorposfun cursor_pos_callback = nullptr;
|
||||
GLFWscrollfun scroll_callback = nullptr;
|
||||
|
||||
std::array<unsigned char, 1024> key_state{};
|
||||
std::array<unsigned char, 8> mouse_button_state{};
|
||||
double cursor_x = 0.0;
|
||||
double cursor_y = 0.0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kWindowClassName[] = L"OpenCGE_GLFWStub";
|
||||
HINSTANCE g_instance = nullptr;
|
||||
bool g_initialized = false;
|
||||
bool g_resizable = true;
|
||||
|
||||
int map_key(WPARAM vk) {
|
||||
if (vk >= 'A' && vk <= 'Z') {
|
||||
return GLFW_KEY_A + static_cast<int>(vk - 'A');
|
||||
}
|
||||
if (vk >= '0' && vk <= '9') {
|
||||
return GLFW_KEY_0 + static_cast<int>(vk - '0');
|
||||
}
|
||||
switch (vk) {
|
||||
case VK_ESCAPE:
|
||||
return GLFW_KEY_ESCAPE;
|
||||
case VK_LEFT:
|
||||
return GLFW_KEY_LEFT;
|
||||
case VK_RIGHT:
|
||||
return GLFW_KEY_RIGHT;
|
||||
case VK_UP:
|
||||
return GLFW_KEY_UP;
|
||||
case VK_DOWN:
|
||||
return GLFW_KEY_DOWN;
|
||||
case VK_SPACE:
|
||||
return GLFW_KEY_SPACE;
|
||||
case VK_RETURN:
|
||||
return GLFW_KEY_ENTER;
|
||||
case VK_TAB:
|
||||
return GLFW_KEY_TAB;
|
||||
case VK_BACK:
|
||||
return GLFW_KEY_BACKSPACE;
|
||||
case VK_DELETE:
|
||||
return GLFW_KEY_DELETE;
|
||||
case VK_LSHIFT:
|
||||
return GLFW_KEY_LEFT_SHIFT;
|
||||
case VK_RSHIFT:
|
||||
return GLFW_KEY_RIGHT_SHIFT;
|
||||
case VK_LCONTROL:
|
||||
return GLFW_KEY_LEFT_CONTROL;
|
||||
case VK_RCONTROL:
|
||||
return GLFW_KEY_RIGHT_CONTROL;
|
||||
case VK_LMENU:
|
||||
return GLFW_KEY_LEFT_ALT;
|
||||
case VK_RMENU:
|
||||
return GLFW_KEY_RIGHT_ALT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
int current_mods() {
|
||||
int mods = 0;
|
||||
if (GetKeyState(VK_SHIFT) & 0x8000) {
|
||||
mods |= GLFW_MOD_SHIFT;
|
||||
}
|
||||
if (GetKeyState(VK_CONTROL) & 0x8000) {
|
||||
mods |= GLFW_MOD_CONTROL;
|
||||
}
|
||||
if (GetKeyState(VK_MENU) & 0x8000) {
|
||||
mods |= GLFW_MOD_ALT;
|
||||
}
|
||||
if ((GetKeyState(VK_LWIN) & 0x8000) || (GetKeyState(VK_RWIN) & 0x8000)) {
|
||||
mods |= GLFW_MOD_SUPER;
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
void handle_key(GLFWwindow* window, UINT message, WPARAM wparam, LPARAM lparam) {
|
||||
if (window == nullptr || window->key_callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int key = map_key(wparam);
|
||||
const int scancode = static_cast<int>((lparam >> 16) & 0x1ff);
|
||||
const int action = message == WM_KEYDOWN || message == WM_SYSKEYDOWN
|
||||
? (((lparam & (1U << 30)) != 0) ? GLFW_REPEAT : GLFW_PRESS)
|
||||
: GLFW_RELEASE;
|
||||
|
||||
if (key >= 0 && key < static_cast<int>(window->key_state.size())) {
|
||||
window->key_state[key] = action == GLFW_RELEASE ? GLFW_RELEASE : GLFW_PRESS;
|
||||
}
|
||||
|
||||
window->key_callback(window, key, scancode, action, current_mods());
|
||||
}
|
||||
|
||||
int map_mouse_button(UINT message, WPARAM wparam) {
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
return GLFW_MOUSE_BUTTON_LEFT;
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
return GLFW_MOUSE_BUTTON_RIGHT;
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP:
|
||||
return GLFW_MOUSE_BUTTON_MIDDLE;
|
||||
case WM_XBUTTONDOWN:
|
||||
case WM_XBUTTONUP:
|
||||
return GET_XBUTTON_WPARAM(wparam) == XBUTTON1 ? GLFW_MOUSE_BUTTON_4
|
||||
: GLFW_MOUSE_BUTTON_5;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_mouse_button(GLFWwindow* window, UINT message, WPARAM wparam) {
|
||||
if (window == nullptr || window->mouse_button_callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int button = map_mouse_button(message, wparam);
|
||||
if (button < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_down = message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN ||
|
||||
message == WM_MBUTTONDOWN || message == WM_XBUTTONDOWN;
|
||||
window->mouse_button_state[button] = is_down ? GLFW_PRESS : GLFW_RELEASE;
|
||||
window->mouse_button_callback(window, button, is_down ? GLFW_PRESS : GLFW_RELEASE,
|
||||
current_mods());
|
||||
}
|
||||
|
||||
LRESULT CALLBACK window_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
|
||||
auto* window = reinterpret_cast<GLFWwindow*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
|
||||
switch (message) {
|
||||
case WM_CLOSE:
|
||||
if (window != nullptr) {
|
||||
window->should_close = true;
|
||||
}
|
||||
return 0;
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
handle_key(window, message, wparam, lparam);
|
||||
return 0;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_XBUTTONDOWN:
|
||||
case WM_XBUTTONUP:
|
||||
handle_mouse_button(window, message, wparam);
|
||||
return 0;
|
||||
case WM_MOUSEMOVE:
|
||||
if (window != nullptr) {
|
||||
window->cursor_x = static_cast<double>(GET_X_LPARAM(lparam));
|
||||
window->cursor_y = static_cast<double>(GET_Y_LPARAM(lparam));
|
||||
if (window->cursor_pos_callback != nullptr) {
|
||||
window->cursor_pos_callback(window, window->cursor_x, window->cursor_y);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
case WM_MOUSEWHEEL:
|
||||
if (window != nullptr && window->scroll_callback != nullptr) {
|
||||
const double delta = static_cast<double>(GET_WHEEL_DELTA_WPARAM(wparam)) /
|
||||
static_cast<double>(WHEEL_DELTA);
|
||||
window->scroll_callback(window, 0.0, delta);
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, message, wparam, lparam);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
int glfwInit(void) {
|
||||
if (g_initialized) {
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
g_instance = GetModuleHandleW(nullptr);
|
||||
|
||||
WNDCLASSEXW wc{};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
||||
wc.lpfnWndProc = window_proc;
|
||||
wc.hInstance = g_instance;
|
||||
wc.hCursor = LoadCursorW(nullptr, MAKEINTRESOURCEW(32512));
|
||||
wc.lpszClassName = kWindowClassName;
|
||||
if (RegisterClassExW(&wc) == 0) {
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void glfwTerminate(void) {
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
void glfwWindowHint(int hint, int value) {
|
||||
if (hint == GLFW_RESIZABLE) {
|
||||
g_resizable = value != GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor*, GLFWwindow*) {
|
||||
if (!g_initialized) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int wide_length = MultiByteToWideChar(CP_UTF8, 0, title, -1, nullptr, 0);
|
||||
std::vector<wchar_t> wide_title(wide_length);
|
||||
MultiByteToWideChar(CP_UTF8, 0, title, -1, wide_title.data(), wide_length);
|
||||
|
||||
DWORD style = WS_OVERLAPPEDWINDOW;
|
||||
if (!g_resizable) {
|
||||
style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
|
||||
}
|
||||
|
||||
RECT rect{0, 0, width, height};
|
||||
AdjustWindowRectEx(&rect, style, FALSE, 0);
|
||||
|
||||
HWND hwnd = CreateWindowExW(
|
||||
0, kWindowClassName, wide_title.data(), style, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, g_instance, nullptr);
|
||||
if (hwnd == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* window = new GLFWwindow();
|
||||
window->handle = hwnd;
|
||||
window->width = width;
|
||||
window->height = height;
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
|
||||
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
UpdateWindow(hwnd);
|
||||
return window;
|
||||
}
|
||||
|
||||
void glfwDestroyWindow(GLFWwindow* window) {
|
||||
if (window == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (window->handle != nullptr) {
|
||||
DestroyWindow(window->handle);
|
||||
}
|
||||
delete window;
|
||||
}
|
||||
|
||||
void glfwSetWindowUserPointer(GLFWwindow* window, void* pointer) {
|
||||
if (window != nullptr) {
|
||||
window->user_pointer = pointer;
|
||||
}
|
||||
}
|
||||
|
||||
void* glfwGetWindowUserPointer(GLFWwindow* window) {
|
||||
return window != nullptr ? window->user_pointer : nullptr;
|
||||
}
|
||||
|
||||
GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun callback) {
|
||||
if (window == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
GLFWkeyfun previous = window->key_callback;
|
||||
window->key_callback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmousebuttonfun callback) {
|
||||
if (window == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
GLFWmousebuttonfun previous = window->mouse_button_callback;
|
||||
window->mouse_button_callback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursorposfun callback) {
|
||||
if (window == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
GLFWcursorposfun previous = window->cursor_pos_callback;
|
||||
window->cursor_pos_callback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWscrollfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun callback) {
|
||||
if (window == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
GLFWscrollfun previous = window->scroll_callback;
|
||||
window->scroll_callback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void glfwPollEvents(void) {
|
||||
MSG msg{};
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
int glfwWindowShouldClose(GLFWwindow* window) {
|
||||
return window != nullptr && window->should_close ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
int glfwGetKey(GLFWwindow* window, int key) {
|
||||
if (window == nullptr || key < 0 || key >= static_cast<int>(window->key_state.size())) {
|
||||
return GLFW_RELEASE;
|
||||
}
|
||||
return window->key_state[key] != 0 ? GLFW_PRESS : GLFW_RELEASE;
|
||||
}
|
||||
|
||||
int glfwGetMouseButton(GLFWwindow* window, int button) {
|
||||
if (window == nullptr || button < 0 ||
|
||||
button >= static_cast<int>(window->mouse_button_state.size())) {
|
||||
return GLFW_RELEASE;
|
||||
}
|
||||
return window->mouse_button_state[button] != 0 ? GLFW_PRESS : GLFW_RELEASE;
|
||||
}
|
||||
|
||||
void glfwGetCursorPos(GLFWwindow* window, double* xpos, double* ypos) {
|
||||
if (xpos != nullptr) {
|
||||
*xpos = window != nullptr ? window->cursor_x : 0.0;
|
||||
}
|
||||
if (ypos != nullptr) {
|
||||
*ypos = window != nullptr ? window->cursor_y : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height) {
|
||||
if (window == nullptr || window->handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
RECT client{};
|
||||
GetClientRect(window->handle, &client);
|
||||
if (width != nullptr) {
|
||||
*width = client.right - client.left;
|
||||
}
|
||||
if (height != nullptr) {
|
||||
*height = client.bottom - client.top;
|
||||
}
|
||||
}
|
||||
|
||||
const char** glfwGetRequiredInstanceExtensions(uint32_t* count) {
|
||||
static const char* extensions[] = {
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
if (count != nullptr) {
|
||||
*count = static_cast<uint32_t>(sizeof(extensions) / sizeof(extensions[0]));
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkSurfaceKHR* surface) {
|
||||
if (instance == VK_NULL_HANDLE || window == nullptr || surface == nullptr) {
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
auto create_surface = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(
|
||||
vkGetInstanceProcAddr(instance, "vkCreateWin32SurfaceKHR"));
|
||||
if (create_surface == nullptr) {
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
info.hinstance = g_instance;
|
||||
info.hwnd = window->handle;
|
||||
return create_surface(instance, &info, allocator, surface);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1 @@
|
||||
#include <openceg.hpp>
|
||||
+1340
File diff suppressed because it is too large
Load Diff
Vendored
-1
Submodule third_party/ConsoleLib deleted from 5f51a52ca6
Reference in New Issue
Block a user