구간 반복 logic은 두 가지가 있다. 


1. 속도 더하기

2. 속도 곱하기 


속도 더하기 

속도를 update문에서 더해서 위치를 이동시키는 방식.

조건문 체크하기 -> 위치값에 속도값 더하기 -> 이동시키기(실시간 위치 할당)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class transform : MonoBehaviour {
 
    private float speed = 0.5f;
    private float speedX = 0.3f;
    private float speedZ = 0.2f;
    private Vector3 pos;
    
    void Update () {
        //x축 방향 전환 범위 조건
        if (pos.x > 20.0f)
        {
            pos.x = 20.0f;
            speedX = -speedX;
        }
        else if (pos.x < -20.0f)
        {
            pos.x = -20.0f;
            speedX = -speedX;
        }
        //z축 방향 전환 범위 조건
        if (pos.z > 20.0f)
        {
            pos.z = 20.0f;
            speedZ = -speedZ;
        }
        else if (pos.z < -20.0f)
        {
            pos.z = -20.0f;
            speedZ = -speedZ;
        }
 
        //위치에 속도 더하기 
        pos.x += speedX;
        pos.z += speedZ;
 
        //실시간 위치 할당  
        transform.position = pos;
 
    }
}
 
cs


속도 곱하기 

위치 값 받기 -> 범위 체크 -> 이동하기 


tr.Translate(Vector3 * Time.deltaTime)


Vector는 방향과 크기이다. 그렇기 때문에 

정확한 수치만큼 이동이 불가능하기 때문에 범위에서 이동거리를 제한해야 한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class transformX : MonoBehaviour {
 
    private Transform tr;
    private float speedX = 8.0f;
    private float speedZ = 5.0f;
    private Vector3 pos;
 
    private void Start()
    {
        tr = GetComponent<Transform>();
    }
 
    void Update () {
        //실시간 위치 받아오기 
        pos = tr.position;
 
        //x축 방향 전환 범위 조건
        if (pos.x > 20.0f)
        {
            pos.x = 20.0f;
            speedX = -speedX;
        }
        else if(pos.x < -20.0f)
        {
            pos.x = -20.0f;
            speedX = -speedX;
        }
        //z축 방향 전환 범위 조건
        if (pos.z > 20.0f)
        {
            pos.z = 20.0f;
            speedZ = -speedZ;
        }  
        else if(pos.z < -20.0f)
        {
            pos.z = -20.0f;
            speedZ = -speedZ;
        }
 
        //x축 이동하기
        tr.Translate(Vector3.right * speedX * Time.deltaTime);
        //z축 이동하기
        tr.Translate(Vector3.forward * speedZ * Time.deltaTime);
 
    }
}
 
cs


최종 결과 


숙제

Rene Schulte - Fluid Demo  분석하기

유체역학 수학 이론 공부하기 

개발 flow 작성하기 (crossDevice, 유체역학(interaction, GPU instancing), 머신러닝)

Posted by 도이(doi)
,

05. 플레이어 동기화 시키기

동기화는 업데이트 빈도에 따라 크게 3가지 타입이 있다. 


1.  잦은 업데이트(위치, 문자상태) 

2. 가끔 업데이트(플레이어 행동)

3. 희귀 업데이트 & 상태(문의 개폐, 지도, 캐릭터 장비) - 생략



1.  잦은 업데이트(위치, 문자상태) 

업데이트가 연속적으로 작동해야 되는 경우. ex) 캐릭터의 위치 값을 받아오는

객체 동기화를 사용한다. 

객체 동기화는 움직임을 동기화 시킬 때 사용한다고 이해하면 쉽다. 


2.  가끔 업데이트(플레이어 행동)

업데이트를 연속적으로 할 필요는 없으나 event를 받아야 되는 경우.

RPC동기화를 사용한다. 

RPC동기화는 플레이어의 행동에 의한 event 메시지를 받을 때 사용한다.



< 객체 동기화 >


Photon View(Script)


<객체 동기화 photon View사용 기본 구조> 


객체 동기화를 사용하기 위해서는 

먼저, 동기화 시킬 게임오브젝트에 PhotonView Component를 가져와야 한다.



PhotonView Component의 구성요소를 하나하나 뜯어보면, 

크게 Observed ComponentsObserveOption으로 구성되어있다. 


