'unity3d'에 해당되는 글 33건

  1. 2013/01/16 글뻥 기술을 흉내내다.
  2. 2013/01/06 글뻥 초간단 NGUI - 4
  3. 2013/01/06 글뻥 초간단 NGUI - 3
  4. 2013/01/06 글뻥 초간단 NGUI - 2
  5. 2013/01/04 글뻥 초간단 NGUI - 1 (3)
  6. 2012/12/04 글뻥 Photon Cloud 강좌 4 (15)
  7. 2012/11/29 글뻥 Photon Cloud 강좌 3 (11)
  8. 2012/11/28 글뻥 Photon Cloud 강좌 2 (5)
  9. 2012/11/26 글뻥 Photon Cloud 강좌 1 (6)
  10. 2012/10/01 글뻥 unity3D Photon Cloud 4th TEST
동영상중에 하나가 저를 끌어서...

http://indiejam.dk/lazer-valkyriez/


결국 따라서 만든다고 만든게...




그럭저럭 나온것 같네요 =)
2013/01/16 01:46 2013/01/16 01:46
내친김에 써보는 하일라이트 마지막 NGUI 강좌입니다.(이럴때 들리는게 두둥~!)
아마도 이까지 진행하시고 나면 NGUI에 대한 자심감 + 별거없다는 사실을 깨달으시고 나머지 부분은 Facebook으로 요청주시거나 댓글이 달리면 그 때 고민해보겠습니다.
(모든 노하우를 다 공개하고 싶지만... Free버전의 기능상 한계라는 핑계로...)
* 열분들은 그냥 쳐묵쳐묵하신 다음에 잘 소화를~ ㅋㅋ
사용자 삽입 이미지

이번에는 3강의 결과물을 잘 관찰해야 문제를 풀수 있습니다.
(관찰을 하라고 하면 보통은 "내가 기대하는 일이 일어나길 바라면서 쳐다 봅니다." 인간은 원래 그런 동물입니다. 그냥 봅시다.)
사용자 삽입 이미지

위와 같은 등수를 만들때 템플릿은 그대로 인데 "아이디"와 "점수"만 다르다는 사실을 눈치 챘을 겁니다.
따라서, NGUI의 Label을 사용하면 쉽게 구현할 수 있다는 사실을 눈치 챌 수 있습니다.
두번째 관찰사항은 NGUI의 하이라키 구조입니다.
사용자 삽입 이미지

Panel -> Grid -> Sprite 순서로만 들어가면 우케든 스크롤이 된다는 사실을 알았습니다. 이제부터 쉽게 가보죠.
우선 Sprite와 Label은 같은 레벨로 Label을 Sprite 하위에 넣거나 반대로 넣는건 막혀있습니다.
그래서 Sprite와 Label을 싸줄 Package가 필요합니다. 즉, 빈 게임오브젝트죠!
3강에서 했듯이 GameObject > Create Empty 로 빈 게임오브젝트를 하나 만들고 Item이라는 이름으로 변경한후 Drag & Drop해둡니다. (Scale이나 Position Reset 잊지 마세요!!)
사용자 삽입 이미지

Sprite를 Item밑으로 Drag&Drop합니다.
사용자 삽입 이미지
이제 1강에서 보았던 Widget 메뉴에서 Label을 추가합니다.
사용자 삽입 이미지

화면에 이렇게 보이시면 잘 따라하신 겁니다.
사용자 삽입 이미지
* Label의 위치가 잘 안보여서 Position Y를 조금 조정했습니다.
이제 Item을 잡고 Project창의 임의의 폴더에 Drag&Drop해서 Prefab으로 생성합니다.
사용자 삽입 이미지

하이라키에서는 더 이상 Item이 필요없으니 삭제!
사용자 삽입 이미지

이제 코딩질입니다.
"AddItem"이라는 이름의 CS파일을 하나 생성하시고 아래와 같이 넣습니다.
using UnityEngine;
using System.Collections;
public class AddItem : MonoBehaviour {
    public GameObject Item;
// Use this for initialization
void Start () {
           InitItem();
}


    void InitItem()
    {
        //모두 10개의 Item을 생성합니다.
        for (int i = 0; i < 10; i++)
        {
            //일단 생성합니다. 무조건...
            GameObject obj = Instantiate(Item, new Vector3(0f, 0f, 0f), Quaternion.identity) as GameObject;
            //생성된 GameObject의 부모가 누구인지 명확히 알려줍니다. (내가 니 애비다!!)
            obj.transform.parent = this.transform;
            //NGUI는 자동이 너무많이 짜증나니 수동으로 Scale을 조정해줍니다.
            obj.transform.localScale = new Vector3(1f, 1f, 1f);
            //Label에 i값을 넣습니다.
            obj.GetComponentInChildren<UILabel>().text = i.ToString();
        }
       //Prefab을 생성한 이후에 Position이 모두 같아서 겹쳐지므로 Reposition시키도록 합니다.
        GetComponent<UIGrid>().Reposition();
    }
}

이제 Grid로 Attach시킵니다. (Item Prefab도 Drag&Drop!)
사용자 삽입 이미지
이제 실행하면...
사용자 삽입 이미지

위와 같이 TEXT는 변경되어 있고 하이라키에서도...
사용자 삽입 이미지

이렇게 아이템이 들어가 있네요!
여기서 마치기 아쉬우니 2강에서 했던 OnClick 이벤트 코드를 조금 손봐서 Item Prefab 밑에 있는 Sprite에 넣어보면...
using UnityEngine;
using System.Collections;

public class ShowBye : MonoBehaviour {

    void OnClick()
    {
        string Label = this.transform.parent.GetComponentInChildren<UILabel>().text;
        Debug.Log(string.Format("안녕하세요? {0}번 입니다.", Label));
    }
}
사용자 삽입 이미지
진짜 끄읏~~~~
뭐하세요? 이제 삽질하러 가셔야죠~ ㅋ
사용자 삽입 이미지
* 본 강좌의 저작권은 링고게임즈에 있습니다.
출처없는 불펌은 법대로 할겁니다.
2013/01/06 12:46 2013/01/06 12:46
안녕하세요? 자고 일어나 아침에 하는일이... 또 강좌질입니다. ㅋㅋㅋ
* 다음 이미지는 마노비기의 여해적입니다. 강좌의 예제로 쓸겁니다. =)
사용자 삽입 이미지
오늘 새벽에 예고한 바와 같이 스프라이트 스크롤 한번 구현해보겠습니다.
1,2강에서 신물나게 클릭했던 "NGUI"에 "Create a New UI" 아시죠? (모르시면 다시 1, 2강으로...)
사용자 삽입 이미지

마찬가지로 Layer는 2DGUI로 설정합니다.
사용자 삽입 이미지

이제부터 중요합니다.
하이라키에서 "Panel"을 클릭한채로 메뉴의 Component > NGUI > Interaction > Draggable Panel 을 차례대로 눌러주세요.
사용자 삽입 이미지

그럼 Panel의 Inspector가 아래와 같이 바뀔 겁니다.
사용자 삽입 이미지

우선 여기까지 진행하신후에 빈 껍데기 GameObject를 만듭니다.
(방법은 GameObject에 "Create Empty"누르시면 됩니당...)
사용자 삽입 이미지

