354 lines
7.4 KiB
Markdown
354 lines
7.4 KiB
Markdown
|
|
# 03. 节点系统
|
|||
|
|
|
|||
|
|
Extra2D 的节点系统是构建游戏对象的基础。所有可见的游戏元素都是节点的子类。
|
|||
|
|
|
|||
|
|
## 核心节点类型
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Node (基类)
|
|||
|
|
├── Sprite (精灵)
|
|||
|
|
├── Text (文本)
|
|||
|
|
├── Button (按钮)
|
|||
|
|
├── Widget (UI控件基类)
|
|||
|
|
│ ├── Label (标签)
|
|||
|
|
│ ├── CheckBox (复选框)
|
|||
|
|
│ ├── RadioButton (单选按钮)
|
|||
|
|
│ ├── Slider (滑块)
|
|||
|
|
│ └── ProgressBar (进度条)
|
|||
|
|
└── 自定义节点...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 基础节点操作
|
|||
|
|
|
|||
|
|
### 创建和添加节点
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 创建精灵
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 节点属性
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 位置
|
|||
|
|
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 模式)
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 使用链式调用快速配置节点
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
### 创建精灵
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 精灵动画
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 创建帧动画
|
|||
|
|
auto anim = AnimatedSprite::createFromGrid(
|
|||
|
|
"player.png", // 纹理
|
|||
|
|
32, 32, // 单帧宽高
|
|||
|
|
100.0f, // 帧间隔(ms)
|
|||
|
|
8 // 总帧数
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 播放动画
|
|||
|
|
anim->play();
|
|||
|
|
|
|||
|
|
// 设置帧范围
|
|||
|
|
anim->setFrameRange(0, 3);
|
|||
|
|
|
|||
|
|
// 循环播放
|
|||
|
|
anim->setLoop(true);
|
|||
|
|
|
|||
|
|
// 停止动画
|
|||
|
|
anim->stop();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 文本(Text)
|
|||
|
|
|
|||
|
|
### 创建文本
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 加载字体
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 文本样式
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
### 创建按钮
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 透明按钮(用于菜单)
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 创建纯文本按钮(透明背景)
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 节点层级管理
|
|||
|
|
|
|||
|
|
### 父子关系
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 添加子节点
|
|||
|
|
parent->addChild(child);
|
|||
|
|
|
|||
|
|
// 移除子节点
|
|||
|
|
parent->removeChild(child);
|
|||
|
|
|
|||
|
|
// 移除所有子节点
|
|||
|
|
parent->removeAllChildren();
|
|||
|
|
|
|||
|
|
// 获取父节点
|
|||
|
|
Node* parent = child->getParent();
|
|||
|
|
|
|||
|
|
// 获取子节点列表
|
|||
|
|
const auto& children = parent->getChildren();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Z轴顺序
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 设置Z轴顺序(值越大越在上层)
|
|||
|
|
node->setZOrder(10);
|
|||
|
|
|
|||
|
|
// 重新排序子节点
|
|||
|
|
parent->reorderChild(child, newZOrder);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 自定义节点
|
|||
|
|
|
|||
|
|
### 继承 Node 创建自定义节点
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 空间索引(碰撞检测)
|
|||
|
|
|
|||
|
|
### 启用空间索引
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 查询碰撞
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
// 在场景中查询所有碰撞
|
|||
|
|
auto collisions = scene->queryCollisions();
|
|||
|
|
|
|||
|
|
for (const auto& [nodeA, nodeB] : collisions) {
|
|||
|
|
// 处理碰撞
|
|||
|
|
handleCollision(nodeA, nodeB);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 完整示例
|
|||
|
|
|
|||
|
|
参考 `examples/push_box/PlayScene.cpp` 中的节点使用:
|
|||
|
|
|
|||
|
|
```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_);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 下一步
|
|||
|
|
|
|||
|
|
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
|
|||
|
|
- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
|
|||
|
|
- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
|