Observed Components에는 동기화 시킬 components를 가져올 수 있다. 

그 중 script component를 가져오는 경우는 특수한 경우이다.


script를 Observed Components로 가져오게 될 경우에 자동으로 네트워크 처리되는 것을 

수동으로 조정할 수 있다. 


수동으로 조정이 필요한 이유는 dead reckoning(데드 레커닝)을 해결하기 위해서이다. 

*dead reckoning은 추측항해기법이라는 의미를 가지고 있다. 

네트워크 연결시 지연현상(Latency)를 해결하기 위한 기법이다. 

신호를 받지 못할 경우에 미리 다음 신호를 예측하여 행동하게 할 때 사용한다. (interpolate 보간 사용)


스크립트를 동기화 시키기 위해서 기본적으로 스크립트에 OnPhotonSerializeView를 추가해야 한다. 

OnPhotonSerializeView를 사용하기 위해서는 class에 IPunObservable를 넣어줘야 한다. 


ObservedOption은 네트워크의 통신규약을 정하는 옵션이다. 

reliableTCP통신 unreliableUDP통신이다. 

*TCP통신이란?

TCP통신에는 error correction이 존재한다. 이는 에러를 다시 바로 잡아준다는 것을 의미한다. 

서버에서 클라이언트에게 정보를 보낼 때 확인 메시지를 서버에게 돌려주고, 클라이언트가 제대로 메시지를 받지 못하면 resend를 서버에 요청한다. 손실된 데이터를 다시 클라이언트에게 보낼 수 있다. 데이터를 손실하지 않고 보낼 수 있다. 


*UDP통신이란?

UDP통신은 단방향 통신이다. 단방향 통신은 무조건 서버에서 클라이언트에게 메시지를 보내기만 한다. 그렇기 때문에 메시지 손실이 중간에 발생해도 이를 무시하고 메시지가 손실된 상태에서 계속 메시지를 보낸다. 따라서 UDP통신은 TCP통신에 비해서 빠르다는 장점을 가지고 있다. 


작성자는 Unreliable On Change 옵션을 사용하는데. 

이 통신 방식은 포톤에 특수하게 있는 것으로

변경사항이 있을 경우에만 통신을 지속하는 것이다. (쓸모없는 통신을 줄임.)




photon Transform View(Script)

포톤에서는 많이 사용하는 transform동기화를 script로 제공하고 있다. 

하지만, 이는 자동으로 dead reckoning 문제를 해결해주지 않기 때문에 끊김 현상을 보완하고 싶을 경우 스크립트를 작성해야 한다.  



< RPC동기화 >

RPC는 remote procedure calls의 축약어이다. 

이는 원격으로 함수를 부른다는 의미를 가진다. 

즉, RPC동기화는 이벤트가 발생(함수호출)하는 경우에 사용한다. 


rpc동기화를 시키고 싶은 함수 위에 [PunRPC]를 사용한다.


그리고 PhotonView.RPC("함수 이름");으로 원하는 곳에서 RPC함수를 호출하여 사용한다. 



동기화 전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
 
public class moveCtrl : MonoBehaviourPunCallbacks, IPunObservable {
 
    public float speed = 10.0f;
    private Transform tr;
 
    void Start()
    {
        tr = GetComponent<Transform>();
    }
 
    void Update () {
        //controlled locally일 경우 이동(자기 자신의 캐릭터일 때)
        if (photonView.IsMine)
        {
            float moveHorizontal = Input.GetAxis("Horizontal");
            float moveVertical = Input.GetAxis("Vertical");
 
            Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
            tr.Translate(movement * Time.deltaTime * speed);
        } 
        else
        {
            //끊어진 시간이 너무 길 경우(텔레포트)
            if ((tr.position - currPos).sqrMagnitude >= 10.0f * 10.0f)
            {
                tr.position = currPos;
                tr.rotation = currRot;
            }
            //끊어진 시간이 짧을 경우(자연스럽게 연결 - 데드레커닝)
            else
            {
                tr.position = Vector3.Lerp(tr.position, currPos, Time.deltaTime * 10.0f);
                tr.rotation = Quaternion.Slerp(tr.rotation, currRot, Time.deltaTime * 10.0f);
            }
        }
 
    }
 
