Make Scene

Scene

씬(Scene)은 게임의 한 영역이나 레벨을 나타내는 컨테이너로, 게임 객체(GameObject)들의 집합체입니다.

  • 씬 구조

// Scene.h의 핵심 구조
class Scene
{
public:
    void Start();
    void Update();
    void LateUpdate();

public:
    void AddGameObject(shared_ptr<GameObject> gameObject);
    void AddBoneGameObject(shared_ptr<GameObject> boneGameObject);
    void RemoveGameObject(shared_ptr<GameObject> gameObject);
    void Picking();
    void UIPicking();
    void CheckCollision();
    void AddPickedObject(shared_ptr<GameObject> pickedObject) { picked = pickedObject; }
    void SetMainCamera(shared_ptr<GameObject> camera) { _mainCamera = camera; }
    void SetSceneName(wstring sceneName) { _sceneName = sceneName; }
    void SwitchMainCameraToEditorCamera() { _mainCamera = Find(L"EditorCamera"); }
    void SwitchMainCameraToMainCamera() { _mainCamera = Find(L"MainCamera"); }
    const vector<shared_ptr<GameObject>>& GetGameObjects() { return _gameObjects; }
    shared_ptr<GameObject> Find(const wstring& name);

private:
    vector<shared_ptr<GameObject>> _gameObjects;
    vector<shared_ptr<GameObject>> _boneGameobjects;
    wstring _sceneName = L"";
    shared_ptr<GameObject> _mainCamera;
    shared_ptr<GameObject> _mainLignt;
    // ...
};

  • Scene XML 구조 예시

<!-- test_scene.xml의 일부 -->
<Scene>
    <GameObject name="EditorCamera">
        <Transform posX="-2" posY="2" posZ="-10" rotX="0" rotY="0" rotZ="0" scaleX="1" scaleY="1" scaleZ="1"/>
        <Camera projectionType="0"/>
        <Script type="EditorCamera"/>
    </GameObject>
    <GameObject name="MainCamera" parent="Kachujin_OBJ">
        <Transform posX="-4.5776367e-05" posY="184.85187" posZ="257.14355" rotX="0" rotY="-1.3660378e-05" rotZ="0" scaleX="100" scaleY="100" scaleZ="100"/>
        <Camera projectionType="0"/>
    </GameObject>
    <!-- 다른 GameObject들 -->
</Scene>

Scene 로드 방법

씬 로딩은 SceneManager 클래스의 LoadScene 메서드를 통해 이루어집니다.

// SceneManager.cpp
void SceneManager::LoadScene(wstring sceneName)
{
    shared_ptr<Scene> scene = make_shared<Scene>();
    _scenes[sceneName] = scene;
    _activeScene = scene;

    LoadSceneXML(sceneName);
    SCENE.Init();
    RENDER.Init();
}

void SceneManager::LoadSceneXML(wstring sceneName)
{
    filesystem::path xmlPath = L"../Resource/Scene/" + sceneName + L".xml";
    string path = Utils::ToString(xmlPath.generic_wstring());
    
    tinyxml2::XMLDocument doc;
    if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS)
        return;
    
    tinyxml2::XMLElement* root = doc.FirstChildElement("Scene");
    if (root == nullptr)
        return;

    // 첫 번째 패스: GameObject 생성
    map<wstring, shared_ptr<GameObject>> gameObjects;
    map<wstring, wstring> parentNames;

    for (tinyxml2::XMLElement* gameObjectElem = root->FirstChildElement("GameObject");
        gameObjectElem != nullptr;
        gameObjectElem = gameObjectElem->NextSiblingElement("GameObject"))
    {
        // GameObject 이름 가져오기
        const char* nameAttr = gameObjectElem->Attribute("name");
        if (nameAttr == nullptr)
            continue;
            
        // 부모 정보 가져오기
        const char* parentAttr = gameObjectElem->Attribute("parent");
        
        wstring name = Utils::ToWString(nameAttr);
        shared_ptr<GameObject> gameObject = make_shared<GameObject>();
        gameObject->SetName(name);
        
        if (parentAttr != nullptr)
            parentNames[name] = Utils::ToWString(parentAttr);
            
        // 컴포넌트 추가 로직
        // ...
        
        // Scene에 GameObject 추가
        _activeScene->AddGameObject(gameObject);
        gameObjects[name] = gameObject;
    }
    
    // 두 번째 패스: 부모-자식 관계 설정
    for (const auto& pair : parentNames)
    {
        const wstring& childName = pair.first;
        const wstring& parentName = pair.second;
        
        shared_ptr<GameObject> child = gameObjects[childName];
        shared_ptr<GameObject> parent = gameObjects[parentName];
        
        if (child && parent)
            child->SetParent(parent);
    }
}

