종잇장
유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 6일차
종잇장
언제까지 유니티만 할거냐
종잇장
전체
오늘
어제
  • 분류 전체보기 (60)
    • 내 생각 (1)
    • 개인프로젝트 일지 (11)
    • Study Log (8)
    • Unity Tips (18)
    • Unity Entities Tutorial (10)
    • Unity Fungus (9)
    • Unity VContainer (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • inject
  • 싱글턴
  • DOP
  • 스토리텔링
  • Dependency
  • 비주얼 노벨
  • injection
  • 펑거스
  • 싱글톤
  • Dots
  • Entities
  • 유니티
  • Awakening
  • 꿈섬
  • ECS
  • tutorial
  • 젤다
  • 꿈꾸는 섬
  • scene
  • Unity
  • singleton
  • 미연시
  • Visual Novel
  • 튜토리얼
  • c#
  • Fungus
  • 비주얼노벨
  • vcontainer
  • 링크
  • Zelda

최근 댓글

최근 글

hELLO · Designed By 정상우.
개인프로젝트 일지

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 6일차

2022. 9. 12. 20:58
목차
  1. 1. 서브 씬 툴로 계산된 영역 보여주기
  2. 2. 씬 파일 생성 기능 추가
  3. 3. 위치 기준으로 체크하는 함수 구현
  4. 4. 서브씬 로드 구현
  5. 5. 캐릭터 위치에 따라 서브씬 로드하도록 구현
  6. 6. 카메라 계산 로직 추가
  7. 7. 다음에 할 일

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 1일차

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 2일차

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 3일차

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 4일차

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 5일차

 

1. 서브 씬 툴로 계산된 영역 보여주기

 

private void OnFocus()
{
    SceneView.duringSceneGui -= OnDuringSceneGUI;
    SceneView.duringSceneGui += OnDuringSceneGUI;
}

private void OnDestroy()
{
    SceneView.duringSceneGui -= OnDuringSceneGUI;
}
    
private void OnDuringSceneGUI(SceneView sceneView)
{
    using (new Handles.DrawingScope())
    {
        if (SelectedSubSceneSetting != null)
        {
            foreach (var subSceneData in SelectedSubSceneSetting.SubSceneDatList)
            {
                Handles.color = Color.yellow;
                Handles.DrawWireDisc(subSceneData.center, Vector3.up, subSceneData.range);
            }
        }
    }  
}

MonoBehaviour였다면 OnDrawGizmos함수를 통해 Gizmos로 드리면 됐는데, EditorWindow에서 그리게 되는 거라 Handles로 그리게 되었습니다.

중심점과 범위 정도 보여주도록 작업했습니다.

 

 


 

2. 씬 파일 생성 기능 추가

처음에는 씬 이름만 입력하면 충분하겠지 했는데, 작업하려고 보니 씬을 생성하면 편하겠다 싶어서 기능을 추가했습니다.

 

var saveDefaultName = $"{SelectedMainScene.name}_SubScene_{SelectedSubSceneSetting.SubSceneDatList.Count}";
var filePath = EditorUtility.SaveFilePanelInProject("Create Scene", saveDefaultName, "unity", "message");
if (string.IsNullOrWhiteSpace(filePath) == false)
{
    var sceneName = System.IO.Path.GetFileNameWithoutExtension(filePath);

    var newScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
    newScene.name = sceneName;
    EditorSceneManager.SaveScene(newScene, filePath);

    var newSubSceneData = new SubSceneSetting.SubSceneData();
    newSubSceneData.sceneName = sceneName;
    SelectedSubSceneSetting.SubSceneDatList.Add(newSubSceneData);
}

유니티에서 다 지원해주고 있어서 쉽게 구현했습니다.

 

New SubScene (New Scene)버튼을 통해 생성해보았습니다.

 

샘플씬의 세 영역을 나눠서 작업해주었습니다.

 


 

3. 위치 기준으로 체크하는 함수 구현

 

const float SUBSCENE_CHECK_POS_RANGE_FACTOR = 1.5f;

public bool IsContainIn(Camera cam, Vector3 playerPos)
{
    playerPos.y = 0f;

    // 영역 내에 플레이어가 들어왔을 때는 확실한 상태.
    if (Vector3.SqrMagnitude(playerPos - center) < range * range)
        return true;

    return false;
}

public bool IsContainOut(Camera cam, Vector3 playerPos)
{
    playerPos.y = 0f;

    // 영역 밖인데 짱멀리 있을 때.
    if (Vector3.SqrMagnitude(playerPos - center) >= (range * SUBSCENE_CHECK_POS_RANGE_FACTOR) * (range * SUBSCENE_CHECK_POS_RANGE_FACTOR))
        return true;
    
    return false;
}

Load 할지 체크하는 IsContainIn, Unload 할지 체크하는 IsContainOut 두 함수로 구현했습니다.

FACTOR는 대충 1.5배면 충분하겠거니 생각돼서 넣은 값입니다.

왜 이렇게 둘로 구현했냐면,

 

짤에서 보듯이 경계선에서 플레이어가 왔다갔다 하는 경우 Load, Unload를 반복하게 되면서 불필요한 프레임드랍을 만들어내기 때문입니다.

그래서 Load는 그려야하는 경우는  실행하고, Unload는 Load 이후 거리가 한참 멀리 떨어졌을 경우에 실행하게 됩니다.

 

위에서 구현한 코드에서 FACTOR를 곱한 영역을 연한 선으로 보여주도록 수정했습니다.

 


 

4. 서브씬 로드 구현

 

var subScenes = subSceneSetting.SubSceneDatList.FindAll(m => m.IsContainIn(Camera.main, Vector3.zero));
float subSceneProgress = (1.0f / subScenes.Count) * 0.4f;
for (int i = 0; i < subScenes.Count; i ++)
{
    async = SceneManager.LoadSceneAsync(subScenes[i].sceneName, LoadSceneMode.Additive);
    while(async.isDone == false)
    {
        m_SceneChangeProgress = 0.6f + (subSceneProgress * i) + async.progress * subSceneProgress;
        yield return null;
    }
}

SceneChanger에서 로드 때 가까이에 있는 씬을 로드하도록 추가했습니다.

 

위치 기준으로 보여주도록 동작하는 것을 확인할 수 있습니다.

 


 

5. 캐릭터 위치에 따라 서브씬 로드하도록 구현

 

private void LateUpdate()
{
    CheckSubSceneLoad();
}

매 프레임 체크해야하므로 LateUpdate함수에 추가했습니다.

Update가 아닌 LateUpdate인 이유는, 일반적으로 Update때 플레이어 위치가 갱신되기 때문에 LateUpdate로 했습니다.

 

private struct SubSceneLoadForm
{
    public bool IsLoad;
    public string SceneName;
}
List<SubSceneLoadForm> m_SubSceneLoadFormList = new List<SubSceneLoadForm>();

씬을 Load, Unload 할지 정보를 담아둘 struct와 List를 선언했습니다.

사실 동작은 Queue와 동작이 똑같은데 중복된 씬을 제거해주는 케이스가 있어서 List로 구현하게 됐습니다.

 

//영역 밖으로 벗어난 서브씬 체크
for (int i = 0; i < m_LoadedSubSceneNameList.Count; i ++)
{
    var subSceneData = m_CurrentSubSceneSetting.GetSubSceneDataBySceneName(m_LoadedSubSceneNameList[i]);
    if (subSceneData.IsContainOut(Camera.main, PlayerPosition))
    {
        var index = m_SubSceneLoadFormList.FindIndex(m => m.SceneName == m_LoadedSubSceneNameList[i]);
        if (index >= 0)
        {
            if (m_SubSceneLoadFormList[index].IsLoad == true)
            {
                m_SubSceneLoadFormList.RemoveAt(index);
            }
        }
        m_SubSceneLoadFormList.Add(new SubSceneLoadForm() { IsLoad = false, SceneName = m_LoadedSubSceneNameList[i]});
    }
}

//역역 안으로 들어온 서브씬 체크
foreach (var subSceneData in m_CurrentSubSceneSetting.SubSceneDatList)
{
    if (m_LoadedSubSceneNameList.Contains(subSceneData.sceneName) == true) continue;

    var index = m_SubSceneLoadFormList.FindIndex(m => m.SceneName == subSceneData.sceneName);
    if (index >= 0)
    {
        if (m_SubSceneLoadFormList[index].IsLoad == true)
        continue;
    }

    if (subSceneData.IsContainIn(Camera.main, PlayerPosition))
    {
        if (index >= 0)
        {
            if (m_SubSceneLoadFormList[index].IsLoad == false)
            {
                m_SubSceneLoadFormList.RemoveAt(index);
            }
        }
        m_SubSceneLoadFormList.Add(new SubSceneLoadForm() { IsLoad = true, SceneName = subSceneData.sceneName});
    }
}

영역 안으로 들어왔을 때, 밖으로 나갔을 때 두 가지 케이스로 체크하도록 했습니다.

 

while (m_SubSceneLoadFormList.Count > 0)
{
    var form = m_SubSceneLoadFormList[0];
    m_SubSceneLoadFormList.RemoveAt(0);
    if (form.IsLoad == true)
    {
        m_LoadedSubSceneNameList.Add(form.SceneName);
        yield return SceneManager.LoadSceneAsync(form.SceneName, LoadSceneMode.Additive);
    }
    else
    {
        m_LoadedSubSceneNameList.Remove(form.SceneName);
        yield return SceneManager.UnloadSceneAsync(form.SceneName);
    }
}

코루틴에서 간단하게 Load, Unload 되도록 간단하게 구현했습니다.

 

 

 


 

6. 카메라 계산 로직 추가

 

public bool IsContainIn(Camera cam, Vector3 playerPos)
{
    playerPos.y = 0f;

    // 영역 내에 플레이어가 들어왔을 때는 확실한 상태.
    if (Vector3.SqrMagnitude(playerPos - center) < range * range)
        return true;

    // 영역 밖인데 짱멀리 있을 때는 고려하지 않는다.
    if (Vector3.SqrMagnitude(playerPos - center) >= (range * SUBSCENE_CHECK_POS_RANGE_FACTOR) * (range * SUBSCENE_CHECK_POS_RANGE_FACTOR))
        return false;

    // 카메라와 가장 가까운 포지션을 구해서, 카메라에 그려지는 지 WorldToViewportPoint로 체크
    if (cam == null) return false;

    var angle = 90 - Mathf.Atan2(cam.transform.position.z - center.z, cam.transform.position.x - center.x) * Mathf.Rad2Deg;
    float[] checkAngles = new float[]{
        angle, //카메라와 가장 가까운 각도
        angle - 90, //카메라 기준 옆
        angle + 90, //카메라 기준 옆
    };

    for (int i = 0 ; i < checkAngles.Length; i ++)
    {
        var checkPos = center + Quaternion.Euler(0, checkAngles[i], 0) * Vector3.forward * range;
        var viewportPoint = cam.WorldToViewportPoint(checkPos);
        if (SUBSCENE_CHECK_VIEWPORT_RECT.Contains(viewportPoint) == true)
            return true;
    }
    return false;
}

영역 중 카메라를 바라보는 각도를 구하고, 해당 각도 최대 거리의 좌표와 좌우로 최대거리 좌표 모두 체크하도록 구현했습니다.

 

 

좀 더 넓은 맵을 기준으로 생각해서 FACTOR를 1.5로 잡았는데, 맵이 작아서 fov를 줄였습니다.

 


 

7. 다음에 할 일

다음에는 샘플 맵을 더 만들어서 구현한 기능들을 테스트해 보고 모자란 부분들도 체크해서 더 개발 진행할 예정입니다.

'개인프로젝트 일지' 카테고리의 다른 글

유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 8일차  (0) 2022.09.25
유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 7일차  (1) 2022.09.19
유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 5일차  (2) 2022.09.10
유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 4일차  (0) 2022.09.08
유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 3일차  (0) 2022.09.08
  • 1. 서브 씬 툴로 계산된 영역 보여주기
  • 2. 씬 파일 생성 기능 추가
  • 3. 위치 기준으로 체크하는 함수 구현
  • 4. 서브씬 로드 구현
  • 5. 캐릭터 위치에 따라 서브씬 로드하도록 구현
  • 6. 카메라 계산 로직 추가
  • 7. 다음에 할 일
'개인프로젝트 일지' 카테고리의 다른 글
  • 유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 8일차
  • 유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 7일차
  • 유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 5일차
  • 유니티로 "젤다의 전설: 꿈꾸는 섬" 모작 4일차
종잇장
종잇장
언제까지 유니티만 할거냐종잇장 님의 블로그입니다.
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.