네트워크 시스템 개념
네트워크 관리자 사용(Using the Network Manager)

멀티플레이어 프로젝트 처음부터 시작

이 문서는 새로운 네트워킹 시스템을 사용하여 신규 멀티플레이어 프로젝트를 설정하는 방법을 다룹니다. 이 단계별 과정은 일반적인 것이지만 커스텀화하여 다양한 타입의 멀티플레이어 게임에 적용할 수 있습니다.

우선 새로운 빈 Unity 프로젝트를 생성해야 합니다.

NetworkManager 설치

첫 단계는 프로젝트에 NetworkManager 오브젝트를 생성하는 것입니다.

  • 메뉴: Game Object -> Create Empty에서 새로운 빈 게임 오브젝트를 추가합니다.
  • 새로 생성된 오브젝트를 계층 구조 뷰에서 찾아 선택합니다.
  • 오브젝트를 (마우스)오른쪽 버튼 클릭하여 컨텍스트 메뉴를 열거나 또는 오브젝트의 이름을 클릭하고 타이핑하여 오브젝트명을 “NetworkManager”로 변경합니다.
  • 오브젝트의 인스펙터 창에서 컴포넌트 추가 버튼을 클릭합니다.
  • Network -> NetworkManager 컴포넌트를 찾아서 오브젝트에 추가합니다. 이 컴포넌트는 게임의 네트워크 상태를 관리합니다.
  • Network -> NetworkManagerHUD 컴포넌트를 찾아서 오브젝트에 추가합니다. 컴포넌트는 네트워크 상태를 제어할 수 있는 게임의 단순한 사용자 인터페이스를 제공합니다.

자세한 내용은 NetworkManager 사용을 참조하십시오.

플레이어 프리팹(Player Prefab) 설정

다음 단계는 게임에서 플레이어를 나타내는 Unity 프리팹을 만드는 것입니다. NetworkManager는 디폴트로 플레이어 프리팹을 복제하는 방식으로 각 플레이어에 대해 오브젝트를 인스턴스화합니다. 이 예제에서 플레이어 오브젝트는 단순한 큐브입니다.

  • 메뉴: Game Object -> 3D Object -> Cube에서 새로운 큐브를 생성합니다.
  • 큐브를 계층 구조 뷰에서 찾아 선택합니다.
  • 오브젝트 이름을 “PlayerCube”로 변경합니다.
  • 오브젝트의 인스펙터 창에서 컴포넌트 추가 버튼을 클릭합니다.
  • Network -> NetworkIdentity 컴포넌트를 이 오브젝트에 추가합니다. 이 컴포넌트는 서버와 클라이언트 사이에서 오브젝트를 식별하기 위해 사용됩니다.
  • NetworkIdentity의 “로컬 플레이어 권한” 체크박스를 true로 설정합니다. 이를 통해 클라이언트가 플레이어 오브젝트의 움직임을 제어할 수 있게 됩니다.
  • 플레이어 큐브 오브젝트를 에셋 창으로 드래그하여 프리팹을 만듭니다. 이렇게 하면 “PlayerCube”라는 프리팹이 생성됩니다.
  • 씬에서 PlayerCube 오브젝트를 삭제합니다. 이제 프리팹이 있으므로 이 오브젝트는 필요 없습니다.

플레이어 오브젝트를 참조하십시오.

플레이어 프리팹 등록

플레이어 프리팹을 생성했으면 이제 네트워크 시스템에 등록해야 합니다.

  • NetworkManager 오브젝트를 계층 구조 뷰에서 찾아 선택합니다.
  • NetworkManager의 인스펙터에서 “스폰 정보” 폴드아웃을 엽니다.
  • “플레이어 프리팹” 슬롯을 찾습니다.
  • PlayerCube 프리팹을 “플레이어 프리팹” 슬롯에 드래그합니다.

이제 프로젝트를 처음으로 저장할 때입니다. 메뉴에서 File -> Save Project로 가서 프로젝트를 저장해야 합니다. 씬 역시 저장할 수 있습니다. 이 씬을 “오프라인” 씬이라고 합시다.

