SceneNode란 무엇인가?

SceneNode란 무엇인가?

지난 글에서는 씬 그래프가 무엇인지 간단히 살펴봤습니다.

씬 그래프는 게임 오브젝트를 단순한 목록으로만 관리하지 않고, 부모-자식 계층 구조로 관리하는 방식입니다.

World
 └─ Player
     ├─ Camera
     └─ Weapon

이번 글에서는 이 구조를 코드로 만들기 위한 가장 작은 단위인 SceneNode 클래스를 만들어보겠습니다.

아직 부모-자식 관계를 연결하지는 않습니다.
Transform도 아직 다루지 않습니다.
먼저 씬 그래프를 구성하는 가장 기본 단위가 무엇인지부터 확인해보겠습니다.


왜 SceneNode가 필요할까?

게임 월드에는 여러 오브젝트가 존재합니다.

  • 플레이어
  • 카메라
  • 무기
  • 몬스터
  • 이펙트

이 오브젝트들을 씬 그래프 구조로 관리하려면, 각 오브젝트를 하나의 노드로 표현할 수 있어야 합니다.

여기서 노드란 씬 그래프 안에 들어가는 하나의 구성 요소입니다.

예를 들어 Player, Camera, Weapon은 각각 하나의 SceneNode가 될 수 있습니다.

SceneNode("Player")
SceneNode("Camera")
SceneNode("Weapon")

아직은 단순히 이름만 가진 객체로 시작하겠습니다.
중요한 것은 처음부터 너무 많은 기능을 넣지 않는 것입니다.

씬 그래프는 계층 구조, Transform, Tick, Render, Component 등으로 계속 확장될 수 있습니다.
하지만 처음부터 모든 기능을 넣으면 구조를 이해하기 어려워집니다.

그래서 이번 글에서는 SceneNode를 아주 작은 클래스에서 시작하겠습니다.


이번 글에서 만들 SceneNode

이번 글에서 만들 SceneNode는 다음 기능만 가집니다.

  • 노드 이름 저장하기
  • 생성자에서 이름 초기화하기
  • 이름 출력하기

아직 만들지 않을 기능은 다음과 같습니다.

  • 부모 노드
  • 자식 노드 목록
  • Local Position
  • World Position
  • Tick 함수

이 기능들은 이후 글에서 하나씩 추가할 예정입니다.


프로젝트 준비

이번 예제는 윈도우즈 콘솔 프로젝트를 기준으로 진행합니다.
Visual Studio에서 새 C++ 콘솔 프로젝트를 만든 뒤, 아래와 같은 파일 구성을 사용하겠습니다.

SceneGraphTutorial
 ├─ main.cpp
 ├─ SceneNode.h
 └─ SceneNode.cpp

아주 작은 예제이기 때문에 main.cpp 하나에 모두 작성해도 됩니다.
하지만 앞으로 기능을 계속 확장할 예정이므로, 처음부터 SceneNode.h와 SceneNode.cpp를 분리해서 작성하겠습니다.


SceneNode.h 작성하기

먼저 SceneNode.h 파일을 작성하겠습니다.

#pragma once

#include <string>

class SceneNode
{
public:
    SceneNode(const std::string& name);

    void Print() const;

private:
    std::string name;
};

현재 SceneNode 클래스는 매우 단순합니다.

  • name 멤버 변수는 노드의 이름을 저장합니다.
  • 생성자는 노드 이름을 받아 초기화합니다.
  • Print 함수는 노드 이름을 콘솔에 출력합니다.

여기서 중요한 점은 SceneNode가 아직 게임 오브젝트 전체를 의미하지 않는다는 것입니다.
지금 단계에서는 씬 그래프에 들어갈 수 있는 가장 작은 단위 정도로만 생각하면 됩니다.


SceneNode.cpp 작성하기

다음으로 SceneNode.cpp 파일을 작성하겠습니다.

#include "SceneNode.h"

#include <iostream>

SceneNode::SceneNode(const std::string& name)
    : name(name)
{
}

void SceneNode::Print() const
{
    std::cout << name << "\n";
}

생성자에서는 전달 받은 name 값을 멤버 변수 name에 저장합니다.
Print 함수에서는 저장된 이름을 콘솔에 출력합니다.

아직은 매우 단순하지만, 이 구조가 이후 부모-자식 관계와 Transform 구조의 출발점이 됩니다.


main.cpp에서 테스트하기

이제 main.cpp에서 SceneNode를 생성해보겠습니다.

#include "SceneNode.h"

int main()
{
    SceneNode world("World");
    SceneNode player("Player");
    SceneNode camera("Camera");
    SceneNode weapon("Weapon");

    world.Print();
    player.Print();
    camera.Print();
    weapon.Print();

    return 0;
}

실행 결과는 다음과 같습니다.

World
Player
Camera
Weapon

아직은 단순히 이름을 가진 객체를 만들고 출력했을 뿐입니다.
하지만 여기서부터 씬 그래프 구조가 시작됩니다.


