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

#3 UNET으로 간단한 멀티플레이어 예제 만들어보기 - Multiplayer Shooting까지

by 양벨라 2017. 9. 28.

UNET을 이용하여 멀티플레이어 게임 만들기


이제 실행 모드에서 Player 프리펩이 생성되고 호스트와 클라이언트 각각에서 로컬 Player의 색깔과 위치가 동기화되는 것을 확인했다. 이번에는 총을 만들어주고 총알이 날아가는 부분과 이를 동기화하는 작업까지 진행해보려고 한다. 예제는 Simple MultiPlayer Example 에서 확인할 수 있다. 범위는 10~11 이다.

Shooting(Single Player)

① Sphere 게임 오브젝트를 생성하고 이름을 Bullet라고 지정한다.
이를 선택한채로 Bullet의 scale을 (0.2, 0.2, 0.2)로 설정한다.
Physics > Rigidbody 컴포넌트를 추가한다. Rigidbody에서 Use Gravity 체크박스를 false로 바꾼다.
② Bullet 게임 오브젝트를 프리펩으로 만들고 해당 오브젝트는 씬에서 지워준다. 씬을 저장한다.
③ PlayerController 스크립트를 수정하여 shooting에 해당하는 코드를 업데이트한다.
Bullet 프리펩과 Bullet Spawn의 위치 public field를 추가한다.
public GameObject bulletPrefab;
public Transform bulletSpawn;
Input 핸들링 부분과 Fire 함수를 추가한다.
if(Input.GetKeyDown(KeyCode.Space))
{
   Fire();
}
void Fire()
{
   // Create the Bullet from the Bullet Prefab
    var bullet = (GameObject)Instantiate (
        bulletPrefab,
        bulletSpawn.position,
        bulletSpawn.rotation);

    // Add velocity to the bullet
    bullet.GetComponent().velocity = bullet.transform.forward * 6;

    // Destroy the bullet after 2 seconds
    Destroy(bullet, 2.0f);
}
④ 이제 Player 프리펩을 다시 Hierachy View로 가지고와서 스크립트에 반응할 수 있도록 설정한다.
Player 프리펩의 차일드 오브젝트로 Cylinder 게임 오브젝트를 만들고, 이름을 Gun로 지정한다.
⑤ Gun을 선택하고 설정을 변경한다.
Capsule Collider 컴포넌트를 제거한다.
Gun의 Position은 (0.5, 0.0, 0.5)로 설정한다.
Gun의 Rotation은 (90.0, 0.0, 0.0)로 설정한다.
Gun의 Scale은 (0.25, 0.5, 0.25)로 설정한다.
Materior은 Visor와 똑같은 Black Material로 설정한다.

↓ ↓ Player 는 이제 총을 가진 아래와 같은 모양이 된다.

총을 만들었으니 총알을 만들고 총알이 날아가는 부분을 구현해본다.

⑥ Player 프리펩의 차일드 오브젝트로 빈 게임 오브젝트를 만들고, 이름을 Bullet Spawn로 지정한다.
Bullet Spawn의 Position은 (0.5, 0.0, 1.0)로 설정한다. Spawn의 위치는 총알이 총에서 나가는 총구에 해당하는 부분이다.
Player 프리펩을 갱신시킨다.

⑦ Player 프리펩의 PlayerController 컴포넌트를 설정한다.

↓ ↓ 아래와 같이 끌어다가 놓으면 설정된다.

⑧ 테스트해본다.
스페이스바를 누르면 로컬에서 총알이 생성되고 날아가는 것을 알 수 있다. 하지만 network aware한 코드는 아니기 때문에 다른 클라이언트에서는 나타나지 않는다.

Adding Multiplayer shooting

① Bullet prefab을 선택하고 Network > NetworkIdentity Network > NetworkTransform 컴포넌트를 추가한다.
NetworkTransform 컴포넌트의 Network Send Rate는 0으로 설정한다.

Bullet은 일단 총구에서 쏘고나면 방향이나 속도를 바꿀 수 없기 때문에 움직임을 업데이트할 필요는 없다. send rate를 0으로 설정하면 네트워크를 통한 동기화가 이루어지지 않기에 각 클라이언트는 bullet의 위치값만 계산하면 된다. 네트워크 트래픽이 줄어들고 게임의 성능이 좋아진다.

② Network Manager를 선택하고 Spawn Info를 열고 Registered Spawnable Prefabs list+버튼을 누르고 Bullet프리팹을 추가한다.

③ PlayerController 스크립트를 열고 수정한다.

앞서 보았듯이, 멀티플레이어 네트워킹 HLAPI의 기본 원리는 서버와 모든 클라이언트가 동시에 같은 게임오브젝트의 같은 스크립트로부터 같은 코드를 실행시킨다는 것이다. 같은 코드를 공유하여 컨트롤 하는 하나의 방법이 isLocalPlayer를 이용하는 것이고 또 다른 방법은[Command]를 이용하는 것이다. Command attribute는 클라이언트로 인해 불리고 서버에서 작동된다. 모든 아규먼트들은 자동으로 서버로 가지만 Commands는 로컬 플레이어로만 불릴 수 있다. 또한 이런 networed command를 만들 때에는 function의 이름이 무조건 "Cmd"가 붙어야한다.

[Command]를 추가하고 function의 이름을 변경한다.
[Command]
void CmdFire();
④ NetworkServer.Spawn function을 추가한다.
/*PlayerController.cs*/
using UnityEngine;
using UnityEngine.Networking;

public class PlayerController : NetworkBehaviour
{
    public GameObject bulletPrefab;
    public Transform bulletSpawn;

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

        var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
        var z = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;

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

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

    // This [Command] code is called on the Client …
    // … but it is run on the Server!
    [Command]
    void CmdFire()
    {
        // Create the Bullet from the Bullet Prefab
        var bullet = (GameObject)Instantiate(
            bulletPrefab,
            bulletSpawn.position,
            bulletSpawn.rotation);

        // Add velocity to the bullet
        bullet.GetComponent().velocity = bullet.transform.forward * 6;

        // Spawn the bullet on the Clients
        NetworkServer.Spawn(bullet);

        // Destroy the bullet after 2 seconds
        Destroy(bullet, 2.0f);
    }

    public override void OnStartLocalPlayer ()
    {
        GetComponent().material.color = Color.blue;
    }
}
⑤ 테스트해본다.