플레이어 이동(싱글 플레이어 버전)

구현할 게임의 첫 기능은 플레이어 오브젝트를 움직이는 것입니다. 우선 네트워킹 없이 구현할 것이므로, 싱글플레이어 모드에서만 작동합니다.

  • 에셋 뷰에서 PlayerCube 프리팹을 찾습니다.
  • 컴포넌트 추가 버튼을 클릭하고 “새 스크립트”를 선택합니다.
  • 새 스크립트 명으로 “PlayerMove”를 입력합니다. 새 스크립트가 생성됩니다.
  • 새 스크립트를 더블 클릭하여 Visual Studio와 같은 에디터에서 엽니다.
  • 아래의 간단한 이동 동작 코드를 스크립트에 추가해야 합니다.
using UnityEngine;

public class PlayerMove : MonoBehaviour
{
    void Update()
    {
        var x = Input.GetAxis("Horizontal")*0.1f;
        var z = Input.GetAxis("Vertical")*0.1f;

        transform.Translate(x, 0, z);
    }
}

이 코드는 방향키 또는 컨트롤러 패드를 통해 큐브를 조종할 수 있게 연결합니다. 큐브는 아직 네트워크 연결이 되어있지 않기 때문에 클라이언트에서만 움직입니다.

프로젝트를 다시 저장합니다.

호스트된 게임 테스트

에디터에서 플레이(Play) 버튼을 클릭하여 플레이 모드로 진입합니다. 아래와 같이 NetworkManagerHUD 디폴트 사용자 인터페이스가 나타날 것입니다.

“호스트”를 누르면 게임 호스트로서 게임을 시작하게 됩니다. 이제 플레이어 오브젝트가 생성되고 HUD가 변경되어 서버가 활성화된 것으로 나타날 것입니다. 게임은 서버와 클라이언트가 동일 프로세스에 있는 “호스트”로서 실행됩니다.

네트워크 개념을 참조하십시오.

방향키를 누르면 플레이어 큐브 오브젝트가 이동합니다.

에디터에서 중지 버튼을 눌러 플레이 모드를 종료해야 합니다.

클라이언트용 플레이어 이동 테스트

  • 메뉴에서 파일 -> File -> Build Settings으로 이동하여 빌드 설정 다이얼로그를 엽니다.
  • “Add Open Scenes” 버튼을 눌러서 빌드에 현재 씬을 추가합니다.
  • “Build and Run” 버튼을 눌러서 빌드를 생성합니다. 실행 가능 파일의 이름을 입력하라고 요구할 것이며, “networkTest”와 같은 이름을 입력하면 됩니다.
  • 스탠드얼론 플레이어가 시작되고, 해상도 선택 다이얼로그를 보여줄 것입니다.
  • “창 모드” 체크박스를 선택하고 640x480와 같은 낮은 해상도를 선택합니다.
  • 스탠드얼론 플레이어가 시작되고 NetworkManager HUD를 보여줄 것입니다.
  • 호스트로 시작하기 위해 메뉴에서 “호스트”를 선택합니다. 플레이어 큐브가 생성됩니다.
  • 방향키를 눌러 플레이어 큐브를 약간 이동시켜 보십시오.
  • 에디터로 돌아가서 빌드 설정 다이얼로그를 닫습니다.
  • 플레이 버튼을 눌러 플레이 모드로 진입합니다.
  • NetworkManagerHUD 사용자 인터페이스에서 “LAN Client”를 선택하여 클라이언트로 호스트에 접속합니다.
  • 두 개의 큐브가 나타날 것입니다. 하나는 호스트의 로컬 플레이어이고, 다른 하나는 이 클라이언트에 대한 원격 플레이어입니다.
  • 방향키를 눌러 플레이어 큐브를 이동시켜 보십시오.
  • 큐브 둘 다 움직일 것입니다. 이동 스크립트가 네트워크를 인식하지 못하기 때문입니다.