    //클론이 통신을 받는 변수 설정
    private Vector3 currPos;
    private Quaternion currRot;
 
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        //통신을 보내는 
        if (stream.IsWriting)
        {
            stream.SendNext(tr.position);
            stream.SendNext(tr.rotation);
        }
 
        //클론이 통신을 받는 
        else
        {
            currPos = (Vector3)stream.ReceiveNext();
            currRot = (Quaternion)stream.ReceiveNext();
        }
    }
}
 
cs



<mini 네트워크 과제>

움직이는 네모 박스 코드 직접 짜서 네트워크로 만들어보기.



Posted by 도이(doi)
,

<네트워크의 세계란?>

처음에 네트워크라는 개념을 이해하기가 매우 어렵다. 

예시를 들어보자면, Rick & Morty를 본 사람은 평행 우주 개념에 대한 이해가 있을 것이다. 

평행우주는 내가 살고 있는 세계가 다중으로 존재하고 있다는 것이다. 

위의 이미지처럼 똑같은 내가 동시에 다른 시공간에 존재하고 있는 것이다. 

그렇기 때문에 다른 우주에 있는 나는 같은 존재이지만 동시에 다른 존재이기도 하다. 

위의 이미지를 자세히 보면 등장인물은 모두 동일하지만 행동이 조금씩 다르다. 


네트워크의 개념은 평행 우주의 개념과 동일하다. 

네트워크는 네트워킹하고 있는 유저의 수만큼 세계를 만든다. 

그리고 다른 세계에 있는 캐릭터들을 동기화시켜서 

다른 세계에 있는 캐릭터를 같은 캐릭터인 것처럼 보이게 하는 것이다. 


말로 설명하면 추상적이라 잘 이해가 되지 않을 것 같아 도식화로 네트워크 세계를 표현해보았다.


Rick & Morty의 평행우주와는 다르게 네트워크의 평행우주에는 master(방장)가 존재한다. 

master는 평행우주에서 최초의 세계를 생성한다. master가 생성한 세계를 다른 유저가 접속을하는 것처럼 보이지만, 사실은 다른 유저의 클론이 접속하는 것이다. 

만약에 네트워크에 접속하는 유저가 4명이라면, 

동일한 세계가 4개가 생기는 것이고 유저는 총 16명이 되는 것이다. 



Photon Network PUN이란?

PUN은 Photon Unity Networking의 약칭이며, Unity 상의 멀티플레이어 네트워크 리얼타임 온라인 기능을 강화한 키트로서 기존 Unity Networking과도 호환됩니다.  로우레벨부터 완전히 새로 제작되었으며 Photon Cloud/Server를 게임 백엔드로 사용했기 때문에 개발하기에 상당히 편리합니다.


*PUN과 PUN2의 코드 라이브러리가 약간 다르니 미리 이를 숙지해두어야 한다.

PUN2에 PUN의 라이브러리 코드를 사용하는 삽질은 하지 마시길... 포스팅 작성자는 PUN2를 사용합니다. 



PUN 콜백함수 참조 리스트


포톤네트워크 지역설정하기 


포톤네트워크 동기화  


포톤네트워크 플레이어 인스턴스


포톤네트워크 UI


마스터 클라이언트에서 호스트 클라이언트로 전환



<포톤네트워크 사용해보기>

포톤네트워크 기본구조

01. 포톤 네트워크 계정 생성

02. 네트워크 연결

03. 로비 

04. 방 생성/ 방 입장

05. 플레이어 동기화 시키기



01. 포톤 네트워크 계정 생성

1. 포톤 엔진에 가입해서 계정을 생성한다.


2. 우측 상단에 있는 사용자 아이콘을 눌러서 public cloud > your application으로 간다.


3. appID주소를 복사한다. 

4. unity에서 새로운 프로젝트를 만든다.


5. asset store에서 PUN2 asset을 import 한다.


6. PUN wizard에 본인의 appID를 입력한다. 


02. 네트워크 연결

1. NetworkMgr이라는 게임 오브젝트를 생성한 후에 

   NetworkMgr스크립트를 만들어서 게임오브젝트에 넣는다. 


2. NetWorkMgr 스크립트를 열어서 아래와 같이 작성한다.

(보라색 밑줄쳐진 것이 수정 부분)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.SceneManagement;
 
