ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lua, C++ behavior tree
    카테고리 없음 2025. 4. 3. 16:13

    트리의 깊이가 깊어지면 lua_gettop(L)을 직접 사용하여 노드를 파싱하는 방식이 불안정할 수 있습니다. 이를 해결하기 위해 **lua_next()**를 사용하여 테이블을 안전하게 순회하도록 개선하겠습니다.


    📌 개선된 C++ 코드

    • 깊은 트리 구조에서도 안전하게 동작
    • lua_next()를 사용해 노드 테이블을 안정적으로 탐색
    • 불필요한 스택 조작을 줄이고 가독성 개선
    #include <iostream>
    #include <lua.hpp>
    #include <vector>
    #include <memory>
    #include <string>
    
    // 행동 트리 노드 기본 클래스
    class Node {
    public:
        virtual ~Node() = default;
        virtual bool execute() = 0;
    };
    
    // Sequence 노드
    class Sequence : public Node {
        std::vector<std::shared_ptr<Node>> children;
    public:
        void addChild(std::shared_ptr<Node> child) { children.push_back(child); }
    
        bool execute() override {
            for (auto& child : children) {
                if (!child->execute()) return false;
            }
            return true;
        }
    };
    
    // Selector 노드
    class Selector : public Node {
        std::vector<std::shared_ptr<Node>> children;
    public:
        void addChild(std::shared_ptr<Node> child) { children.push_back(child); }
    
        bool execute() override {
            for (auto& child : children) {
                if (child->execute()) return true;
            }
            return false;
        }
    };
    
    // Leaf 노드 (실제 동작 구현)
    class Leaf : public Node {
        std::string name;
    public:
        Leaf(const std::string& name) : name(name) {}
    
        bool execute() override {
            std::cout << "Executing " << name << std::endl;
            return true;
        }
    };
    
    // Lua에서 트리 로드 및 C++ 트리 변환
    std::shared_ptr<Node> parseNode(lua_State* L, int index);
    
    std::shared_ptr<Node> parseChildren(lua_State* L, int index, const std::string& type) {
        std::shared_ptr<Node> node = (type == "Sequence") ? std::make_shared<Sequence>() : std::make_shared<Selector>();
    
        lua_pushnil(L);  // 테이블 순회 시작
        while (lua_next(L, index) != 0) {
            if (lua_istable(L, -1)) {
                std::shared_ptr<Node> child = parseNode(L, lua_gettop(L));
                if (child) {
                    if (type == "Sequence") {
                        std::dynamic_pointer_cast<Sequence>(node)->addChild(child);
                    } else {
                        std::dynamic_pointer_cast<Selector>(node)->addChild(child);
                    }
                }
            }
            lua_pop(L, 1);  // 값 팝
        }
    
        return node;
    }
    
    std::shared_ptr<Node> parseNode(lua_State* L, int index) {
        lua_pushstring(L, "type");
        lua_gettable(L, index);
        std::string type = lua_tostring(L, -1);
        lua_pop(L, 1);
    
        if (type == "Leaf") {
            lua_pushstring(L, "name");
            lua_gettable(L, index);
            std::string name = lua_tostring(L, -1);
            lua_pop(L, 1);
            return std::make_shared<Leaf>(name);
        }
        else if (type == "Sequence" || type == "Selector") {
            lua_pushstring(L, "children");
            lua_gettable(L, index);
    
            std::shared_ptr<Node> node = nullptr;
            if (lua_istable(L, -1)) {
                node = parseChildren(L, lua_gettop(L), type);
            }
            lua_pop(L, 1);
            return node;
        }
    
        return nullptr;
    }
    
    std::shared_ptr<Node> loadTreeFromLua(lua_State* L, const std::string& filename) {
        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, "BehaviorTree");
        if (!lua_istable(L, -1)) {
            std::cerr << "Invalid BehaviorTree format" << std::endl;
            return nullptr;
        }
    
        return parseNode(L, lua_gettop(L));
    }
    
    int main() {
        lua_State* L = luaL_newstate();
        luaL_openlibs(L);
    
        auto behaviorTree = loadTreeFromLua(L, "behavior_tree.lua");
        if (behaviorTree) {
            behaviorTree->execute();
        }
    
        lua_close(L);
        return 0;
    }
    

    📌 개선 내용

    lua_next() 사용하여 안전한 테이블 순회
    불필요한 lua_gettop() 제거 → 안정적인 스택 관리
    더 깊은 트리 구조에서도 작동 가능
    각각의 기능을 함수로 분리하여 가독성 증가


    📌 예제 Lua 코드 (behavior_tree.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
    

    📌 실행 결과

    Executing CheckEnemy
    Executing AttackEnemy
    

    🚀 개선된 점 요약

    1. 트리의 깊이가 깊어도 작동 가능
    2. Lua 스택 관리가 안정적
    3. C++에서 더 직관적인 트리 파싱

    이제 깊이가 깊은 Behavior Tree도 문제없이 동작할 것입니다! 🔥

Designed by Tistory.