플레이어 이동 네트워크화

  • 스탠드얼론 플레이어를 닫습니다.
  • 에디터에서 플레이 모드를 종료합니다.
  • PlayerMove 스크립트를 엽니다.
  • 로컬 플레이어만을 이동하도록 스크립트를 업데이트합니다.
  • “using UnityEngine.Networking”을 추가합니다.
  • “MonoBehaviour”를 “NetworkBehaviour”로 변경합니다.
  • Update 함수에서 “isLocalPlayer”를 체크하도록 추가하여 로컬 플레이어만이 입력을 처리하도록 변경합니다.
using UnityEngine;
using UnityEngine.Networking;

public class PlayerMove : NetworkBehaviour
{
    void Update()
    {
        if (!isLocalPlayer)
            return;

        var x = Input.GetAxis("Horizontal")*0.1f;
        var z = Input.GetAxis("Vertical")*0.1f;

        transform.Translate(x, 0, z);
    }
}
  • 에셋 뷰에서 PlayerCube 프리팹을 찾아 선택합니다.
  • “Add Component” 버튼을 클릭하고 Networking -> NetworkTransform 컴포넌트를 추가합니다. 이 컴포넌트는 오브젝트가 자신의 포지션을 네트워크에 걸쳐 동기화하도록 합니다.
  • 프로젝트를 다시 저장합니다.

멀티플레이어 이동 테스트

  • 스탠드얼론 플레이어를 다시 빌드 및 실행하고 호스트로 시작합니다.
  • 에디터에서 플레이 모드로 진입하고 클라이언트로 접속합니다.
  • 플레이어 오브젝트는 이제 각각 독립적으로 이동하며, 각 클라이언트의 로컬 플레이어에 의해 조종됩니다.

플레이어 식별

이 게임의 큐브는 현재 모두 흰색이어서, 사용자가 어느 것이 자신의 큐브인지 알 수 없습니다. 플레이어를 식별하기 위해 로컬 플레이어의 큐브를 빨간색으로 만들 것입니다.

  • PlayerMove 스크립트를 엽니다.
  • 플레이어 오브젝트의 컬러를 변경하기 위해 OnStartLocalPlayer 함수의 구현을 추가합니다.
    public override void OnStartLocalPlayer()
        {
            GetComponent<MeshRenderer>().material.color = Color.red;
        }

함수는 해당 클라이언트에서 로컬 플레이어에만 호출됩니다. 이를 통해 사용자에게는 자신의 큐브가 빨간색으로 보이게 됩니다. OnStartLocalPlayer 함수는 카메라 및 입력 환경설정 등과 같은 로컬 플레이어에만 해당되는 초기화 작업을 수행하기에 적합합니다.

NetworkBehaviour 베이스 클래스에는 기타 유용한 가상 함수들이 있습니다. 스포닝을 참조하십시오.

  • 게임을 빌드하고 실행합니다.
  • 이제 로컬 플레이어가 제어하는 큐브는 빨간색으로 나타나고, 나머지는 여전히 흰색으로 나타날 것입니다.

탄환 발사(네트워크 미적용)

멀티플레이어 게임에서 플레이어가 탄환을 발사하는 것은 흔한 기능입니다. 이 섹션은 예제에 네트워크가 적용되지 않는 탄환을 추가합니다. 탄환에 네트워크를 적용하는 것은 다음 섹션에서 다룹니다.

  • 구형 게임 오브젝트를 생성합니다.
  • 오브젝트의 이름을 “Bullet”으로 변경합니다.
  • 탄환의 스케일을 1.0에서 0.2로 변경합니다.
  • 탄환을 에셋 폴더로 드래그하여 총알 프리팹을 만듭니다.
  • 씬에서 탄환 오브젝트를 삭제합니다.
  • 탄환에 Rigidbody 컴포넌트를 추가합니다.
  • 리지드바디에서 “Use Gravity” 체크박스를 사용 안 함(false)으로 설정합니다.
  • 다음을 통해 PlayerMove 스크립트가 탄환을 발사하도록 해야 합니다.
  • 탄환 프리팹에 대해 공용 슬롯을 추가합니다.
  • Update() 함수에 입력 처리를 추가합니다.
  • 탄환 발사 함수를 추가합니다.
