반응형

플레이어의 체력을 관리하는 스크립트 'PlayerHealthController'

플레이어의 체력과 관련된 기능을 관리하는 PlayerHealthController라는 스크립트에 무적 상태를 구현했습니다.

먼저 float 타입의 변수 invincibleLength와 invincibleCounter를 선언했습니다.

invincibleLength에는 무적 상태가 유지되는 시간을 할당하고,

invincibleCounter에는 무적 상태가 유지된 시간을 관리할 것입니다.

 

자세히 보시면 invincibleLength의 경우 public으로 선언되어 있고

invincibleCounter는 private으로 선언되어 있습니다.

invincibleLength 변수는 인스펙터창에서 값을 설정할 수 있어야 하기 때문에 public으로 선언하였습니다.

DealDamge함수 내에서 무적 상태가 유지되는 시간(invincibleLength)를

무적 상태가 유지되는 시간(invincibleCounter)에 할당했습니다.

저는 유니티 에디터에서 invincibleLength에 1이라는 값을 할당해둔 상태입니다.

즉 무적 상태가 유지되는 시간은 1초입니다.

 

왜 1초인가 하면

프레임마다 호출되는 Update 함수 내에서

invincibleCounter값에서 Time.deltaTime만큼의 시간을 빼주고 있기 때문입니다.

 

Time.deltaTime은 이전 프레임과 현재 프레임 사이의 시간 간격을 나타냅니다.

만약 플레이 환경이 초당 프레임이 60이라면(1초에 60번 화면이 업데이트)

1프레임과 1프레임 사이의 간격은 1초를 60으로 나눈 값이 될 것입니다.

 

즉, invincibleCounter에 할당된 1이라는 값(invincibleLength에 할당된 값)에서

1초를 60으로 나눈 값을 매 프레임마다 빼는 것입니다.

정리해보겠습니다.

 

1. 60FPS = 1초에 화면이 60번 업데이트 된다는 뜻

2. 1프레임 마다 1/60초를 1초에서 뺌.

3. 그럼 2번 과정이 1초에 60번 실행됨. 

4. 1초가 지나면 invincibleCounter의 값은 0에 수렴함.

 

invincibleCounter값이 0보다 크다면 프레임마다 1/60초를 빼줍니다.

invincibleCounter값이 0이하인 경우 1이라는 값(invincibleLength)을 넣어 줌으로써

캐릭터의 체력이 떨어지는 함수인 DealDamage가 실행되지 않습니다.

반응형
반응형

유니티에서 배경을 활용하는 방법은 매우 다양할 것입니다.

저는 오늘 2D플랫포머 게임을 만드는 과정에서

MainCamera 오브젝트와 스크립트를 사용하여,

배경을 재활용하는 방법에 대해 정리해보고자 합니다.

 

만드는 게임의 배경과 오브젝트들

제가 따라 만들고 있는 게임에서 배경은 2가지입니다.

우측 하이어라키창에 Background의 자식 오브젝트인 Far와 Middle입니다.

Far는 좌측 사진에서 빨간 네모 안의 하늘이미지, Middle은 빨간 네모 안의 수풀 이미지입니다.

 

CameraController라는 스크립트를 보면서

카메라와 배경이 플레이어를 따라다니도록 구현하는 코드들을 살펴보겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{

    public Transform Player;
    public Transform farBackground, middleBackground;

    public float minHeight, maxHeight;
    public float cameraSpeed;

    private Vector2 lastPos;

    void Start()
    {
        lastPos = transform.position; // 카메라의 초기 위치를 저장합니다.
    }

    void Update()
    {	// 플레이어가 y축으로 움직일 때, 카메라를 범위 내에서 제한합니다.
        float clampedY = Mathf.Clamp(Player.position.y, minHeight, maxHeight); 
        
        // 카메라의 위치를 플레이어와 같은 위치로 맞춥니다.
        transform.position = new Vector3(Player.position.x, clampedY, transform.position.z); 
        
		// 카메라가 움직인 만큼 배경 이미지를 움직입니다.
        Vector2 amountToMove = new Vector2(transform.position.x - lastPos.x, transform.position.y - lastPos.y);
        
		// 먼 배경 이미지를 카메라와 같은 속도로 움직입니다.
        farBackground.position += new Vector3(amountToMove.x, amountToMove.y, 0f); 
        // 가까운 배경 이미지를 카메라와 같은 속도로 움직입니다.
        middleBackground.position += new Vector3(amountToMove.x, amountToMove.y, 0f); 
        
        // 카메라의 현재 위치를 저장합니다.
        lastPos = transform.position; 

    }
}

CameraController스크립트는 main camera 오브젝트에 컴포넌트 형태로 추가했습니다.

하나하나 뜯어서 살펴보겠습니다.

