์นดํ
๊ณ ๋ฆฌ ์์
Lua, C++ behavior tree ์ต์ ํ2
denny
2025. 4. 3. 16:35
๐ C++ ๊ธฐ๋ฐ Behavior Tree (Lua + C++)
์ด์ Behavior Tree๊ฐ Lua์์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๊ณ , C++์์ ์คํ ๋ฐ ์ ์ดํ ์ ์๋๋ก ๊ตฌํํฉ๋๋ค.
๐น ๋ชจ๋ Leaf ๋
ธ๋์ Monster*์ ์ ๋ฌํ๊ณ ,
๐น switch ๋์ ๋คํ์ฑ์ ํ์ฉํ ๊ตฌ์กฐ๋ก ๋ณ๊ฒฝํ์ฌ ํ์ฅ์ฑ ๋ฐ ์ฑ๋ฅ ๊ฐ์ ์ ์ ์ฉํ์ต๋๋ค.
๐ Lua์์ ํธ๋ฆฌ ์ ์ (Lua Script)
์ฐ์ , Lua์์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์ํฉ๋๋ค.
function Selector(children)
return { type = "Selector", children = children }
end
function Sequence(children)
return { type = "Sequence", children = children }
end
function Leaf(name)
return { type = "Leaf", name = name }
end
BehaviorTree = Selector({
Sequence({
Leaf("CheckEnemy"),
Selector({
Leaf("AttackEnemy"),
Leaf("FallbackDefense")
})
}),
Leaf("PatrolArea")
})
return BehaviorTree
๐ C++ ์ฝ๋
์ด์ C++์์ Lua ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๊ณ , Behavior Tree๋ฅผ ์คํํ๋ ์ ์ฒด ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
1๏ธโฃ Node ๊ธฐ๋ณธ ํด๋์ค
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <unordered_map>
#include <lua.hpp>
class Monster {
public:
std::string name;
int health;
Monster(std::string name, int hp) : name(std::move(name)), health(hp) {}
void attack() {
std::cout << name << " attacks the enemy!\n";
}
std::string getName() const {
return name;
}
};
class Node {
public:
virtual ~Node() = default;
virtual bool execute() = 0;
};
2๏ธโฃ Composite (Selector / Sequence)
enum class NodeType { Sequence, Selector };
class CompositeNode : public Node {
protected:
std::vector<std::unique_ptr<Node>> children;
NodeType type;
public:
explicit CompositeNode(NodeType type) : type(type) {}
void addChild(std::unique_ptr<Node> child) {
children.push_back(std::move(child));
}
bool execute() override {
if (type == NodeType::Sequence) {
for (auto& child : children) {
if (!child->execute()) return false;
}
return true;
} else { // Selector
for (auto& child : children) {
if (child->execute()) return true;
}
return false;
}
}
};
3๏ธโฃ Leaf ๊ธฐ๋ณธ ํด๋์ค
class Leaf : public Node {
protected:
Monster* monster;
public:
explicit Leaf(Monster* monster) : monster(monster) {}
virtual ~Leaf() = default;
virtual bool execute() override = 0;
};
4๏ธโฃ Leaf ํ๋ ํด๋์ค
class AttackEnemyLeaf : public Leaf {
public:
using Leaf::Leaf;
bool execute() override {
if (monster) {
monster->attack();
return true;
}
return false;
}
};
class CheckEnemyLeaf : public Leaf {
public:
using Leaf::Leaf;
bool execute() override {
if (monster) {
std::cout << monster->getName() << " is checking for enemies...\n";
}
return true;
}
};
class FallbackDefenseLeaf : public Leaf {
public:
using Leaf::Leaf;
bool execute() override {
if (monster) {
std::cout << monster->getName() << " is falling back to defense mode!\n";
}
return true;
}
};
5๏ธโฃ LeafFactory
class LeafFactory {
public:
static std::unique_ptr<Leaf> createLeaf(const std::string& name, Monster* monster) {
if (name == "AttackEnemy") return std::make_unique<AttackEnemyLeaf>(monster);
if (name == "CheckEnemy") return std::make_unique<CheckEnemyLeaf>(monster);
if (name == "FallbackDefense") return std::make_unique<FallbackDefenseLeaf>(monster);
return nullptr;
}
};
6๏ธโฃ Lua์์ ํธ๋ฆฌ ๋ฐ์ดํฐ ํ์ฑ
std::unique_ptr<Node> parseNode(lua_State* L, int index, Monster* monster) {
lua_getfield(L, index, "type");
std::string typeStr = lua_tostring(L, -1);
lua_pop(L, 1);
if (typeStr == "Leaf") {
lua_getfield(L, index, "name");
std::string name = lua_tostring(L, -1);
lua_pop(L, 1);
return LeafFactory::createLeaf(name, monster);
}
else if (typeStr == "Sequence" || typeStr == "Selector") {
lua_getfield(L, index, "children");
auto compositeNode = std::make_unique<CompositeNode>(
(typeStr == "Sequence") ? NodeType::Sequence : NodeType::Selector
);
if (lua_istable(L, -1)) {
int len = luaL_len(L, -1);
for (int i = 1; i <= len; ++i) {
lua_rawgeti(L, -1, i);
compositeNode->addChild(parseNode(L, lua_gettop(L), monster));
lua_pop(L, 1);
}
}
lua_pop(L, 1);
return compositeNode;
}
return nullptr;
}
7๏ธโฃ ํธ๋ฆฌ ๋ก๋ ๋ฐ ์คํ
std::unique_ptr<Node> loadTreeFromLua(lua_State* L, const std::string& filename, Monster* monster) {
if (luaL_dofile(L, filename.c_str()) != LUA_OK) {
std::cerr << "Failed to load Lua file: " << lua_tostring(L, -1) << std::endl;
return nullptr;
}
lua_getglobal(L, "behavior_tree");
if (!lua_istable(L, -1)) {
std::cerr << "Invalid behavior tree format\n";
return nullptr;
}
auto rootNode = parseNode(L, lua_gettop(L), monster);
lua_pop(L, 1);
return rootNode;
}
8๏ธโฃ ์คํ ์ฝ๋ (main)
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
Monster monster("Goblin", 100);
auto behaviorTree = loadTreeFromLua(L, "behavior_tree.lua", &monster);
if (behaviorTree) {
behaviorTree->execute();
}
lua_close(L);
return 0;
}
๐ ์ฃผ์ ๊ฐ์ ์ฌํญ
ํญ๋ชฉ ๊ธฐ์กด ๋ณ๊ฒฝ
Leaf ๋ ธ๋ Monster* ์ฌ์ฉ | ์ผ๋ถ Leaf๋ง ์ฌ์ฉ | ๋ชจ๋ Leaf์์ Monster*์ ์ฌ์ฉ |
ํธ๋ฆฌ ์คํ ๋ฐฉ์ | switch ๋ฌธ | ๋คํ์ฑ(์์) ๊ธฐ๋ฐ ๊ตฌ์กฐ |
Lua ๋ฐ์ดํฐ ํ์ฑ | ์๋ ํ๋ ์ ๊ทผ | parseNode() ํจ์๋ก ์๋ํ |
Factory ํจํด | switch๋ก ์์ฑ | LeafFactory ์ฌ์ฉ |
๐ฏ ์ต์ข ์์ฝ
- ๐ Lua์์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ง ์ค๊ณ, C++์์ ์คํ
- ๐ Leaf ๋ ธ๋์ ๋คํ์ฑ ๊ธฐ๋ฐ ์ค๊ณ (switch ์ ๊ฑฐ)
- ๐ Monster*์ ๋ชจ๋ Leaf์์ ์ฌ์ฉํ์ฌ ์ ์ฐ์ฑ ์ฆ๊ฐ
- ๐ Lua ๋ฐ์ดํฐ ์๋ ํ์ฑํ์ฌ ํธ๋ฆฌ ๊ตฌ์ฑ
- ๐ ํ์ฅ์ฑ์ด ๋ฐ์ด๋ Behavior Tree ๊ตฌ์กฐ ์์ฑ
์ด์ Lua์์ ํ๋์ ์ฝ๊ฒ ๋ณ๊ฒฝํ ์ ์๊ณ , C++์์ ๋น ๋ฅด๊ณ ์ต์ ํ๋ ์คํ์ด ๊ฐ๋ฅํฉ๋๋ค. ๐๐ฅ