using UnityEngine;
using UnityEngine.Networking;

public class PlayerMove : NetworkBehaviour
{
    public GameObject bulletPrefab;

    public override void OnStartLocalPlayer()
    {
        GetComponent<MeshRenderer>().material.color = Color.red;
    }

    void Update()
    {
        if (!isLocalPlayer)
            return;

        var x = Input.GetAxis("Horizontal")*0.1f;
        var z = Input.GetAxis("Vertical")*0.1f;

        transform.Translate(x, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }
    }

    void Fire()
    {
        // create the bullet object from the bullet prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            transform.position - transform.forward,
            Quaternion.identity);

        // make the bullet move away in front of the player
        bullet.GetComponent<Rigidbody>().velocity = -transform.forward*4;
        
        // make bullet disappear after 2 seconds
        Destroy(bullet, 2.0f);        
    }
}
  • 스크립트를 저장하고 에디터로 돌아갑니다.
  • PlayerCube 프리팹을 선택하고 PlayerMove 컴포넌트를 찾습니다.
  • 컴포넌트에서 bulletPrefab 슬롯을 찾습니다.
  • 탄환 프리팹을 bulletPrefab 슬롯에 드래그합니다.
  • 빌드 후 스탠드얼론 플레이어를 호스트로 하여 시작합니다.
  • 에디터에서 플레이 모드로 진입하고 클라이언트로 접속합니다.
  • 스페이스바를 누르면 탄환이 생성되어 플레이어 오브젝트로부터 발사됩니다.
  • 탄환은 다른 클라이언트에서는 발사되지 않고, 스페이스바가 눌린 클라이언트에서만 발사됩니다.

탄환 발사에 네트워킹 적용

이 섹션에서는 직전의 예제에 네트워킹을 적용합니다.

  • 탄환 프리팹을 찾아 선택합니다.
  • 탄환 프리팹에 NetworkIdentity를 추가합니다.
  • 탄환 프리팹에 NetworkTransform 컴포넌트를 추가합니다.
  • 탄환 프리팹의 NetworkTransform 컴포넌트에서 전송률을 0으로 설정합니다. 탄환은 발사된 후 방향 또는 속도가 바뀌지 않으므로, 이동 업데이트를 전송할 필요가 없습니다.
  • NetworkManager를 선택하고 “Spawn Info” 폴드아웃을 엽니다.
  • 더하기 버튼을 눌러 새 스폰 프리팹을 추가합니다.
  • 탄환 프리팹을 새 스폰 프리팹 슬롯에 드래그합니다.
  • PlayerMove 스크립트를 엽니다.
  • 탄환에 네트워크 기능을 추가하도록 PlayerMove 스크립트를 업데이트합니다.
  • 발사 함수에 [커맨드] 커스텀 특성과 “Cmd” 접두사를 추가하여 네트워크가 적용된 커맨드로 변경합니다.
  • 탄환 오브젝트에 Network.Spawn()을 사용합니다.
using UnityEngine;
using UnityEngine.Networking;

public class PlayerMove : NetworkBehaviour
{
    public GameObject bulletPrefab;
    
    public override void OnStartLocalPlayer()
    {
        GetComponent<MeshRenderer>().material.color = Color.red;
    }

    [Command]
    void CmdFire()
    {
       // This [Command] code is run on the server!

       // create the bullet object locally
       var bullet = (GameObject)Instantiate(
            bulletPrefab,
            transform.position - transform.forward,
            Quaternion.identity);

       bullet.GetComponent<Rigidbody>().velocity = -transform.forward*4;
       
       // spawn the bullet on the clients
       NetworkServer.Spawn(bullet);
       
       // when the bullet is destroyed on the server it will automaticaly be destroyed on clients
       Destroy(bullet, 2.0f);
    }

    void Update()
    {
        if (!isLocalPlayer)
            return;

        var x = Input.GetAxis("Horizontal")*0.1f;
        var z = Input.GetAxis("Vertical")*0.1f;

        transform.Translate(x, 0, z);

        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Command function is called from the client, but invoked on the server
            CmdFire();
        }
    }
}

