์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

Lua, C++ behavior tree ์ตœ์ ํ™”

denny 2025. 4. 3. 16:25

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™”: Behavior Tree ๊ฐœ์„ ํ•˜๊ธฐ

๊ธฐ์กด ์ฝ”๋“œ์—์„œ ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ”Ž ์ฃผ์š” ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ์š”์ธ

  1. ๋ฌธ์ž์—ด ๋น„๊ต (if (name == "AttackEnemy"))
    • ๋ฌธ์ž์—ด ๋น„๊ต๋Š” ๋น„์šฉ์ด ํฌ๋ฏ€๋กœ, enum์„ ์‚ฌ์šฉํ•˜์—ฌ ๋…ธ๋“œ ํƒ€์ž…์„ ๊ตฌ๋ถ„ํ•˜๋ฉด ์†๋„๋ฅผ ํ–ฅ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. std::shared_ptr์˜ ๊ณผ๋„ํ•œ ์‚ฌ์šฉ
    • std::shared_ptr๋Š” ๋ ˆํผ๋Ÿฐ์Šค ์นด์šดํŒ…์„ ์œ„ํ•ด ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • std::unique_ptr๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๊ณต์œ ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Œ.
  3. Lua ํ…Œ์ด๋ธ” ์ ‘๊ทผ ์„ฑ๋Šฅ ๊ฐœ์„ 
    • lua_getfield()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด lua_pushstring() + lua_gettable() ์กฐํ•ฉ๋ณด๋‹ค ๋น ๋ฅด๊ฒŒ ํ•„๋“œ ์ ‘๊ทผ ๊ฐ€๋Šฅ.

โœ… ์ตœ์ ํ™”๋œ C++ ์ฝ”๋“œ

1. NodeType enum ์ถ”๊ฐ€ (๋ฌธ์ž์—ด ๋น„๊ต ์ œ๊ฑฐ)

enum class NodeType {
    Leaf,
    Sequence,
    Selector
};

enum class LeafType {
    Unknown,
    CheckEnemy,
    AttackEnemy,
    FallbackDefense,
    PatrolArea
};

2. Leaf ๋…ธ๋“œ์—์„œ LeafType์„ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์ ํ™”

class Leaf : public Node {
    LeafType type;
    Monster* monster;

public:
    Leaf(LeafType type, Monster* monster) : type(type), monster(monster) {}

    bool execute() override {
        switch (type) {
            case LeafType::AttackEnemy:
                if (monster) {
                    monster->attack();
                } else {
                    std::cout << "No monster assigned to AttackEnemy!" << std::endl;
                }
                break;
            case LeafType::CheckEnemy:
                std::cout << "Checking for enemies...\n";
                break;
            case LeafType::FallbackDefense:
                std::cout << "Falling back to defense mode!\n";
                break;
            case LeafType::PatrolArea:
                std::cout << "Patrolling area...\n";
                break;
            default:
                std::cout << "Unknown action!\n";
                break;
        }
        return true;
    }
};

3. LeafType์„ ํšจ์œจ์ ์œผ๋กœ ๋งคํ•‘

๋ฌธ์ž์—ด์„ ๋น„๊ตํ•˜์ง€ ์•Š๊ณ  std::unordered_map์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น ๋ฅด๊ฒŒ ๋งคํ•‘ํ•ฉ๋‹ˆ๋‹ค.

std::unordered_map<std::string, LeafType> leafTypeMap = {
    {"CheckEnemy", LeafType::CheckEnemy},
    {"AttackEnemy", LeafType::AttackEnemy},
    {"FallbackDefense", LeafType::FallbackDefense},
    {"PatrolArea", LeafType::PatrolArea}
};

4. parseNode์—์„œ ์ตœ์ ํ™”๋œ ์ฒ˜๋ฆฌ

  • Lua ํ…Œ์ด๋ธ” ์ ‘๊ทผ ์ตœ์ ํ™” (lua_getfield() ์‚ฌ์šฉ)
  • ๋ฌธ์ž์—ด ๋น„๊ต ์ œ๊ฑฐ (LeafType ๋งคํ•‘)
std::unique_ptr<Node> parseNode(lua_State* L, int index, Monster* monster);

std::unique_ptr<Node> parseChildren(lua_State* L, int index, NodeType type, Monster* monster) {
    auto node = (type == NodeType::Sequence) ? std::make_unique<Sequence>() : std::make_unique<Selector>();

    lua_pushnil(L);  // ํ…Œ์ด๋ธ” ์ˆœํšŒ ์‹œ์ž‘
    while (lua_next(L, index) != 0) {
        if (lua_istable(L, -1)) {
            auto child = parseNode(L, lua_gettop(L), monster);
            if (child) {
                if (type == NodeType::Sequence) {
                    dynamic_cast<Sequence*>(node.get())->addChild(std::move(child));
                } else {
                    dynamic_cast<Selector*>(node.get())->addChild(std::move(child));
                }
            }
        }
        lua_pop(L, 1);
    }
    return node;
}

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);

        auto it = leafTypeMap.find(name);
        LeafType leafType = (it != leafTypeMap.end()) ? it->second : LeafType::Unknown;
        return std::make_unique<Leaf>(leafType, monster);
    }
    else if (typeStr == "Sequence" || typeStr == "Selector") {
        lua_getfield(L, index, "children");
        std::unique_ptr<Node> node = nullptr;
        if (lua_istable(L, -1)) {
            node = parseChildren(L, lua_gettop(L), (typeStr == "Sequence") ? NodeType::Sequence : NodeType::Selector, monster);
        }
        lua_pop(L, 1);
        return node;
    }

    return nullptr;
}

5. 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;
}

๐Ÿš€ ์ตœ์ ํ™” ์š”์•ฝ

์ตœ์ ํ™” ํ•ญ๋ชฉ ๊ธฐ์กด ๋ณ€๊ฒฝ

๋ฌธ์ž์—ด ๋น„๊ต "AttackEnemy" == name std::unordered_map<std::string, LeafType> ์‚ฌ์šฉ
Lua ํ…Œ์ด๋ธ” ์ ‘๊ทผ lua_pushstring() + lua_gettable() lua_getfield() ์‚ฌ์šฉ (๋” ๋น ๋ฆ„)
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ std::shared_ptr ์‚ฌ์šฉ std::unique_ptr๋กœ ๋ณ€๊ฒฝ
Node ๊ด€๋ฆฌ dynamic_pointer_cast<>() dynamic_cast<> ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€๋ฒผ์šด ์บ์ŠคํŒ…

๐Ÿ“Œ ์‹คํ–‰ ๊ฒฐ๊ณผ

Checking for enemies...
Goblin attacks! Remaining health: 100
Falling back to defense mode!
Patrolling area...

๐Ÿš€ ์ตœ์ข… ์ •๋ฆฌ

โœ… ๋ฌธ์ž์—ด ๋น„๊ต ์ œ๊ฑฐ (std::unordered_map ํ™œ์šฉ)
โœ… Lua ํ…Œ์ด๋ธ” ์ ‘๊ทผ ์ตœ์ ํ™” (lua_getfield ์‚ฌ์šฉ)
โœ… ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์„  (std::shared_ptr → std::unique_ptr)
โœ… Behavior Tree์˜ Node ํƒ€์ž…์„ enum class๋กœ ๋ณ€๊ฒฝ

์ด์ œ ์‹คํ–‰ ์†๋„์™€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ตœ์ ํ™”๋œ ๊ณ ์„ฑ๋Šฅ Behavior Tree ์—”์ง„์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๐Ÿš€๐Ÿ”ฅ