Scene 저장 방법

씬 저장은 다양한 메서드들을 통해 GameObject 정보를 XML로 저장합니다.

// SceneManager.cpp
void SceneManager::SaveAndLoadGameObjectToXML(const wstring& sceneName, const wstring& name,
    const Vec3& position, const Vec3& rotation, const Vec3& scale, 
    shared_ptr<GameObject> parent)
{
	wstring path = L"Resource/Scene/" + sceneName + L".xml";

	// XML에 저장
	SaveGameObjectToXML(path, name, &position, &rotation, &scale, parent);


	// 실제 GameObject 생성 및 Scene에 추가
	shared_ptr<GameObject> gameObject = make_shared<GameObject>();
	shared_ptr<Transform> transform = make_shared<Transform>();

	transform->SetLocalPosition(position);
	transform->SetLocalRotation(rotation);
	transform->SetLocalScale(scale);
	gameObject->AddComponent(transform);

	if (parent != nullptr)
		gameObject->SetParent(parent);

	gameObject->SetName(name);

	_activeScene->AddGameObject(gameObject);
}
void SceneManager::SaveGameObjectToXML(const wstring& path, const wstring& name, const Vec3* position, const Vec3* rotation, const Vec3* scale, const shared_ptr<GameObject>& parent)
{
	if (ENGINE.GetEngineMode() != EngineMode::Edit)
		return;

	tinyxml2::XMLDocument doc;

	// 기존 파일이 있으면 로드
	string pathStr = Utils::ToString(path);
	doc.LoadFile(pathStr.c_str());

	tinyxml2::XMLElement* root = doc.FirstChildElement("Scene");
	if (!root)
	{
		root = doc.NewElement("Scene");
		doc.InsertFirstChild(root);
	}

	// 이미 존재하는 GameObject인지 확인
	tinyxml2::XMLElement* existingObj = root->FirstChildElement("GameObject");
	while (existingObj)
	{
		if (string(existingObj->Attribute("name")) == Utils::ToString(name))
		{
			root->DeleteChild(existingObj);
			break;
		}
		existingObj = existingObj->NextSiblingElement("GameObject");
	}

	// GameObject 요소 생성
	tinyxml2::XMLElement* gameObjectElem = doc.NewElement("GameObject");
	gameObjectElem->SetAttribute("name", Utils::ToString(name).c_str());

	if (parent)
		gameObjectElem->SetAttribute("parent", Utils::ToString(parent->GetName()).c_str());

	// Transform 정보 저장
	tinyxml2::XMLElement* transformElem = doc.NewElement("Transform");
	transformElem->SetAttribute("posX", position->x);
	transformElem->SetAttribute("posY", position->y);
	transformElem->SetAttribute("posZ", position->z);
	transformElem->SetAttribute("rotX", rotation->x);
	transformElem->SetAttribute("rotY", rotation->y);
	transformElem->SetAttribute("rotZ", rotation->z);
	transformElem->SetAttribute("scaleX", scale->x);
	transformElem->SetAttribute("scaleY", scale->y);
	transformElem->SetAttribute("scaleZ", scale->z);
	gameObjectElem->InsertEndChild(transformElem);

	root->InsertEndChild(gameObjectElem);
	doc.SaveFile(pathStr.c_str());
}

컴포넌트 정보 업데이트를 위한 다양한 메서드들도 있습니다:

// SceneManager.cpp
void SceneManager::UpdateGameObjectTransformInXML(const wstring& sceneName, const wstring& objectName, 
                                                const Vec3& position, const Vec3& rotation, const Vec3& scale)
{
    if (ENGINE.GetEngineMode() != EngineMode::Edit)
        return;

    tinyxml2::XMLDocument doc;
    string pathStr = "Resource/Scene/" + Utils::ToString(sceneName) + ".xml";
    doc.LoadFile(pathStr.c_str());

    tinyxml2::XMLElement* root = doc.FirstChildElement("Scene");
    if (!root)
        return;
        
    // GameObject 찾기
    for (tinyxml2::XMLElement* gameObjectElem = root->FirstChildElement("GameObject");
        gameObjectElem != nullptr;
        gameObjectElem = gameObjectElem->NextSiblingElement("GameObject"))
    {
        const char* nameAttr = gameObjectElem->Attribute("name");
        if (nameAttr && Utils::ToWString(nameAttr) == objectName)
        {
            // Transform 컴포넌트 찾기
            tinyxml2::XMLElement* transformElem = gameObjectElem->FirstChildElement("Transform");
            if (transformElem != nullptr)
            {
                // 속성 업데이트
                transformElem->SetAttribute("posX", position.x);
                transformElem->SetAttribute("posY", position.y);
                transformElem->SetAttribute("posZ", position.z);
                transformElem->SetAttribute("rotX", rotation.x);
                transformElem->SetAttribute("rotY", rotation.y);
                transformElem->SetAttribute("rotZ", rotation.z);
                transformElem->SetAttribute("scaleX", scale.x);
                transformElem->SetAttribute("scaleY", scale.y);
                transformElem->SetAttribute("scaleZ", scale.z);
                
                doc.SaveFile(path.c_str());
                return;
            }
            // 다른 컴포넌트들...
        }
    }
}

새로운 씬 만드는 방법

새로운 Scene 생성은 CreateNewScene 메서드를 통해 이루어집니다:

// SceneManager.cpp
void SceneManager::CreateNewScene(wstring sceneName)
{
    _isCreateNewScene = true;

    string pathStr = "Resource/Scene/" + Utils::ToString(sceneName) + ".xml";

    // 기존 XML 파일이 있다면 삭제
    if (filesystem::exists(pathStr))
    {
	filesystem::remove(pathStr);
    }


    // EditorCamera
    wstring path = L"Resource/Scene/" + sceneName + L".xml";
    Vec3 editorCameraPos = Vec3(0.0f, 5.0f, -10.0f);
    Vec3 editorCameraRotation = Vec3(22.0f, 0.0f, 0.0f);
    Vec3 editorCameraScale = Vec3(1.0f, 1.0f, 1.0f);
    SaveGameObjectToXML(path, L"EditorCamera", &editorCameraPos, &editorCameraRotation, &editorCameraScale, nullptr);
    auto editorCameraComp = make_shared<Camera>();
    editorCameraComp->SetProjectionType(ProjectionType::Perspective);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"EditorCamera", editorCameraComp);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"EditorCamera", make_shared<EditorCamera>());

    // MainCamera
    Vec3 mainCameraPos = Vec3(0.0f, 5.0f, -10.0f);
    Vec3 mainCameraRotation = Vec3(22.0f, 0.0f, 0.0f);
    Vec3 mainCameraScale = Vec3(1.0f, 1.0f, 1.0f);
    SaveGameObjectToXML(path, L"MainCamera", &mainCameraPos, &mainCameraRotation, &mainCameraScale, nullptr);
    auto camera = make_shared<Camera>();
    camera->SetProjectionType(ProjectionType::Perspective);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"MainCamera", camera);

    // UICamera
    Vec3 uiCameraPos = Vec3(0, 0, -3);
    Vec3 uiCameraRotation = Vec3::Zero;
    Vec3 uiCameraScale = Vec3(1.0f, 1.0f, 1.0f);
    SaveGameObjectToXML(path, L"UICamera", &uiCameraPos, &uiCameraRotation, &uiCameraScale, nullptr);
    auto uiCamera = make_shared<Camera>();
    uiCamera->SetProjectionType(ProjectionType::Orthographic);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"UICamera", uiCamera);

    // MainLight
    Vec3 mainLightPos = Vec3(-50.0f, 37.0f, 0.0f);
    Vec3 mainLightRotation = Vec3::Zero;
    Vec3 mainLightScale = Vec3(10.0f, 10.0f, 10.0f);
    SaveGameObjectToXML(path, L"MainLight", &mainLightPos, &mainLightRotation, &mainLightScale, nullptr);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"MainLight", make_shared<Light>());
    auto lightRenderer = make_shared<MeshRenderer>();
    lightRenderer->SetMesh(RESOURCE.GetResource<Mesh>(L"Sphere"));
    lightRenderer->SetModel(nullptr);
    lightRenderer->SetMaterial(RESOURCE.GetResource<Material>(L"SimpleMaterial"));
    lightRenderer->SetRasterzierState(D3D11_FILL_SOLID, D3D11_CULL_BACK, false);
    lightRenderer->AddRenderPass();
    lightRenderer->GetRenderPasses()[0]->SetPass(Pass::DEFAULT_RENDER);
    lightRenderer->GetRenderPasses()[0]->SetMeshRenderer(lightRenderer);
    lightRenderer->GetRenderPasses()[0]->SetTransform(_activeScene->Find(L"MainLight")->transform());
    lightRenderer->GetRenderPasses()[0]->SetDepthStencilStateType(DSState::NORMAL);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"MainLight", lightRenderer,
	L"SimpleMaterial", L"Sphere");


    // SkyBox
    Vec3 skyboxPosition = Vec3::Zero;
    Vec3 skyboxRotation = Vec3::Zero;
    Vec3 skyboxScale = Vec3::One;
    SaveGameObjectToXML(path, L"skyBox", &skyboxPosition, &skyboxRotation, &skyboxScale, nullptr);
    auto skyBoxRenderer = make_shared<MeshRenderer>();
    skyBoxRenderer->SetMesh(RESOURCE.GetResource<Mesh>(L"Sphere"));
    skyBoxRenderer->SetModel(nullptr);
    skyBoxRenderer->SetMaterial(RESOURCE.GetResource<Material>(L"GrassSkybox_Material"));
    skyBoxRenderer->SetRasterzierState(D3D11_FILL_SOLID, D3D11_CULL_BACK, true);
    skyBoxRenderer->AddRenderPass();
    skyBoxRenderer->GetRenderPasses()[0]->SetPass(Pass::DEFAULT_RENDER);
    skyBoxRenderer->GetRenderPasses()[0]->SetMeshRenderer(skyBoxRenderer);
    skyBoxRenderer->GetRenderPasses()[0]->SetTransform(_activeScene->Find(L"skyBox")->transform());
    skyBoxRenderer->GetRenderPasses()[0]->SetDepthStencilStateType(DSState::NORMAL);
    AddComponentToGameObjectAndSaveToXML(sceneName, L"skyBox", skyBoxRenderer,
	L"GrassSkybox_Material", L"Sphere");

    _isCreateNewScene = false;
}

