GameObject

GameObject의 정의와 역할

GameObject는 게임 엔진에서 가장 기본적인 개체로, 게임 세계의 모든 요소(캐릭터, 카메라, 조명 등)를 표현합니다. Unity와 유사한 컴포넌트 기반 아키텍처를 채택하여 확장성과 유연성을 제공합니다.

// GameObject.h에서 정의된 기본 구조
class GameObject : public enable_shared_from_this<GameObject>
{
public:
    GameObject();
    ~GameObject();

    void Start();
    void Update();
    void LateUpdate();

    // 컴포넌트 관리 메서드
    void AddComponent(shared_ptr<Component> component);
    void RemoveComponent(shared_ptr<Component> component);
    
    // 계층 구조 관리 메서드
    void SetParent(shared_ptr<GameObject> parent);
    void AddChild(shared_ptr<GameObject> child);
    
    // 기타 속성 관리
    void SetName(const wstring& name) { _name = name; }
    wstring& GetName() { return _name; }
    
    // ... 기타 메서드들 ...

private:
    vector<shared_ptr<Component>> _components;
    vector<shared_ptr<GameObject>> _children;
    wstring _name;
    GameObjectType _type;
    shared_ptr<GameObject> _parent;
    // ... 기타 멤버 변수들 ...
};

GameObject는 다음과 같은 주요 기능을 제공합니다:

  1. 컴포넌트 관리 (추가/제거)

  2. 계층 구조 관리 (부모-자식 관계)

  3. 생명 주기 관리 (Start, Update, LateUpdate)

  4. 속성 관리 (이름, 타입 등)

SceneManager를 통한 GameObject 생성

SceneManager는 다양한 유형의 GameObject를 생성하는 편리한 방법을 제공합니다:

// SceneManager.h에 정의된 생성 메서드들
shared_ptr<GameObject> CreateCubeToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateSphereToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateCylinderToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateQuadToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateGridToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateTerrainToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateParticleToScene(const wstring& sceneName);
shared_ptr<GameObject> CreateAnimatedMeshToScene(const wstring& sceneName, const wstring& modelName);
shared_ptr<GameObject> CreateStaticMeshToScene(const wstring& sceneName, const wstring& modelName);

예를 들어, 큐브 오브젝트 생성 과정을 살펴보겠습니다:

shared_ptr<GameObject> SceneManager::CreateCubeToScene(const wstring& sceneName)
{
	// 새로운 오브젝트 생성 시 기본 이름 설정
	int count = 1;
	wstring baseName = L"cube";
	wstring newName = baseName;

	// 이미 존재하는 오브젝트 이름인지 확인
	while (_activeScene->Find(newName) != nullptr)
	{
		newName = baseName + to_wstring(count);
		count++;
	}

	SaveAndLoadGameObjectToXML(sceneName, newName,
		Vec3(0.0f, 0.0f, 0.0f));
	auto cubeRenderer = make_shared<MeshRenderer>();
	cubeRenderer->SetMesh(RESOURCE.GetResource<Mesh>(L"Cube"));
	cubeRenderer->SetModel(nullptr);
	cubeRenderer->SetMaterial(RESOURCE.GetResource<Material>(L"SolidWhiteMaterial"));
	cubeRenderer->SetRasterzierState(D3D11_FILL_SOLID, D3D11_CULL_BACK, false);
	cubeRenderer->AddRenderPass();
	cubeRenderer->GetRenderPasses()[0]->SetPass(Pass::DEFAULT_RENDER);
	cubeRenderer->GetRenderPasses()[0]->SetMeshRenderer(cubeRenderer);
	cubeRenderer->GetRenderPasses()[0]->SetTransform(_activeScene->Find(newName)->transform());
	cubeRenderer->GetRenderPasses()[0]->SetDepthStencilStateType(DSState::NORMAL);
	AddComponentToGameObjectAndSaveToXML(sceneName, newName, cubeRenderer,
		L"SolidWhiteMaterial", L"Cube");
	auto boxCollider = make_shared<BoxCollider>();
	boxCollider->SetScale(Vec3(1.0f, 1.0f, 1.0f));
	AddComponentToGameObjectAndSaveToXML(sceneName, newName, boxCollider);

	RENDER.GetRenderableObject();

	return _activeScene->Find(newName);
}

SceneManager에서 위와 같이 오브젝트 추가를 호출하게되면 다음과 같은 작업을 Scene에서 처리합니다:

// Scene.cpp에 정의된 메서드들
void Scene::AddGameObject(shared_ptr<GameObject> gameObject)
{
    _gameObjects.push_back(gameObject);
}

