Extra2D/docs/quick_start.md

12 KiB

Extra2D 快速入门指南

本指南将帮助您快速上手 Extra2D 游戏引擎,从安装到创建您的第一个游戏。


目录

  1. 环境准备
  2. 创建项目
  3. 基础概念
  4. 创建场景
  5. 添加节点
  6. 处理输入
  7. 自定义模块
  8. 完整示例

环境准备

安装 xmake

Windows (PowerShell):

Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content

macOS:

brew install xmake

Linux:

sudo add-apt-repository ppa:xmake-io/xmake
sudo apt update
sudo apt install xmake

克隆项目

git clone https://github.com/ChestnutYueyue/extra2d.git
cd extra2d

构建项目

# 配置项目
xmake f -p mingw -a x86_64 -m release -y

# 构建
xmake build

# 运行示例
xmake run demo_basic

创建项目

最小示例

创建一个 main.cpp 文件:

#include <extra2d/extra2d.h>

using namespace extra2d;

int main() {
    // 1. 配置应用
    AppConfig config;
    config.appName = "My First Game";
    config.appVersion = "1.0.0";
    
    // 2. 获取应用实例
    Application& app = Application::get();
    
    // 3. 初始化
    if (!app.init(config)) {
        return -1;
    }
    
    // 4. 创建场景
    auto scene = Scene::create();
    scene->setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
    
    // 5. 进入场景
    app.enterScene(scene);
    
    // 6. 运行游戏循环
    app.run();
    
    // 7. 清理
    app.shutdown();
    return 0;
}

基础概念

模块系统

Extra2D 使用模块化架构,通过 Application::use() 注册模块:

// 内置模块会自动注册,您也可以自定义
MyModule myModule;
app.use(myModule);

内置模块优先级:

模块 优先级 说明
Logger -1 日志系统
Config 0 配置管理
Platform 10 平台检测
Window 20 窗口管理
Input 30 输入系统
Render 40 渲染系统

服务系统

服务提供运行时功能,通过 Application 的便捷方法访问:

auto sceneService = app.scenes();    // 场景管理
auto timerService = app.timers();    // 计时器
auto eventService = app.events();    // 事件分发
auto cameraService = app.camera();   // 相机系统

场景图

场景图是一个树形结构,由 SceneNode 组成:

Scene (根节点)
├── Node (父节点)
│   ├── Node (子节点)
│   └── ShapeNode (形状节点)
└── Sprite (精灵)

创建场景

基本场景

class MyScene : public Scene {
public:
    static Ptr<MyScene> create() {
        return makeShared<MyScene>();
    }
    
    void onEnter() override {
        Scene::onEnter();
        
        // 设置背景颜色
        setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
        
        // 在这里添加节点和设置游戏逻辑
    }
    
    void onUpdate(float dt) override {
        Scene::onUpdate(dt);
        
        // 每帧更新逻辑
    }
};

使用场景

auto scene = MyScene::create();
app.enterScene(scene);

添加节点

创建形状节点

// 矩形
auto rect = ShapeNode::createFilledRect(
    Rect(0, 0, 100, 100),           // 位置和大小
    Color(1.0f, 0.4f, 0.4f, 1.0f)   // 颜色
);
scene->addChild(rect);

// 圆形
auto circle = ShapeNode::createFilledCircle(
    Vec2(50, 50),                    // 圆心
    30,                              // 半径
    Color(0.4f, 0.4f, 1.0f, 1.0f)    // 颜色
);
scene->addChild(circle);

// 三角形
auto triangle = ShapeNode::createFilledTriangle(
    Vec2(50, 0),                      // 顶点1
    Vec2(0, 100),                     // 顶点2
    Vec2(100, 100),                   // 顶点3
    Color(0.4f, 1.0f, 0.4f, 1.0f)     // 颜色
);
scene->addChild(triangle);

// 线段
auto line = ShapeNode::createLine(
    Vec2(0, 0),                       // 起点
    Vec2(100, 100),                   // 终点
    Color(1.0f, 1.0f, 1.0f, 1.0f),    // 颜色
    2.0f                              // 线宽
);
scene->addChild(line);

