진행상황

-kinect, htc vive, projector 연결에 이상 없음 모두 테스트 완료.

-기획서 작성, 발표자료, 일정표 작성 완료


제가 댄스댄스 하고 있습니다.


Posted by 도이(doi)
,

1. 플레이어 닉네임 가져오기 

2. DeadReckoning 

3. 채팅창 만들기


1.플레이어 닉네임 가져오기 

1-1. 충돌체에 actornumber를 public 변수로 부여 

나는 cannon이라는 충돌체 script에 public actornumber = -1;이라고 입력해두었다.


1-2. 플레이어 닉네임 가져오는 코드

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
public class userName : MonoBehaviourPunCallbacks, IPunObservable {
 
    public TextMeshProUGUI nickName;
 
    void Start () {
        //입력해서 저장된 userid지우는 코드 
        PlayerPrefs.DeleteAll();

  //플레이어의 닉네임을 가져오는
        nickName.text = photonView.Owner.NickName;
    }
 
 
    //원격지의 플레이어 nickname을 가져오는 
    string GetNickNameByActorNumber(int actorNumber)
    {
        foreach (Player player in PhotonNetwork.PlayerListOthers)
        {
            if (player.ActorNumber == actorNumber)
            {
                return player.NickName;
            }
        }
        return "Ghost";
    }
 
}
 
cs


1-3. 텍스트가 들어있는 게임오브젝트를 생성된 스크립트의 nickName 칸에 넣기


2. DeadReckoning (위치 동기화를 위한 추측항법 사용)


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
public class deadReckoning : MonoBehaviourPunCallbacks, IPunObservable {
  
    void Update () {
        //photonView.isMine 내 탱크는 내가 움직인다 
        if(photonView.IsMine && !isDie)
        {
            v = Input.GetAxis("Vertical");
            h = Input.GetAxis("Horizontal");
 
            tr.Translate(Vector3.forward * Time.deltaTime * v * speed);
            tr.Rotate(Vector3.up * Time.deltaTime * h * rotSpeed);
        }
        //내 게임에 접속한 다른 유저들의 움직임을 보정 
        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



3. 채팅창 만들기 

3-1. canvas생성하기(아래와 같이 생성하기)



3-2. 빈 게임오브젝트 생성 후 이름을 GameManager로 변경 후 GameManager 스크립트를 생성한다.

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
 
public class GameManager : MonoBehaviourPunCallbacks {
 
    public Text msgList;
    public InputField ifSendMsg;
 
    public void OnSendChatMsg()
    {
        string msg = string.Format("[{0}] {1}"
                                   , PhotonNetwork.LocalPlayer.NickName
                                   , ifSendMsg.text);
        photonView.RPC("ReceiveMsg", RpcTarget.OthersBuffered, msg); //buffer는 적재되어있던 것을 한 번에 가져옴
        ReceiveMsg(msg);
    }
 
    [PunRPC]
    void ReceiveMsg(string msg)
    {
        msgList.text += "\n" + msg;
    }
}
 
cs


3-3. InputField 게임오브젝트에 OnEndEdit에 GameManager 추가하기 GameManager.OnSendMsg() 함수 선택하기


3-4. 생성한 GameManager 게임 오브젝트에 txt 연결하기


4. 로비 생성하기 

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine.SceneManagement;

public class PhotonInit : MonoBehaviourPunCallbacks
{
    public enum ActivePanel
    {
        LOGIN = 0,
        ROOMS = 1
    }

    public ActivePanel activePanel = ActivePanel.LOGIN;

    private string gameVersion = "1.0";
    public string userId = "cuttySexy";
    public byte maxPlayer = 20;

    public TMP_InputField txtUserId;
    public TMP_InputField txtRoomName;

    public GameObject[] panels;

    private void Awake()
    {
        //같은 룸에 들어온 모든 유저들의 씬을 동기화 
        PhotonNetwork.AutomaticallySyncScene = true;
    }

    //아이디를 저장하는 (초기에는 랜덤한 유저 이름이 들어간다)
    void Start()
    {
        txtUserId.text = PlayerPrefs.GetString("USER_ID", "User_" + Random.Range(1, 999));
        txtRoomName.text = PlayerPrefs.GetString("ROOM_NAME", "ROOM" + Random.Range(1, 999));
    }
 #region SELF_CALLBACK_FUNCTION
    public void OnLogin()
    {
        PhotonNetwork.GameVersion = this.gameVersion;
        PhotonNetwork.NickName = txtUserId.text;

        PhotonNetwork.ConnectUsingSettings();

        PlayerPrefs.SetString("USER_ID", PhotonNetwork.NickName);
        ChangePanel(ActivePanel.ROOMS);
    }

    public void OnCreateRoomClick()
    {
        PhotonNetwork.CreateRoom(txtRoomName.text
                                 , new RoomOptions { MaxPlayers = this.maxPlayer });
    }

    public void OnJoinRandomRoomClick()
    {
        PhotonNetwork.JoinRandomRoom();
    }
 #endregion
    //패널 바꾸기 
    private void ChangePanel(ActivePanel panel)
    {
        foreach (GameObject _panel in panels)
        {
            _panel.SetActive(false);
        }

        panels[(int)panel].SetActive(true);
    }

 #region PHOTON_CALLBACK_FUNCTIONS
    //랜덤룸 입장에 실패하면 룸 생성하기
    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        Debug.Log("fail");
        PhotonNetwork.CreateRoom(null
                                 , new RoomOptions { MaxPlayers = this.maxPlayer });
    }
    //룸에 들어가기 
    public override void OnJoinedRoom()
    {
        Debug.Log("join room");
        PhotonNetwork.IsMessageQueueRunning = false;
        SceneManager.LoadScene("Level01");
    }
#endregion
}

#region의 기능은 참조형이다. 부분을 접었다 폈다 폴딩할 수 있다. 

끝은 항상 #endregion으로 끝맞추어야 한다. 


Posted by 도이(doi)
,

L-system은 린덴마이어 시스템이라고도 불리워지는 식물의 성장 프로세스를 기초로 한 다양한 자연물의 구조를 기술하거나 표현을 가능케 하는 알고리즘이다. 


기원[편집]

생물학자였던 린덴마이어는 효모와 곰팡이, 그리고 남세균류의 Anabaena catenula와 같은 조류 등, 다양한 생물의 성장패턴을 연구했다. 이와 같이 L-system은 단세포생물 또는 단순한 다세포생물의 성장식, 식물세포에 있어서 인접한 세포의 상호관계를 기술하기 위해 개발된 것이었다. 이후 L-system은 더욱 고도로 발달한 식물의 형태, 복잡한 분기구조를 기술하기 위한 도구로 발전하게 되었다.


주): 위키피디아




