Unity-multiplayer


Project maintained by co2meal Hosted on GitHub Pages — Theme by mattgraham

Unity - Multiplayer

Unity와 Socket.io 이용해서 Multiplayer를 구현하는 방법에 대해 소개하려고 함. 대략 이정도 수준의 결과물이 나올 예정.

먼저 설치해야 할 것. (버전은 달라도 잘 될 가능성 높음.)

Unity 5, Node.js v6.5.0, Atom Editor v1.10.2

서버의 역할

클라이언트의 역할

1. 스타팅 포인트 다운받기

먼저 공굴리기 유니티 프로젝트 준비하기.
공에 콘트롤러 붙여서 맵을 돌아다니기만 하는게 끝임.
그냥 다운받아도 되고 직접 따라가면서 해봐도 됨.

그냥 다운받기 (권장)
직접 따라가기 (2-2 Setting up the play area 까지.)

다운받았으면, 압축을 풀고 Unity에서 불러옴.

2. Socket.io 설치 및 서버 프로그래밍

1. Project 폴더에 새폴더를 만들고 Server라고 이름을 지음

2. Server폴더에 Shift - 우클릭으로 "여기서 명령 창 열기" 누름.

3. npm install socket.iosocket.io 패키지 설치. 아래와 같이 결과가 나오면 성공한 것임.

E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server>npm install socket.io
E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server
`-- socket.io@1.4.8
  +-- debug@2.2.0
  | `-- ms@0.7.1
  +-- engine.io@1.6.11
  | +-- accepts@1.1.4
  | | +-- mime-types@2.0.14
  | | | `-- mime-db@1.12.0
  | | `-- negotiator@0.4.9
  | +-- base64id@0.1.0
  | +-- engine.io-parser@1.2.4
  | | +-- after@0.8.1
  | | +-- arraybuffer.slice@0.0.6
  | | +-- base64-arraybuffer@0.1.2
  | | +-- blob@0.0.4
  | | +-- has-binary@0.1.6
  | | `-- utf8@2.1.0
  | `-- ws@1.1.0
  |   +-- options@0.0.6
  |   `-- ultron@1.0.2
  +-- has-binary@0.1.7
  | `-- isarray@0.0.1
  +-- socket.io-adapter@0.4.0
  | `-- socket.io-parser@2.2.2
  |   +-- debug@0.7.4
  |   `-- json3@3.2.6
  +-- socket.io-client@1.4.8
  | +-- backo2@1.0.2
  | +-- component-bind@1.0.0
  | +-- component-emitter@1.2.0
  | +-- engine.io-client@1.6.11
  | | +-- component-inherit@0.0.3
  | | +-- has-cors@1.1.0
  | | +-- parsejson@0.0.1
  | | +-- parseqs@0.0.2
  | | +-- ws@1.0.1
  | | +-- xmlhttprequest-ssl@1.5.1
  | | `-- yeast@0.1.2
  | +-- indexof@0.0.1
  | +-- object-component@0.0.3
  | +-- parseuri@0.0.4
  | | `-- better-assert@1.0.2
  | |   `-- callsite@1.0.0
  | `-- to-array@0.1.4
  `-- socket.io-parser@2.2.6
    +-- benchmark@1.0.0
    +-- component-emitter@1.1.2
    `-- json3@3.3.2

npm WARN enoent ENOENT: no such file or directory, open 'E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server\package.json'
npm WARN Server No description
npm WARN Server No repository field.
npm WARN Server No README data
npm WARN Server No license field.

4. Atom으로 프로젝트 폴더 열기

E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server>cd ..
E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer>atom .

5. Server 폴더에 app.js 파일 생성

6. 아주 단순한 서버 코드 복붙하기

var io = require('socket.io')(80)

io.on('connection', function(socket){
  console.log('a user connected')

  socket.on('disconnect', function() {
    console.log('user disconnect')
  })
})

7. 서버 코드 실행 및 보안 허용

E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server>node app.js

Socket IO 클라이언트

  1. SocketIO 애셋 다운로드

  2. Project - Assets-SocketIO-Prefabs 에 있는 SocketIO Scene에 복사

  3. SocketIO Prefab의 Inspector에서 URL을 수정

  4. 실행 및 유저가 접속하고 나가는 것을 서버에서 확인.

E:\Users\ㅁㅁㅁ\Documents\Unity-Multiplayer\Server>node app.js
a user connected
user disconnect

위치 동기화

App.js 수정 (step by step 설명 필요하지만 일단 넘어가기)

/*
  Server version 2
  It supports syncing players.
*/
var io = require('socket.io')(80)

var players = {}