씬 전환 방법

씬 전환은 LoadScene 메서드를 통해 이루어집니다:

// 이미 위에서 설명한 LoadScene 메서드를 통해 씬 전환
void SceneManager::LoadScene(wstring sceneName)
{
    shared_ptr<Scene> scene = make_shared<Scene>();
    _scenes[sceneName] = scene;
    _activeScene = scene;

    LoadSceneXML(sceneName);
    SCENE.Init();
    RENDER.Init();
}

엔진 초기화 시 SceneInfo.xml에서 시작 씬을 결정합니다:

// EngineBody.cpp
void EngineBody::Init(HWND hwnd, int width, int height)
{
    // ... 초기화 코드 ...
    
    wstring sceneName = L"";
    tinyxml2::XMLDocument doc;
    string pathStr = "../SceneInfo.xml";

    // SceneInfo.xml 파일에서 시작 씬 이름 로드
    if (doc.LoadFile(pathStr.c_str()) == tinyxml2::XML_SUCCESS) {
        tinyxml2::XMLElement* root = doc.FirstChildElement("SceneInfo");
        if (root) {
            const char* sceneNameAttr = root->Attribute("sceneName");
            if (sceneNameAttr) {
                sceneName = Utils::ToWString(sceneNameAttr);
            }
        }
    }
    else {
        // 파일이 없으면 기본값으로 생성
        tinyxml2::XMLDocument newDoc;
        tinyxml2::XMLElement* root = newDoc.NewElement("SceneInfo");
        root->SetAttribute("sceneName", "test_scene");
        newDoc.InsertFirstChild(root);
        newDoc.SaveFile(pathStr.c_str());
    }

    // 초기 씬 로드
    SCENE.LoadScene(sceneName);

    _editScene = SCENE.GetActiveScene();
    GUI.Init();
}

Last updated