节点变换

// 位置
node->setPos(100, 200);
Vec2 pos = node->getPosition();

// 旋转(角度)
node->setRotation(45);
float rotation = node->getRotation();

// 缩放
node->setScale(2.0f);           // 统一缩放
node->setScale(2.0f, 1.5f);     // 分别设置 X/Y
Vec2 scale = node->getScale();

// 锚点(变换中心)
node->setAnchor(0.5f, 0.5f);    // 中心(默认)

// 透明度
node->setOpacity(0.5f);         // 0.0 - 1.0

// 可见性
node->setVisible(false);

// Z 序(渲染顺序)
node->setZOrder(10);

节点层级

// 添加子节点
parent->addChild(child);

// 移除子节点
parent->removeChild(child);

// 从父节点分离
child->detach();

// 查找子节点
Ptr<Node> found = parent->findChild("nodeName");
Ptr<Node> foundByTag = parent->findChildByTag(1);

// 清除所有子节点
parent->clearChildren();

变换继承

子节点会继承父节点的变换:

auto parent = makeShared<Node>();
parent->setPos(400, 300);
parent->setRotation(30);  // 旋转 30 度

auto child = ShapeNode::createFilledRect(
    Rect(-25, -25, 50, 50),
    Color(1.0f, 0.0f, 0.0f, 1.0f)
);
child->setPos(100, 0);  // 相对于父节点

parent->addChild(child);
scene->addChild(parent);

// child 会随 parent 一起旋转
// child 的世界位置约为 (486.6, 350)

处理输入

在场景中处理事件

class MyScene : public Scene {
public:
    void onEnter() override {
        Scene::onEnter();
        
        // 键盘事件
        addListener(EventType::KeyPress, [](Event& e) {
            auto& key = std::get<KeyEvent>(e.data);
            
            if (key.scancode == static_cast<int>(Key::Escape)) {
                e.handled = true;
                Application::get().quit();
            }
            
            if (key.scancode == static_cast<int>(Key::Space)) {
                E2D_LOG_INFO("Space pressed!");
            }
        });
        
        // 鼠标事件
        addListener(EventType::MousePress, [](Event& e) {
            auto& mouse = std::get<MouseEvent>(e.data);
            E2D_LOG_INFO("Click at ({}, {})", mouse.position.x, mouse.position.y);
        });
        
        // 手柄事件
        addListener(EventType::GamepadPress, [](Event& e) {
            auto& gamepad = std::get<GamepadEvent>(e.data);
            E2D_LOG_INFO("Gamepad button: {}", gamepad.button);
        });
    }
};

实时输入查询

void onUpdate(float dt) override {
    Scene::onUpdate(dt);
    
    auto& input = Application::get().input();
    
    // 键盘
    if (input.down(Key::W)) {
        // W 键被按住
    }
    if (input.pressed(Key::Space)) {
        // 空格键刚按下
    }
    if (input.released(Key::Space)) {
        // 空格键刚释放
    }
    
    // 鼠标
    Vec2 mousePos = input.mouse();
    if (input.down(Mouse::Left)) {
        // 左键被按住
    }
    
    // 手柄
    if (input.gamepad()) {
        Vec2 leftStick = input.leftStick();
        Vec2 rightStick = input.rightStick();
        
        if (input.down(Gamepad::A)) {
            // A 键被按住
        }
        
        // 振动
        input.vibrate(0.5f, 0.5f);
    }
}

自定义模块

创建模块

#include <extra2d/core/module.h>

class GameModule : public extra2d::Module {
public:
    const char* getName() const override { return "GameModule"; }
    
    int getPriority() const override { return 1000; }
    
    void setupModule() override {
        // 初始化游戏资源
        E2D_LOG_INFO("Game module initialized");
    }
    
    void destroyModule() override {
        // 清理资源
        E2D_LOG_INFO("Game module destroyed");
    }
    
    void onUpdate(extra2d::UpdateContext& ctx) override {
        // 更新游戏逻辑
        float dt = ctx.getDeltaTime();
        
        // ...
        
        ctx.next();  // 继续下一个模块
    }
};