Grid라는 이름으로 변경후의 모습입니다.
사용자 삽입 이미지
이제 Panel 아래로 이동시켜 주세요.
Layer와 Position, Scale은 초기화 시켜주시는걸 잊지마십시오.
(카메라가 서로 달라서 초기화 안시켜놓으면 나중에 사이즈 문제등을 겪게 됩니다. T_T)
사용자 삽입 이미지
이제 다시 Component > NGUI > Interaction > Grid 클릭.
사용자 삽입 이미지
Inspector에서 제대로 들어갔는지 확인합니다.
거의다 왔습니다. (NGUI는 쉽다니깐염 ㅋㅋ)
마지막으로 Grid안에 들어갈 Item을 만들어야 겠죠? 마노비기의 여해적 캐릭터를 다운받았습니다.
(님 고소. 이런거 하지 맙시당... 상업적 이용도 아닌데... ㅡㅡ;;)
첨부파일로 다운로드 받을 수 있구요.
다음과 같이 Import 하신다음에
사용자 삽입 이미지

Atlas를 Item이라는 이름으로 만듭니다. (이건 2강)
사용자 삽입 이미지

아래와 같이 됐다면 잘 하신겁니다.
사용자 삽입 이미지
Atlas를 만들었으니 Widget으로 Sprite로 만들차례입니다.
사용자 삽입 이미지

Grid안으로 잘 들어갔는지 확인 해주세요.
사용자 삽입 이미지
다시 Component > NGUI > Interaction > Drag Panel Contents 클릭!
사용자 삽입 이미지

다음과 같이 Item을 클릭했을 때 변경되어야 합니다.
사용자 삽입 이미지

그리고 Drag하는 Event가 먹혀야 하니 NGUI > Attach a Collider 클릭
사용자 삽입 이미지

그리고 나서 Sprite Component에 있는 Collider와 Transform의 Scale을 200으로 조정합니다.
사용자 삽입 이미지

이제 실행해보면서 Drag해보면 지멋대로 움직이는 Sprite를 볼 수 있습니다. =)
사용자 삽입 이미지

우리가 원하는건 횡으로 (X축)으로 움직이는 스크롤이기때문에 (혹은 Y축으로...) 다음과 같이 Panel설정을 변경해줍니다.
사용자 삽입 이미지

다시 실행해보면 X축으로만 움직이는 해적을 보실 수 있을 겁니다.
이제 해야할 일은 Sprite를 복제해서 많이 만드는 겁니다!
사용자 삽입 이미지

대략 5개쯤 복제한 뒤에 실행해보면...
사용자 삽입 이미지

사용자 삽입 이미지

이번에는 코드가 안들어 갔습니다.
다음회에는 이걸 응용해서 Dynamic하게 Item을 추가하는 걸 해보겠습니다.
* 긴급패치!!!!!
위의 그림에서 Collider를 200으로 설정했습니다만, 잠이 덜깬 Miss입니다.
사용자 삽입 이미지

위와 같이 1로 설정하셔도 됩니다.
* 본 강좌의 저작권은 링고게임즈에 있습니다.
출처없는 불펌은 법대로 할겁니다.
2013/01/06 12:10 2013/01/06 12:10
오늘도 늦은 야근에 몸을 질질질 끌고 집에 와서는 한다는게 NGUI 강의입니다.


아참. 혹시 이 글을 보시는 분중에 3D Modling / Animation 하시고, 스타트업의 저희 팀에 합류하고 싶으신 분은 vicviper@live.co.kr로 "너네는 앞으로 뭘 만들거냐?"고 물어봐주세요! 연락 부탁드립니다. 디자이너 구해요.... 급여는 별로 못드려요... 경력 쌓으실 신입환영입니다. 근무지는 분당서현이예요... 이렇게 구인광고 드립... ㅋㅋ
사용자 삽입 이미지

오늘은 어제에 이은 NGUI 버튼입니다.
약간의 script가 들어가니 참고하시고요... NGUI메뉴에서 "Create a New UI" 클릭!
그리고 1강과 같이 2D GUI Layer 설정.
사용자 삽입 이미지


요렇게 바뀐다면 1강을 잘 들으신겁니다.
이제 심플하게 버튼을 만들어 보죠. 이미지는 첨부파일 다운로드 해주세요!
간단하게 3개의 PNG파일이 있을겁니다.
이 녀석들을 새로 폴더를 하나 만드신후에 Drag&Drop으로 넣어둡니다.
사용자 삽입 이미지

Import가 끝났다면 이제 다시 NGUI 메뉴에서 "Atlas 어쩌구하는 녀석을 불러옵니다."
사용자 삽입 이미지

다음과 같이 불러오는데 성공하셨다면 "참 잘하셨어요~!" =)
사용자 삽입 이미지
위의 창은 그대로 두시고, 아까 import한 png 파일 3개를 클릭합니다.
사용자 삽입 이미지

맨 위에 녀석을 클릭한뒤에 Shift키를 누른채로 마지막 녀석을 클릭하면 3개 모두 선택이 가능하겠죠?
그럼 위와 같이 자동으로 파일이름이 들어갑니다. (오~ 똑똑이..)
이름을 바꿔준 후에 "Create"버튼을 클릭하시면 다음과 같이 변합니다. (진짭니다.)
사용자 삽입 이미지

Texture Size는 256*256으로 잘 생성된 것 같군요.
이제 생성된 Atlas를 확인해봅시다. 아주 간단히 마우스로 콕콕 찔러보는 센스.
사용자 삽입 이미지

중요한건 Atlas가 만들어질때 이렇게 3개의 이미지라면 모든 공간을 제대로 활용하지 못하고 이렇게 구멍이 있는채로 나온다는 겁니다. 항상 Atlas를 만들때는 되도록 텍스쳐의 사이즈를 고려해서 만들기 바랍니다.
사용자 삽입 이미지

이제 또 다시 NGUI 메뉴에서 "Create a Widget"을 클릭하시고
사용자 삽입 이미지

Widget 메뉴에서 다음과 같이 설정합니다.
사용자 삽입 이미지
("Button"은 1개의 이미지만 사용하는데 비해 "Image Button"은 3개의 이미지를 사용합니다. )
마지막으로 "Add to"를 클릭하면...
사용자 삽입 이미지

많이 지저분한 녀석이 나옵니다.
이제 실행!
사용자 삽입 이미지

마우스를 올려보고, 클릭도 해보시면서 자랑스러운 버튼 작동을 확인해보세요!
사용자 삽입 이미지

그리고 명색이 버튼인데 글자 정도는 올라가줘야 하지 않을까요? 이번에는 "GO"라는 글자를 1회차에서 만든 Font를 활용해서 적용해 보겠습니다.
방금 만드신 Button을 삭제하신후에 다시 "Widget"메뉴를 눌러서 다음과 같이 Font를 설정해준뒤 똑같이 "Add to"를 눌러줍니다.
사용자 삽입 이미지
네이버가 이럴때는 너무 고맙습니다. =)
아까와는 다르게 Image Button이라는 글자가 들어가는 군요! =)
사용자 삽입 이미지
하이라키에서 보면 아까와는 다르게 Label이 추가된게 보이실 겁니다.
사용자 삽입 이미지

다음과 같이 한번 Inspector창에서 장난을!
사용자 삽입 이미지

그럼 다음과 같이 변경 되는걸 보실 수 있을 거예요.
사용자 삽입 이미지

이제 항상 이바닥의 전통인 머리 쥐나게 하는 모드로 돌입합니다.
버튼을 만드는건 NGUI가 해주지만, 버튼을 작동시키는건 여러분들의 몫이니까요.
기본적으로 NGUI는 Collider로 작동됩니다. 그렇죠! 물리입니다. =)
사용자 삽입 이미지