기본적인 구조는 공리를 지정하고 규칙을 설정하여 그 규칙에 따라 점점 복잡한 구조를 띄게 되는 것을 말한다. 대표적인 예시로는 프렉탈 구조가 있다. 아래에 간단한 예를 들어보겠다.


숫자: 0, 1

공리: 0

규칙: 0 -> 010

  1  -> 111



만약 위와 같은 규칙으로 l-system을 표현하면 아래와 같다.


0 -> 010 -> 010111010 -> ...


이런식으로 간단한 규칙에서 점점 복잡한 식을 생성한다.

그 모습이 마치 나무가 가지를 뻗고 생장하는 것과 유사하여 주로 이 시스템을 설명하는데 나무를 예시로 많이 든다.


기본적인 개념은 Daniel Shiffman의 강좌를 참고하였다. 

설명을 깔끔하게 잘 해주신다.

https://www.youtube.com/watch?v=f6ra024-ASY&list=PLRqwX-V7Uu6bXUJvjnMWGU5SmjhI-OXef&index=5


Daniel Shiffman이 추천한 책이다. 

자연의 알고리즘 Algorithm Botany라는 책인데 사이트에 들어가 보면 다양한 예시자료와 논문이 있다.

http://algorithmicbotany.org/papers/ 


위 사이트에서 생장 모델을 찾아볼 수 있을 것 같다.


아이디어>

세계를 구축하는 간단한 모델들을 만들어놓고

간단한 구조를 짜서 복잡성을 더한다. 그것은 마치 생장하는 것처럼 보인다.

한 가지 더 아이디어를 얻은 것은 그 구조가 수치를 치환하여 진행하여 짤 수 있다는 것이다.

생장, 퇴화 구조와 관련된 알고리즘을 찾아 생명의 시스템 형성에 응용하면 될 것 같다.



<reference>

https://ko.wikipedia.org/wiki/L-system

https://m.blog.naver.com/PostView.nhn?blogId=at3650&logNo=40204923897&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F




Posted by 도이(doi)
,

오늘은 네트워크 게임 만드는 방법을 공부했다.

네트워크라니...뭔가 복잡해보이는데 


그렇기에 photon서버를 사용하여 따로 서버를 구축할 필요 없이 

나름 쉽게 작업을 할 수 있다. 


photon서버 구축하는 방법은 생략한다. 

photon은 통신을 하는 것이다. 


우선 기본적으로 컨트롤 가능한 탱크와 환경을 만든다. 

탱크 컨트롤 하는 스크립트는 키입력을 받아서 사용. 간단하여 생략.


오늘 배운 코드 중 제일 어려운 코드는 

!!Screen Point to Ray!!를 이용하여서  2차원 좌표를 받아서 3차원좌표를 감지하는 기능을 사용하는 것이었다. 

이 코드를 이용하여 마우스의 방향으로 탱크의 헤드가 회전하도록 만들어보았다. 


우선 ray를 camera에서 나오도록 생성합니다. Camera.main.ScreenPointToRay

 카메라에서 출발하여 마우스 포지션을 향해서 ray를 쏜다. 


hit.point는 world좌표이기 때문에 local좌표를 가지고 있는 현재 사물과의 관계에 대한 각도를 구할려고 하면 오차가 일어난다. 그렇기 때문에 InverseTransformPoint 를 사용하여서 현재 게임오브젝트tr을 기준으로 local position을 구해준다. 