코드는 [커맨드]를 사용해 서버에서 탄환을 발사합니다. 자세한 내용은 네트워크 액션을 참조하십시오.

  • 빌드 후 스탠드얼론 플레이어를 호스트로 하여 시작합니다.
  • 에디터에서 플레이 모드로 진입하고 클라이언트로 접속합니다.
  • 스페이스바를 누르면 해당 플레이어만이 모든 클라이언트에 대해 탄환을 발사합니다.

탄환 충돌

이제 탄환에 충돌 핸들러를 추가하여 플레이어 큐브 오브젝트와 충돌하면 사라지도록 합니다.

  • 탄환 프리팹을 찾아 선택합니다.
  • 컴포넌트 추가를 선택하여 새 스크립트를 추가합니다.
  • 새 스크립트를 “Bullet”이라고 명명합니다.
  • 새 스크립트를 열고 탄환이 플레이어 오브젝트를 맞추면 탄환을 파괴하는 충돌 핸들러를 추가합니다.
using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var hitPlayer = hit.GetComponent<PlayerMove>();
        if (hitPlayer != null)
        {
            Destroy(gameObject);
        }
    }
}

이제 탄환이 플레이어 오브젝트를 맞추면 파괴됩니다. 서버에서 탄환이 제거되면, 탄환은 네트워크가 관리하는 스폰된 오브젝트이므로 클라이언트에서도 제거됩니다.

플레이어 상태(네트워크 미적용 체력)

보통 플레이어는 “체력” 프로퍼티가 있어서 처음에는 최대치로 시작하지만 총알에 피격되어 플레이어가 피해를 입으면 해당 프로퍼티의 값이 줄어들게 됩니다. 이 섹션에서는 플레이어 오브젝트에 네트워크가 적용되지 않은 체력 프로퍼티를 추가합니다.

  • PlayerCube 프리팹을 선택합니다.
  • 컴포넌트 추가를 선택하여 새 스크립트를 추가합니다.
  • 새 스크립트를 “Combat”이라고 명명합니다.
  • Combat 스크립트를 열고 health 변수와 TakeDamage 함수를 추가합니다.
using UnityEngine;

public class Combat : MonoBehaviour 
{
    public const int maxHealth = 100;
    public int health = maxHealth;

    public void TakeDamage(int amount)
    {
        health -= amount;
        if (health <= 0)
        {
            health = 0;
            Debug.Log("Dead!");
        }
    }
}

Bullet 스크립트를 업데이트하여 피격시 TakeDamage 함수를 호출하도록 해야 합니다. * Bullet 스크립트를 엽니다. * 충돌 핸들러 함수에서 Combat 스크립트의 TakeDamage()를 호출하도록 추가합니다.

using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var hitPlayer = hit.GetComponent<PlayerMove>();
        if (hitPlayer != null)
        {
            var combat = hit.GetComponent<Combat>();
            combat.TakeDamage(10);

            Destroy(gameObject);
        }
    }
}

이제 플레이어 오브젝트 체력은 탄환에 피격될 때 줄어듭니다. 하지만 게임에서는 이 현상을 볼 수 없으므로, 간단한 체력 바를 추가해야 합니다. * PlayerCube 프리팹을 선택합니다. * 컴포넌트 추가 버튼을 선택하여 HealthBar라는 새 스크립트를 추가합니다. * HealthBar 스크립트를 엽니다.

이 코드는 이전 GUI 시스템을 사용하기에 길이가 깁니다. 네트워킹과는 큰 관련이 없으므로 특별히 설명하지 않고 그냥 사용하도록 하겠습니다.

using UnityEngine;
using System.Collections;

public class HealthBar : MonoBehaviour 
{
    GUIStyle healthStyle;
    GUIStyle backStyle;
    Combat combat;

    void Awake()
    {
        combat = GetComponent<Combat>();
    }

