public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T Instance;
public static T instance
{
get
{
if (Instance == null)
{
Instance = FindObjectOfType<T>();
}
return Instance;
}
}
}
팀장님이 짜신 싱글톤 패턴
싱글톤 패턴은 특정 클래스의 인스턴스를 1개만 생성되는 것을 보장하는 디자인 패턴이다. 즉, 생성자를 통해서 여러 번 호출이 되더라도 인스턴스를 새로 생성하지 않고 최초 호출 시에 만들어두었던 인스턴스를 재활용하는 패턴이다.
싱글톤 패턴을 사용함으로써 얻을 수 있는 이점 중 하나는 메모리 낭비를 방지할 수 있다는 것이다.
사용자가 1초에 10번 똑같은 요청을 보내면 요청을 처리하기 위한 똑같은 객체를 1초에 10번 생성하고 소멸되는 메모리 낭비 문제가 발생하게 된다.
하지만 싱글톤 패턴을 사용하면 최초 한번 new로 객체를 생성하고 해당 객체를 이후에도 사용하도록 공유(static)하면 메모리 낭비 문제를 방지할 수 있다.
1. 의존성이 높아진다.
싱글톤 패턴을 사용하는 경우 클래스의 객체를 미리 생성한 뒤에 필요한 경우 정적 메서드를 이용하기 대문에 클래스 사이에 의존성이 높아지게 된다는 문제점이 있다. (= 높은 결합)
싱글톤의 인스턴스가 변경 되면 해당 인스턴스를 참조하는 모든 클래스들을 수정해야 하는 문제가 발생한다.
2. private 생성자 때문에 상속이 어렵다.
싱글톤 패턴은 기본 생성자를 private로 만들었기 때문에 상속을 통한 자식 클래스를 만들 수 없다는 문제점이 있다. 즉, 자바의 객체지향 언어의 장점 중 하나인 다형성을 적용하지 못한다는 문제로 이어진다.
3. 테스트하기가 힘들다.
싱글톤 패턴의 인스턴스는 자원을 공유하고 있다는 특징이 있다. 이는 서로 독립적이어야 하는 단위 테스트를 하는데 문제가 된다.
독립적인 테스트가 진행이 되려면 전역에서 상태를 공유하고 있는 인스턴스의 상태를 매번 초기화해야 한다. 초기화해주지 않으면 전역에서 상태를 공유 중이기 때문에 테스트가 정상적으로 수행되지 못할 가능성이 존재한다.
이러한 문제점들 때문에 싱글톤 패턴은 안티패턴이라고 불리기도 한다.
게임 대기 화면 전에 시작됨 : 새로 만든 맵 가져올 때 남아있던 캐릭터 때문에 메인 카메라가 이미 있었음
UI가 안 보임 : 게임뷰 중앙 상단 렌더링 디스플레이 배율 조절 이상해서 그랬음
빌드 뽑는 법 : 빌드 세팅
이쁜 애셋에 달려있는 코드들이 어려워서 초보자 분쇄기다
Bake하면 온갖 곳에 다 NavMesh가 칠해짐
navmeshsurface에 달려있는 parameter들 전부 설명해줘
Unity에서 NavMeshSurface 컴포넌트는 특정 표면에 대해 NavMesh(네비게이션 메시)를 베이크할 수 있게 해주는 중요한 컴포넌트입니다. 각종 파라미터들이 NavMesh를 어떻게 생성할지 결정하는데, 이 파라미터들을 모두 설명하겠습니다:
Collect Objects
가 "Volume"으로 설정된 경우 NavMesh가 어느 위치를 중심으로 생성될지를 결정합니다.Collect Objects
가 "Volume"으로 설정된 경우에만 사용됩니다. 기본적으로 이 값은 NavMeshSurface의 경계를 나타냅니다.이러한 파라미터들을 통해 NavMeshSurface 컴포넌트는 다양한 상황과 필요에 맞게 NavMesh를 생성할 수 있습니다. 파라미터를 조정하면서 정확도, 성능, 에이전트의 경로 탐색 효율성을 최적화할 수 있습니다.
current object hierarchy는 무슨 뜻인지 알아? Object Collection - Collect Objects에 들어있는 선택지인데.
"Current Object Hierarchy"는 Unity의 NavMeshSurface 컴포넌트에서 Object Collection - Collect Objects 옵션 중 하나로, NavMesh를 베이크할 때 어떤 객체들을 포함할지를 설정하는 기능입니다. 이 선택지는 현재 오브젝트의 계층 구조에 있는 객체들만 NavMesh 베이크에 포함시키겠다는 뜻입니다.
즉, Current Object Hierarchy를 선택하면 NavMeshSurface가 붙어 있는 객체와 그 객체의 자식들만 NavMesh를 만들 때 고려됩니다. 이 옵션을 사용하면 다른 씬의 객체나 동일한 계층 구조 밖에 있는 객체들은 NavMesh 계산에 포함되지 않고, 특정 객체나 그 하위 객체들만을 대상으로 베이크할 수 있습니다.
이 옵션은 Collect Objects가 전체 씬에서 모든 객체를 대상으로 NavMesh를 생성하는 것과는 다르게, 더 제한적이고 구조적인 범위에서 NavMesh를 만들고 싶을 때 유용합니다.
floor 검색해서 바닥들에만 navmesh component 달고 navmesh center를 2에서 -1로 수정하고 size 10에서 2로 바꿔서 bake 했더니 나쁘지 않게 수정됨. 원래는 천장에도 죄다 파란색 (navmesh) 칠해져있었음. 오른쪽 천장은 floor 타일을 천장으로 사용해서 그랬던거. 그냥 저곳만 component 삭제하면 될듯.
바닥만 칠하면 안되고 층고가 높은 계단 쪽은 size를 늘려서 칠했더니 아름답게 다 칠해짐
navmesh 안 칠해지게 하는게 obstacle임 modifier임?
NavMesh에서 특정 영역에 NavMesh가 생성되지 않도록 하는 것은 NavMesh Obstacle과 NavMesh Modifier 둘 다 가능하지만, 그 용도와 차이가 있습니다:
따라서, NavMesh가 안 칠해지게 하려면 NavMesh Obstacle을 사용해서 Carve 옵션을 적용하거나, NavMesh Modifier의 Ignore From Build 옵션을 사용하는 방법이 있습니다.
예외가 있는 부분이 있길래 그냥 size 왕창 늘리고 천장에는 modifier 적용하면 되지 않을까?
바로 영역전개 복마어주자
modifier 다른 곳에도 추가하다가 이상하게 돼서 ctrl + z 하다가 온갖 곳에 modifier가 붙어버려서
using UnityEngine;
using UnityEngine.AI;
using Unity.AI.Navigation; // NavMeshModifier를 포함하는 네임스페이스
using UnityEditor;
public class RemoveNavMeshModifiersEditor : MonoBehaviour
{
[MenuItem("Tools/Remove NavMesh Modifiers")]
public static void RemoveModifiersInSelectedObject()
{
// 현재 선택된 오브젝트
GameObject selectedObject = Selection.activeGameObject;
if (selectedObject == null)
{
Debug.LogWarning("오브젝트를 선택하세요.");
return;
}
// 선택된 오브젝트와 모든 자식들에 대해 NavMeshModifier 삭제
NavMeshModifier[] modifiers = selectedObject.GetComponentsInChildren<NavMeshModifier>();
foreach (var modifier in modifiers)
{
Undo.DestroyObjectImmediate(modifier); // Undo 기능을 사용하여 에디터에서 바로 삭제
}
Debug.Log("NavMesh Modifiers가 삭제되었습니다.");
}
}
에디터 스크립트 추가해서 modifier 다 삭제해버림
복마어주자 썼더니 이상한 곳에도 NavMesh가 색칠돼버림.
필요한 부분들만 size 늘려서 해결하려고 했는데
obstacle 넣어도 NavMesh Obstacles랑 안 닿으면 막무가내로 bake 해버림
navmesh bake할 때 분명 obstacle 컴포넌트 추가한 오브젝트 위에도 navmesh가 칠해지는 현상이 있는데 어떻게 고쳐?
NavMesh Obstacle 컴포넌트를 추가한 오브젝트 위에도 NavMesh가 생성되는 경우, Carve 옵션이 활성화되지 않았을 가능성이 큽니다. Carve 옵션을 사용해야 NavMesh Obstacle이 NavMesh에 직접적인 구멍을 뚫어, 그 영역에 NavMesh가 생성되지 않게 할 수 있습니다.
NavMesh Obstacle의 Carve 옵션 활성화:
Obstacle 크기 확인:
Dynamic Obstacle 고려:
이 설정으로 Obstacle이 있는 영역에 NavMesh가 생성되지 않도록 할 수 있을 것입니다.
이거 설명해줘
해당 스크린샷은 NavMeshObstacle 컴포넌트의 Carve 옵션과 그 세부 설정에 대한 내용입니다. 각 항목을 설명해드리겠습니다.
이 옵션들은 NavMesh의 실시간 갱신과 성능을 최적화하기 위해 사용됩니다. 이동하는 오브젝트가 있을 때 빈번한 재계산을 피하면서, 정지한 경우에만 NavMesh에 영향을 주도록 조정할 수 있습니다.
안 움직이는 물체에 carve 넣으면 연산량 불필요하게 늘어나 안 늘어나?
Carve 옵션을 활성화한 NavMeshObstacle을 정적인, 즉 움직이지 않는 물체에 적용하면 불필요한 연산이 발생하지는 않습니다. 이유는 다음과 같습니다:
정적 Obstacle:
움직이는 Obstacle과의 차이:
Carve Only Stationary 옵션:
update문마다 움직였는지 아닌지 확인하는게 아니라 exception을 일으키는거임?
Unity에서 NavMeshObstacle의 Carve 옵션은 매 프레임마다 오브젝트가 움직였는지 확인하는 방식으로 작동하는 것이 아닙니다. 대신, 내부적으로 효율적으로 설계되어 있으며, 움직임이 발생할 때만 NavMesh를 업데이트하게 되어 있습니다.
carve 옵션 켰더니 높이가 낮은 오브젝트를 잡아먹어버림.
ad-hoc이지만 오브젝트 삭제하는 식으로 해결.
현재 : 데미지를 받으면 카메라와 함께 플레이어가 사라진 후 마스터 클라에 "나 죽었어" 얘기를 한다
마스터 클라가 "나 죽었어"라고 말한 애한테 딱히 뭐 해주진 않는다
그냥 클라에서 OnDamaged에 카메라 변경 로직 추가해주면 되는거
각 플레이어 머리 위에 카메라 달아놓고 마우스 움직이면 카메라 움직이게
이 코드 설명 좀
public virtual void OnDead()
{
throw new System.NotImplementedException();
}
이 코드는 C#에서 virtual method로 정의된 OnDead
라는 메서드를 나타냅니다. 메서드가 가상으로 선언되었고, 아직 구현되지 않은 상태임을 의미합니다. 여기서 각각의 요소를 설명하겠습니다:
OnDead()
는 아마도 기본 클래스에서 선언되었으며, 이 메서드는 파생 클래스에서 재정의(Override)될 것입니다. 각 파생 클래스는 자신만의 방식으로 OnDead()
메서드를 구현할 수 있습니다.NotImplementedException
이 발생하여 기능이 구현되지 않았음을 알립니다.override 해버리면 throw new System.NotImplementedException();는 무시되는거임? 원래 override하면 원래 건 무시돼?
Override는 기본 클래스의 가상 메서드 구현을 완전히 대체합니다.
재정의된 메서드에서는 기본 클래스의 가상 메서드 구현이 무시됩니다.
기본 메서드를 호출하려면 base 키워드를 사용해 명시적으로 호출해야 합니다.
확인하긴 했는데 OnDead는 애초에 지금 안 쓰는 함수라고 함.
PhotonNetwork.Destroy(this.gameObject); 이건 뭔가?
그냥 Destory()인데 포톤 네트워크에 다른 애들한테서도 이거 없애주세요 라는 신호를 보내는 코드
이 코드 설명 좀
if (GetComponent<PhotonView>().AmOwner)
{
PhotonNetwork.Destroy(this.gameObject);
NEPlayerDeath.PlayerDeath();
}
이 코드는 Photon Unity Networking (PUN)을 사용하여 네트워크 상에서 플레이어의 소유 여부를 확인하고, 그 플레이어의 오브젝트를 삭제한 후 특정 사망 처리 기능을 호출하는 역할을 합니다. 한 줄씩 설명해 보겠습니다.
PhotonView
를 통해 네트워크 상에서 특정 오브젝트가 플레이어 소유인지를 확인한 후, 해당 오브젝트를 네트워크에서 삭제하고 사망 처리를 진행합니다.if (GetComponent<PhotonView>().AmOwner)
GetComponent<PhotonView>()
: 현재 게임 오브젝트에 붙어 있는 PhotonView
컴포넌트를 가져옵니다. PhotonView는 PUN에서 네트워크 상에서 오브젝트를 식별하고 관리하는 핵심 컴포넌트입니다.AmOwner
: 이 속성은 현재 네트워크 오브젝트가 로컬 플레이어(자신)의 소유인지 여부를 나타냅니다. true
이면 해당 오브젝트가 로컬 플레이어의 소유이고, false
이면 다른 플레이어의 소유입니다.PhotonNetwork.Destroy(this.gameObject);
PhotonNetwork.Destroy(this.gameObject)
: 현재 오브젝트(즉, 이 스크립트가 붙어 있는 오브젝트)를 네트워크 상에서 삭제하는 명령입니다.PhotonNetwork.Destroy
는 네트워크 상에 있는 모든 클라이언트에서 이 오브젝트를 제거합니다. 이 명령을 호출하면 해당 오브젝트는 모든 플레이어의 게임에서 삭제됩니다.Destroy()
와 달리, PUN을 사용하여 네트워크 상에서 동기화된 삭제가 이루어집니다.NEPlayerDeath.PlayerDeath();
NEPlayerDeath.PlayerDeath()
: 이 코드는 NEPlayerDeath
라는 클래스의 PlayerDeath()
메서드를 호출합니다.AmOwner
가 true
일 경우에만 해당 로직이 실행됩니다.PlayerDeath()
메서드를 호출하여 추가적인 사망 로직을 처리합니다.관전 모드 구현하려고 하는데 각 플레이어 위에 카메라가 이미 있고 활성화만 바꾸는게 나을까 아니면 시점 바꿀 때마다 이미 있던 카메라는 사라지고 다른 플레이어 위에 카메라가 새로 생성되는게 나을까?
관전 모드를 구현할 때, 이미 존재하는 카메라를 활성화/비활성화하는 방식과 카메라를 매번 새로 생성하는 방식 중 어떤 방법이 더 나은지는 성능과 관리의 용이성을 고려해야 합니다.
카메라 활성화/비활성화 방식이 더 나은 선택일 가능성이 높습니다. 이유는 성능 최적화 측면에서 카메라를 미리 생성해 두고 필요할 때마다 활성화하는 것이 훨씬 부드럽고 빠른 전환을 제공하기 때문입니다. 대부분의 게임에서는 관전 모드에서도 즉각적인 전환이 필요하기 때문에 이 방식이 더 적합할 수 있습니다.
다만, 플레이어 수가 아주 많고 메모리가 걱정되는 상황이거나, 각 플레이어마다 카메라 설정이 크게 달라야 한다면 새로운 카메라 생성 방식을 고려할 수 있습니다.
카메라 다 달아놓으면 될듯
모델 있는 프리팹에 오른쪽 공통 프리팹 오브젝트들이 달리고 있다
서버 코드를 고쳐야할 것 같아서 서버팀에게 물어봤더니
"카메라를 굳이 다 달아주지 말고 카메라 하나를 각 플레이어 시점으로 이동시키면 될 것" 이라고 조언해줌.
천잰가?
카메라에 코드 붙일 건데 자기 자신을 지칭하려면 코드에서 뭐라고 해야됨
카메라에 코드를 붙여서 카메라 자신의 게임 오브젝트를 지칭하려면, 코드에서 gameObject
또는 this
를 사용하면 됩니다. 두 가지가 상황에 따라 조금씩 다르게 동작합니다.
gameObject
gameObject
는 현재 스크립트가 부착된 게임 오브젝트를 가리킵니다. // 현재 카메라 게임 오브젝트를 비활성화
gameObject.SetActive(false);
// 현재 카메라 게임 오브젝트의 이름 출력
Debug.Log(gameObject.name);
this
this
는 현재 스크립트 인스턴스(즉, 스크립트 클래스 자체)를 가리킵니다. // 스크립트 내부 메서드 호출
this.SomeMethod();
대부분의 경우, 카메라 게임 오브젝트 자체를 조작하려는 경우에는 gameObject
를 사용하는 것이 적절합니다.
기타
꼭 SOLID 원칙을 지켜야되는 건 아님. 확장성을 고려한다면 일부러 무시하기도 하는듯.
GameObject.FindObjectOfType<SpectatorMode>()
// 이건 효율이 안 좋음
그냥 gameObject를 해도 되지만 this.gameObject를 하면 더 명확하게 GameObject랑 구분 가능
transform.SetParent(RemainingPlayers[0].transform);
자기 자신의 transform 접근할 땐 transform만 써도 됨
부모 지정하는 건 transform에서 관리
Debug.Log("Remaining Players: " + SpectatingPlayer.name);
이게 자기 자신이 찍히고 있었음.
자기 자신도 remove 해야 되는데 그걸 안해서 카메라가 옮겨가지 못했던 것.
그거 해결하니 일단 관전은 됐다.
using System.Collections.Generic;
using UnityEngine;
public class SpectatorMode : MonoBehaviour
{
public List<GameObject> RemainingPlayers = new List<GameObject>();
private GameObject SpectatingPlayer; // 내가 관전 중인 플레이어
private bool isSpectating = false;
private void UpdateSpectatingPlayer()
{
if (RemainingPlayers.Count == 0)
{
Debug.Log("No players to spectate");
return;
}
// 플레이어가 죽었을 때 다음 플레이어로 변경
if (SpectatingPlayer == null)
{
SpectatingPlayer = RemainingPlayers[0];
Debug.Log("Remaining Players: " + SpectatingPlayer.name);
transform.SetParent(RemainingPlayers[0].transform);
transform.position = RemainingPlayers[0].transform.position;
transform.rotation = RemainingPlayers[0].transform.rotation;
}
else
{
Debug.Log("your remaining player is null");
}
}
public void StartSpectating()
{
isSpectating = true;
Debug.Log("Start Spectating");
RemainingPlayers.AddRange(GameObject.FindGameObjectsWithTag("Player"));
Player p = NetworkManager.Instance.myPlayer; // 자기가 조종하고 있는 플레이어
RemainingPlayers.Remove(p.gameObject);
UpdateSpectatingPlayer();
}
public void RemovePlayer(GameObject player)
{
if (isSpectating)
{
RemainingPlayers.Remove(player);
// 죽은 친구가 지금 관전 중인 친구면 카메라를 옮겨야 함
if (player == SpectatingPlayer)
UpdateSpectatingPlayer();
}
}
}
나는 rotation 따라가는 로직 안 구현했는데 초기화 이후에도 알아서 따라감.
카메라의 부모가 바뀌었으니 부모의 움직임을 알아서 따라가기 때문이라고 함.
카메라의 부모 오브젝트를 따로 만들고 그 부모 오브젝트는 각 플레이어의 위치랑 동기화시켜야됨
마우스 클릭하면 관전하는 사람이 바뀌어야 함
https://artiper.tistory.com/121
아니 굳이 새로운 부모를 만드는게 아니라 그냥 카메라 부모만 옮기면 되는데?
왜 그렇게 생각해놓고 이상하게 하고 있었지?
내일 마저 할 것