이번엔 맵 이동과 이동했을때 맞춰 업데이트 되는 맵을 구현해보겠다
0. 준비
다른맵을 같은 형식으로 만들어 주었다 ( Terrain 타일맵과 미니맵로 사용할 타일맵 )
1. 플레이어
간단하게 캡슐로 만들어주고 리지드바디와 콜라이더를 붙여주었다
플레이어의 자식으로 빈게임오브젝트를 하나 만들자
Sprite Renderer만 추가하고 레이어를 미니맵으로 변경해주면
미니맵에서 플레이어의 위치를 표시해줄 수 있다
플레이어 부분 코드는 아래쪽에서 한꺼번에 작성하였습니다
2. 카메라
유니티 레지스트리에서 시네머신 다운로드
빈게임 오브젝트에 폴리곤 콜라이더로 시네머신이 돌아다닐 수 있는 범위 설정
폴리곤 콜라이더 컴포넌트의 Points 값을 조정하여 반듯한 모양을 만들 수 있다
버츄얼 카메라 생성 후 세팅
플레이어를 따라가야 하니 Follow에 플레이어를 지정해준다
그후 Confiner 라는 컴포넌트를 추가하여 카메라가 일정 범위를 벗어나지 않게 만든다
하지만 이 상태로 씬이 변경되면 카메라의 제한 범위를 잃어버리게 되므로 스크립트를 간단하게 만들었다
폴리곤 콜라이더로 만든 카메라 제한범위의 태그를 변경해줘야 함
using Cinemachine;
using UnityEngine;
using UnityEngine.SceneManagement;
public class CamRange : MonoBehaviour
{
private CinemachineConfiner2D cineCam;
private void OnEnable()
{
SceneManager.sceneLoaded += onSceneLoaded;
UpdateCameraConfiner();
}
private void OnDisable()
{
SceneManager.sceneLoaded -= onSceneLoaded;
}
void onSceneLoaded(Scene scene, LoadSceneMode mode)
{
UpdateCameraConfiner();
}
void UpdateCameraConfiner()
{
cineCam = GetComponent<CinemachineConfiner2D>();
GameObject cameraRangeObj = GameObject.FindGameObjectWithTag("CameraRange");
Collider2D cameraRange = cameraRangeObj.GetComponent<Collider2D>();
cineCam.m_BoundingShape2D = cameraRange;
}
}
3. 정리
몇 가지 요소가 추가되고 씬 이동을 대비하여 몇가지를 정리해보았다
하이어라키
Don't Destroy ( 빈게임오브젝트 )
빈게임 오브젝트에 이 스크립트를 부착하고
씬이 이동되도 파괴되지 말아야 될 요소들을 모두 자식으로 만들었다
using UnityEngine;
public class DontDestroy : MonoBehaviour
{
public static DontDestroy instance;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(this);
}
else
{
Destroy(gameObject);
}
}
}
맵컨트롤러, 맵 매니저 → 플레이어 컨트롤러
최적화가 되어있다거나 깔끔한 코드는 아니지만 이러한 행동들을 한꺼번에 묶는 편이 적합하다 판단하였다
using UnityEngine;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour
{
// 플레이어 컨트롤러
float moveSpeed = 7f;
Rigidbody2D rigid;
int jumpCount = 1;
bool canUsePotal = false;
// 맵 컨트롤러
public RawImage minimapImage;
public RawImage fullmapImage;
private KeyCode toggleMapKey = KeyCode.M;
public Camera mapCamera;
private Vector2 mapSize;
// 전체맵 줌인 줌아웃
private float zoomSpeed = 3f;
private float oriZoom = 5f;
// 드래그
private Vector3 dragOrigin;
private bool isDragging = false;
private float currentZoom;
private void Awake()
{
minimapImage.gameObject.SetActive(true);
fullmapImage.gameObject.SetActive(false);
}
void Start()
{
rigid = GetComponent<Rigidbody2D>();
}
void Update()
{
PlayerMove();
MapKey();
}
void PlayerMove()
{
float moveDir = 0f;
if (Input.GetKey(KeyCode.LeftArrow))
moveDir = -1f;
if(Input.GetKey(KeyCode.RightArrow))
moveDir = 1f;
if (Input.GetKey(KeyCode.UpArrow) && canUsePotal)
Potal.Instance.Interact();
if (Input.GetKey(KeyCode.Space) && jumpCount == 0)
{
rigid.velocity = Vector2.up * 7;
jumpCount++;
}
if (rigid.velocity.y == 0f)
jumpCount = 0;
transform.position += Vector3.right * moveDir * moveSpeed * Time.deltaTime;
}
void MapKey()
{
if (Input.GetKeyDown(toggleMapKey))
ToggleMap();
if (fullmapImage.gameObject.activeSelf)
{
CalculateMapSize();
HandleMapZoom();
HandleMapDrag();
}
else
{
mapCamera.orthographicSize = oriZoom;
currentZoom = oriZoom;
mapCamera.transform.position = Camera.main.transform.position;
}
}
private void ToggleMap()
{
bool isMiniMapActive = minimapImage.gameObject.activeSelf;
minimapImage.gameObject.SetActive(!isMiniMapActive);
fullmapImage.gameObject.SetActive(isMiniMapActive);
}
// 맵 크기 계산
private void CalculateMapSize()
{
// Raw Image의 실제 크기 계산
Rect rect = fullmapImage.rectTransform.rect;
Vector2 size = new Vector2(rect.width, rect.height);
Vector2 worldSize = size / fullmapImage.canvas.scaleFactor;
mapSize = worldSize / 2f;
}
// 맵 줌
private void HandleMapZoom()
{
// 마우스 휠 값
float scrollDelta = Input.mouseScrollDelta.y;
if (scrollDelta != 0)
{
if (currentZoom < oriZoom)
{
currentZoom = oriZoom;
}
currentZoom -= scrollDelta * zoomSpeed;
mapCamera.orthographicSize = currentZoom;
}
}
// 맵 드래그
private void HandleMapDrag()
{
if (Input.GetMouseButtonDown(0))
{
isDragging = true;
dragOrigin = mapCamera.ScreenToWorldPoint(Input.mousePosition);
}
if (Input.GetMouseButtonUp(0))
{
isDragging = false;
}
if (isDragging)
{
Vector3 difference = dragOrigin - mapCamera.ScreenToWorldPoint(Input.mousePosition);
mapCamera.transform.position += difference;
}
}
// 포탈에 닿았을때
private void OnTriggerEnter2D(Collider2D collision)
{
canUsePotal = true;
}
private void OnTriggerExit2D(Collider2D collision)
{
canUsePotal= false;
}
}
맵 컨테이너와 미니맵으로 사용하는 타일맵
맵 매니저
using UnityEngine;
using UnityEngine.SceneManagement;
public class MapManager : MonoBehaviour
{
public static MapManager instance;
private MapData[] mapDatas;
private void Awake()
{
if (instance == null)
{
instance = this;
}
mapDatas = GetComponentsInChildren<MapData>(true);
}
public void RevealRoom()
{
string newLoadedScene = SceneManager.GetActiveScene().name;
for (int i = 0; i < mapDatas.Length; i++)
{
if (mapDatas[i].roomScene.SceneName == newLoadedScene && !mapDatas[i].HasBeenRevealed)
{
mapDatas[i].gameObject.SetActive(true);
mapDatas[i].HasBeenRevealed = true;
return;
}
}
}
}
맵 데이터
using UnityEngine;
public class MapData : MonoBehaviour
{
public SceneField roomScene;
public bool HasBeenRevealed { get; set; }
}
미니맵마다 데이터를 만들고 해당하는 맵으로 이동하면 true값을 받아
미니맵이 켜지는 방법으로 만들었다
4. 포탈
빈게임오브젝트를 만들어 박스콜라이더를 붙여주었다
이것은 마찬가지로 다른 맵에도 만들어 주어야 한다
포탈 스크립트
스크립트 내용
각 포탈마다 이동할 씬, 이동할 포탈 번호, 현재포탈번호를 지정해준다
씬을 이동하면 해당하는 씬과 포탈을 찾고
플레이어를 해당하는 포탈로 이동
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Potal : MonoBehaviour
{
public static Potal Instance;
public enum Door
{
None,
One,
Two,
}
private GameObject player;
private Vector3 playerSpawnPosition;
private Collider2D doorCollider;
private Collider2D playerCollider;
[SerializeField] private SceneField toLoad;
[SerializeField] private Door toSpawnDoor;
public Door currentDoor;
private void Awake()
{
if(Instance == null)
{
Instance = this;
}
player = GameObject.FindGameObjectWithTag("Player");
playerCollider = player.GetComponent<Collider2D>();
}
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoded;
}
public void Interact()
{
SwapScene(toLoad, toSpawnDoor);
}
public static void SwapScene(SceneField toScene, Potal.Door toPotal)
{
Instance.StartCoroutine(Instance.ChangeScene(toScene, toPotal));
}
private IEnumerator ChangeScene(SceneField toScene, Potal.Door toPotal = Potal.Door.None)
{
yield return null;
SceneManager.LoadScene(toScene);
}
private void OnSceneLoded(Scene scene, LoadSceneMode mode)
{
FindDoor(toSpawnDoor);
player.transform.position = playerSpawnPosition;
MapManager.instance.RevealRoom();
}
private void FindDoor(Door potalNum)
{
Potal[] potals = FindObjectsOfType<Potal>();
for (int i = 0; i < potals.Length; i++)
{
if (potals[i].currentDoor == potalNum)
{
doorCollider = potals[i].gameObject.GetComponent<Collider2D>();
CalculateSpawnPosition();
return;
}
}
}
private void CalculateSpawnPosition()
{
float colliderHeight = playerCollider.bounds.extents.y;
playerSpawnPosition = doorCollider.transform.position - new Vector3(0f, colliderHeight, 0f);
}
}
이동 중간에 페이드인과 페이드 아웃 효과를 추가하면 더욱 훌륭한 연출이 될 것이다
SceneField
원래는 string으로 씬을 찾아야 했으나, 간편한 코드를 발견하여 이것으로 대체하였다
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[System.Serializable]
public class SceneField
{
[SerializeField]
private Object m_SceneAsset;
[SerializeField]
private string m_SceneName = "";
public string SceneName
{
get { return m_SceneName; }
}
// makes it work with the existing Unity methods (LoadLevel/LoadScene)
public static implicit operator string(SceneField sceneField)
{
return sceneField.SceneName;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SceneField))]
public class SceneFieldPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
{
EditorGUI.BeginProperty(_position, GUIContent.none, _property);
SerializedProperty sceneAsset = _property.FindPropertyRelative("m_SceneAsset");
SerializedProperty sceneName = _property.FindPropertyRelative("m_SceneName");
_position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label);
if (sceneAsset != null)
{
sceneAsset.objectReferenceValue = EditorGUI.ObjectField(_position, sceneAsset.objectReferenceValue, typeof(SceneAsset), false);
if (sceneAsset.objectReferenceValue != null)
{
sceneName.stringValue = (sceneAsset.objectReferenceValue as SceneAsset).name;
}
}
EditorGUI.EndProperty();
}
}
#endif
5. 완성
'유니티 > 기능구현' 카테고리의 다른 글
[Unity] 유니티 C#) 2D 게임 사다리(Ladder)와 피킹(Peeking) (1) | 2024.12.25 |
---|---|
[Unity] 유니티 C#) 메트로배니아 미니맵과 전체맵 ( 기본기능편 ) (1) | 2024.12.20 |
[Unity] 유니티 C#) 2D 매트로베니아 미니맵과 전체맵 ( 유니티 설정편 ) (0) | 2024.12.19 |
[Unity] 유니티 C#) FPS 주무기를 어떻게 구현할까? ( 저격총 ) (4) | 2024.10.27 |
[Unity] 유니티 C#) FPS 주무기를 어떻게 구현할까? ( 샷건 ) (0) | 2024.10.23 |