    void OnGUI()
    {
        InitStyles();

        // Draw a Health Bar

        Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
        
        // draw health bar background
        GUI.color = Color.grey;
        GUI.backgroundColor = Color.grey;
        GUI.Box(new Rect(pos.x-26, Screen.height - pos.y + 20, Combat.maxHealth/2, 7), ".", backStyle);
        
        // draw health bar amount
        GUI.color = Color.green;
        GUI.backgroundColor = Color.green;
        GUI.Box(new Rect(pos.x-25, Screen.height - pos.y + 21, combat.health/2, 5), ".", healthStyle);
    }

    void InitStyles()
    {
        if( healthStyle == null )
        {
            healthStyle = new GUIStyle( GUI.skin.box );
            healthStyle.normal.background = MakeTex( 2, 2, new Color( 0f, 1f, 0f, 1.0f ) );
        }

        if( backStyle == null )
        {
            backStyle = new GUIStyle( GUI.skin.box );
            backStyle.normal.background = MakeTex( 2, 2, new Color( 0f, 0f, 0f, 1.0f ) );
        }
    }
    
    Texture2D MakeTex( int width, int height, Color col )
    {
        Color[] pix = new Color[width * height];
        for( int i = 0; i < pix.Length; ++i )
        {
            pix[ i ] = col;
        }
        Texture2D result = new Texture2D( width, height );
        result.SetPixels( pix );
        result.Apply();
        return result;
    }
}
  • 프로젝트를 저장합니다.
  • 게임을 빌드 및 실행하고, 플레이어 오브젝트에 체력 바가 나타나는지 확인합니다.
  • 이제 플레이어가 다른 플레이어를 사격하면 해당 클라이언트의 체력은 줄어들지만 나머지 클라이언트는 그대로일 것입니다.

플레이어 상태(네트워크 적용된 체력)

체력 변경은 클라이언트와 호스트에 독립적으로 각각 모두 적용됩니다. 따라서 체력이 플레이어에 따라 다르게 보일 수 있습니다. 체력은 서버에서만 적용되어야 하며 그 변경은 클라이언트에 복사되어야 합니다. 이를 체력의 “서버 권한”이라고 합니다.

  • Combat 스크립트를 엽니다.
  • 스크립트를 NetworkBehaviour로 변경합니다.
  • 체력을 [SyncVar]로 만듭니다.
  • TakeDamage에 isServer 체크를 추가하여 서버에만 적용되도록 합니다.

SyncVars에 대한 자세한 내용은 상태 동기화를 참조하십시오.

using UnityEngine;
using UnityEngine.Networking;

public class Combat :  NetworkBehaviour 
{
    public const int maxHealth = 100;

    [SyncVar]
    public int health = maxHealth;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
        if (health <= 0)
        {
            health = 0;
            Debug.Log("Dead!");
        }
    }
}

사망과 리스폰

현재까지는 플레이어의 체력이 0이 되더라도 로그 메시지 이외에는 아무것도 일어나지 않습니다. 게임을 더 현실적으로 만드려면 체력이 0이 되는 시점에서 플레이어가 다시 체력이 전부 채워진 상태로 시작 지점으로 돌아가도록 해야 합니다.

  • Combat 스크립트를 엽니다.
  • 플레이어 오브젝트를 리스폰하기 위한 [ClientRpc] 함수를 추가해야 합니다. 자세한 내용은 네트워크 액션을 참조하십시오.
  • 체력이 0이 되면 서버에서 리스폰 함수를 호출합니다.
using UnityEngine;
using UnityEngine.Networking;

public class Combat :  NetworkBehaviour 
{
    public const int maxHealth = 100;

    [SyncVar]
    public int health = maxHealth;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
        if (health <= 0)
        {
            health = maxHealth;

            // called on the server, will be invoked on the clients
            RpcRespawn();
        }
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // move back to zero location
            transform.position = Vector3.zero;
        }
    }
}