Transform 타입의 변수 3개(Player, farBackground, middleBackground는 public으로 선언되어 있습니다.

awake나 start와 같은 생명주기 함수에서 초기화하지 않고,

인스펙터 창에서 오브젝트를 할당해 초기화를 진행해 주었습니다.

 

Player변수에는 카메라가 따라다닐 Player 오브젝트를 할당했습니다.

farBackground와, middleBackground 변수에는 두 배경을 할당했습니다.

float minHeight변수와 maxHeight변수에는 카메라가 플레이어를 따라

수직이동을 하는 과정에서 한계선 지정을 위해 선언한 것입니다.

예를 들어, 플레이어가 낮은 지형으로 이동할 경우

빨간 박스 안에 땅 바깥의 부분이 게임창에 보일 수 있습니다.

반대로 너무 높이 점프하거나, 높은 지형에서는 하늘 바깥의 검은색 부분이 보일 수도 있습니다.

이를 방지하기 위해 높이의 한계선을 만들어 준 것입니다.

매 프레임마다 실행되는 생명주기 함수인 Update함수의 내부를 살펴보겠습니다.

먼저 float타입의 clampedY입니다.

Mathf.Clamp함수가 사용되었는데요.

Mathf.Clamp함수는 총 3개의 매개변수를 갖습니다.

첫 번째는 반환될 값, 두 번째는 최솟값, 세 번째는 최댓값입니다.

즉, Player 오브젝트의 Y좌표 중에서 minHeight, maxHeight에 속하는 값만 clampedY 변수에 할당됩니다.

 

transfrom.position은 카메라의 위치입니다.

매 프레임마다 카메라의 위치에 Player의 x좌표, clampedY에 할당된 Player의 y좌표가 더해집니다.

 

Vector2 타입의 amounToMove 변수에는

카메라가 얼마나 이동했는지 계산한 값이 할당됩니다.

 

Start함수는 생명주기함수로써 게임이 실행된 후 1번만 실행됩니다.

함수 안에는 latsPos라는 변수 안에 카메라의 위치가 할당된 것을 확인할 수 있습니다.

프레임마다 카메라의 위치를 추적하고, 그 위치에서 초기에 선언한 lastPos를 빼면 이동한 거리가 나옵니다.

카메라가 이동한 거리를 배경의 위치에도 더해주게 되면, 배경도 카메라와 함께 플레이어를 쫓게 됩니다.

마지막으로 lastPos 값에 카메라의 현재 위치를 할당하면서,

프레임 사이에 카메라가 플레이어를 쫓아 얼마큼 이동했는지 알 수 있습니다.

 

이렇게 하면 배경 오브젝트를 수없이 생성하지 않아도,

게임 화면에서는 배경이 무한히 반복되는 것처럼 보이게 할 수 있습니다.

반응형
반응형

Player의 Rigidbody2D 컴포넌트 화면

유니티에서는 캐릭터의 기본적인 물리 기능을 구현할 때 Rigidbody라는 컴포넌트를 사용합니다.

컴포넌트라는 것은 유니티의 게임 오브젝트에 다양한 기능을 구현하게 만들어 주는 부품이라고 보시면 됩니다.

 

화면에 파란색 텍스트로 되어있는 Collision Detection이라는 것이 보이시나요?

이 설정값에 따라 오브젝트 간 충돌을 검사하는 방식이 달라집니다.

설정값은 Discrete(이산)과 Continuous(연산)이 있습니다.

 

이산(Discrete)

착지과정에서 땅에 발이 들어간 모습

이산 충돌 검사의 경우 Rigidbody2D가 적용된 오브젝트의 위치를 미리 예측합니다.

때문에 빠르게 움직이거나, 작은 물체 간의 충돌검사에서는 정확하지 않을 수 있습니다.

위 사진은 점프 후 착지 상태를 캡처한 것입니다.

 

연산(Continuous)

정상적으로 착지된 모습

연산 충돌 검사의 경우 이동하면서 생기는 충돌을 비교적 정확하게 검사합니다.

정교한 충돌 감지를 원한다면 Collision Detection 옵션을 Continuous로 설정해 주시면 되겠습니다.

 

 

 

 

반응형
반응형

게임 화면

레이어의 역할

 

유니티에서 Sorting Layer와 Order in Layer는

게임 오브젝트들이 화면상에서 어떻게 그려질지 결정하는 데에 사용됩니다.

위 그림은 2D게임의 한 장면입니다.

이미지 자체가 원근감을 주기도 하지만 레이어가 따로 설정되어 있습니다.

포토샵의 레이어와 비슷한 개념이라고 생각하셔도 좋을 거 같아요.

 

Sorting Layer와 Order in Layer

 

Sorting Layer와 Order in Layer 모두 게임 오브젝트들이 그려지는 순서를 결정하는 데에 사용됩니다.

Sorting Layer 설정창

Sorting Layer의 경우 가장 위에 있는 레이어가 가장 먼저 그려집니다.

사진 상에서 Layer 0이 가장 위에 위치하고 있습니다.

즉, 가장 뒤에 그려진다는 얘기입니다.

 

BackGround 레이어가 "Layer0", Player레이어가 Layer3으로 설정되어 있습니다. 

땅과 벽, 풀 등은 World레이어(Layer1)로 설정되어 있습니다.

아래 사진을 보시면 하늘색 배경이 가장 뒤에, 우거진 수풀이 그다음에,

풀, 벽, 땅 등이 그려지고 마지막에 플레이어가 위치한 모습을 볼 수 있습니다.

수풀 앞에 여우가 그려진 모습

 

그런데 사진을 보다 보니 의문이 하나 생깁니다.

수풀과 배경 모두 BackGround Layer에 포함되어 있습니다.

즉,  Sorting Layer가 같습니다.

그런데 왜 파란색 배경이 뒤에 그려지는 것일까요?

좌측 : 파란배경 오브젝트 / 우측 : 우거진 수풀 오브젝트

동일한 Sorting Layer에서는 Order in Layer의 값이 그려지는 위치를 다르게 합니다.

back 오브젝트가 '파란색 배경', middle 오브젝트가 '우거진 수풀'입니다.

앞서 말한 바와 같이 Sorting Layer값은 BackGround로 동일하지만,

Order in Layer 값이 다릅니다.

back 오브젝트는 -2, middle 오브젝트는 -1입니다.

즉 값이 낮을수록 뒤에 그려집니다.

 

Sorting Layer와 Order in Layer를 따로 사용하는 이유

 

지금처럼 오브젝트가 많지 않은 경우는 Order in Layer만 사용해도 관리가 쉬울 것입니다.

그런데 게임에 사용되는 오브젝트가 수천 개가 넘어간다면 어떻게 될까요?

오브젝트 하나하나 Order in Layer의 값을 조정해 주는 건 어렵고, 비효율적인 일이 될 겁니다.

그래서 오브젝트의 성질에 따라 Sorting Layer로 분류를 해주고,

세부적인 조정은 Order in Layer로 해주는 것입니다.

 

즉, '효율적인 관리'를 위해 Sorting Layer와 Order in Layer를 혼용한다고 생각하면 될 거 같아요.

반응형
반응형

오늘은 유니티 해외 강의 채널인 Code Monkey의 비기너 튜토리얼을

진행하다 생긴 궁금증을 정리해보고자 합니다.

유니티 프로젝트를 생성할 때 여러 가지 템플릿을 고를 수가 있습니다.

Unity Hub

저는 여기서 2D Core와 3D Core 만 사용해 봤습니다.

3d게임을 만드는 강의에서는 3d를,

2d게임을 만드는 강의에서는 2d를 선택하는 정도밖에 모르는 수준입니다.

 

코드 몽키의 튜토리얼 영상에서는 URP 생성을 안내했습니다.

영어로 샬라샬라해서 무슨 말인지는 하나도 못 알아들었지만...

URP(Universal Render Pipeline)는 유니티 엔진에서 사용되는 렌더 파이프라인이라고 합니다.

입문자는 당연히 이런 의문이 이어질 것입니다.

'랜더 파이프라인'은 또 뭔데???

 

랜더 파이프라인 (Render pipline)

URP를 소개하기 전에 '랜더 파이프라인'에 대해 먼저 알고 넘어가야 할 거 같습니다.

그래픽스 파이파 라인(graphics pipeline)이라고도 불립니다.

유니티 공식 문서를 참조해 보자면,

랜더 파이프라인은 Scene의 콘텐츠를 가져와서 화면에 표시하는 일련의 작업을 수행한다고 합니다

저와 같은 입문자는 전혀 이해하지 못할 거 같다는 생각이 들어서 더 찾아봤습니다.

 

3차원의 도형 또는 이미지를 2차원의 이미지로 표현하기 위한 단계적 방법이 '랜더 파이프라인'입니다.

3차원의 도형을 만드는 것을 '3D 모델링'이라고 하고,

만들어진 3D 모델을 2차원의 이미지로 표현하는 과정이 '랜더링'입니다.

출처 : https://www.gamersnexus.net/guides/2429-gpu-rendering-and-game-graphics-explained

 

그림에서 보이는 입체감 있는 도형들이 3D 모델입니다.

저 것을 viewplane이라는 화면에 보이는 것처럼 변환하는 과정이 랜더링인 것이죠.

정리하자면, 3D 모델을 2D 이미지로 보이게 하는 전반적인 과정을 '랜더 파이프라인'이라고 하는 것입니다. 

 

URP(Universal Render Pipeline)

Universal Render Pipeline (URP)는 Unity 게임 엔진에서 사용되는 렌더 파이프라인 중 하나입니다.

출처 : Unity

URP 특징

다양한 조명과 그림자 효과, 그리고 후처리 효과를 지원한다고 합니다. 

기존 랜더 파이프라인을 사용했을 때보다 효율적으로 고퀄리티의 이미지를 구현할 수 있다고 해요.

모바일 환경이나 저사양 pc에서도 높은 품질의 이미지 구현이 가능합니다.

 

정리 : 3D vs 3D URP

 

1. 유니티에서 3D 템플릿을 생성하면 유니티의 기본 랜더 파이프라인인 '빌트인 랜더 파이프라인'이 사용된다.

2. 3D URP 템플릿을 생성하면 '유니버설 랜더 파이프라인'이 사용된다. 

3. 높은 성능을 요구하거나, 고퀄리티의 이미지를 사용할 경우 URP가 게임제작에 적합할 수 있다.

 

반응형

+ Recent posts