아직 씬 그래프처럼 보이지 않는 이유

현재 출력 결과만 보면 씬 그래프처럼 보이지 않습니다.

World
Player
Camera
Weapon

왜냐하면 아직 노드 사이의 관계가 없기 때문입니다.

우리가 만들고 싶은 구조는 다음과 같습니다.

World
 └─ Player
     ├─ Camera
     └─ Weapon

하지만 현재 코드는 각 노드를 독립적으로 생성하기만 했습니다.
World가 Player의 부모인지, Player가 Camera의 부모인지, Weapon이 Player 아래에 있는지 알 수 없습니다.

지금 만든 SceneNode는 씬 그래프의 재료일 뿐입니다.

아직 그래프 구조 자체는 만들어지지 않았습니다.


객체 목록 방식과 현재 구조 비교

기존 방식에서는 오브젝트를 목록으로 관리할 수 있습니다.

std::vector<SceneNode*> nodes;

nodes.emplace_back(&world);
nodes.emplace_back(&player);
nodes.emplace_back(&camera);
nodes.emplace_back(&weapon);

이 방식은 모든 노드를 한 번에 순회하기에는 편리합니다.
하지만 노드 사이의 관계를 표현하지 못합니다.

Player가 Camera의 부모라는 정보, Weapon이 Player에 붙어 있다는 정보는 별도로 관리해야 합니다.

반면 씬 그래프 방식에서는 각 노드가 자신의 자식 노드를 가질 수 있습니다.

world.AddChild(&player);
player.AddChild(&camera);
player.AddChild(&weapon);

아직 AddChild 함수는 만들지 않았습니다.
하지만 다음 글에서 이 함수를 추가하면, 노드 사이의 계층 관계를 코드로 표현할 수 있게 됩니다.


왜 이름부터 저장할까?

SceneNode에 가장 먼저 이름을 넣은 이유는 디버깅과 출력 때문입니다.

씬 그래프는 계층 구조를 다루기 때문에,
현재 어떤 노드가 어디에 연결되어 있는지 확인하는 일이 중요합니다.

이름이 없다면 콘솔에 노드를 출력해도 어떤 노드인지 구분하기 어렵습니다.

Node
Node
Node
Node

반면 이름이 있으면 현재 구조를 훨씬 쉽게 확인할 수 있습니다.

World
Player
Camera
Weapon

이후 글에서는 이 이름을 이용해 계층 구조를 출력할 예정입니다.


현재 코드의 한계

이번 글에서 작성한 SceneNode는 아직 매우 제한적입니다.

  • 부모/자식 계층 없음
  • 위치 정보 없음
  • 계층 구조를 출력 불가능

하지만 이것이 오히려 중요합니다.
처음부터 복잡한 엔진 구조를 만들기보다, 가장 작은 단위부터 시작해야 씬 그래프가 어떻게 확장되는지 이해할 수 있습니다.

현재 SceneNode는 이름만 가진 단순한 클래스입니다.
다음 글에서 여기에 자식 노드 목록을 추가하면, 비로소 씬 그래프다운 구조가 만들어지기 시작합니다.


이번 글의 전체 코드

마지막으로 이번 글에서 작성한 전체 코드를 정리하겠습니다.

SceneNode.h

#pragma once

#include <string>

class SceneNode
{
public:
    SceneNode(const std::string& name);

    void Print() const;

private:
    std::string _name;
};

SceneNode.cpp

#include "SceneNode.h"

#include <iostream>

SceneNode::SceneNode(const std::string& name)
    : _name(name)
{
}

void SceneNode::Print() const
{
    std::cout << _name << "\n";
}

main.cpp

#include "SceneNode.h"

int main()
{
    SceneNode world("World");
    SceneNode player("Player");
    SceneNode camera("Camera");
    SceneNode weapon("Weapon");

    world.Print();
    player.Print();
    camera.Print();
    weapon.Print();

    return 0;
}

마무리

이번 글에서는 씬 그래프의 가장 작은 구성 요소인 SceneNode 클래스를 만들어봤습니다.

아직은 이름만 가진 단순한 클래스이지만, 이 클래스가 앞으로 부모-자식 관계, Transform 상속, Tick 순서, 컴포넌트 구조로 확장될 예정입니다.

씬 그래프는 처음부터 거대한 구조로 이해하려고 하면 어렵습니다.
하지만 SceneNode 하나에서 시작하면 훨씬 단순하게 접근할 수 있습니다.

다음 글에서는 SceneNode에 자식 노드 목록을 추가하고, AddChild 함수를 구현해 부모-자식 관계를 직접 만들어보겠습니다.


👉 게임 엔진 구조를 더 깊이 이해하고 싶다면

아래 강의를 통해 직접 구현해보는 것을 추천드립니다.

C++로 만드는 게임 엔진 프레임워크 강의

👉 C++로 만드는 게임 엔진 프레임워크 강의 바로가기

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다