게임에서 클라이언트는 플레이어 오브젝트의 포지션을 제어합니다. 플레이어 오브젝트는 클라이언트에서 “로컬 권한”을 가지기 때문입니다. 만일 서버가 플레이어 포지션을 시작 포지션로 설정하면, 클라이언트가 권한이 있으므로 이를 오버라이드하게 됩니다. 이를 방지하려면 서버가 권한이 있는 클라이언트에 플레이어 오브젝트를 시작 포지션으로 이동하라고 요청해야 합니다.

  • 게임을 빌드하고 실행합니다.
  • 플레이어 오브젝트를 시작 포지션에서 떨어진 포지션으로 이동합니다.
  • 한 플레이어의 체력이 0이 될때까지 탄환을 발사합니다.
  • 이 플레이어 오브젝트는 시작 포지션으로 이동됩니다.

비플레이어 오브젝트

플레이어 오브젝트는 클라이언트가 호스트에 연결되면 생성되지만, 대부분의 게임은 게임 월드에 존재하는, 적과 같은 비플레이어 오브젝트가 있습니다. 이 섹션에서는 스포너를 추가하여 피격하고 죽일 수 있는 비플레이어 오브젝트를 생성합니다.

  • 게임 오브젝트 메뉴에서 비어 있는 새 게임 오브젝트를 생성합니다.
  • 오브젝트의 이름을 “EnemySpawner”로 변경합니다.
  • EnemySpawner 오브젝트를 선택합니다.
  • 컴포넌트 추가 버튼을 선택하여 이 오브젝트에 NetworkIdentity를 추가합니다.
  • NetworkIdentity에서 “Server Only” 체크박스를 클릭합니다. 이렇게 하면 스포너가 클라이언트에 전송되지 않습니다.
  • Add Component 버튼을 선택하여 “EnemySpawner”라는 새 스크립트를 생성합니다.
  • 새 스크립트를 편집합니다.
  • 이 스크립트를 NetworkBehaviour로 만듭니다.
  • 적을 생성하기 위해 OnStartServer 가상 함수를 구현합니다.
using UnityEngine;
using UnityEngine.Networking;

public class EnemySpawner : NetworkBehaviour {

    public GameObject enemyPrefab;
    public int numEnemies;

    public override void OnStartServer()
    {
        for (int i=0; i < numEnemies; i++)
        {
            var pos = new Vector3(
                Random.Range(-8.0f, 8.0f),
                0.2f,
                Random.Range(-8.0f, 8.0f)
                );

            var rotation = Quaternion.Euler( Random.Range(0,180), Random.Range(0,180), Random.Range(0,180));

            var enemy = (GameObject)Instantiate(enemyPrefab, pos, rotation);
            NetworkServer.Spawn(enemy);
        }
    }
}

이제 Enemy 프리팹을 생성합니다.

  • 게임 오브젝트 메뉴에서 새 캡슐을 생성합니다.
  • 이 오브젝트 이름을 “Enemy”로 변경합니다.
  • 컴포넌트 추가 버튼을 선택하여 Enemy에 NetworkIdentity 컴포넌트를 추가합니다.
  • 컴포넌트 추가 버튼을 선택하여 Enemy에 NetworkTransform 컴포넌트를 추가합니다.
  • Enemy 오브젝트를 에셋 뷰로 드래그하여 프리팹을 생성합니다.
  • 이제 “Enemy”라는 이름의 프리팹 에셋이 생성되었습니다.
  • 씬에서 Enemy 오브젝트를 삭제합니다.
  • Enemy 프리팹을 선택합니다.
  • 컴포넌트 추가 버튼을 선택하여 이 Enemy에 Combat 스크립트를 추가합니다.
  • 컴포넌트 추가 버튼을 선택하여 이 Enemy에 HealthBar 스크립트를 추가합니다.
  • NetworkManager를 선택하고 스폰 정보 항목에서 새로운 스폰 가능한 프리팹을 추가합니다.
  • 새 스폰 프리팹을 Enemy 프리팹에 설정합니다.

Bullet 스크립트는 플레이어에 대해서만 작동하도록 설정되었으므로, 이를 업데이트하여 Combat 스크립트가 있는 모든 오브젝트에 작동하도록 해야 합니다.

  • Bullet 스크립트를 엽니다.
  • 충돌 체크 시 PlayerMove 대신 Combat을 사용하도록 변경합니다:
using UnityEngine;

public class Bullet : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var hitCombat = hit.GetComponent<Combat>();
        if (hitCombat != null)
        {
            hitCombat.TakeDamage(10);
            Destroy(gameObject);
        }
    }
}

EnemySpawner를 Enemy 오브젝트에 연결합니다.

  • EnemySpawner 오브젝트를 선택합니다.
  • EnemySpawner 컴포넌트에서 “Enemy” 슬롯을 찾습니다.
  • Enemy 프리팹을 이 슬롯에 드래그합니다.
  • numEnemies 값을 4로 설정합니다.

적을 테스트합니다.

  • 게임을 빌드하고 실행합니다.
  • 호스트로 시작하면 네 명의 적이 무작위 위치에 생성됩니다.
  • 플레이어는 적을 사격할 수 있으며, 피격시 체력 바가 줄어듭니다.
  • 클라이언트가 참여하면 이들도 적이 동일 포지션에 있는 것을 볼 수 있으며, 서버와 동일한 체력 값을 볼 수 있습니다.

적 파괴

적은 피격당하면 체력이 줄어들기는 하지만, 플레이어와 같이 리스폰합니다. 적은 체력이 0이 되면 리스폰하지 않고 파괴되어야 합니다.

  • Combat 스크립트를 엽니다.
  • “destroyOnDeath” 변수를 추가합니다.
  • 체력이 0이 되면 destroyOnDeath를 체크합니다.
using UnityEngine;
using UnityEngine.Networking;

public class Combat :  NetworkBehaviour 
{
    public const int maxHealth = 100;
    public bool destroyOnDeath;

    [SyncVar]
    public int health = maxHealth;

    public void TakeDamage(int amount)
    {
        if (!isServer)
            return;

        health -= amount;
        if (health <= 0)
        {
            if (destroyOnDeath)
            {
                Destroy(gameObject);
            }
            else
            {
                health = maxHealth;

                // called on the server, will be invoked on the clients
                RpcRespawn();
            }
        }
    }

    [ClientRpc]
    void RpcRespawn()
    {
        if (isLocalPlayer)
        {
            // move back to zero location
            transform.position = Vector3.zero;
        }
    }
}

  • Enemy 프리팹을 선택합니다.
  • 적에 대해서는 destroyOnDeath 체크박스를 true로 설정합니다.

이제 적은 체력이 0이 되면 파괴되고, 플레이어는 체력이 0이 되면 리스폰됩니다.

플레이어 스폰 포지션

현재 플레이어는 생성될 때 모두 영점에 나타나게 됩니다. 즉, 서로 겹칠 가능성이 있다는 의미입니다. 플레이어는 서로 다른 위치에 스폰되어야 합니다. NetworkStartPosition 컴포넌트를 사용하여 이 문제를 해결할 수 있습니다.

  • 비어 있는 새 게임 오브젝트를 생성합니다.

  • 오브젝트 이름을 “Pos1”으로 변경합니다.

  • 컴포넌트 추가 버튼을 선택하여 NetworkStartPosition 컴포넌트를 추가합니다.

  • Pos1 오브젝트를 (–3,0,0) 포지션으로 이동합니다.

  • 비어 있는 두 번째 게임 오브젝트를 생성합니다.

  • 이 오브젝트 이름을 “Pos2”로 변경합니다.

  • 컴포넌트 추가 버튼을 선택하여 NetworkStartPosition 컴포넌트를 추가합니다.

  • Pos2 오브젝트를 (3,0,0) 포지션으로 이동합니다.

  • NetworkManager를 찾아 선택합니다.

  • “Spawn Info” 폴드아웃을 엽니다.

  • “Player Spawn Method”를 “Round Robin”으로 변경합니다.

  • 게임을 빌드하고 실행합니다.

  • 플레이어 오브젝트는 이제 영점이 아닌 Pos1과 Pos2 오브젝트의 위치에 생성됩니다.

네트워크 시스템 개념
네트워크 관리자 사용(Using the Network Manager)