Tank Health
탱크의 체력을 나타내는 슬라이더를 탱크 둘레에 표시되게 하고 체력이 떨어짐에 따라 녹색->오렌지색->붉은색으로 표시되게 하려 합니다.
이 작업을 하기에 앞서서 먼저 상단의 Pivot/Center 토글을 Pivot으로 표시되게 해 주시기 바랍니다.
그리고, Create>UI>Slider를 선택합니다. 그러면 새로운 캔버스와 그 자식으로 설정된 슬라이더가 나타납니다.
새로운 UI 요소가 도입되면 그것은 언제나 Canvas의 자식으로 표현됩니다. 전체 화면을 채우는 캔버스를 screen space 캔버스라고 부르고, 3D나 2D world 내의 요소로서 하나의 오브젝트처럼 표현된 경우는 world space 캔버스라고 부릅니다. 제가 이해한 바로 screen space 캔버스라 하는 것은 아이언맨의 헬멧 안에 표시되는 수치나 그림과 같이 현실 세계 위에 덧입혀진 스크린 같은 것이고, world space 캔버스란 우리가 손에 들고 다니는 핸드폰 화면 같은 느낌이라 할 수 있겠습니다. 이 중 디폴트는 screen space입니다.
전에 언급한 바와 같이 월드는 유닛 단위로 되어있고, 이 캔버스는 실제 픽셀 단위로 되어 있어 월드에 비해 어마어마하게 크게 표시됩니다. 그리고 하단에 슬라이더가 적용되어 있습니다.
이 상태로는 도저히 앞에서 보여드렸던 것과 같은 개별 탱크의 체력을 나타내는 슬라이더를 표현할 수가 없습니다. 이제 이 screen space 캔버스를 world space캔버스로 전환하고 재 디자인하려 합니다.
캔버스가 생성되면서 함께 생성된 Event System이라는 것이 Hierarchy 상에 존재합니다. 이 게임을 확장하다보면 조작 가능한 UI가 필요할 수도 있습니다. 따라서 Event System은 그대로 남겨둘 것입니다. 그러나 Standalone Input Module이라는 컴포넌트는 클릭이나 조이스틱 같은 것으로 입력을 받아들이는 용도의 컴포넌트인데 여기에 입력 축으로 Horizontal, Vertical이 있어서 실제 탱크 조종할 때 입력하는 키와 혼동될 우려가 있습니다. 이것을 피하기 위해 UI라는 두 글자를 각 키에 붙여서 혼동을 피하게 해 줍니다.
다음으로 캔버스의 크기를 조정합니다. Hierarchy창에서 Canvas를 선택하고 인스펙터창의 Canvas Scaler를 보면 Reference for Pixels Per Unit이라는 것이 100으로 되어 있습니다. 이것을 1로 바꾸고, Canvas 컴포넌트의 Render Mode를 World Space로 해줍니다.
이제 이 캔버스는 월드 상에 오브젝트로서 존재할 수 있습니다. 따라서 캔버스를 드래그 해서 탱크의 자식으로 만들 수가 있습니다. 바로 끌어다 놓고 포지션을 (0, 0.1, 0), 크기는 (3.5, 3.5)로, 로테이션을 (90, 0, 0)으로 바꿉니다.
여기까지 하면 이제 캔버스가 탱크 아래에 사각형의 모습으로 위치하게 됩니다.
캔버스는 탱크 아래에 왔지만 슬라이더는 우리가 원하는 자리가 아닌 엄한 자리에 엄한 크기로 존재합니다. 이제 슬라이더를 조정합니다.
Hierarchy의 Canvas를 Alt+클릭하여 모든 자식들까지 다 펼칩니다.
헬스포인트를 보여주는 슬라이더이므로 핸들은 필요 없습니다. Handle Slide Area는 삭제합니다. 그리고 나머지 네개를 모두 선택하고(첫 번째 Slider를 클릭하고 마지막 Fill을 Shift+클릭하면 멀티 선택이 가능합니다) 인스펙터에서 앵커를 조정하여 전부 같은 앵커를 적용하려 합니다.
앵커 프리셋을 클릭하여 열고 Alt를 누른 상태에서 우하방 사각형을 선택하여 전부 부모 크기에 맞게 합니다.
다음은 Hierarchy에서 Slider를 선택하고 Interactable에 체크 해제(아까 핸들을 없애긴 했지만 핸들만 없어졌다고 non-interactable이 되는 것이 아니라 이 과정까지 거쳐야 완전히 non-interactable이 됩니다), Transition은 None, Max Value는 100, Value도 100으로 맞춰줍니다.
나중에 이 탱크에 발사버튼을 누르고 있을 때 거리가 늘어나는 것을 표현하기 위한 슬라이더가 하나 더 적용되어야 하므로 이름이 혼동되지 않도록 Slider를 HealthSlider로 바꿉니다.
HealthSlider의 Background를 선택하고 Source Image를 Health Wheel로 선택, Color의 Alpha는 80으로 합니다.
Background 다음은 Fill 차례입니다. 역시 Fill을 선택하고, Source Image는 Health Wheel, Color의 Alpha는 150으로 합니다. Image Type은 Filled로 바꾸고, Fill Origin은 Left, Clockwise는 체크해제 합니다.
이제 탱크의 방향 전환시에 이 슬라이더도 따라 움직일 수 있도록 스크립트를 덧붙입니다. Scripts>UI 폴더에 UIDirectionControl이라는 짧은 스크립트가 있습니다. 이 스크립트를 드래그하여 HealthSlider에 연결합니다. 간단한 코드이므로 설명은 생략합니다.
public class UIDirectionControl : MonoBehaviour
{
public bool m_UseRelativeRotation = true;
private Quaternion m_RelativeRotation;
private void Start()
{
m_RelativeRotation = transform.parent.localRotation;
}
private void Update()
{
if (m_UseRelativeRotation)
transform.rotation = m_RelativeRotation;
}
}
| cs |
이쯤에서 지금까지 탱크에 추가된 것들을 Apply를 눌러 프리팹에 반영하고, 씬을 저장한 뒤 다음으로 넘어갑시다. 게임 매니저가 탱크 프리팹을 스폰하여 다룰 정도로 완성하기까지 얼마 남지 않았습니다.
탱크가 포탄을 맞으면 HP가 줄어들고, 굉음과 함께 폭파되는 효과도 구현해야 합니다.
Prefabs 폴더에 TankExplosion이라는 파티클 시스템 프리팹이 준비되어 있습니다. 이 프리팹을 드래그하여 일단 Hierarchy창에 둡니다. 아직 소리가 들어 있지는 않은데 Audio Source 컴포넌트를 추가하고 TankExplosion이라는 클립을 연결합니다. 탱크가 터질때만 소리가 나면 되므로 Play On Awake도 체크 해제 합니다.
그리고 Apply를 눌러 TankExplosion 프리팹에 반영시키고, 아무 때나 사용할 수 있게 되었으므로 Hierarchy에서는 삭제합니다.
Tank의 HP를 직접 다루기 위해 Scripts>Tank 폴더에서 TankHealth라는 스크립트를 Hierarchy의 Tank에 연결하고 엽니다.
많은 부분이 주석 처리된 스크립트가 나타났습니다. 13~36의 주석을 제거합니다.
여느 때와 마찬가지로 public 변수들로 시작합니다.
public float m_StartingHealth = 100f; // 시작 HP
public Slider m_Slider; // 슬라이더 레퍼런스
public Image m_FillImage; // 슬라이더의 Fill Image
public Color m_FullHealthColor = Color.green; // HP 최고시 색
public Color m_ZeroHealthColor = Color.red; // HP 최저시 색
public GameObject m_ExplosionPrefab; // Awake에서 인스턴스화하여 사용
| cs |
private 변수들로 이어집니다.
private AudioSource m_ExplosionAudio; // 폭발음
private ParticleSystem m_ExplosionParticles; // 파괴될 때 파티클 시스템
private float m_CurrentHealth; // 현재 HP
private bool m_Dead; // 죽었나요?
| cs |
Awake() 내에서는 m_ExplosionPrefab을 인스턴스화하고 그 중 파티클 시스템을 받아와서 m_ExplosionParticles에 연결하고, 비활성화 하여 둡니다. m_ExplosionParticles 중에서 오디오소스도 레퍼런스로 받아서 따로이 저장합니다.
private void Awake()
{
m_ExplosionParticles = Instantiate(m_ExplosionPrefab).GetComponent<ParticleSystem>();
m_ExplosionAudio = m_ExplosionParticles.GetComponent<AudioSource>();
m_ExplosionParticles.gameObject.SetActive(false);
}
| cs |
한번 인스턴스화 한 오브젝트를 레퍼런스로 하여 필요시 플레이했다가 멈췄다 하는 것이 필요할 때마다 오브젝트를 생성하는 것보다 훨씬 현명한 일입니다. 후자의 경우 가비지 컬렉터가 작용하여 게임을 방해할 수 있기 때문입니다.
그러나, 나중에 구현하게 될 포탄 같은 경우는 게임을 하면서 얼마나 많이 사용하게 될지 알 수 없습니다. 이 경우 object pooling이라는 기법을 사용합니다. 이 방법에 대해서는 Unity3D.com/learn 에 튜토리얼이 있으니 참고 바랍니다.
게임 내에서 탱크가 스폰되면, OnEnable()에서 처음 상태로 만듭니다.
private void OnEnable()
{
m_CurrentHealth = m_StartingHealth;
m_Dead = false;
SetHealthUI();
}
| cs |
HP는 초기화되어 가득 차고, m_Dead는 false로 설정됩니다. 그리고 HP를 나타내는 UI를 SetHealthUI()라는 메소드로 재설정합니다. SetHealthUI()는 나중에 구현하기로 하고 그 전에 TakeDamage 메소드를 작성합니다.
public void TakeDamage(float amount)
{
// Adjust the tank's current health, update the UI based on
// the new health and check whether or not the tank is dead.
m_CurrentHealth -= amount;
SetHealthUI();
if (m_CurrentHealth<=0f && !m_Dead)
{
OnDeath();
}
}
| cs |
데미지에 해당하는 amount만큼 현재의 HP에서 빼 주고, 현재의 HP가 0이하가 되었고 이전에 이미 죽은 상태가 아니었다면 OnDeath()를 호출합니다.
SetHealthUI()에서는 체력 슬라이더를 조절합니다.
private void SetHealthUI()
{
// Adjust the value and colour of the slider.
m_Slider.value = m_CurrentHealth;
m_FillImage.color = Color.Lerp(
m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
}
| cs |
슬라이더의 value를 현재의 HP로 두고, 배경색을 Lerp를 이용해서 부드럽게 전환합니다.
다음은 OnDeath() 차례입니다.
private void OnDeath()
{
// Play the effects for the death of the tank and deactivate it.
m_Dead = true;
m_ExplosionParticles.transform.position = transform.position;
m_ExplosionParticles.gameObject.SetActive(true);
m_ExplosionParticles.Play();
m_ExplosionAudio.Play();
gameObject.SetActive(false);
}
| cs |
m_Dead는 true로 설정하고, m_ExplosionParticles의 위치를 탱크 자리로 옮겨서 활성화시틴 후, 플레이합니다. 그리고, 탱크 오브젝트는 비활성화합니다.
스크립트를 저장하고 유니티로 돌아갑니다.
지금 저장한 스크립트에 필요한 퍼블릭 프로퍼티 세 개를 그림과 같이 각각 연결합니다.
인스펙터 창에서 Apply를 눌러 지금까지의 변화된 내용을 프리팹에 적용시키고 이번 시간을 마칩니다.
댓글 없음:
댓글 쓰기