위의 이미지를 보시면 Box Collider라는 녀석이 떠억~ 자리를 잡고 있습죠.
이 녀석에게 어떤 이벤트가 생기면... (아마도 내부구조는 잘 모르겠지만, Raycast가 가장 유력한 범인이 아닐까 싶습니다만...) 이벤트 메시지를 올려주는 메커니즘으로 작동합니다.
이 이벤트를 미리 약속해뒀는데, 다음과 같은 메소드에 알고리즘을 넣어 주셔야 합니다.
사용자 삽입 이미지

영어 울렁증이 있으시겠지만... 찬찬히 (우웩~) 살펴보다보면... OnClick이라는 메소드가 보이네요!
이제 아무생각없이 코딩질...
스크립트명을 Click이라는 이름으로 했습니다.
사용자 삽입 이미지

이 녀석을 열어서 담과 같이 코딩해보죠.
using UnityEngine;
using System.Collections;

public class Click : MonoBehaviour {

    void OnClick()
    {
        Debug.Log("어서와~ 버튼은 처음이지?");
    }

}
코드를 드레그해서 넣고...
사용자 삽입 이미지
실행하면 요롷고롬 됩니다. =)
사용자 삽입 이미지

윈도우에서나 가능한 매직입죠... 네네~
이상... 지뢀같은 한글지원땜시 iOS 버전도 윈도우에서 코딩해서 최종 빌드만 MAC에서 돌리는 글뻥이었습니다.
담에는 NGUI가 자랑하는 스크롤 버튼을 만들어 보겠습니다. =)
이런겁니다. =)
사용자 삽입 이미지
* 본 강좌의 저작권은 링고게임즈에 있습니다.
출처없는 불펌은 법대로 할겁니다.
2013/01/06 12:02 2013/01/06 12:02
안녕하세요? 글뻥입니다.
(이득우님께 잡혀서 열심히 하라는 일은 안하고 강좌 올리는... ㅋㅋ)
연일 강추위 건강 유의하시기 바라며, http://www.unitystudy.net에 올린 글을 옮겨서 올려봅니다.
먼저 NGUI를 사시기 전에 (꽤 비싸죠... ㅋ) Free 버전으로 초간단 NGUI 강좌를 진행해 보겠습니다.
사용자 삽입 이미지
(어서와~ NGUI는 처음이지?)
다운로드는
http://forum.unity3d.com/threads/124032-NGUI-Free-Edition
여기서 받을 수 있구요. NGUI 로고 클릭하면 unitypackage 다운로드 가능합니다.
그럼 import하고 시작!
사용자 삽입 이미지
import가 완료되면 메뉴에 NGUI라는 넘이 생기고 그걸 클릭하면 이제 모든 준비가 완료됩죠. 네네
사용자 삽입 이미지

영어 울렁증이 올라오지만 꾸욱 참고 가장 많이 쓰는 기능들만 간단히 살펴봅시다.
가장 많이 궁금해 하시는 부분이 바로 한글 출력일텐데요.
이것 부터 짚고 넘어가죠.
우리의 공짜주의에 입각해서 네이버의 나눔폰트를 사용하겠습니다.
http://hangeul.naver.com/font
폰트 설치후 BMFONT라는 녀석을 다운 받아야 하는데욤. 이녀석은...
http://www.angelcode.com/products/bmfont/
여기서 다운로드 가능합니다.
그리고도...
아직 다 준비가 안됐습니다.
Font파일중에 한글만 뽑아낼 녀석이 필요하니까요. 많이 사용하는 한글만 뽑아낸 txt파일이 필요합니다.
첨부파일로 저희쪽에서 사용하는 한글 파일을 드리지요. (서비스 확실하게...)
드디어 모든 준비가 끝났습니다.
사용자 삽입 이미지

일전에도 설명드렸듯이... 이바닥이 원래 이럽니다.
쉽다고 해놓고 뒷통수 치기... 이런게 뭐..
암튼... 이제 BMFont를 실행합니다.
사용자 삽입 이미지
이렇게 생긴 녀석이 뜰텐데요.
Option에서 폰트설정을 다음과 같이 해줍니다.
사용자 삽입 이미지

폰트를 25폰트 이상 올릴 수도 있겠지만.. 나중에 용량관리하시며 정신건강에 해로 울 수도 있사오니... 걍 25폰트로 뽑습니다. 이렇게 뽑으면 딱 1024x1024 사이즈의 텍스쳐에 들어갈 수 있죠.
Option에서 Export Option을 클릭하고 다음과 같이 1024x1024, Alpha값이 있는 흰색, text에 png로 뽑아냅니다.
사용자 삽입 이미지

이제부터 진짜입니다.
첨부된 KS1001.txt파일을 읽어 들입니다. "Edit"에서 Select char from file로 읽을 수 있죠.
사용자 삽입 이미지

그럼 아래와 같이 txt파일에 있는 폰트만 highlight됩니다.
사용자 삽입 이미지

이제 제대로 출력되는지 Option메뉴에서 Visualize를 클릭해서 확인합니다.
아래와 같이 1024x1024 사이즈에 한장에 들어오면 잘 따라오신겁니다.
사용자 삽입 이미지

마지막으로 Export합니다.
Option에서 "Save..."어쩌구 된넘을 누르면 끝.
사용자 삽입 이미지

Export된 파일중에 "fnt"확장자를 "txt"로 고쳐줍시다.
사용자 삽입 이미지

사용자 삽입 이미지

위와 같이 고쳐주시면 한글 사용할 준비가 정말... 진짜 완료되었습니다.
사용자 삽입 이미지

위와 같이 Font폴더를 만들고, 아까 변경해준 이름의 파일을 Drag&Drop해서 Unity로 Import 시킵니다.
NGUI메뉴에서 Font Maker를 클릭해, 다음의 화면을 호출하도록 합시다.
사용자 삽입 이미지

Font Data에는 ".txt"파일을 나머지 texture에는 아까 생성한 ".png"를 Drag&Drop으로 넣어줍니다.
그럼 다음과 같이 알수 없는 욕을 해대면서 UI가 변경될 겁니다.
사용자 삽입 이미지

이럴때 Fuc* u를 날려주는 Sense... 이제 X 먹으라고 빨간색 버튼 눌러주면...
다음과 같이 폰트 Atlas가 생성됩니다.
사용자 삽입 이미지

(한글 한번 쓰기 디따 힘들군요. T_T)
드디어~~ 진짜.. NGUI 강좌들어갑니다.
사용자 삽입 이미지

NGUI 메뉴에서 "Create a New UI"를 누르고 Layer를 하나 추가합니다.
사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지

(쓸데없는 고퀄로 차례로 이미지를 보여드렸슴돠... ㅡㅡ;;)
이제 과감히 버튼 클릭!
사용자 삽입 이미지

그럼 다음과 같이 하이라키가 변경되는게 보이실 겁니다.
사용자 삽입 이미지

이제 다시 NGUI 메뉴에서 Create a Widget 버튼을 눌러줍니다. 살포시...
사용자 삽입 이미지

위젯 툴에서 Label로 변경하고 "Add to" 버튼을 클릭!
사용자 삽입 이미지

갑작이 화면이 바뀌면서... New Label이라는 메시지가 보입니다.
사용자 삽입 이미지

인스펙터 메뉴에서 설정을 변경하면...
사용자 삽입 이미지
사용자 삽입 이미지

변경 완료!
오늘은 간단히 Label을 한글로 사용하는걸 봤습니다.
담 강좌는 버튼한번 해볼까요?
사용자 삽입 이미지
* 본 강좌의 저작권은 링고게임즈에 있습니다.
출처없는 불펌은 법대로 할겁니다.
KS1001 파일입니다.
2013/01/04 21:31 2013/01/04 21:31
마지막 회로 중요한 Method들을 정리하는 시간을 가질까 합니다.
그리고 어떻게 흘러가는지에 대한 개괄을 설명드리고 Photon Cloud 강좌는 종료할께요 =)