注册模块

int main() {
    Application& app = Application::get();
    
    GameModule gameModule;
    app.use(gameModule);
    
    app.init();
    app.run();
    return 0;
}

完整示例

下面是一个完整的游戏示例,展示如何创建一个简单的交互式场景:

#include <extra2d/extra2d.h>

using namespace extra2d;

// 自定义场景
class GameScene : public Scene {
public:
    static Ptr<GameScene> create() {
        return makeShared<GameScene>();
    }
    
    void onEnter() override {
        Scene::onEnter();
        
        // 设置背景
        setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
        
        // 创建玩家
        player_ = ShapeNode::createFilledRect(
            Rect(-25, -25, 50, 50),
            Color(1.0f, 0.4f, 0.4f, 1.0f)
        );
        player_->setPos(getWidth() / 2, getHeight() / 2);
        addChild(player_);
        
        // 键盘控制
        addListener(EventType::KeyPressed, [this](Event& e) {
            auto& key = std::get<KeyEvent>(e.data);
            
            if (key.keyCode == static_cast<int>(Key::Escape)) {
                e.handled = true;
                Application::get().quit();
            }
        });
        
        E2D_LOG_INFO("Game scene entered");
    }
    
    void onUpdate(float dt) override {
        Scene::onUpdate(dt);
        
        // 移动玩家
        auto& input = Application::get().input();
        
        float speed = 200.0f * dt;
        Vec2 pos = player_->getPosition();
        
        if (input.down(Key::W) || input.down(Key::Up))    pos.y -= speed;
        if (input.down(Key::S) || input.down(Key::Down))  pos.y += speed;
        if (input.down(Key::A) || input.down(Key::Left))  pos.x -= speed;
        if (input.down(Key::D) || input.down(Key::Right)) pos.x += speed;
        
        // 边界检测
        pos.x = std::clamp(pos.x, 25.0f, getWidth() - 25.0f);
        pos.y = std::clamp(pos.y, 25.0f, getHeight() - 25.0f);
        
        player_->setPos(pos);
        
        // 旋转
        rotation_ += 90.0f * dt;
        player_->setRotation(rotation_);
    }
    
private:
    Ptr<ShapeNode> player_;
    float rotation_ = 0.0f;
};

int main() {
    // 配置
    AppConfig config;
    config.appName = "My Game";
    config.appVersion = "1.0.0";
    
    // 初始化
    Application& app = Application::get();
    if (!app.init(config)) {
        return -1;
    }
    
    // 创建并进入场景
    auto scene = GameScene::create();
    app.enterScene(scene);
    
    // 运行
    app.run();
    
    // 清理
    app.shutdown();
    return 0;
}

下一步


常见问题

Q: 如何设置窗口大小?

WindowModule windowModule;
WindowConfigData windowConfig;
windowConfig.width = 1920;
windowConfig.height = 1080;
windowConfig.title = "My Game";
windowModule.setWindowConfig(windowConfig);
app.use(windowModule);

Q: 如何启用垂直同步?

RenderModule renderModule;
RenderModuleConfig renderConfig;
renderConfig.vsync = true;
renderModule.setRenderConfig(renderConfig);
app.use(renderModule);

Q: 如何处理窗口大小变化?

// 在场景中
void onEnter() override {
    Scene::onEnter();
    
    addListener(EventType::WindowResize, [this](Event& e) {
        auto& resize = std::get<WindowResizeEvent>(e.data);
        E2D_LOG_INFO("Window resized: {}x{}", resize.width, resize.height);
        
        // 更新视口
        setViewportSize(resize.width, resize.height);
    });
}

Q: 如何使用计时器?

void onEnter() override {
    Scene::onEnter();
    
    auto timerService = Application::get().timers();
    
    // 延迟执行
    timerService->addTimer(2.0f, []() {
        E2D_LOG_INFO("2 seconds passed!");
    });
    
    // 重复执行
    timerService->addRepeatingTimer(1.0f, [](int count) {
        E2D_LOG_INFO("Count: {}", count);
        return true;  // 返回 false 停止
    });
}

获取帮助