본문 바로가기
시행착오 스토리/Unity Network

#4 UNET으로 간단한 멀티플레이어 예제 만들어보기 - Networking Player Health까지

by 양벨라 2017. 10. 23.

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);
    }
}
④ 테스트해본다.