UNET을 이용하여 멀티플레이어 게임 만들기
현재 총을 만들고 총알이 날아가고 그 총알이 각 클라이언트에 동기화되는 부분까지 진행하였다. 이번에는 플레이어의 체력 수치를 보여주고 그 체력 수치가 총알을 맞을 때 깎이는 부분, 그리고 동기화까지 진행해보도록 할 것이다. 예제는 Simple MultiPlayer Example 에서 확인할 수 있다. 범위는 12~13 이다.
Create Bullet Collisions
① Bullet 프리펩에 새로운 스크립트를 생성하고 이름을 Bullet으로 지정한다.
② Bullet 스크립트를 열고 기존에 있는 샘플코드를 지운다.
bullet이 또다른 물체와 충돌했을 때 bullet 프리펩이 없어지고 충돌에 대한 핸들링 로직을 추가할 것이다.
/*Bullet.cs*/
using UnityEngine;
using System.Collections;
public class Bullet : MonoBehaviour {
void OnCollisionEnter()
{
Destroy(gameObject);
}
}
③ 테스트 해보면 bullet이 다른 게임 오브젝트와 충돌했을 때 없어지는 것을 볼 수 있다.
이 때 bullet은 NetworkManager에 의해 컨트롤되고 있기 때문에 서버에서 bullet이 없어질 때 모든 클라이언트에서도 역시 사라짐을 볼 수 있다.
Player Health
① Player 프리펩에 새로운 스크립트를 생성하고 이름을 Health로 지정한다.
② Health 스크립트를 열고 기존에 있는 샘플코드를 지운다.
③ 플레이어의 체력 최대값과 현재 체력값을 관리할 field와 데미지를 받았을 때 플레이어의 체력값이 줄어들는 로직을 추가한다.
/*Health.cs*/
using UnityEngine;
public class Health : MonoBehaviour
{
public const int maxHealth = 100;
public int currentHealth = maxHealth;
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = 0;
Debug.Log("Dead!");
}
}
}
④ Bullet스크립트의 OnCollisionEnter function을 수정한다.
OnCollisionEnter은 Collision 파라미터를 받도록 수정하고 Health스크립트로부터 TakeDamage를 콜할 수 있도록 수정한다.
/*Bullet.cs*/
using UnityEngine;
using System.Collections;
public class Bullet : MonoBehaviour {
void OnCollisionEnter(Collision collision)
{
var hit = collision.gameObject;
var health = hit.GetComponent();
if (health != null)
{
health.TakeDamage(10);
}
Destroy(gameObject);
}
}
Createing A Simple Healthbar
① 씬에 UI Image를 생성한다.
UI Image를 생성하게 되면 Canvas와 EventSystem GameObject 또한 동시에 생성된다.
② Canvas 게임오브젝트의 이름을 Healthbar Canvas로 지정하고 Image 게임오브젝트의 이름을 Background로 지정한다.
③ Background 게임오브젝트를 선택한 채로 아래를 설정한다.
RectTransform 컴포넌트의 Width 프로퍼티를 100으로, Height 프로퍼티를 10으로 둔다.
Image 컴포넌트의 Source Image를 InputFieldBackground로, Color를 Red로 설정한다.
Anchor나 Pivot은 변경하지 않는다.
④ Background 게임오브젝트를 복사하고 이름을 Foreground로 수정한다.
⑤ Foreground 게임오브젝트를 Background 게임오브젝트의 Child로 만든다.
⑥ Foreground 게임오브젝트를 선택한 채로 아래를 설정한다.
Image 컴포넌트를 Green로 설정한다.
Anchor Presets Window를 열고 Pivot과 Position을 Middle Left로 설정한다.
⑦ 이 Healthbar는 Player 프리펩에 추가되어져야 한다.
Healthbar Canvas를 선택한 채로 Canvas의 Render Mode를 World Space로 바꾼다.
Healthbar Canvas를 Player의 Child로 만든다.
⑧ Healthbar Canvas를 선택한 채로 아래를 설정한다.
gear 메뉴에서 RectTransform 컴포넌트를 reset한다.
RectTransform의 Scale을 (0.01, 0.01, 0.01)로, Position을 (0.0, 1.5, 0.0)으로 설정한다.
⑨ Player 프리펩을 선택하고 변경된 사항을 Player 프리펩에 적용한다.
⑩ Health 스크립트를 열고 field를 추가하고 Healthbar를 체력에 따라 업데이트 될 수 있도록 로직을 추가한다.
/*Health.cs*/
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Health : MonoBehaviour {
public const int maxHealth = 100;
public int currentHealth = maxHealth;
public RectTransform healthBar;
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = 0;
Debug.Log("Dead!");
}
healthBar.sizeDelta = new Vector2(currentHealth, healthBar.sizeDelta.y);
}
}
⑪ Player 게임오브젝트를 선택하고 Hierachy창의 Health 컴포넌트에서 Health Bar의 Field를 Foreground(Rect Transform)으로 설정한다.
Player 프리펩에 변경된 사항을 적용한다.
⑫ Player 프리펩의 Healthbar Canvas에 새로운 스크립트를 생성하고 이름을 Billboard로 지정한다.
Healthbar가 메인 카메라를 향해 있을 수 있도록 Update로직을 추가한다.
/*Billboard.cs*/
using UnityEngine;
using System.Collections;
public class Billboard : MonoBehaviour {
void Update () {
transform.LookAt(Camera.main.transform);
}
}
⑬ 테스트해본다.
Networking Player Health
Player의 현재 체력을 변화시켜 적용하는 것은 서버에서만 이루어져야 한다. 그리고 클라이언트에서 동기화되어야 한다. 이를 Server Authority 라 한다. 현재의 체력과 데미지를 Server Authority로 만들기 위해서는 State synchronization를 이용해야하고 동기화될 변수를 SyncVars로 만들어야 한다.
① Health 스크립트를 다음과 같이 수정한다.
namespace를 추가하고 NetworkBehaviour로 변경한다.
using UnityEngine.Networking;
public class Health : NetworkBehaviour
{
...
}
currentHealth를 [SyncVar]로 만들고, isServer를 체크하는 부분을 추가해준다.
[SyncVar]
public int currentHealth = maxHealth;
f (!isServer)
{
return;
}
여기까지 진행하게 되면 서버가 붙은 호스트 클라이언트에서는 Healthbar 가 작동하는 것을 볼 수 있지만 다른 클라이언트에서는 확인할 수 없다. 그래서 State Synchronization의 또다른 툴인 SyncVar hook를 이용할 것이다. hook은 SyncVar의 function과 연결시켜주는 역할을 한다.
② Health 스크립트를 다음과 같이 수정한다.
OnChangeHealth function을 추가하고 코드의 위치를 바꾼다.
void OnChangeHealth (int currentHealth)
{
healthBar.sizeDelta = new Vector2(health, currentHealth.sizeDelta.y);
}
이 function은 같은 [SyncVar]타입의 변수를 파라미터로 가짐으로써 작동한다.
SyncVar에 hook을 설정한다.
[SyncVar(hook = "OnChangeHealth")]
③ 최종 코드는 아래와 같다.
/*Health.cs*/
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour {
public const int maxHealth = 100;
[SyncVar(hook = "OnChangeHealth")]
public int currentHealth = maxHealth;
public RectTransform healthBar;
public void TakeDamage(int amount)
{
if (!isServer)
return;
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = 0;
Debug.Log("Dead!");
}
}
void OnChangeHealth (int health)
{
healthBar.sizeDelta = new Vector2(health, healthBar.sizeDelta.y);
}
}
④ 테스트해본다.