사이각을 구하는 공식은 aTan(탄젠트의 역함수)를 이용하여서 구한다. 

기본 공식은 Mathf.Atan2(localPos.x, localPos.z) * Mathf.Rad2Deg;이다.

현재 위 공식은 localPos.x와 localpos.z 사이의 각을 구하는 것이다. 

x와 z의 사이각을 구하고 그 각도 만큼 y축을 돌리면 된다. 


!!삼각함수에 대한 부분은 추가적으로 더 공부를 요한다.!! (나중에 책 오면 삼각함수 부분 정리하기)


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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ScreenPointToRay : MonoBehaviour {
 
    private Transform tr;
    public float rotSpeed = 60.0f;
    private RaycastHit hit;
 
    void Start()
    {
 
        tr = GetComponent<Transform>();
    }
 
    void Update()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 50.0f, Color.green);
 
        if(Physics.Raycast(ray, out hit, Mathf.Infinity, 1<<9))
        {
            //사이각 구하기 
            Vector3 localPos = tr.InverseTransformPoint(hit.point);
            float angle = Mathf.Atan2(localPos.x, localPos.z) * Mathf.Rad2Deg;
            tr.Rotate(0, angle, 0);
        }
    }
}
 
cs



photon은 resources폴더에 있는 파일을 호출한다.(탱크는 resources파일에 있어 호출 가능.)

계속 동기화가 필요한 부분에 photon의 기본형을 사용한다.  


<photon의 기본형>

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
using Photon.Pun;
using Photon.Realtime;
using TMPro;
 
public class MoveCtrl : MonoBehaviourPunCallbacks {
 
    public TextMeshProUGUI nickName;
 
    void Start () {
 
        //룸에 들어가서 자기자신이면 camera follow 스크립트가 활성화 되도
        if(photonView.IsMine)
        {
            Camera.main.GetComponent<SmoothFollow>().target = tr.Find("camPivot").transform;
        }
        nickName.text = photonView.Owner.NickName;
    }
    
    void Update () {
        //remote시키는 객체
        if(!photonView.IsMine)
        {
            return;
        }
    }
}
 
cs


그리고 계속 동기화시킬 부분에는  photon view와 photon transform view 컴포넌트를 추가한다.

RPC는 send message의 기능을 한다. 

(현재 본인 뿐만 아니라 원격으로 존재하는 본인에게도 똑같은 행위를 하도록 명령하는 것.)

동일한 룸 내의 같은 게임오브젝트의 다른 클라이언트를 호출한다. 


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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
 
public class FireCannon : MonoBehaviourPunCallbacks {
 
    public Transform firePos;
    public GameObject cannon;
    public AudioClip fireSfx;
 
    private AudioSource _audio;
 
    private void Start()
    {
        _audio = this.gameObject.AddComponent<AudioSource>();
    }
 
    void Update () {
 
        if (!photonView.IsMine) return;
 
        if(Input.GetMouseButtonDown(0))
        {
            //원격지에 있는 자기 자신에게 총을 쏘도록 
            photonView.RPC("Fire", RpcTarget.Others, null);
            //현재 자신이 총을 쏘는 함수 호출
            Fire();
        }
 
    }
 
    [PunRPC]
    void Fire()
    {
        _audio.PlayOneShot(fireSfx, 0.8f);
        Instantiate(cannon, firePos.position, firePos.rotation);
    }
 
}
 
cs


TextMash Pro를 사용하여 상대방의 닉네임 확인하기. 

+textmash pro는 한국어나 특수한 외국어를 사용하고 싶을 때 사용한다. 글자 깨짐 현상도 해결해준다.

패키지는 window - textmashpro 에서 import tmp essential과 import tmp examples 두 개를 import 하면된다.

그 후에 font asset creator에서 원하는 폰트를 고른다. 



게임 오브젝트에 canvas를 생성하고 panel(배경) 생성 후에 textmeshpro text를 차일드한다.

RectTransform은 0,0으로 바꿔주고 extra padding을 선택해준다. 



textmeshpro text를 유저의 닉네임을 보는데 사용하기 위해서는 아래와 같은 코드가 필요하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
using TMPro;
 
public class MoveCtrl : MonoBehaviourPunCallbacks {
 
    public TextMeshProUGUI nickName;
 
    void Start () {
 
        nickName.text = photonView.Owner.NickName;
    }
 
}
 
cs


스크립트 작성 후에 textMeshPro Text를 Nick Name으로 끌어다 놓는다. 


카메라를 향해서 캔버스 바라보게 만들기(lookat)

1
2
3
void LateUpdate () {
        transform.LookAt(Camera.main.transform.position);
    }
cs



잘 작동한다! :)



<Reference>

[나중에 게임 서버상에서 다른 사람과 대화할 때 사용가능 함.]

http://loadofprogrammer.tistory.com/168?category=661940



Posted by 도이(doi)
,