io.on('connection', function(socket){
  console.log('a user connected')

  socket.on('disconnect', function() {
    console.log('user disconnect')
    if (socket.id in players) delete players[socket.id]
  })

  socket.on('sync', function(player) {
    /*
      player:
        id: String, but empty
        position: [float, float, float]
        rotation: [float, float, float]
    */
    player.id = socket.id

    // update server's player list
    players[player.id] = player

    // broadcast to other clients
    socket.broadcast.emit('sync', player)

    // Print current players for debugging
    printPlayers()
  })
})

function printPlayers() {
  console.log(players)
}

Client 코드 작성

1. 플레이어 게임 오브젝트에 PlayerSyncer.cs 스크립트 생성하고 다음을 복붙.

using UnityEngine;
using SocketIO;
using System;
using System.Collections;

public class PlayerSyncer : MonoBehaviour {

    public static SocketIOComponent socket = null;
    // Use this for initialization
    void Start () {
        GameObject go = GameObject.Find("SocketIO");
        socket = go.GetComponent<SocketIOComponent>();
    }

    // Update is called once per frame
    void Update () {
        PlayerData player_data = new PlayerData(transform);
        socket.Emit("sync", player_data.ToJSONObject());
    }
}

[Serializable]
public class PlayerData {
    public string id = "undefined";
    public Vector3 position;
    public Quaternion rotation;

    public PlayerData(Transform transform) {
        position = transform.position;
        rotation = transform.rotation;
    }

    public PlayerData(JSONObject json_data) {
        PlayerData data = JsonUtility.FromJson<PlayerData>(json_data.ToString());
        id = data.id;
        position = data.position;
        rotation = data.rotation;
    }

    public JSONObject ToJSONObject() {
        return new JSONObject(JsonUtility.ToJson(this));
    }
}
2. 다른플레이어들 게임오브젝트 생성 및 프리팹 준비
  1. Projects -> Create -> Create Empty
  2. "다른플레이어들" 으로 이름 변경
  3. "플레이어"에 우클릭 -> Duplicate
  4. "다른플레이어들"로 이동, "다른플레이어 프리팹"으로 이름 변경
  5. "다른플레이어 프리팹"의 스크립트 모두 삭제
  6. Project/Assets/Materials 밑의 Albedo Material을 "다른플레이어 프리팹"에 적용
  7. Project에서 Assets/Prefabs 폴더 생성 후 "다른플레이어 프리팹" 드래그앤 드롭
  8. 다른플레이어들 아래 다른플레이어 프리팹 삭제
3. "다른플레이어들"에 OtherPlayersSyncer.cs 스크립트 생성하고 복붙, 프리팹 적용
using UnityEngine;
using SocketIO;
using System.Collections;
using System.Collections.Generic;


public class OtherPlayersSyncer : MonoBehaviour {
    public GameObject otherPlayerPrefab;
    private static SocketIOComponent socket = null;
    private Dictionary<string, GameObject> otherPlayersDict = new Dictionary<string, GameObject>();
    // Use this for initialization
    void Start () {
        GameObject go = GameObject.Find("SocketIO");
        socket = go.GetComponent<SocketIOComponent>();

        socket.On("sync", OnSync);
        socket.On("remove", OnRemove);
    }

    GameObject FindOrCreatePlayer(PlayerData player_data) {
        if (!otherPlayersDict.ContainsKey(player_data.id)) {
            GameObject newPlayer = Instantiate(
                    otherPlayerPrefab,
                    player_data.position,
                    player_data.rotation) as GameObject;
            newPlayer.transform.parent = GameObject.Find("다른플레이어들").transform;

            otherPlayersDict.Add(player_data.id, newPlayer);
        }

        return otherPlayersDict[player_data.id];
    }

    void OnSync(SocketIOEvent e) {
        PlayerData player_data = new PlayerData(e.data);

        // Create other player object if it does not exist.
        GameObject player = FindOrCreatePlayer(player_data);
        player.transform.position = player_data.position;
        player.transform.rotation = player_data.rotation;
    }

    void OnRemove(SocketIOEvent e) {
        PlayerData player_data = new PlayerData(e.data);
        if (otherPlayersDict.ContainsKey(player_data.id)) {
            Destroy(otherPlayersDict[player_data.id]);
            otherPlayersDict.Remove(player_data.id);
        }
    }
}

4. Build & Run
  1. File-Bulid Setting-Player Settings...-Run In Background 체크
  2. Fild-Build Run-파일이름 run.exe으로 저장 및 실행

마무리

이상으로 멀티플레이어 당구공 만들었음
숙제로 Transformposition, rotation만 보낼 것이 아니라 Rigidbodyposition, rotation, velocity 를 동기화하는 걸 구현해 보시길.