* 소스는 댓글확인 부탁드립니다.

* 아마도 더 하게 된다면 Photon Server SDK로 dll 뽑아서 dll만든뒤에 Microsoft의 Azure에 올리는 방법 (기존 서버와 같이 만드는 방법)을 할 것 같습니다만, 개발 일정이... 흐흐흐흐

* 돈은 못벌면서 겁나 일하는 신세인지라.. T_T
사용자 삽입 이미지


암튼, 각설하고 Photon Cloud의 주요흐름은

1. 서버접속
2. 로비접속
3. 방으로 접근
4. 방으로 접근시도후 실패시 방만듬
5. 방입장
6. "방장"의 명령에 따라 Object들을 Sync


이상 6가지가 주요흐름입니다. 물론, 게임이 끝나면 종료되는건데 그건 알아서... (쿨럭)


1. 서버접속관련
* Method (PhotonNetwork)
PhotonNetwork.ConnectUsingSettings("0.1");
이건 뭐... 너무 간단해서요... ㅡㅜ

* 서버접속후 발생하는 이벤트
아래 Event들은 다음과 같이 사용됩니다.
void OnConnectedToPhoton(){ ~이벤트처리 로직~ }
- OnConnectedToPhoton : 포톤에 접속되었을 때
- OnLeftRoom : 방에서 나갔을 때
- OnMasterClientSwitched : 마스터클라이언트가 바뀌었을 때
- OnPhotonCreateRoomFailed : 방만들기 실패
- OnPhotonJoinRoomFailed : 방에 들어가기 실패
- OnCreatedRoom : 방이 만들어 졌을 때
- OnJoinedLobby : 로비에 접속했을 때
- OnLeftLobby : 로비에서 나갔을 때
- OnDisconnectedFromPhoton : 포톤 접속 종료
- OnConnectionFail : 연결실패 void OnConnectionFail(DisconnectCause cause){ ... }
- OnFailedToConnectToPhoton : 포톤에 연결 실패 시  void OnFailedToConnectToPhoton(DisconnectCause cause){
... }- OnReceivedRoomList : 방목록 수신시
- OnReceivedRoomListUpdate : 방목록 업데이트 수신시
- OnJoinedRoom : 방에 들어갔을 때
- OnPhotonPlayerConnected : 다른 플레이어가 방에 접속했을 때 void OnPhotonPlayerConnected(PhotonPlayer new-
Player){ ... }- OnPhotonPlayerDisconnected : 다른 플레이어가 방에서 접속 종료시  void OnPhotonPlayerDisconnected(Photon-
Player otherPlayer){ ... }- OnPhotonRandomJoinFailed : 렌덤하게 방으로 입장하는게 실패했을 때
- OnConnectedToMaster : 마스터로 접속했을 때
- OnPhotonSerializeView : 네트워크싱크시  void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){ ...}
- OnPhotonInstantiate : 네트워크 오브젝트 생성시 void OnPhotonInstantiate(PhotonMessageInfo info){ ... }

위 접속관련 메소드와 이벤트를 참고한 결과가 전회의 "Master_Join"와 "NetworkObj" 입니다. =)


2. 로비관련
일단 로비 접속에 성공하면, 이제부터는 방목록을 얻어 온다던가... 아니면 방을 만든다던가... 이것도 아니라면 아무 방에 들어가기 위한 노력이 다입니다. =) 고로, 실재로는 몇재 안쓴다는 진실이...

* Method
RoomInfo [] PhotonNetwork.GetRoomList ( ) //방목록을 가져옵니다.
void PhotonNetwork.CreateRoom ( string roomName ) //방을 이름으로만 만듭니다.
void PhotonNetwork.CreateRoom ( string roomName, bool isVisible, bool isOpen, int maxPlayers ) //방을 만들되 이름, 보임여부, 오픈여부, 최대 플레이어 수를 지정합니다.
void PhotonNetwork.JoinRandomRoom ( ) //아무방이나 들어갑니다.
void PhotonNetwork.JoinRoom ( string roomName ) //지정한 방으로 들어갑니다.
아주 쉽죠? ㅡㅡ?

물론, 이전 회에서 보신적이 있으실 [RPC]도 호출이 가능하니 로비에서 웃고 떠들고 하는 것도 가능합니다!

3. 방에서...
방에서는 할 일이 그닥 없습니다. ㅡㅡ;; 진짭니다... 믿어 주세염 T_T

* Method
GameObject PhotonNetwork.Instantiate ( string prefabName, Vector3 position, Quaternion rotation, int group) //resources폴더에 있는 prefab의 이름으로 게임오브젝트를 생성합니다.
GameObject PhotonNetwork.InstantiateSceneObject ( string prefabName, Vector3 position, Quaternion rotation, int group, object[] data ) //씬에 종속된 게임오브젝트를 생성합니다.

위 2개의 Method 차이는 플레이어에 종속된 GameObject를 만들것인가, 플레이어의 접속이 끝나도 계속 살아남을 Object를 만들것인가입니다.

* Event
방에서 PhotonView로 설정한 객체는 서로 데이터를 Sync할 수 있습니다. 이때 발생하는 이벤트는 딱 1개라고 생각하심 될 듯 싶어요.
void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){ ...}
전회 강좌에서 보신 "NetworkObj"입니다.

네트워크상에서의 동기화는 다음과 같이 수행됩니다.

void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            this.correctPlayerPos = (Vector3)stream.ReceiveNext();
            this.correctPlayerRot = (Quaternion)stream.ReceiveNext();
        }
}

위 코드에서 사용가능한 데이터형은
- Bool
- Int
- string
- char
- short
- float
- PhotonPlayer
- Vector3
- Vector2
- Quaternion
- PhotonViewID

입니다. =)

Component로 반드시 PhotonView 객체가 추가되어 있어야 겠지요?


나머지는 일반 Unity3D로 만드는 게임과 대동소이 합니다. =)

여기까지 Photon Cloud의 기초강좌를 마칩니다. =)


사용자 삽입 이미지

* Photon cloud의 전체 Manual은 다음을 클릭하시면 PDF로 받으실 수 있습니다. =)





2012/12/04 01:01 2012/12/04 01:01
오늘은 마지막! 어제 만들어서 공개했던 "파리 플라이트"를 Photon Cloud에 맞춰서 개조하는 작업을 할겁니다.
(어제의 작업은 사실상 불필요했던 작업이지만, 제 머리속이 복잡해서 정리차원에서 만든 프로토타입으로 이해를...)
(한편 마음 속깊이에서는 "고급코딩 기술 알려줬음 됐어." 하는 의미부여도... 쿨럭...)

중요한 메소드별 정리는 4회 마지막회차에서 정리드리도록 하고 어제 포스팅한 http://www.wolfpack.pe.kr/800 과 비교바랍니다.

먼저 어제 보셨던 MainGame이 어떻게 변화되었는지 주목해주세요.

using UnityEngine;
using System.Collections;

public class MainGame : MonoBehaviour
{

    #region 이하 Photon 때문에 삭제!
    //public GameObject Player1;
    //public GameObject enemy;
    #endregion

    private Vector3[] GenPoint = new Vector3[]{
        new Vector3(-4.5f, 10f, 0f), 
        new Vector3(-3f, 10f, 0f), 
        new Vector3(-1.5f, 10f, 0f),
        new Vector3(0f, 10f, 0f),
        new Vector3(1.5f, 10f, 0f),
        new Vector3(3f, 10f, 0f),
        new Vector3(4.5f, 10f, 0f)};

