Extra2D/docs/API_Tutorial/03_Node_System.md

7.4 KiB
Raw Blame History

03. 节点系统

Extra2D 的节点系统是构建游戏对象的基础。所有可见的游戏元素都是节点的子类。

核心节点类型

Node (基类)
├── Sprite (精灵)
├── Text (文本)
├── Button (按钮)
├── Widget (UI控件基类)
│   ├── Label (标签)
│   ├── CheckBox (复选框)
│   ├── RadioButton (单选按钮)
│   ├── Slider (滑块)
│   └── ProgressBar (进度条)
└── 自定义节点...

基础节点操作

创建和添加节点

// 创建精灵
auto sprite = Sprite::create(texture);
sprite->setPosition(Vec2(100, 200));
sprite->setAnchor(Vec2(0.5f, 0.5f));  // 中心锚点
addChild(sprite);

// 创建文本
auto text = Text::create("Hello World", font);
text->setPosition(Vec2(400, 300));
text->setTextColor(Color(1, 1, 1, 1));
addChild(text);

节点属性

// 位置
node->setPosition(Vec2(x, y));
Vec2 pos = node->getPosition();

// 旋转(角度)
node->setRotation(45.0f);
float angle = node->getRotation();

// 缩放
node->setScale(Vec2(2.0f, 2.0f));
Vec2 scale = node->getScale();

// 锚点0,0 左上角0.5,0.5 中心1,1 右下角)
node->setAnchor(Vec2(0.5f, 0.5f));

// 可见性
node->setVisible(true);
bool visible = node->isVisible();

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

链式调用Builder 模式)

// 使用链式调用快速配置节点
auto text = Text::create("标题", font)
    ->withPosition(640.0f, 100.0f)
    ->withAnchor(0.5f, 0.5f)
    ->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f))
    ->withCoordinateSpace(CoordinateSpace::Screen);
addChild(text);

精灵Sprite

创建精灵

auto& resources = Application::instance().resources();

// 从纹理创建
auto texture = resources.loadTexture("assets/player.png");
auto sprite = Sprite::create(texture);

// 设置精灵属性
sprite->setPosition(Vec2(400, 300));
sprite->setAnchor(Vec2(0.5f, 0.5f));

// 切换纹理
auto newTexture = resources.loadTexture("assets/player2.png");
sprite->setTexture(newTexture);

精灵动画

// 创建帧动画
auto anim = AnimatedSprite::createFromGrid(
    "player.png",  // 纹理
    32, 32,        // 单帧宽高
    100.0f,        // 帧间隔(ms)
    8              // 总帧数
);

// 播放动画
anim->play();

// 设置帧范围
anim->setFrameRange(0, 3);

// 循环播放
anim->setLoop(true);

// 停止动画
anim->stop();

文本Text

创建文本

// 加载字体
auto font = resources.loadFont("assets/font.ttf", 24, true);

// 创建文本
auto text = Text::create("Hello World", font);
text->setPosition(Vec2(400, 300));
addChild(text);

// 动态修改文本
text->setText("新的文本内容");

// 使用格式化文本
text->setFormat("得分: %d", score);

文本样式

text->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));  // 颜色
text->setFontSize(48);                              // 字体大小
text->setAnchor(Vec2(0.5f, 0.5f));                  // 锚点

// 坐标空间
text->withCoordinateSpace(CoordinateSpace::Screen)  // 屏幕空间
      ->withScreenPosition(100.0f, 50.0f);

按钮Button

创建按钮

auto button = Button::create();
button->setFont(font);
button->setText("点击我");
button->setPosition(Vec2(400, 300));
button->setCustomSize(200.0f, 60.0f);

// 设置颜色
button->setTextColor(Colors::White);
button->setBackgroundColor(
    Colors::Blue,    // 正常状态
    Colors::Green,   // 悬停状态
    Colors::Red      // 按下状态
);

// 设置点击回调
button->onClick([]() {
    E2D_LOG_INFO("按钮被点击!");
});

addChild(button);

透明按钮(用于菜单)

// 创建纯文本按钮(透明背景)
auto menuBtn = Button::create();
menuBtn->setFont(font);
menuBtn->setText("菜单项");
menuBtn->setTextColor(Colors::Black);
menuBtn->setBackgroundColor(
    Colors::Transparent,
    Colors::Transparent,
    Colors::Transparent
);
menuBtn->setBorder(Colors::Transparent, 0.0f);
menuBtn->setAnchor(0.5f, 0.5f);
menuBtn->setPosition(centerX, centerY);
addChild(menuBtn);

节点层级管理

父子关系

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

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

// 移除所有子节点
parent->removeAllChildren();

// 获取父节点
Node* parent = child->getParent();

// 获取子节点列表
const auto& children = parent->getChildren();

Z轴顺序

// 设置Z轴顺序值越大越在上层
node->setZOrder(10);

// 重新排序子节点
parent->reorderChild(child, newZOrder);

自定义节点

继承 Node 创建自定义节点

class Player : public Node {
public:
    static Ptr<Player> create(Ptr<Texture> texture) {
        auto player = makePtr<Player>();
        if (player->init(texture)) {
            return player;
        }
        return nullptr;
    }
    
    bool init(Ptr<Texture> texture) {
        sprite_ = Sprite::create(texture);
        addChild(sprite_);
        return true;
    }
    
    void update(float dt) override {
        // 更新逻辑
        velocity_.y += gravity_ * dt;
        setPosition(getPosition() + velocity_ * dt);
    }
    
    void jump() {
        velocity_.y = jumpForce_;
    }
    
private:
    Ptr<Sprite> sprite_;
    Vec2 velocity_;
    float gravity_ = -980.0f;
    float jumpForce_ = 500.0f;
};

// 使用
auto player = Player::create(texture);
scene->addChild(player);

空间索引(碰撞检测)

启用空间索引

class CollidableNode : public Node {
public:
    CollidableNode() {
        // 启用空间索引
        setSpatialIndexed(true);
    }
    
    // 必须实现 getBoundingBox
    Rect getBoundingBox() const override {
        Vec2 pos = getPosition();
        return Rect(pos.x - 25, pos.y - 25, 50, 50);
    }
};

查询碰撞

// 在场景中查询所有碰撞
auto collisions = scene->queryCollisions();

for (const auto& [nodeA, nodeB] : collisions) {
    // 处理碰撞
    handleCollision(nodeA, nodeB);
}

完整示例

参考 examples/push_box/PlayScene.cpp 中的节点使用:

void PlayScene::onEnter() {
    Scene::onEnter();
    
    auto& resources = app.resources();
    
    // 加载纹理资源
    texWall_ = resources.loadTexture("assets/images/wall.gif");
    texBox_ = resources.loadTexture("assets/images/box.gif");
    
    // 创建地图层
    mapLayer_ = makePtr<Node>();
    addChild(mapLayer_);
    
    // 创建地图元素
    for (int y = 0; y < mapHeight; ++y) {
        for (int x = 0; x < mapWidth; ++x) {
            char cell = map_[y][x];
            
            if (cell == '#') {
                auto wall = Sprite::create(texWall_);
                wall->setPosition(x * tileSize, y * tileSize);
                mapLayer_->addChild(wall);
            }
            else if (cell == '$') {
                auto box = Sprite::create(texBox_);
                box->setPosition(x * tileSize, y * tileSize);
                mapLayer_->addChild(box);
            }
        }
    }
    
    // 创建UI文本
    font28_ = resources.loadFont("assets/font.ttf", 28, true);
    levelText_ = Text::create("Level: 1", font28_);
    levelText_->setPosition(50, 30);
    addChild(levelText_);
}

下一步