void Scene::RemoveGameObject(shared_ptr<GameObject> gameObject)
{
    gameObject->DetachFromParent();

    // 특수 관계 처리 (본 오브젝트 등)
    shared_ptr<GameObject> nonBoneChildrenParent = gameObject->GetNoneBoneChildrenParent();
    if (nonBoneChildrenParent)
    {
        nonBoneChildrenParent->GetBoneParentObject().lock()->RemoveActiveBoneIndex(nonBoneChildrenParent->GetBoneIndex());
        nonBoneChildrenParent->SetHasNoneBoneChildrenFlag(false);
    }

    // 자식 오브젝트 재귀적 제거
    vector<shared_ptr<GameObject>> children = gameObject->GetChildren(); // 복사본 생성
    for (shared_ptr<GameObject> child : children)
    {
        child->DetachFromParent();
        RemoveGameObject(child); // 재귀적으로 자식들도 제거
        SCENE.RemoveGameObjectFromXML(SCENE.GetActiveScene()->GetSceneName(), child->GetName());
    }

    // 목록에서 제거
    auto it = std::find(_gameObjects.begin(), _gameObjects.end(), gameObject);
    if (it != _gameObjects.end())
    {
        _gameObjects.erase(it);
    }

    // 본 오브젝트 목록에서도 확인하여 제거
    it = std::find(_boneGameobjects.begin(), _boneGameobjects.end(), gameObject);
    if (it != _boneGameobjects.end())
    {
        _boneGameobjects.erase(it);
    }
    
    // 렌더링 목록 업데이트
    RENDER.GetRenderableObject();
}

Scene을 통해 GameObject를 검색할 수도 있습니다:

shared_ptr<GameObject> Scene::Find(const wstring& name)
{
    for (shared_ptr<GameObject> gameObject : _gameObjects)
    {
        if (gameObject->GetName() == name)
            return gameObject;
    }
    for (shared_ptr<GameObject> gameObject : _boneGameobjects)
    {
        if (gameObject->GetName() == name)
            return gameObject;
    }
    return nullptr;
}

shared_ptr<GameObject> Scene::FindWithComponent(ComponentType type)
{
    for (shared_ptr<GameObject> gameObject : _gameObjects)
    {
        switch (type)
        {
            case ComponentType::Transform:
                if (gameObject->GetComponent<Transform>() != nullptr)
                    return gameObject;
                break;
            case ComponentType::MeshRenderer:
                if (gameObject->GetComponent<MeshRenderer>() != nullptr)
                    return gameObject;
                break;
            case ComponentType::Camera:
                if (gameObject->GetComponent<Camera>() != nullptr)
                {
                    if (GUI.isSceneView())
                    {
                        if (gameObject->GetName() != L"EditorCamera")
                            continue;
                    else
                        return gameObject;
                    }
                else
                {
                    if (gameObject->GetName() != L"MainCamera")
                        continue;
                    else
                        return gameObject;
                    }
                }
                break;
            case ComponentType::Animator:
                if (gameObject->GetComponent<Animator>() != nullptr)
                    return gameObject;
                break;
            case ComponentType::Light:
                if (gameObject->GetComponent<Light>() != nullptr)
                    return gameObject;
                break;
            default:
                break;
        }
    }
    return nullptr;
}

계층 구조 관리

GameObject는 부모-자식 관계를 통해 계층적으로 구성될 수 있습니다:

void GameObject::SetParent(shared_ptr<GameObject> parent)
{
    DetachFromParent();

    _parent = parent;
    shared_from_this()->transform()->SetParent(_parent->transform());
    _parent->AddChild(shared_from_this());
    _parent->transform()->AddChild(shared_from_this()->transform());
}

void GameObject::AddChild(shared_ptr<GameObject> child)
{
    _children.push_back(child);
}

void GameObject::DetachFromParent()
{
    if (_parent)
    {
        // 부모의 자식 목록에서 제거
        auto it = std::find(_parent->_children.begin(), _parent->_children.end(), shared_from_this());
        if (it != _parent->_children.end())
            _parent->_children.erase(it);

        // Transform 계층도 분리
        transform()->DetachFromParent();

        _parent = nullptr;
    }
}

부모가 변경되면 Transform 계층도 함께 업데이트되어, 위치, 회전, 크기가 부모의 변환에 영향을 받게 됩니다.

XML을 통한 GameObject저장

SceneManager는 GameObject를 XML 형태로 저장하고 로드합니다:

void SceneManager::SaveAndLoadGameObjectToXML(const wstring& sceneName, const wstring& name,
    const Vec3& position, const Vec3& rotation, const Vec3& scale, shared_ptr<GameObject> parent)
{
    // 파일 경로 결정
    wstring xmlPath = L"../GameCoding/Resource/Scene/" + sceneName + L".xml";
    string strPath(xmlPath.begin(), xmlPath.end());
    
    // XML 문서 로드 또는 생성
    tinyxml2::XMLDocument doc;
    if (filesystem::exists(strPath))
        doc.LoadFile(strPath.c_str());
    else
    {
        tinyxml2::XMLDeclaration* decl = doc.NewDeclaration();
        doc.InsertFirstChild(decl);
        
        tinyxml2::XMLElement* sceneElem = doc.NewElement("Scene");
        doc.InsertEndChild(sceneElem);
    }
    
    // 씬 요소 가져오기
    tinyxml2::XMLElement* sceneElem = doc.FirstChildElement("Scene");
    
    // 게임 오브젝트 생성 및 씬에 추가
    shared_ptr<GameObject> gameObject = make_shared<GameObject>();
    gameObject->SetName(name);
    gameObject->transform()->SetLocalPosition(position);
    gameObject->transform()->SetLocalRotation(rotation);
    gameObject->transform()->SetLocalScale(scale);
    
    // 부모 설정
    if (parent)
        gameObject->SetParent(parent);
    
    GetActiveScene()->AddGameObject(gameObject);
    
    // XML에 게임 오브젝트 정보 저장
    tinyxml2::XMLElement* gameObj = doc.NewElement("GameObject");
    gameObj->SetAttribute("name", string(name.begin(), name.end()).c_str());
    
    // Transform 정보 저장
    tinyxml2::XMLElement* transformElem = doc.NewElement("Transform");
    
    tinyxml2::XMLElement* posElem = doc.NewElement("Position");
    posElem->SetAttribute("x", position.x);
    posElem->SetAttribute("y", position.y);
    posElem->SetAttribute("z", position.z);
    transformElem->InsertEndChild(posElem);
    
    tinyxml2::XMLElement* rotElem = doc.NewElement("Rotation");
    rotElem->SetAttribute("x", rotation.x);
    rotElem->SetAttribute("y", rotation.y);
    rotElem->SetAttribute("z", rotation.z);
    transformElem->InsertEndChild(rotElem);
    
    tinyxml2::XMLElement* scaleElem = doc.NewElement("Scale");
    scaleElem->SetAttribute("x", scale.x);
    scaleElem->SetAttribute("y", scale.y);
    scaleElem->SetAttribute("z", scale.z);
    transformElem->InsertEndChild(scaleElem);
    
    gameObj->InsertEndChild(transformElem);
    sceneElem->InsertEndChild(gameObj);
    
    // 부모-자식 관계 저장
    if (parent)
    {
        tinyxml2::XMLElement* parentElem = doc.NewElement("Parent");
        parentElem->SetAttribute("name", string(parent->GetName().begin(), parent->GetName().end()).c_str());
        gameObj->InsertEndChild(parentElem);
    }
    
    // XML 저장
    doc.SaveFile(strPath.c_str());
}

저장된 XML형태는 다음과 같습니다:

<GameObject name="cube">
 <Transform posX="40.368896" posY="4.1358805" posZ="44.429325" rotX="0" rotY="0" rotZ="0" scaleX="1" scaleY="1" scaleZ="1"/>
  <MeshRenderer useEnvironmentMap="false" fillMode="3" cullMode="3" frontCounterClockwise="false" material="DefaultMaterial" mesh="Sphere">
   <RenderPass pass="1" depthStencilState="1"/>
  </MeshRenderer>
 <BoxCollider scaleX="1" scaleY="1" scaleZ="1" centerX="0" centerY="0" centerZ="0"/>
</GameObject>

GameObject 활용 예시

다음은 코드에서 GameObject를 활용하는 몇 가지 예시입니다:

// 씬에서 게임 오브젝트 찾기
shared_ptr<GameObject> camera = scene->Find(L"MainCamera");

// 컴포넌트 접근
shared_ptr<Camera> cameraComponent = camera->GetComponent<Camera>();
shared_ptr<Transform> transform = camera->transform();

// 위치 변경
transform->SetLocalPosition(Vec3(0, 5, -10));

// 새 컴포넌트 추가
shared_ptr<BoxCollider> collider = make_shared<BoxCollider>();
camera->AddComponent(collider);

// 부모-자식 관계 설정
shared_ptr<GameObject> child = make_shared<GameObject>();
child->SetName(L"ChildObject");
child->SetParent(camera);

// 사용자정의 컴포넌트 추가
shared_ptr<MoveObject> moveScript = make_shared<MoveObject>();
child->AddComponent(moveScript);

// 씬에 추가
scene->AddGameObject(child);

Last updated