    private float GenStart = 0f;
    private float GenTerm = 3f;
    private float DropSpeed = 5f;



	// Use this for initialization
	void Start () {
        GenStart = Time.time;
        //이하 Photon으로 인한 삭제 
        //Instantiate(Player1, new Vector3(0f, 0f, 0f), Quaternion.Euler(new Vector3(0f,0f,180f)));
	}
	
	// Update is called once per frame
	void Update () {
        //이하 Photon으로 인한 변화
        if (PhotonNetwork.isMasterClient == true ) EnemyGen();
	}

    void EnemyGen()
    {
        if (GenStart + GenTerm > Time.time) return;
        
        //GenTerm -= 0.01f;
        DropSpeed += 0.02f;
        StartCoroutine("EnemyDrop");
        GenStart = Time.time;

    }

    IEnumerator EnemyDrop()
    {
        foreach (Vector3 genposition in GenPoint)
        {
            //이하 Photon으로 인한 변화
           GameObject gobj = PhotonNetwork.InstantiateSceneObject("enemy", genposition, Quaternion.identity, 9, null) as GameObject;
           if (gobj.GetComponent<Enemy>() != null) gobj.GetComponent<Enemy>().Set_Speed(DropSpeed);
        }
        return null;
    }
}

중요한 몇가지 변화가 있었습니다. 기존에는 Prefab을 생성할때 GameObject에 할당하고 호출했지만,

Prefab이라는 폴더명을 "Resources"라는 이름으로 변경함으로써 공통 Resource로 할당한 뒤
PhotonNetwork.InstantiateSceneObject("enemy", genposition, Quaternion.identity, 9, null)

와 같이 string(문자열)로 호출하였습니다.
(위의 Method는 Scene에 종속된 Object를 생성하는 것 입니다. =) 상세설명은 4회차에서 차근차근 설명하죠.)

나머지 소스도 확인해보겠습니다.
Enemy의 경우는 다음과 같이 " IEnumerator Die()"이 아주 소소하게 변화되었습니다.

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {


    private float Dropspeed = 0f;


	// Update is called once per frame
	void Update () {
        Dropit();
        CheckAutoDie();
	}

    void Dropit()
    {
        transform.Translate(new Vector3(0f, -1f, 0f) * Dropspeed * Time.deltaTime);
    }

    void CheckAutoDie()
    {
        if (transform.position.y > -5f) return;
        StartCoroutine("Die");
    }

    public void Set_Speed(float x)
    {
        Dropspeed = x;
    }

    void OnTriggerEnter(Collider col)
    {
        if (col.tag != "beam") return;
        StartCoroutine("Die"); 
    }

    IEnumerator Die()
    {
       //바로 여기!! 여기가 바뀌었습니다. 
        if (PhotonNetwork.isMasterClient) PhotonNetwork.Destroy(gameObject);
        //if (!PhotonNetwork.isMasterClient) Destroy(gameObject);
        return null;
    }
}

역시 4회차에서 상세히 설명하겠습니다. 단지 하나 중요한 개념은 MasterClient라는 개념인데, 이녀석은 요 강좌 말미에 다시 말씀드리죠.

Beam(총알)의 경우는 변화가 없습니다.


using UnityEngine;
using System.Collections;

public class Beam : MonoBehaviour {

    private float speed = 10f;

	// Update is called once per frame
	void Update () {
        Moving();
        AutoDie();
	}

    void Moving()
    {
        transform.Translate(Vector3.up * speed * Time.deltaTime);
    }

    void AutoDie()
    {
        if (transform.position.y < 6f) return;
        StartCoroutine("Die");
    }

    IEnumerator Die()
    {
        Destroy(gameObject);
        return null;
    }
}


우리의 친구인 Player의 경우 약간 변화가 많습니다.

using UnityEngine;
using System.Collections;

public class Player : Photon.MonoBehaviour {

    public GameObject beam;
    
    private float fire_last = 0f;
    private float fire_term = 0.3f;
    private float Speed = 10f;
    
    //이하 네트워크로 인한 추가로 반드시 내 클라이언트는 내가 조작할 수 있도록 해주는 Augment값입니다.
    public bool isControl = false;

	// Use this for initialization
	void Start () {
	
	}
	
	// Update is called once per frame
	void Update () {
        Fire();
        if (isControl != true) return; //상당히 중요합니다. 내가 통제권이 없다면 조작을 못하도록 막는 역할을 합니다.
        if (Input.GetKey(KeyCode.RightArrow)) Move_Right();
        if (Input.GetKey(KeyCode.LeftArrow)) Move_Left();
	}

    void Move_Right()
    {
        transform.Translate(Vector3.left * Speed * Time.deltaTime);
        if (transform.position.x > 5f) transform.position = new Vector3(5f, 0f, 0f);
    }

    void Move_Left()
    {
        transform.Translate(Vector3.right * Speed * Time.deltaTime);
        if (transform.position.x < -5f) transform.position = new Vector3(-5f, 0f, 0f);
    }

    void Fire()
    {
        if (fire_last + fire_term > Time.time) return;
        StartCoroutine("Fire_Beam");
        fire_last = Time.time;
    }

    IEnumerator Fire_Beam()
    {
        Instantiate(beam, transform.position, Quaternion.identity);
        return null;
    }

    //기존에 OnCollisionEnter에서 TriggerEnter로 약간 변화를 주었습니다.
    void OnTriggerEnter(Collider col)
    {
        if (col.transform.tag != "enemy") return;
        PhotonNetwork.Destroy(gameObject); //없앨때 그냥 Destory가 아니라 PhotonNetwork.Destroy입니다.
    }

    //통제권을 할당하는 부분입니다. 호출되어야 하므로 당근 public으로 선언합니다.
    public void Set_isControl(bool x)
    {
        isControl = x;
    }
}



지금부터는 새로 추가된 녀석을 보도록 하겠습니다.
새로 추가한 녀석이 2넘 있는데, 이 녀석들이야 말로 Photon Network를 제대로 작동시키는 녀석들이라 중요합니다.

추가된 녀석중에 1회에서 보았던 서버에 로그인하고 로비에서 다른 방으로 들어가는 녀석인 Master_Join 보겠습니다.
조금 많이 추가됐지만, 눈으로 그냥 한번 흩어 본다는 생각으로 봐주세요.
역시 상세한 설명은 마지막회차에서 설명드리겠습니다.


using UnityEngine;
using System.Collections;

public class Master_Join : Photon.MonoBehaviour
{
    public static int playerWhoIsIt = 0;
    private static PhotonView ScenePhotonView;

	// Use this for initialization
	void Start () {
        PhotonNetwork.ConnectUsingSettings("0.1"); //아시죠? 서버 로그인 부분
        ScenePhotonView = this.GetComponent<PhotonView>(); //이녀석은 씬을 관리하는 PhotonView입니다.
	}

    //로비에 접속됐을 때 호출되는 메소드로 아무런 방이나 기어들어갑니다.
    void OnJoinedLobby()
    {
        PhotonNetwork.JoinRandomRoom();
    }
   //들어갈 방이 없으면 4인 Room으로 방을 만듭니다.
    void OnPhotonRandomJoinFailed()
    {
        PhotonNetwork.CreateRoom(null, true, true, 4); 
    }