public class NetworkMgr: MonoBehaviourPunCallbacks
{
    private void Awake()
    {
        PhotonNetwork.AutomaticallySyncScene = true
    }
 
    //네트워크 연결
    private void Start()
    {
PhotonNetwork.GameVersion = "1.0";
        PhotonNetwork.ConnectUsingSettings();
    }
}
cs

ConnectUsingSettings 

즉시, 온라인 상태로 만들어줍니다. 

bool PhotonNetwork.automaticallySyncScene

방의 모든 클라이언트가 마스터 클라이언트와 동일한 레벨을로드해야하는지 여부를 정의합니다.



03. 로비 

방을 여러개 만들 때만 로비가 필요하다. 

(방을 선택해야 될 때) 

작성자는 한 개의 방만 필요하기 때문에 따로 로비를 생성하지 않음. 



04. 방 생성/ 방 입장

네트워크에 연결 된 이후에 랜덤하게 방에 입장한다. 

처음에는 방이 없기 때문에 방입장에 실패할 경우에 새로운 방을 생성한다. 

1
2
3
4
5
6
7
8
9
10
11
public override void OnConnectedToMaster()
    {
        Debug.Log("Connect To Master");
        PhotonNetwork.JoinRandomRoom();
    }
 
public override void OnJoinRandomFailed(short returnCode, string message)
    {
       //maxplayer설정(방 생성시)
RoomOptions roomOptions = new RoomOptions();    roomOptions.MaxPlayers = 2;
//방생성
        PhotonNetwork.CreateRoom("virtualWrld"new RoomOptions { MaxPlayers = 2 });
    }

cs

virtual void OnConnectedToMaster

방의 모든 클라이언트가 마스터 클라이언트와 동일한 레벨을로드해야하는지 여부를 정의합니다.


방에 입장한 이후에 플레이어를 생성한다. 

네트워크에 연결 후에 플레이어가 생성되어야 통신이 되기 때문이다. 

1
2
3
4
5
    //방에 입장한 후에 플레이어 생성
    public override void OnJoinedRoom()
    {
        PhotonNetwork.Instantiate("cube", Vector3.zero, Quaternion.identity);
    }
cs


인스턴스할 플레이어는 project창에 Resources폴더를 만들어서 prefab으로 생성한다. 



전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
 
public class NetworkMgr : MonoBehaviourPunCallbacks {
 
    private void Awake()
    {
        PhotonNetwork.AutomaticallySyncScene = true;
    }
 
    // Use this for initialization
    void Start () {
        PhotonNetwork.GameVersion = "1.0";
        PhotonNetwork.ConnectUsingSettings();
    }
 
    //포톤 서버에 접속
    public override void OnConnectedToMaster()
    {
        PhotonNetwork.JoinRandomRoom();
    }
 
    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.MaxPlayers = 2;
 
        PhotonNetwork.CreateRoom("room", roomOptions);
    }
 
    //방에 입장한 후에 플레이어 생성
    public override void OnJoinedRoom()
    {
        PhotonNetwork.Instantiate("cube", Vector3.zero, Quaternion.identity);
    }
}
 
cs



동기화 관련 포스팅은 내일.....



<참조>

https://support.photonengine.jp/hc/ko/articles/236154968-Tanks-Multiplayer-with-PUN-Part-1-

https://doc.photonengine.com/en-us/pun/v2/demos-and-tutorials/pun-basics-tutorial/intro

https://doc.photonengine.com/ko-kr/pun/current/connection-and-authentication/regions

https://doc.photonengine.com/ko-kr/pun/v2/getting-started/pun-intro

http://doc-api.photonengine.com/en/pun/v2/class_photon_1_1_pun_1_1_mono_behaviour_pun_callbacks.html

Posted by 도이(doi)
,

포톤 네트워크를 사용하여 환경을 세팅해보자


우선, photon viking multiplayer showcase를 asset store에서 가져옵니다. 

pun wizard에 나의 photon app ID를 입력합니다. (정확히 이유는 알 수 없는데 메일 주소는 인식을 못함.)


그러면 네트워크가 잘 작동합니다. ^^

네트워크 테스트에 용이함.



참조

포톤 네트워크 연결: http://citynetc.tistory.com/176

데모 사용: http://loadofprogrammer.tistory.com/161


Posted by 도이(doi)
,