    //방에 들어가면 MasterClient인걸 확인하고 Player_1 또는 Player_2를 생성하니다.
    void OnJoinedRoom()
    {
        if (PhotonNetwork.playerList.Length == 1) playerWhoIsIt = PhotonNetwork.player.ID;
        string player = (PhotonNetwork.isMasterClient == true) ? "Player_1" : "Player_2";
        if (Gen(player) != null) StartCoroutine(Gen(player));
    }

     //플레이어가 들어오면 플레이어에게 ID를 Tagging합니다. 보너스 메소드
    void OnPhotonPlayerConnected(PhotonPlayer player)
    {
        if (PhotonNetwork.isMasterClient) TagPlayer(playerWhoIsIt);
    }

    public static void TagPlayer(int playerID)
    {
        ScenePhotonView.RPC("TaggedPlayer", PhotonTargets.All, playerID);
    }

    //PhotonNetwork에 player를 생성합니다.
    IEnumerator Gen(string player)
    {
        PhotonNetwork.Instantiate(player, new Vector3(0f, 0f, 0f), Quaternion.Euler(new Vector3(0f, 0f, 180f)), 1);
        return null;
    }

    [RPC]
    void TaggedPlayer(int playerID)
    {
        playerWhoIsIt = playerID;
    }

    //플레이어가 나가게되면 MasterClient인지 확인후 다른 플레이어를 MasterClient로 설정합니다.
    void OnPhotonPlayerDisconnected(PhotonPlayer player)
    {
        Debug.Log("Disconnected: " + player);

        if (PhotonNetwork.isMasterClient)
        {
            if (player.ID == playerWhoIsIt)
            {
                TagPlayer(PhotonNetwork.player.ID);
            }
        }
    }

   //MasterClient가 바뀌게되면 호출되는 녀석인데 거의 불필요합니다.
    void OnMasterClientSwitched()
    {
        Debug.Log("OnMasterClientSwitched");
    }
}


여기까지는 누차 말씀드리지만 1회에서 보았던 바대로 서버에 로그인하고 로비에서 방까지 이동하는 로직입니다.
실재 Object들의 움직임이나 상태를 Sync하는 로직은 다음의 NetworkObj입니다.

using UnityEngine;
using System.Collections;

public class NetworkObj : Photon.MonoBehaviour {
    //싱크될 Position과 Rotation을 저장할 초기값입니다.
    private Vector3 correctPlayerPos = Vector3.zero;
    private Quaternion correctPlayerRot = Quaternion.identity;

    //AWAKE 네트워크로 인한 추가
    void Awake()
    {
        //만약 Player라면 PhotonView가 내 클라이언트인지 확인하고 제어권을 True또는 False로 만듭니다.
        //Player에서 만든 Set_isControl이 여기서 사용됩니다.
        if (GetComponent<Player>() != null) GetComponent<Player>().Set_isControl(photonView.isMine);
        //만약 Enemy라면 Player들과 중첩되어 생성되지 않도록 보다 윗쪽으로 초기호되게 합니다.
        if (GetComponent<Enemy>() != null) correctPlayerPos = new Vector3(0f, 10f, 0f);
    }

    void Update()
    {
        //여기서 중요한건 내 Object가 아니라 Network상의 다른 Object의 Position과 rotation을 업데이트 시키는 겁니다.
        if (photonView.isMine == true) return;
        //원래 위치와 업데이트된 위치가 2f이상 차이가 나면 즉시 반영하고 아니라면 부드럽게 움직이도록 설정합니다.
        float distance = Vector3.Distance(transform.position, this.correctPlayerPos);
        if (distance < 2f)
        {
            transform.position = Vector3.Lerp(transform.position, this.correctPlayerPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, this.correctPlayerRot, Time.deltaTime * 5);
        }
        else
        {
            transform.position = this.correctPlayerPos;
            transform.rotation = this.correctPlayerRot;
        }
    }

    //데이터를 Sync할 때 호출되는 Method입니다.
    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            this.correctPlayerPos = (Vector3)stream.ReceiveNext();
            this.correctPlayerRot = (Quaternion)stream.ReceiveNext();
        }
    }
}


위 코드의 결과는 다음과 같습니다. =)
오고가는 Sync때문에 두개의 클라이언트에 뜨는 Object는 상이할 수 있습니다.

오늘은 MasterClient 개념을 설명하고 다음에 기회를 봐서 상세한 설명이 들어가는 대망의 4회차를 진행할께요.

Exitgames의 Photon제품군은 크게 2개로 나눠져 있습니다.
1. Photon Server : 자체적으로 개발팀에서 운영해야 하며 100 CCU가 무료입니다.
2. Photon Cloud : 20CCU가 무료이며 별도의 서버가 필요하지 않습니다.

다시말해, Photon Cloud와 Server는 기능상 약간 다르지만 가장 큰 차이가 바로 서버를 내가 사서 관리하느냐, ExitGame에서 빌려서 쓰냐의 차이입니다.

상황이 이렇다보니 원래 Photon Server에서 제공되던 Server Logic 제공이 불가능해졌습니다.
이 글을 읽는 개발자 모두에게서 위와 같은 느낌이 밀려올거라는 상상이 드는 군욤. ㅋㅋ

그래서, 우리의 Exitgames 창업자이자 CTO인 Christof Wegmann 아저씨께서 만든 개념이 MasterClient입니다.
P2P개념에서 Master Client와 혼동하시면 아니됩니다.


쉽게 정리하자면 전문용어로 "방장"입니다.
이미지화 해보면 이런 정도?
그럼 방장은 뭐하는 녀석이냐?
방을 만들고 처음 들어간 넘이 "방장(Master Client)" 권한을 획득하는데, 서버에서 하는 역할 모두를 할 수 있습니다.


즉, 기존에 서버프로그래머가 헉헉 대면서 개발했던 부분을 이제 클라이언트 개발자가 해야 한다는 불편한 진실을 담고 있죠. 네...
음... 뭐 원래 이바닥이 원래 이런 중요한 정보는 나중에 말해주죠.
실컷 정보줘서 공부시킨담에 빼도 박도 못할때...

그리고 또 하나의 문제가 있습니다. Photon Server의 경우는 서버를 자체운영하다보니 Database연동 기능도 담고 있지만, Photon Cloud는 그런거 없습니다.
진짜 없습니다.
이쯤 되면 아마도 짱돌 날라 올 듯 싶기도 하지만, 어쨌든 없으니 잇몸으로 씹어야 겠죠?
그래서, 저희 회사에서 동원하는건 Microsoft사의 Azure입니다.

일전에 Posting했던 "남벌" Prototype의 경우 Azure위에 올라간 ASP MVC에 MSSQL에 붙여서 사용했죠.

다시말해 Master Client는 방안에서 일어나는 모든 정보를 파악하고 있다가 게임이 종료되면 비동기 방식으로 서버에 저장합니다.
뭐... 이런건 서버개발자나 웹개발자에게 술사줘가며 시키던가...
아님 이제부터 공부하셔야 겠죠. ㅋㅋㅋ

아니면 사장님 졸라서 Photon Server 사시고, 서버 2대 이상 사신담에 Clustering하시어 L4장비에 방화벽 장비 붙여서 IDC센터에 입고하는 방법도 있습니다.
그리고, 서버관리자는 3교대 24시간 서버 확인하는 수 밖에 없으니 추가로 3명의 인력을 더 뽑으셔야 겠네욤 ㅜㅜ

오늘은 더 진행하면 짱돌날라올테니 일단 몸부터 피했다가,
차회에 마지막 소스 공개와 함깨 Photon Cloud의 주요 변수를 확인해 보도록 하겠습니다. =)

* 마지막 부분에 틀린 정보를 드렸습니다.
Photon Server의 모듈을 사용해서 서버부분을 개발하고나면 Azure, 아마존 등의 Cloud에 올려서 서비스 할 수 있다고 하는군욤!



2012/11/29 14:50 2012/11/29 14:50

안녕하세요? 글뻥입니다. Photon Cloud 2회차 강좌입니다.

* 링크와 소스가 깨진관계로 새로운 강좌와 예제파일은 http://www.wolfpack.pe.kr/939 에서 다운로드 받아주세요.

역시 퍼가는건 자유입니다. =)
하지만 출처 안밝히면 정말 정말 정말 싫어 할겁니다.
(자신이 쓴글도 아닌데 자신이 쓴 글처럼 자신의 블로그에 달아 놓는 행위는 도둑질에다가 글쓴이에 대한 모욕입니다.)

이번에는 Photon에 관련된 내용보다는 차회에 진행할 강좌에서 사용할 게임부터 만들어 보겠습니다.

이름하야~ 짝퉁 파리플라이트!

사용자 삽입 이미지

관련 소스는 [여기]에서 다운로드 가능합니다.

중요한 소스는 달랑 4개입니다.
먼저 MainGame은 적은 전체 게임을 관리하는 녀석입니다.
Player는 말그대로 아군 전투기에 대한 Script이구요,
Enemy는 적입니다.
그리고 나머지 하나가 Beam(총알)에 대한 스크립트입니다.

아마도 초보분들께는 퍼포먼스 튜닝이나 혹은 게임 구조를 만드실때 유용한 Tip 몇개가 포함되어 있을 것 같네요.

MainGame.cs

using UnityEngine;
using System.Collections;

public class MainGame : MonoBehaviour {
    
   //플레이어 Prefab 
    public GameObject Player1;
   //적 Prefab
    public GameObject enemy;
    
   //적이 생성되는 포지션입니다. 총 7개예요.
    private Vector3[] GenPoint = new Vector3[]{
        new Vector3(-4.5f, 10f, 0f), 
        new Vector3(-3f, 10f, 0f), 
        new Vector3(-1.5f, 10f, 0f),
        new Vector3(0f, 10f, 0f),
        new Vector3(1.5f, 10f, 0f),
        new Vector3(3f, 10f, 0f),
        new Vector3(4.5f, 10f, 0f)};

   //적생성된 시간을 기록할 변수입니다.
    private float GenStart = 0f;
   //적생성에 관한 Term 변수입니다.
    private float GenTerm = 3f;
   //적이 떨어지는 속도입니다.
    private float DropSpeed = 5f;

//시작하면 적 생성 시간을 Baseline으로 설정하고 아군전투기를 생성합니다.
        //마지막에  "Quaternion.Euler(new Vector3(0f,0f,180f)"를 사용한 이유는 전투기가 꺼꾸로 출현해서... 쿨럭..
void Start () {
        GenStart = Time.time;
        Instantiate(Player1, new Vector3(0f, 0f, 0f), Quaternion.Euler(new Vector3(0f,0f,180f)));
}

// 매 프레임마다 적 생성 Method를 호출합니다.
void Update () {
        EnemyGen();
}

    //적 생성 Method로 지정한 Term만큼의 시간이 지나면 작동됩니다. 점차 Drop속도가 빨라지도록 설정했습니다.
    void EnemyGen()
    {
        if (GenStart + GenTerm > Time.time) return;
        
        //GenTerm -= 0.01f;
        DropSpeed += 0.02f;
        GenStart = Time.time;
        StartCoroutine("EnemyDrop");
    }
 
    //유니티의 문제점이 C# Base다 보니 Init하거나 Destroy하는데 많은 시간을 소모합니다.
    //기본적으로 Class 즉, Reference Type이기에 어쩔수 없는... ㅡㅡ;;
    //그래서 별도의 Thread로 돌리기 위해 Coroutine을 사용합니다.
    IEnumerator EnemyDrop()
    {
        foreach (Vector3 genposition in GenPoint)
        {
            //생성한 GameObject에 값을 할당합니다.
            GameObject gobj = Instantiate(enemy, genposition, Quaternion.identity) as GameObject;
            gobj.GetComponent<Enemy>().Set_Speed(DropSpeed);
        }
        return null;
    }
}


뭐 게임 끝나는 조건도 없습니다. 걍 끄십시오. ㅡㅡ;;

지금부터는 별게 없는 Enemy.cs를 보겠슴돠.

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {
    //MainGame에서 설정해주는 떨어지는 속도입니다.
    private float Dropspeed = 0f;

// Update is called once per frame
void Update () {
        //떨어지도록...
        Dropit();
        //어느정도 떨어지면 자동으로 없어지게...
        CheckAutoDie();
}

    //아래로 이동시키는 Method입니다.
    void Dropit()
    {
        transform.Translate(new Vector3(0f, -1f, 0f) * Dropspeed * Time.deltaTime);
    }
    //리소스가 자동으로 없어져야 겠죠? 역시 위에서 설명한대로 Coroutine으로 돌립니다.
    void CheckAutoDie()
    {
        if (transform.position.y > -5f) return;
        StartCoroutine("Die");
    }

    //MainGame에서 이녀석을 호출해서 떨어지는 값을 설정합니다. 그래서 public 선언자는 필수!
    public void Set_Speed(float x)
    {
        Dropspeed = x;
    }

    //총알과 부딪혔을때 죽는 처리해야 합니다.
    void OnTriggerEnter(Collider col)
    {
        if (col.tag != "beam") return;
        StartCoroutine("Die"); 
    }
    //자동으로 리소스에서 없애는 메소드입니다.
    IEnumerator Die()
    {
        Destroy(gameObject);
        return null;
    }
}

참으로 별게 없습니다. ㅡㅡ;; 초보라도 그냥 할 수 있는... (쿨럭..)

이제 주인공이 탈 뱅기인 player.cs파일입니다.

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {
    
    //총알 Prefab
    public GameObject beam;
    //MainGame에서 적을 생성할때와 같은 생성 주기에 대한 변수입니다.
    private float fire_last = 0f;
    private float fire_term = 0.3f;
    //이동속도입니다.
    private float Speed = 10f;

// 드라곤 플라이트와 같이 자동으로 마구마구 쏩니다.
        //대신 키보드의 좌우 키로 움직이도록 했습니다.
void Update () {
        Fire();
        if (Input.GetKey(KeyCode.RightArrow)) Move_Right();
        if (Input.GetKey(KeyCode.LeftArrow)) Move_Left();
}

    //오른쪽 움직임입니다. 현재 오브젝트가 꺼꾸로 뒤집혀 있다는 사실때문에 Vector3.left를 사용했습니다.
    void Move_Right()
    {
        transform.Translate(Vector3.left * Speed * Time.deltaTime);
        if (transform.position.x > 5f) transform.position = new Vector3(5f, 0f, 0f);
    }
   //왼쪽 움직임입니다.
    void Move_Left()
    {
        transform.Translate(Vector3.right * Speed * Time.deltaTime);
        if (transform.position.x < -5f) transform.position = new Vector3(-5f, 0f, 0f);
    }

    //자동으로 마구마구 쏩니다. 역시 Beam(총알)을 생성할때의 Performance 때문에 Coroutine사용!
    void Fire()
    {
        if (fire_last + fire_term > Time.time) return;
        StartCoroutine("Fire_Beam");
        fire_last = Time.time;
    }

    //이제 슬슬 주석다는게 지겨워 지려합니다. ㅡㅡ;; Coroutine땜시 있는겁니다.
    IEnumerator Fire_Beam()
    {
        Instantiate(beam, transform.position, Quaternion.identity);
        return null;
    }

   //적에게 부딪히면 사라지도록 설정. 역시 Coroutine
    void OnCollisionEnter(Collision col)
    {
        if (col.transform.tag != "enemy") return;
        StartCoroutine("Die");
    }

    //죽어야죠... ㅜㅜ
    IEnumerator Die()
    {
        Destroy(gameObject);
        return null;
    }
}

이 코드 역시 별게 없습니다.

마지막으로 총알인 Beam.cs파일입니다.


using UnityEngine;
using System.Collections;

public class Beam : MonoBehaviour {

    //총알 속도입니다.
    private float speed = 10f;

// 생성되면 위로 올라가면서 어느정도 갔을때 자동으로 없어지는게 애가 하는 일 전부입니다.
void Update () {
        Moving();
        AutoDie();
}

    //가자 위로!
    void Moving()
    {
        transform.Translate(Vector3.up * speed * Time.deltaTime);
    }

    //자동으로 없어지게.. 역시 Coroutine!
    void AutoDie()
    {
        if (transform.position.y < 6f) return;
        StartCoroutine("Die");
    }

    //죽어랏!!!
    IEnumerator Die()
    {
        Destroy(gameObject);
        return null;
    }
}


아~ 귀차니즘이 묻어나오는 주석이군요!
결과는 이러합니다.


사용자 삽입 이미지


2~3시간정도 걸린듯 싶네요. 소스작성부터 포스팅하는데 까지...
(가장 많은 시간은 파리 이미지 찾느라... 쿨럭..)

다음에는 Photon Cloud 붙여서 멀티플레이 만들어 보겠습니다. =)

2012/11/28 16:10 2012/11/28 16:10
음... 이글을 써야 할지, 공개하는게 맞는지 잘 모르겠습니다.
왜냐하면, 1월에 OO대 특강이 있어서 그 때 사용할 예정이었거든요.
쩝... 그래도 걍 쓸렵니다. ㅡㅡ;; 학생들이 항의하면 "배째라"할 거니까요. ㅋㅋㅋ

이번에는 첫회로 채팅서버 한번 만들어 보겠습니다. =)
한가지더, 요즘 제가 글을 안쓰는 이유는 일부 블로거들이 자신의 글인양 출처도 안밝히시고 마구 퍼가서 입니다. 의욕이 많이 다운 됐어요. 퍼가시는건 자유지만, 반드시 출처를 밝혀주세요.
발견시 "Lingo Games"의 지적재산권을 해친것으로 간주하고 법적인 절차를 밟겠습니다.

정말입니다.


1. Exit Games에 가입부터 하셔야 합니다.
https://cloud.exitgames.com 으로 접속하세요! 이메일만 넣으시면 자동가입완료!
사용자 삽입 이미지
2. 로그인하신후에 MyApplication에서 New App을 클릭합니다.
App의 이름과 Create만 눌러 주시면 완료!
사용자 삽입 이미지


3. 생성된 App의 ID를 기록해 둡니다.
저기 빨간색 부분입니다.
사용자 삽입 이미지


4. 이제 유니티의 Asset Store에서 Photon Plugin을 다운 받아 Import합니다.
검색어는 "Photon"입니다.
사용자 삽입 이미지


5. 유니티 윈도우에서 Photon Unity Networking 을 클릭하시면 Cloud 관련 메뉴가 뜹니다.
Setup 누르시고 붉은색으로 표현된 부분에 App ID를 입력하면 기본 설정완료!
사용자 삽입 이미지


사용자 삽입 이미지
사용자 삽입 이미지


6. 하지만 애석하게도 Photon의 경우 기본 네델란드로 연결됩니다. 따라서, 가장 가까운 서버로 바꿔줄 필요가 있죠.
사용자 삽입 이미지
위의 "ServerSettings.cs"파일을 찾고
public static string DefaultCloudServerUrl= "app.exitgamescloud.com";

부분을 찾아서 다음과 같이URL을 변경해줍니다.


* 유럽 : app.exitgamescloud.com
* 미국동부 : app-us.exitgamescloud.com
* 아시아(싱가폴) : app-asia.exitgamescloud.com
* 일본 : app-jp.exitgamescloud.com
* 한국 : app-kr.exitgamescloud.com

쉽죠? ^^?

7. 새로운 씬을 하나 만들고 네트워크에 접속할 Script를 넣을 빈 오브젝트를 하나 만듭니다.

사용자 삽입 이미지

8. "Network_Connect.cs"파일을 하나 만들어서 다음과 같이 붉은 색으로 표시된 부분만 고쳐줍니다.
물론 다 고쳐진 소스는 방금 만들었던 GameMaster에 Attach 시켜야 겠죠?

두번째 붉은 박스의 소스에는 "버전"을 넣어서 Client 버전별로 문제가 발생하지 않도록 설정했습니다.
사용자 삽입 이미지


9. 플레이 버튼을 눌러서 에러가 없다면 추가로 소스를 더 수정해보도록 하겠습니다.

OnJoinedLobby는 최초 서버에 접속되었을때 호출되는 메소드입니다.
OnPhotonRandomJoinFailed는 Random Join이 실패했을때 호출되는 메소드입니다.
OnJoinedRoom은 Room에 입장했을때 호출되는 메소드입니다.

사용자 삽입 이미지


정리하자면, 서버접속 -> Lobby에 접속후 Random혹은 지정된 방으로 이동 -> 이동 실패시 방을 새로 만듬
-> 성공시 방에 접속 OK! Log를 찍습니다.

이상이  "Network_Connect.cs" 파일의 내용입니다.

9. 이제 "Network_Chat.cs"파일을 하나 더 만들어서 다음과 같이 개노가다 합니다.

사용자 삽입 이미지

Awake까지 수정하신후에 OnGUI 메소드를 다음과 같이 (쿨럭..) 입력합니다.
GUI에 관련된 소스이지만, 중요한건 RPC를 어떻게 호출하는가를 잘 봐주세요.
사용자 삽입 이미지


마지막으로 AddMessage, SendChat 및 [RPC] 메소드를 노가다 합니다. T_T
중요한건 붉은 박스의 [RPC] 메소드와 이를 호출하는 photonView.RPC 입니다.
사용자 삽입 이미지

[RPC]란 리모트 프로시져로 외부에서 호출될 수 있는 메소드라는 표시이며,
이를 호출하기 위해서는 photonView.RPC("메소드명", 대상, Arg[])의 형태로 호출됩니다.
대상의 Target은
ALL, ALLBUF, OTHER, OTHERBUF, MASTER 이며 BUF가 붙은 넘은 버퍼에 넣어둔다로 이해하시면 됩니다.

이녀석도 앞에서 만들어둔 "GameMaster"에 Attach하시면 이제 1단계만 남습니다.

10. Component에서 PhotonView를 찾아 GameMaster에 Attach합니다.
GameMaster에 Attach된 Script 3종을 확인해 주세요!
사용자 삽입 이미지


이제 실행
사용자 삽입 이미지
하나의 클라이언트는 Build&Run을 했고 하나는 Editor상에서 실행한 겁니다. =)

복잡한 네트워크 지식 없어도 참 편하죠?

차회에는 조금 더 복잡하게 가보겠습니다. 꾸뻑~




2012/11/26 21:53 2012/11/26 21:53
버그가 조금씩 보이기 시작했지만, 조금씩 잡아 가면 될듯...
추석날 신나게 테스트와 코딩중... ㅋㅋㅋ


2012/10/01 11:51 2012/10/01 11:51