실전 Unity3D Engine Programing 과정 9일차 (2013.08.01 (목))



## 일정치 않은 네트워크 데이터 보간법

0.2초 이내로 온 데이터는 믿을 수 있는 데이터 그 이상 차이난 데이터는 오래전 데이터 이다.

interpolation (보간법)  

- 유효한 네트워크 데이터 수신시 데이터를 부드럽게 보간하는 방법

extrapolation (보외법)

- 일정 시간내에 들어오는 데이터가 없을 경우 데이터의 변화를 부드럽게 보간하는 법




최근에 들어온 위치, 회전각도    time, position, rotation

다음에 들어온 위치, 회전각도    time, position, rotation


## 데디니콘.. 

속도는 시간분의 거리 (거리/시간)

거리는 속도 곱하기 시간

얼마나 빠른 속도로 얼마의 시간동안 움직였냐가 지금 내 위치이다. 

현재 위치가 중요하다..... 

데디니콘.. 

- 네트워크 전송 구간의 로스타임을 감안해서 데이터 수신 측에서 위치를 좀더 이동 시켜 주는 방법..



## 개발하자.

- 2DShooting(7월26일)  게임 프로젝트의 내용을 다시 불러서 이어서 처리한다.

- 아셋 > Prefabs > Player 프리팝을 하나 만든다.

- 하이어아키에서 Player 오브젝트를 선택한다.

  - 컨포너트를 붙인다.  Network View..

  - Tag를 Player로 바꾼다.

  - 이 Player오브젝트를 Player 프리팝에 저장한다.   

  - player를 지운다.

- Save scene, project한다.

- 스크립트에 Netowrk 폴더 만든다.

- PlayerRemote 스크립트를 만든다.


using UnityEngine;

using System.Collections;


public class PlayerRemote : MonoBehaviour {

public class State : System.Object {

public Vector3 p;

public Quaternion r;

public float t;

//스트리밍에사용할클래스.

public State(Vector3 p, Quaternion r, float t)

{

//포지션 타임 로테이션값으로만들었다.

this.p = p;

this.r = r;

this.t = t;

}

}

// 물리인터폴리에션이있다. 이것을사용한다.

public bool simulatePhysics = true;

public bool updatePostion = true;

public float phyInterp = 0.1f;

public float netInterp = 0.2f;

public float ping;

public float jitter;  //고른데이터가아니라서.

public bool isResponding = false;

public string netCode = "(No Connections)";

private int m;

private Vector3 p;

private Quaternion r;

private State[] states = new State[15];

private int stateCount = 0;

private Vector3 velocity;

private State nullstate;

//Network View를적용할수있는것 오브젝트, 카메라, 스크립트.

// Use this for initialization

void Start () {

networkView.observed = this;

nullstate = new State(Vector3.zero, Quaternion.identity, 0.0f);

for(int i=0;i<states.Length;i++)

{

states[i] = new State(Vector3.zero, Quaternion.identity, 0.0f);

}

velocity = Vector3.zero;

}

//시스템의성능이든나쁜성은이드udpate는다르다

//따라서FixedUpdate()를사용한다

//시간이오르걸리는것을사용하면안된다. 물리실험만코딩한다.

void FixedUpdate()

{

if(!updatePostion || states[10].Equals(nullstate))

{

//최소데이터가들어오지않았다면. 

return;

}

//jitter, ping formula

float diffTime = (float)Network.time - states[0].t;

//불필요한것을업애기에떨림현상은제거할수있다.

jitter = Mathf.Lerp(jitter, Mathf.Abs(ping-diffTime), Time.deltaTime*0.3f);

//jitter값보다너무높은것은무시한다는것이다.

ping = Mathf.Lerp(ping, (float)Network.time - states[0].t, Time.fixedDeltaTime *0.3f);

//인터포리레이션 익스폴리레이션이중요하다.

//보간타임이필요하다.

float interpolationTime = (float)Network.time - netInterp;

if(states[0].t > interpolationTime)

{

//시간내에온경우이다.

int i;

for(i=0;i<stateCount;i++)

{

//시간내에들어온것이면.

if(states[i] != null && (states[i].t <= interpolationTime || i == stateCount -1))

{

//최근에들어온것과나중에들어온것을비교한다.

State rhs = states[Mathf.Max(i-1, 0)]; //last ex.0

State lhs = states[i]; //current ex.1

float l = rhs.t -lhs.t;

float t = 0.0f;

if(l > 0.0001f)

{

//잘모르면수학책을봐라.

t= ((interpolationTime - lhs.t)/1);

}

if(simulatePhysics){

//정상적인데이터이다.

   //0.5f는반반씩보간한다는뜻.

transform.position = Vector3.Lerp(transform.position, Vector3.Lerp(lhs.p, rhs.p, t), phyInterp);

transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Slerp(lhs.r, rhs.r, t), phyInterp);

velocity = ((rhs.p - states[i+1].p)/(rhs.t - states[i+1].t)); //속도는시간분의거리.

} else{

//물리적으로 박는다 너무늦게와서.

transform.position = Vector3.Lerp(lhs.p, rhs.p, t);

transform.rotation = Quaternion.Slerp(lhs.r, rhs.r, t);

}

//응답이있었고에러코드는없었다.

isResponding = true;

netCode = " ";

return;

}

}

}else{

//시간외의경우너무늦게왔다. 외선보간.

float extrapolationLenght = (interpolationTime - states[0].t);

if(extrapolationLenght < 1.0f && states[0].Equals(nullstate)==false && states[1].Equals(nullstate)==false)

{

if(!simulatePhysics)

{

transform.position = states[0].p + (((states[0].p - states[1].p) / (states[0].t - states[1].t))*extrapolationLenght);

transform.rotation = states[0].r;

}

isResponding = true;

if(extrapolationLenght < 0.5f) 

{

//윺효한응답이다.

netCode = ">allowed";

} else

{

//지연응답이다.

netCode = "(Delayed)";

} else{

netCode = "(Not Responding)";

isResponding = false;

}

}

if(simulatePhysics && states[0].t > states[2].t)

{

velocity = ((states[0].t -states[2].t));

}

//유니티의버그로스케일값이사라진다.스케일이사라지면보간한다.

if(transform.localScale == Vector3.zero)

{

//MakePerfectSize함수를호출해서보간한다.

gameObject.SendMessage("MakePerfectSize");

}

//여기까지가이동이다.

}

//위치와이동을15초간보내야한다.

void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)

{

//두가지가있다 send, receive(쓸때..

if(stream.isWriting)

{

p= gameObject.transform.position;

r = gameObject.transform.rotation;

m = stateCount == 0? 0:(int)(((float)Network.time - states[0].t)*1000);

//아래순서가중용하다 동일하게받아야한다.

stream.Serialize(ref p);

stream.Serialize(ref r);

stream.Serialize(ref m);

}else

{

stream.Serialize(ref p);

stream.Serialize(ref r);

stream.Serialize(ref m);

State state = new State(p, r, (float)(info.timestamp - (m >0?(m/1000):0.0f)));

if(stateCount == 0)

{

states[0] = new State(state.p, state.r, state.t);

} else if(state.t > states[0].t)

{

for(int k= state.Length-1; k > 0;k--)

{

state[k] = states[k-1];

}

states[0] = new State(state.p, state.r, state.t);

} else

{

Debug.Log("Out-of-Order state received and ignored ~");

}

stateCount = Mathf.Min(stateCount +1, states.Length);

}

}

public Vector3 GetVelocity()

{

return this.velocity;

}

}



PlayerSprite 스프라이트를 수정한다. (Alt+Shift+O)

using UnityEngine;

using System.Collections;


public class PlayerSprite : SpriteBase {

public float Speed = 10.0f;

private float LastShootTime;

public float ShootDelay = 0.2f;

public GameObject bullet;

//new code add

public NetworkPlayer owner;

[RPC]

void SetPlayer(NetworkPlayer player)

{

Debug.Log("SetPlayer Called~");

owner = player;

//PlayerRemote 컴포너트를붙여준다.

gameObject.AddComponent("PlayerRemote");

}

// Use this for initialization

void Start () {

LastShootTime = Time.time;

}

// Update is called once per frame

void Update () {

float moveAmt = Input.GetAxis("Horizontal")

*Speed * Time.deltaTime;

transform.Translate(Vector3.right * moveAmt);

if(Time.time > LastShootTime + ShootDelay)

{

LastShootTime = Time.time;

Instantiate(bullet,transform.position,

transform.rotation);

}

}

}



UnityNetworkCs 스크립트만든다.





EnemySprite 수정하자.

using UnityEngine;

using System.Collections;


public class EnemySprite : SpriteBase {

public float Speed = 10.0f;

public GameObject explosion;

// Use this for initialization

void Start () {

gameObject.AddComponent<BoxCollider>();

}

// Update is called once per frame

void Update () {

//add

//시작한적이없으면아무것도하지않는다.

if(GameObject.FindWithTag("Player") == null) return;

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.down * moveAmt);

if(transform.position.y < -4.0f)

{

InitPosition();

}

}

void InitPosition()

{

transform.position = new Vector3(

Random.Range(-3.0f,3.0f), //x

5.0f, //y

0.0f);//z

}

void OnTriggerEnter(Collider other)

{

if(other.tag == "bullet")

{

Instantiate(explosion,transform.position,

transform.rotation);

InitPosition();

GameObject main = GameObject.Find("Main");

main.SendMessage("Hit",50);

}

}

}


mainScript도 열자..

using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour {
public GameObject SkyCamera;
public float CameraRotateSpeed = 0.1f;
public GameObject Distance;
public GameObject Life;
public GameObject Point;
private int playerLife = 3;
private int playerPoint = 0;
private float AccelSpeed = 100.0f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
//add
//시작한적이없으면아무것도하지않는다.
if(GameObject.FindWithTag("Player") == null) return;
SkyCamera.transform.RotateAround(
Vector3.up,Time.deltaTime*CameraRotateSpeed);
Life.SendMessage("SetText",
"X"+playerLife.ToString());
Point.SendMessage("SetText",
"Point:"+playerPoint.ToString());
Distance.SendMessage("SetText",
(Time.time*AccelSpeed).ToString("N0")+" m");
}
void Hit(int p)
{
this.playerPoint += p;
}
void Bomb()
{
Debug.Log("Bomb Button Pressed");
}
}

player 스크립트...에서 다시 만들자.

using UnityEngine;

using System.Collections;


public class PlayerSprite : SpriteBase {

public float Speed = 10.0f;

private float LastShootTime;

public float ShootDelay = 0.2f;

public GameObject bullet;

//new code add

public NetworkPlayer owner;

[RPC]

void SetPlayer(NetworkPlayer player)

{

Debug.Log("SetPlayer Called~");

owner = player;

//PlayerRemote 컴포너트를붙여준다.

gameObject.AddComponent("PlayerRemote");

}

// Use this for initialization

void Start () {

LastShootTime = Time.time;

}

// Update is called once per frame

void Update () {

if(Network.peerType == NetworkPeerType.Disconnected && Network.player == owner)

{

//내것일때만제어가능하다.

float moveAmt = Input.GetAxis("Horizontal")

*Speed * Time.deltaTime;

transform.Translate(Vector3.right * moveAmt);

if(Time.time > LastShootTime + ShootDelay)

{

LastShootTime = Time.time;

Instantiate(bullet,transform.position,

transform.rotation);

}

}

}

}




main 오브젝트에 콤포너트부착한다.

- networkView 상태동기화는 off

- Unity Network 스크립트를 붙인다.

   - player prefabs을 링크를 건다.

   - Spawn Position도 0, -2, 0


실험한다.


New 3D Text를 만든다. 

- 포지션은 0.0.0

- 이것을 Player의 촬일드한다.

- Chareacer Size 0.3

- Anchor upper center

- Alig center

- Bold

-  이름은 Name



정상적으로 진행하지 못했다.

클라우드올린 자료를 봐라...

game_20130801_my



















실전 Unity3D Engine Programing 과정 8일차 (2013.07.31 (수))



복습

터랫

어탯, 충돌, 테크 지정

몬스터 쫒아다니기

카메라 3인칭


카메라 3인칭은 카메라가 공유하고 높이도 알아서 캐릭터를 바라보게 했다.

댐핑을 써서 카메라를 늦게 동작하게 된다...



네트워크 관련된 것 이전에 설명했다.


이제 두개의 스크립트 만든다.

- 네트워크 화면 스크립트

- 출력용 로그 스크립트



@@Tip

앗셋에 폴더 두개 만든다.

- Scripts

   - ScriptsAttribute

     - 클래스의 속성을 정해 줄 수 있다. C#에만 있는 기능이다. 이것을 유니티에서 가능하다.

- Skins

   마우스에서 create > GUI Skin 만든다.



클래스에 속성 주기 스크립트에 기록한다.

using UnityEngine;

using System.Collections;


[ExecuteInEditMode]

public class ScriptsAttribute : MonoBehaviour {

public Rect myRect;

void OnGUI()

{

GUI.Button(myRect,"BUTTON");

}

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}


빈오브젝트 Test만들고 스크립트를 붙인다.
인스팩트의 MyRect를 클릭해서 좌표를 지정하면
OnGUI
Update는 play하지도 않고 Game화면에서 볼 수 잇다.







[RequireComponent(typeof(Rigidbody))] 속성을 붙여주여주면

리지디바디가 없으면 자동으로 붙여 준다.


using UnityEngine;

using System.Collections;


[RequireComponent(typeof(Rigidbody))]

[ExecuteInEditMode]

public class ScriptsAttribute : MonoBehaviour {

public Rect myRect;

void OnGUI()

{

GUI.Button(myRect,"BUTTON");

}

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}



##새로 하자.

1. 로그 출력 스크립트를 만든다.

    - 스크롤과 버튼 등 만든다.

    - RPC로 로그를 받는다.

[RPC]  //함수의 속성을 지정하며 이렇게 RPC로 하면 네트워크모듈이 특별관리한다.

public void ScreenLog(string text)

{

}

using UnityEngine;
using System.Collections;

public class Log : MonoBehaviour {
public GUISkin skin;
public int ScrollSize; // 스크롤에차일드 갯수 꼬리에붙여준다.
private int index = 0;
private Vector2 scrollPosition = Vector2.zero;
private string[] logtext;
public static Log logInstance; //싱글톤으로하자.
public bool IsOn = true;  //로그출력On/Off.
void Awake()
{
//신이호출되자마자불린다.
logInstance = this;
//신이바뀌이도로그화면은 계속나와야한다. 
// 아래 것을지정하면신이바뀌어도계속존재한다.
DontDestroyOnLoad(gameObject);
}
// Use this for initialization
void Start () {
//초기화.
if(ScrollSize == 0)
{
ScrollSize = 1000;
}
logtext = new string[ScrollSize];
for(int i =0;i<ScrollSize;i++)
{
logtext[i] = " ";
}
}
void OnGUI()
{
//초기화버튼.
if(GUI.Button(new Rect(0, 0, 48,16), "X"))
{
index =0;
for(int i =0;i<ScrollSize;i++)
{
logtext[i] = " ";
}
scrollPosition = Vector2.zero;
}
IsOn = GUI.Toggle(new Rect(100,0,100,24), IsOn, "On/Off");
if(IsOn.Equals(true))
{
GUI.skin = skin;
//스크롤영역을잡고그안에스크롤바를넣게한다 BeginScrollView와EndScrollView를지정하면그안에영역을잡는다는것이다
scrollPosition = GUI.BeginScrollView(new Rect(10,50,Screen.width - 10, Screen.height -100),scrollPosition, new Rect(0,0,1000,ScrollSize *24));
GUI.color = Color.white;
for(int i=0;i<ScrollSize;i++)
{
GUI.Label(new Rect(0,i*28, 700,28), logtext[i]);
}
GUI.EndScrollView();
}
}
public void SetOnOff(bool onoff)
{
IsOn = onoff;
}
[RPC]
public void ScreenLog(string text)
{
if(logtext == null)
{
return;
logtext[index] = text;
index++;
if(index > ScrollSize -1)
{
for(int i=0;i<ScrollSize;i++)
{
logtext[i] = logtext[i+1];
}
index = ScrollSize + 1;
}
}
}


빈오브제트 만들어 Chat으로 하고 여기에 상기 스크립트를 붙이다. 

NetChat 스크립트를 만든다.

using UnityEngine;

using System.Collections;


public class NetChat : MonoBehaviour {

public string ConnectToIP = "127,0,0,1";

public int connectPort = 12345;

public ArrayList clientScripts = new ArrayList();

private string chatMessage = "";

void Awake() {

DontDestroyOnLoad(gameObject);

Application.runInBackground = true; //포커스를일어도백그라운드에서도는것 정의. 

}

void OnServerInitialized(){

//소켓준비가완료되면호출된다.

Debug.Log("Server Start ~ ");

}

void OnPlayerConnected(NetworkPlayer newPlayer)

{

Debug.Log("A Client connected to me(server) " + newPlayer.ipAddress.ToString());

}

void OnPlayerDisconnected(NetworkPlayer player)

{

Debug.Log("Player Quit~ " + player.ipAddress.ToString());

}

void OnDisconnectedFromServer(NetworkDisconnection info)

{

Debug.Log("Connecting Failed~ "+info.ToString());

}

void OnConnectedToServer()

{

Debug.Log("Connecting Success ~");

}

void OnGUI()

{

if(Network.peerType == NetworkPeerType.Disconnected)

{

//disconnected

GUILayout.BeginArea(new Rect(Screen.width/2-100, Screen.height/2-129, 200, 256));

GUILayout.Label("Connection Sttus: Disconnected");

ConnectToIP = GUILayout.TextField(ConnectToIP, GUILayout.MinWidth(100));

connectPort = int.Parse(GUILayout.TextField(connectPort.ToString()));

//button 2개만들자.

//서버에접속하는클라이언트.

if(GUILayout.Button("Connect as Client", GUILayout.MinHeight(40)))

{

Debug.Log("Click Connect as Client~");

Network.Connect(ConnectToIP, connectPort);

}

//서버로동작하는버튼.

if(GUILayout.Button("Start as Server", GUILayout.MinHeight(40)))

{

Debug.Log("Click Start as Server~");

//32 user수

Network.InitializeServer(32, connectPort, false);

}

GUILayout.EndArea();

} else

{

   //connected

GUILayout.BeginArea(new Rect(Screen.width-150, 20, 150, 200));

if(Network.peerType == NetworkPeerType.Connecting)

{

GUILayout.Label("Connection Status: Connecting");

}else if(Network.peerType == NetworkPeerType.Client)

{

GUILayout.Label("Connection Status: Client!");

GUILayout.Label("Ping to Server "+Network.GetAveragePing(Network.connections[0]));

}else if(Network.peerType == NetworkPeerType.Server)

{

GUILayout.Label("Connection Status: Server!");

GUILayout.Label("Connections: "+Network.connections.Length.ToString());

if(Network.connections.Length > 0)

{

GUILayout.Label("Ping : "+Network.GetAveragePing(Network.connections[0]));

}

}

if(GUILayout.Button("Disconnect"))

{

Debug.Log("Click Disconnect");

Network.Disconnect(200);

}

GUILayout.EndArea();

//채팅.

chatMessage = GUI.TextField(new Rect(Screen.width / 2 -200, Screen.height -30, 400,30),chatMessage);

if(GUI.Button(new Rect(Screen.width/2+220, Screen.height -30,100,30),"SEND"))

{

if(chatMessage != " ")

{

//SendMsg함수가호출되고서버만전달된다.

networkView.RPC("SendMsg ",RPCMode.Server, Network.player.ipAddress + ":" +chatMessage);

chatMessage = " "; // 초기화.

}

}

}

}

[RPC]

public void SendMsg(string msg)

{

Debug.Log("Called SendMsg");

networkView.RPC("ScreenLog", RPCMode.All, msg);

}

}



UI만들때 자동으로 영역을 채우는 방법도 있다. 거의 쓸일은 없다.

GUILayout.BeginArea(new Rect(Screen.width/2-100, Screen.height/2-129, 200, 256));

GUILayout.EndArea();

Rect가 필요없다.



메인카메라 백그라운칼라를 바꿔준다.


Chat 오브젝트 선택하고 menu > componet > miscellonuse > Network View  그리고 syncronization 은 off한다.

NetChat 스크립트 붙인다.

- IP주소 확인한다. 자신의 컴퓨터 것

실행해 본다.



menu > File > Buld Setting 을 누르고 만든 Scene을 넣어 주다.

빌드후 파일명을 넣어 준다. Build.exe

빌드버튼을 누른다.


탐색기에서 해당 파일을 실행한다. 해상도 지정해 준다.

이것은 클라이언트로 실행한다. 


유니티는 다시 플레이해서 서버로 실행한다.

이제 상호 통신해 본다.



실행 순서는

서버를 킨후 클라이언트를 연결 요청한다.




실전 Unity3D Engine Programing 과정 8일차 (2013.07.31 (수))


네트워크 강의를 한다.

아래 내용은 6개월치인데 간단히 하겠다.


네트워크란 통신, 온라인게임 등.....

네트워크에 연결된 기기는 모두 IP로 통신하고 이것이 주소이다.   Infomation Provider

IP는 공유가능하나, 포트는 유일하다. 



소켓 프로그램

- 네트워크를 연결하는 입구..

- 시스템 안에 



http://www.nettention.com/ 

사이트에 가보자.. 

- 유니티 상용서버   

- 프로젝트당 1900만원

- 샘플을 보자.

  http://www.nettention.com/sample

- 이것의 좋은 점은 한글 네트워크 엔진으로 설명문이 한글이다.. 전화로 요청하면 출장서비를 한다.

- 평가판으로 할 수 있다. 


RakNet  http://www.jenkinssoftware.com/

- 오픈 소스

- 멀티플레이어 게임

- P2P 기능이 뛰어나다.

- 이것이 유니티에 기본 포함되어 있다.

- 그렇지만 최근에는 유니티에서 더이상의 업데이트는 하지 않는다고 선언함


유니티  menu > Edit > Project Setting > Network

두개 항목이 나온다.

 Debug Level  : off, infomation, Full 의 조건이 있다.

Sendrate : 15 <== 서버와 통신을 1초에 15회 한다. 



RPC - Remote Procedure Call 원격 제어 콜

- 서로 다른 시스템에서 유니티의 함수를 호출할 수 있다. 별도의 패킷이 없어도 된다.



네트워크 뷰(Network Views)

- State Synchronizaton

   - 두 시스템이 매번 동기화 되어 있어야 한다. 이동, 상태등이..

   - tranforma, 리지디바디, 등(물리시뮬레이션 정보를 추가할 수 있다. 스크립트도 가능) 초당 15번 주고 받는다..

   - 이것은 RCP 정보와 별개로 동기화 한다.

- Reliable Delta Compressed

   

- Observerd

- GameObject

- View ID 

   게임오브젝트에 대해서 아디를 부여해서 필요한 것만. 주고 받을 수 있다.



적용방법은

   GameObject 선택 menu > Componet > Relatvie > Network 

   이렇게 하면 통신이 가능하게 된다.




네트워크_강의.doc



유니티 네트워크

 

한때 PC플랫폼에서의 전유물이었던 대규모 온라인게임은 모바일 하드웨어와 통신 인프라의 발전으로 수많은 현대인들의 필수품이 된 스마트폰에까지 MMO(massive multi-user online)의 개발 열풍이 불고 있습니다.

그런 흐름에 맞추어 유니티3D엔진에서도 많은 MMO게임이 개발되었고 더더욱 많은 MMO 게임이 개발되고 있습니다. 이제 게임 개발에 있어서 필수불가결 항목이 된 네티워크 프로그래밍과 유니티에서 할 수 있는 네트워크 프로그래밍 기법들에 대해서 일부나마 소개하고자 합니다.

 

유니티의 네트워크 프로그래밍에 대해서 소개하기에 앞서 네트워크 프로그래밍이 생소한 개발자들과 독자를 위해 소켓 프로그래밍에 대해서 간단히 소개를 하고 실제 유니티에 돌아가는 소켓 프로그래밍 예제를 설명하고자 합니다.

그리고 나서 유니티에서 제공하는 NetworkView에 대해서 소개하고 실무에 사용되었던 예제 코드를 설명한 후에, 실무에서 주로 사용한 상용 네트워크 라이브러리들에 대해서 소개하고 그중 포톤(Photon)에 대한 사용법과 실무에서 개발하면서의 노하우를 소개하도록 하겠습니다.

 

 

1.  소켓 프로그래밍

 

소켓(Socket)은 소프트웨어로 작성된 통신 접속점입니다. 네트워크 응용 프로그램은 그 소켓을 이용하여 통신망으로 IP 패킷(Packet)을 송수신하게 됩니다. 소켓은 응용 프로그램에서 TCP/IP 계층을 이용하는 창구 역할을 하고 있으며 응용 프로그램과 소켓 사이의 인터페이스를 소켓 인터페이스라고 합니다.

 

유닉스의 예를 들면, 유닉스의 경우 파일을 열면(open) int 타입의 정수를 리턴하는데 이것을 파일 기술자(file descriptor)라고 합니다. 응용프로그램에서 파일을 액세스할 때 해당 파일 기술자를 사용하면 됩니다.

 

파일 기술자는 기술자 테이블(descriptor table) index 번호입니다. 기술자 테이블이란, 현재 open되어 있는 파일의 각종 정보를 포함하고 있는 구조체를 가리키는 포인터들로 구성된 테이블입니다.

 

프로그램에서 소켓을 개설하면 파일 기술자와 똑 같은 기능을하는 소켓 기술자(socket descriptor)가 리턴됩니다. 응용프로그램에서 이 소켓을 통하여 목적지 호스트와 패킷을 송수신할 때 이 소켓 기술자(소켓번호)를 사용하게 됩니다.

 

소켓을 이용하는 네트워크 응용 프로그램에서 상대방 세션과 IP 패킷을 주고 받기 위해서는 다음의 다섯 가지 정보가 정해져야 합니다.

1.     통신에 사용할 프로토콜(TCP 또는 UDP)

2.     자신의IP주소

3.     자신의 Port 번호

4.     상대방의 IP 주소

5.     상대방의 Port 번호

 

이 소켓을 윈도우 환경에서 사용할 수 있도록 한 것을 윈도우 소켓(WinSock)l라 부릅니다.

 

1.     유닉스 소켓과 다른점

A.     윈도우 소켓은 DLL을 통해 대부분의 기능이 제공되므로DLL초기화와 종료 작업을 위한 함수가 필요합니다.

B.     보통 GUI 를 기반으로 하기 때문에 확장 함수가 존재합니다.

C.     멀티쓰레드를 지원하므로 멀티쓰레드 환경에서 안정적으로 동작하기 위한 구조와 함수가 필요합니다.

2.     윈도우 소켓의 장점

A.     호환성이 높아 기존 프로그램을 포팅하기 쉽습니다.

B.     가장 널리 쓰이는 네트워크 프로그래밍 인터페이스

C.     TCP/IP 외에도 다양한 종류의 프로토콜을 지원

D.     저수준, 중간 정도의 프로그래밍 인터페이스므로, 세부적인 제어가 가능

3.     윈도우 소켓의 단점

A.     어플리케이션 수준의 프로토콜을 프로그래머가 직접 설계해야 합니다. 즉 데이터 포맷, 전송 절차등을 고려해야하며 ,프로토콜 변경시 코드 수정이 불가피 합니다.

B.     서로 다른 바이트 정렬방식을 사용하거나 데이터 처리 단위가 서로 다른 중단 시스템간 통신을 할 경우 어플리케이션 수준에서 데이터 변환을 처리해야 합니다.

4.     윈도우 소켓의 구조

A.     대부분의 기능은 WS2_32.DLL MSWSOCK.DLL 로 제공됩니다.

B.    어느 경우든 어플리케이션은 소수의 DLL과 링크되어 실행됩니다.

 

TCP 소켓 프로그래밍 절차

 

TCP 소켓 프로그래밍의 절차는 아래 그림을 참고하시면 됩니다

 

[그림]1 TCP 소켓 통신 순서도

클라이언트-서버(CS) 통신 모델에서는 항상 서버 프로그램이 먼저 수행되고 있어야 하는데, 서버는 Socket()함수를 호출하여 통신에 사용할 소켓을 개설하고 이때 리턴된 소켓 번호와 자신의 소켓 주소를 Bind()를 호출하여 서로 연결 시켜 둡니다. 서버에서 Bind()가 필요한 이유는 소켓 번호는 응용 프로그램이 알고 있는 통신 창구 번호이고, 소켓 주소는 네트워크 시스템이 알고 있는 주소 이므로 이들의 관계를 묶어 두어야(Bind) 응용 프로세스와 네트워크 시스템 간의 패킷 전달이 가능하기 때문입니다.

 

다음에 서버는 Listing()을 호출하여 클라이언트로 부터의 연결 요청을 기다리는 수동 대기 모드로 들어가며, 클라이언트로부터 연결 요청이 왔을 때 이를 처리하기 위하여 Accept()를 호출합니다. 서버는 Accept() 시스템 콜에서 기다리고 있다가 클라이언트가 Connect()를 호출하여 연결 요청을 해오면 이를 처리합니다. 이때 연결이 성공적으로 이루어지면 서버와 클라이언트가 데이터를 송수신할 수 있게 됩니다.

 

한편 클라이언트는 Socket()을 호출하여 소켓을 만든 후 Bind()를 부를 필요 없이, 서버에게 연결 요청을 보내기 위하여 Connect()를 호출합니다. 이때 클라이언트는 접속할 상대방의 서버의 소켓 주소 구조체를 만들어 Connect()의 인자로 주어야 합니다.

 

클라이언트에서 Bind()를 호출할 필요가 없는 이유는 클라이언트 프로그램은 자신의 IP 주소나 포트 번호를 다른 클라이언트 또는 서버가 미리 알고 있을 필요가 없기 때문입니다. , 대부분의 클라이언트는 포트 번호를 특정한 값으로 지정할 필요가 없습니다. 그러나 서버는 미리 지정한 포트 번호를 통하여 클라이언트가 알고 있는 포트 번호를 Bind()로 연결해 두는 것이 필수 적입니다.

 

클라이언트가 Bind()를 사용하면 오히려 클라이언트 프로그램의 범용성이 떨어지게 되는데 왜냐하면 같은 포트번호를 사용하는 클라이언트 프로그램이 하나의 컴퓨터에 두 개 이상 실행되면 포트 번호 중복 사용으로 인하여 에러가 발생하기 때문입니다. 하지만 경우에 따라 이것은 게임 유저의 클라이언트 중복 사용을 막기 위해서 사용되기도 합니다.

 

클라이언트는 Connect()를 호출하기 전에 연결하고자 하는 서버의 주소를 지정하여야 하는데 string 타입의 IP주소를 이용하여 IPAddress 클래스의 IP, 정수 타입의 소켓번호를 이용하여 IPEndPoint라는 클래스를 생성하여 Connect()를 호출합니다.

 

자세한 예제는 아래와 같습니다.

 

코드1의 서버소스를 살펴보시면 Awake()함수에서 소켓을 생성하고 Bind()함수를 호출하고 Listen()함수가 호출된 것을 확인 하실 수 있습니다. 그리고 Update() 함수에서 Select함수를 정기적으로 호출하면서 연결 요청이 왔다면 Accept()함수를 호출해서 클라이언트와 연결을 합니다.

코드2의 클라이언트의 소스를 살펴보시면 Awake()함수에서 소켓을 생성하고 Connect()함수를 호출해서 서버에 연결 요청을 하는 것을 확인 하실 수 있습니다. Update()함수에서는 마우스 클릭을 하면 서버로의 마우스 위치정보를 Send()함수를 호출해서 전송합니다.

코드3 MessageData.cs 를 살펴보시면 MessageData라는 패킷 클래스를 보실 수 있습니다. Serializable 속성을 주어서 MessageData는 객체직렬화 되도록 합니다. 다른 패킷을 만드시거나 테스트를 할 시에 참고하시면 됩니다.

[그림] 2. SimpleNetwork Server Client가 실행된 화면

 

 

 

MMORPG서버개발패턴

|

Game Programming

서버 구조

서버분산패턴

·        MMORPG는 많은 사람들이 접속해서 플레이하는 게임이기 때문에 CPU 부하의 측면에서나 네트워크 전송량의 측면에서나 한대의 머신으로는 충분한 수의 동시접속자를 받아들이기 어렵다. 따라서, 여러대의 머신이 역할을 분담하게 하는 분산처리가 필수적인데, 이 섹션에서는 어떤 방식으로 각 머신들이 역할을 분담하게 할 것인가에 대한 패턴을 소개하고자 한다.

·        Spatial distributed server - 공간단위로 분할된 서버

·        agent thru server - 에이전트서버를 거치는 접속

·        single login server - 단일 로그인 서버

·        multiple login server - 복수개로 분할된 로그인 서버

·        integrated NPC server - 게임서버와 NPC처리 서버가 통합된 형태

·        Separated NPC server - 게임서버와 NPC처리 서버가 분리된 형태

버프로그램구조패턴

·        게임 서버 프로그램은 크게 (분산되어 있는 경우, 각각의 프로그램을 의미한다) 네트워크 처리와 메인 로직처리, DB 처리의 3 가지 역할을 담당해야 한다. 이러한 각각의 역할을 효율적인 방법 (여러개의 CPU를 풀활용하면서, CPU, 메모리, 네트워크 대역폭등의 자원을 최적화하는 것이 목적이다)과 향후 업데이트등에 쉽게 적응할 수 있는 유연한 구조를 본 섹션에서 다루고자 한다.

·        modular structure - 모듈화된 구조

·        acceptor/connector/logic thread - 접속처리기/접속기/로직처리기

·        proactor - 적극적 송수신처리

·        message queue - 메시지 큐

·        ref-counted packet - 참조카운트 패킷

·        ref-counted packet buffer - 참조카운트 패킷버퍼

오브젝트 구조

오브젝트구조패턴

·        게임에 등장하는 오브젝트에는 여러가지 종류가 있다. 그러한 오브젝트를 서버프로그램 안에서 구체적인 클래스로 나타내는 방법을 다룬다.

·        dynamic property object - 가변적 오브젝트 속성

·        static property object - 고정적 오브젝트 속성

·        schema based type - xml 스키마로부터 속성 타입을 정의

·        index based handle - 핸들을 오브젝트 컨테이너 내의 인덱스 기준으로 발급

·        sequential handle - 핸들을 일렬로 발급

·        GUID handle - 글로벌 유니크 핸들 발급

·        ref-counted long reference - 장기적 참조 관계를 카운팅하기

오브젝트관리패턴

·        게임 안에는 복수개의 다양한 오브젝트들이 존재한다. 그러한 오브젝트들을 어떤 방식으로 다뤄야 효율적이고 정확하게 다룰 수 있는지를 논한다.

·        object manager - 오브젝트 관리자

·        pooled object factory - 풀 기반의 오브젝트 생성기

·        section map - 지역단위로 세분화된 맵

·        unique object - 유니크 오브젝트

·        type-safety handle - 타입정보가 포함된 핸들

·        double dispatch - 2중 관계처리

오브젝트행동패턴

·        게임 내의 오브젝트는 유저의 요청 또는 게임적 흐름에 따라 다양한 일을 수행하고, 다른 오브젝트와 상호작용한다. 그러한 행동을 어떻게 구체적으로 정의할 것인지와 어떻게 서로 상호작용하게 할 것인지를 논한다.

·        synchronous messaging - 동기화된 메시지 전송

·        asynchronous messaging - 비동기식 메시지 전송

·        scripted calculation - 계산식의 스크립트화

·        state machine script - 상태머신의 스크립트 기술

·        latent script function - 스크립트상에서의 레이턴트 함수 구현

·        transaction script - 스크립트에 트랜잭션 개념 포함

오브젝트저장패턴

·        MMORPG의 게임 세션은 지속적으로 유지되어야 하므로 게임 서버상의 내용을 DB에 저장하고 다시 복원하여야 하며, 그러한 모든 작업은 사용자의 플레이를 방해하지 않도록 실시간으로 이루어져야 한다. 그러한 효율적인 처리가 가능하도록 DB를 구성하고 억세스하는 방식에 대해 논한다.

·        row data gateway - 테이블 행단위 연결

·        CLOB serialized objects - 인코딩/디코딩 과정을 이용해 다수의 오브젝트를 LOB필드에 저장

·        periodic dumping - 주기적 덤프

·        offline access - 오프라인 액세스

·        middle server - 미들 서버

커뮤니케이션 구조

클라이언트-서버 커뮤니케이션패턴

·        사용자는 주로 TCP/IP 의 클라이언트 서버 모델로 이루어진 네트워크 환경하에서 게임에 접속하는데, 이에는 네트워크 지연과 대역폭제한등의 제약조건들이 수반한다. 그러한 제약조건하에서 최대한 자연스러운 게임적 처리를 가능케 하는 클라이언트와 서버간의 연결방식에 대해 논한다.

·        bit-condensed packet

·        start-end movement

·        dead reckoning movement

·        Client based Collison Detection

·        client proxy

·        neighborhood cache

·        appearance cache

·        string table

서버-서버 커뮤니케이션패턴

·        global/local daemon

·        publisher/subscriber

·        DB based session

·        zone-cross arbiter

기타

보안/치트방지패턴

·        thin client

·        double login prevention

·        direct login prevention

·        speed hack prevention

·        object unique code

·        packet tempering prevention

분류되지 않은 패턴/Idiom

 

 

프라우드넷 샘플 보기

http://www.nettention.com/proudnet/index.aspx?service=sample

 

 

2.  유니티 네트워크

네트워크(Network) 클래스

유니티의 Network 클래스는 네트워크의 핵심 클래스이며 기본 기능들을 제공하는 클래스입니다. 네트워크에 사용되는 많은 인터페이스와 파라메터를 가지고 있으면 서버와 클라이언트를 세팅할 때 쓰이는 클래스입니다. 네트워크 클래스에 많이 쓰이는 함수와 인자를 알아보면 아래와 같습니다.

이벤트에 따라 호출되는 함수

동작 내용

OnPlayerConnected

새로운 플레이어가 접속하면 서버에서 호출 됨

OnServerInitialized

네크워크 초기화가 완료되면 서버에서 호출 됨

OnConnectedToServer

서버로의 연결이 완료되면 클라이언트에서 호출 됨

OnPlayerDisconnected

서버로부터 플레이어의 연결이 끊어지면 서버에서 호출 됨

OnDisconnectedFromServer

서버로의 연결이 끊어지면 클라이언트에서 호출 됨

OnFailedToConnect

서버로의 연결이 실패할 시에 클라이언트에서 호출 됨

OnNetworkInstantiate

서버에서 Network.Instantiate가 호출되면 클라이언트에서 호출 됨

자주 사용되는 변수

변수 설명

connections

모든 연결된 Player

isClient

현재 작동하는 네트워크 모드가 클라이언트인지, 아닌지

isServer

현재 작동하는 네트워크 모드가 서버인지, 아닌지

중요 클래스 함수

함수 동작 설명

InitializeServer

서버로써의 초기화

Connect

특정 주소와 포트를 이용하여 연결

Disconnect

모든 열려있는 연결들을 닫고 네트워크 인터페이스를 종료

AllocateViewID

이용 가능한 네트워크 View ID 숫자를 가져와서 할당한다.

Instantiate

프리팝(Prefab) 네트워크 인스턴스화

[]1. Network 클래스

네트워크 매니저(Network Manager) 소개

유니티3D Edit ->Project Settings -> Network 를 선택하면 Inspector 창에서 아래와 같은 항목을 볼 수 있습니다.

[그림]3 Network Manager

Debug Level 에서 Off를 선택하면 네트워크 에러 메세지만 볼 수 있고, Informational을 선택하면 통신을 하면서 발생하는 이벤트들을 메시지로 볼 수 있고, Full을 선택하면 모든 이벤트들을 확인 할 수 있습니다.

SendRate는 초당 몇회까지 통신을 하는지 조절할 수 있는 파라메터입니다. 이를 높일시 양질의 네트웤 통신이나 데드레커닝 같은 네트워크 클라이언트간의 위치 보간 등이 쉬우나 그만큼 초당 패킷량이 늘어 부하가 발생하게 됩니다.

 

네트워크 뷰(Network Views) RPC(Remote Procedure Call) 소개

네트워크 뷰는 네트워크 간의 데이터를 공유하게 해주는 운송수단이라고 할 수 있습니다. 통신 방법은 상태동기(State Synchronization)와 원격 프로시져 호출(Remote Procedure Calls)이 있습니다.

많은 클라이언트가 같은 게임을 실행시키고 있을경우, 각 클라이언트는 게임을 이루는 오브젝트들을 각각 가지고 있게 됩니다. 이 둘이상의 클라이언트들이 동일하도록 보이도록 할려면 오브젝트들의 데이터들이 공유로 인해서 동기화가 이루어져야 합니다. 이것을 상태 동기화(State Synchronization)라고 합니다. 상태동기화를 위해서는 많은 양의 데이터들을 주고 받아야 하며 게임 플레이가 가능하도록 네트워크를 통해서 데이터를 신뢰성있고 견고하게 유지시켜주어야 합니다.

유니티의 네트워크 뷰(Network View)에서는 이런 상태 동기화를 위해 특정 부분을 공유하도록 할 수 있게 해주고, 각각의 오브젝트들을 동기화 시켜줍니다.

이렇게 위치라든가, 상태라든가 항상 지켜보고 즉각적으로 동기화 시켜주어야 하는게 있는 반면, 그때 그때 이벤트에 따라 일회성 동기화가 필요한 경우가 있습니다. 예를 들어, 어떤 플레이어가 아이템을 얻었다고 한다면 다른 클라이언트에게 먹었다는 것을 한번만(여러 번 알리게 되면 한번에 여러 개의 아이템을 얻은 셈이 된다)알리어서 다른 클라이언트들 화면에 똑같이 먹은 아이템을 사라지게 하는 이벤트 같은 것이 일어나야 합니다. 이런 경우, 항상 일어나는 이벤트도 아닐뿐더러 일회성이어서 항상 아이템의 상태를 지켜보고 있을 순 없는 일이다. 보통의 경우 이럴 때는 서버에서 주변 클라이언트에게 패킷을 날리어 화면에 있는 아이템을 어떤 클라이언트 얻었으니 알아서 지워라 라는 명령을 내립니다.

유니티의 RPC(Remote Procedure Call)를 사용하게 되면 서버 또는 클라이언트에서 다른 서버 또는 클라이언트들이 갖고 있는 특정 함수를 호출하게 하여 RPC를 받은 서버 또는 클라이언트들의 상태나 이벤트등을 동기화 할 수 있습니다.

상세 설명

유니티의 네트워크 뷰는 네트워크뷰아이디(NetworkViewID)라는 것을 통해 구분을 짓게 됩니다. 한 네트워크뷰아이디는 기본적으로 모든 네트웤에 연결된 머신들 사이에서 유일한(Unique) 아이디입니다. 128비트 정수로 구현됐지만 네트워크 전송시에는 자동으로 16비트로 압축되어 보내지게 됩니다. 그렇게 보내지는 각 패킷은 네트워크 뷰가 적용되어 있는 클라이언트 사이드 측으로 도착하게 되고 네브워크뷰아이디를 통해 네트워크뷰를 구분지어서 네트워크뷰아이디에 해당하는 적절한 오브젝트에 패킷으로 날라온 데이터를 적용시키게 됩니다.

만약에 Network.Instantiate()함수를 사용해서 네트워크 오브젝트를 인스턴스화한다면, 네트워크뷰 아이디에 대한 걱정은 할 필요가 없습니다. 인스턴스화 하면서, 즉 생성되면서 고유의 네트워크 아이디를 갖도록 되어 있기 때문입니다.

또한, 수동적으로 네트워크뷰아이디를 각각의 네트워크 뷰에 Network.AllocateViewID를 활용해서 설정하는 것도 가능합니다.

네트워크 뷰(Network View) 컴포넌트

유니티의 네트워크뷰 컴포넌트에 대해서 상세히 알아보도록 하겠습니다. 유니티 네트워크 시스템을 쓴다고 하면 네트워크 뷰 컴포넌트는 반드시 잘 알아두셔야 합니다. 좀더 상세한 정보는 유니티 홈페이지 매뉴얼 페이지를 참조하시고 이 책에서는 꼭 알아 두어야 할 것만 짚고 넘어가도록 하겠습니다.


[그림]4. 네트워크 뷰 컴포넌트

상태동기화(State Synchronization) 또는 원격 프로시져 호출(Remote Procedure Calls)을 쓸려고 한다면 반드시 해당 게임 오브젝트에 네트워크뷰(Network View) 컴포넌트를 붙여주어야 합니다.

위의 그림을 보시면 네트워크 뷰에는 상태동기(State Synchronization)라는 속성과 관찰자(Observed)라는 속성 , View ID라는 속성이 있습니다. 상태 동기 속성의 타입엔 Off Reliable DeltaCompressed Unreliable 이라는 것들이 있는데 각각 상태 동기를 끄는 것과 지난 상태와 달라진 점만 압축해서 보내는 것과 모든 상태를 보내주어서 좀더 많은 네트워크의 대역폭이 필요하지만, 중요한 패킷 손실은 적습니다. Off의 경우는 실무에서 보통 RPC만 사용할 경우 Off로 설정하고 사용하고 있습니다.

상세 설명

게임오브젝트에 네트워크 뷰를 붙여줄 때 크게 두 가지를 고려해야 합니다.

1.    무슨 종류의 데이터를 네트워크 뷰를 통해 주고 받을 것인지

2.    데이터를 어떻게 보낼 것인지

보낼 데이터를 정하기

네트워크 뷰의 관찰자 속성은 단일 컴포넌트를 가르킬 수 있습니다. , 어떤 하나의 컴포넌트만 골라서 네트워크뷰가 주시하고 있는 것입니다. 여기서 주시가 가능한 컴포넌트는 Transform, Animation, RigidBody 또는 스크립트 입니다. 주시하고 있는 컴포넌트가 어떤 것이든지 네트워크를 통해 데이터를 주고 받게 될 것입니다. 만약에 직접적으로 데이터를 주고 받고 싶지 않다면 RPC의 호출을 통해서도 데이터 전송이 가능합니다.

어떻게 데이터를 보낼 것인지

주시하고 있는 컴포넌트의 데이터를 보내는 두 가지 방법은 앞에서도 말했다시피 상태동기화(State Synchronization)RPC(Remote Procedure Calls)입니다.

상태동기화를 사용한다면 타입을 Reliable Delta Compressed Unreliable, 이 둘 중 하나를 선택하면 됩니다. 그렇게 주시하고 있는 컴포넌트의 데이터들은 자동적으로 데이터를 주고 받게 됩니다.

Reliable Delta Compressed 타입은 순서 지향형입니다. 패킷을 받은 순서대로 보내주기 때문인데만약 패킷이 유실이 된다면 그 패킷은 재전송하게 됩니다. 어떤 패킷이 도착했을 때 기존에 받은 패킷이 있었다면 패킷큐에 자동으로 쌓이게 됩니다지난 전송시에 보낸 상태와 다른 상태만이 전송이 되므로 주시하고 있는 컴포넌트의 상태가 바뀐 것이 없다면 보내는 데이터가 없게 됩니다.

만약 스크립트를 주시하고 있다면 반드시 주시하고자 하는 변수를 시리얼라이즈화 해야하는 데 이것은  OnSerializeNetworkView() 함수를 통해 할 수 있습니다. 다음의 예제를 살펴보도록 하죠, 다음의 예제들은 유니티 홈페이지를 참고한 것입니다.

[코드] 4. SerializeNetworkView 예제 - 1

function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo) {

             var horizontalInput : float = Input.GetAxis ("Horizontal");

             stream.Serialize (horizontalInput);

}

위에 함수는 Horizontal 입력 값을 받아서 매 프레임 들어오는 stream에 입력 값을 더해서 보내는 함수입니다. 만약 받는 것과 보내는 스트림 데이터를 다르게 하고 싶다면 BitStream클래스의 isWriting 속성을 사용하시면 됩니다.

[코드] 5. SerializeNetworkView 예제 – 2

function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo) {

             var horizontalInput : float = 0.0;

             if (stream.isWriting) {

                           // Sending

                           horizontalInput = Input.GetAxis ("Horizontal");

                           stream.Serialize (horizontalInput);

             } else {

                           // Receiving

                           stream.Serialize (horizontalInput);

                           // ... do something meaningful with the received variable

             }

}

 

OnSerializeNetworkView sendRate수치에 따라 호출되고 동작하는 것이 정해집니다. sendRate 수치는 앞에서 설명한 네트워크 매니저에서 설정할 수 있습니다. 기본적으로 초당 15회로 세팅되어 있습니다.

만약 원격 프로시져 호출을 스크립트에서 사용하고자 한다면 게임오브젝트에 네트워크뷰 컴포넌트를 붙여야 합니다. 그리고 호출하고자 하는 함수에 자바스크립트라면 @RPC C#스크립트라면 [RPC]를 함수 구현 전에 붙여야 합니다. 그리고 나선 어떤 스크립트가 붙여져 있던 게임오브젝트라면 networkView.RPC()를 호출해서 원격 프로시져 콜을 수행할 수 있습니다.

[코드] 6. 자바스크립트 - RPC 예제

var playerBullet : GameObject;

function Update () {

             if (Input.GetButtonDown ("Fire1")) {

                           networkView.RPC ("PlayerFire", RPCMode.All);

             }

}

@RPC

function PlayerFire () {

             Instantiate (playerBullet, playerBullet.transform.position, playerBullet.transform.rotation);

}

 

또 다른 예제를 살펴 보도록 하겠습니다.

[코드] 7. RPC예제 2 – 자바스크립트

var cubePrefab : Transform;
function OnGUI () {
if (GUILayout.Button("SpawnBox")) {
    var viewID : NetworkViewID= Network.AllocateViewID();
    networkView.RPC("SpawnBox", RPCMode.AllBuffered, viewID, transform.position);
   }
}

@RPC
function SpawnBox (viewID : NetworkViewID, location : Vector3) {
   // Instantate the prefab locally
   var clone : Transform;
   clone = Instantiate(cubePrefab, location, Quaternion.identity) as Transform;
   var nView : NetworkView;
   nView = clone.GetComponent(NetworkView);
   nView.viewID = viewID;
}

 

 

[코드] 8. RPC 예제 2 - C# 스크립트

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
public Transform cubePrefab;
void OnGUI() {
   if (GUILayout.Button("SpawnBox")) {
      NetworkViewID viewID = Network.AllocateViewID();
      networkView.RPC("SpawnBox",RPCMode.AllBuffered, viewID, transform.position);
   }
}
[RPC]
void SpawnBox(NetworkViewID viewID, Vector3 location) {
  Transform clone;
  clone = Instantiate(cubePrefab, location, Quaternion.identity) as Transform as Transform;
  NetworkView nView;
  nView =clone.GetComponent<NetworkView>();
  nView.viewID = viewID;
 }
}

 

 

RPC 상세 설명

원격 프로시져 호출(Remote Procedure Call) 은 원격 머신에 함수를 호출할 수 있게 해줍니다. 마치 일반 함수를 호출하듯이 쉽게 사용할 수 있지만 몇 가지 중요한 차이점은 꼭 알아 두셔야 합니다.

1.     RPC 호출은 많은 파라메터들을 가질 수 있습니다. 모든 파라메터들은 네트워크를 통해 전송이 됩니다. 많은 파라메터들을 보낼수록 전송 대역폭은 커집니다. 그러므로 할 수 있는한 최대한 최소의 파라메터만으로 통신하는 것이 좋습니다.

2.     RPC 호출을 할 때 어떤 대상에게 보내는 지를 정하는 것도 중요합니다. 몇가지 RPC 호출 모드로 모든 경우의 호출을 할 수 있는데 RPC함수를 사용해서 쉽게 특정 클라이언트만 , 서버에게만, 서버/클라이언트 모두에게 호출 전송이 가능합니다.

RPC호출은 보통 온라인 게임에서 특정 이벤트를 발생시킬 때 사용하거나, 경쟁하는 두 팀 사이에 특정 정보를 주고 받기 위해 사용합니다. 다만, 자동적으로 이루어지는 것은 아니고 개발자가 직접 RPC 함수를 생성하고 사용해야 합니다. 앞에 예를 든 것 중에 서버에 여러 클라이언트들이 접속해 있고 하나의 클라이언트가 아이템을 얻었다면 모든 클라이언트 화면에 아이템이 사라져야 하고 서버에선 아이템을 얻은 클라이언트에게 해당 아이템 정보를 인벤토리에 넣어주어야 합니다. 이런 경우, 아이템을 얻은 클라이언트가 서버에게 아이템을 얻었다는 RPC콜을 하고 서버는 RPC콜을 한 클라이언트에겐 아이템을 인벤토리에 넣으라는 RPC콜을 하고 다른 클라이언트들에겐 화면 안의 아이템을 다른 클라이언트가 먹었으니 화면에서 삭제해라라는 RPC콜을 합니다.

또 다른 경우로, 서버의 브로드캐스팅(BroadCasting)을 줄여서 클라이언트가 호스트(Host)처럼 동작하기 위해서도 사용합니다. 예를 들면,게임안의 마을 같은 곳에서 어떤 클라이언트가 춤을 추는 애니메이션을 한다던가 인사를 하는 제스쳐를 취한다던가 하는 게임에는 영향을 끼치진 않지만 그 클라이언트를 보고 있는 클라이언트들은 같은 상황을 볼 수 있게 할려면 춤을 추거나 인사를 하고자 하는 클라이언트가 주변 클라이언트에게 내 캐릭터가 이제부터 어떤 행동을 하는 애니메이션을 할 것이다라는 RPC콜을 하면 주변 클라이언트는 해당하는 화면 안의 클라이언트의 캐릭터가 애니메이션이 되는 것을 볼 수 있게 됩니다. 이렇게 서버가 꼭 알아야 할 필요가 없는 경우엔 클라이언트가 직접 주변 클라이언트에게 RPC호출을 하는 경우에도 사용할 수 있겠습니다.

RPC 사용법

이제 두 단계로 어떻게 RPC호출을 셋팅하는지를 살펴보도록 하겠습니다. 일단 RPC호출을 할 함수를 선언하고 함수 앞에 Prefix RPC 속성(attribute)을 주면 됩니다.

아래의 소스코드는 RPC 속성과 함께 함수를 정의하는 예제 입니다.

[코드] 9. RPC의 속성과 함께 함수를 정의하는 예제

// All RPC calls need the @RPC attribute!

@RPC

function PrintText (text : String)

{

    Debug.Log(text);

}

 

RPC 속성을 정의해준 것 만으로 앞으로 PrintText함수를 RPC함수로써 호출 할 수 있게 됩니다. 모든 RPC 네트워크 통신은 네트워크뷰가 붙어있는 같은 스크립트가 붙어있는 게임오브젝트들을 통해 이루어지게 됩니다. 다시말하면, RPC 통신을 하기 위해선 네트워크뷰 컴포넌트를 꼭 게임오브젝트에 추가시켜줘야 하는 것입니다.

RPC 함수의 인자로는 다음과 같은 타입을 사용할 수 있습니다. 꼭 유의하세요:

*       int

*       float

*       string

*       NetworkPlayer

*       NetworkViewID

*       Vector3

*       Quaternion

RPC함수를 호출하는 구문은 아래와 같이 사용하면 됩니다..

networkView.RPC ("PrintText", RPCMode.All, "Hello world");

이 코드는 네트워크뷰가 붙어있는 모든 게임오브젝트중에 PrintText()라는 함수를 가지고 있는 스크립트의 함수를 호출하게 됩니다.

RPC 함수의 첫번째 파라미터는 호출될 함수의 이름을 뜻합니다. 두번째 파라미터는 어떤 대상들이 호출하게 될지 결정하는 것이고, 세번째 파라미터는 호출할 함수에 넘겨줄 인자입니다. 이와 같이 RPCMode.All,즉 연결된 모든 대상들을 상대로 RPC 호출을 할 경우 연결된 대상들에겐 즉각적으로 호출을 요청하지만, 아직 접속중인 클라이언트의 경우 Buffer에서 기다렸다고 접속이 완료되는 대로 호출합니다.함수에서 요구하는 모든 파라메터들은 네트워크를 통해서 RPC함수에 전달이 됩니다. 위와 같은 경우 “Hello World” 라는 파라메터가 텍스트 파라메터로써 PrintText함수에 전달이 됩니다.

invoked RPC 호출을 할 때 외부 파라메터 NetworkMessageInfo라는 구조체의 변수를 외부 파라메터로써 호출이 가능합니다. 딱히 필요 없는 정보라면 전송을 하지 않아도 됩니다.

NetworkMessageInfo 파라메터를 사용한 예제 코드는 아래를 참고하세요

[코드] 10. NetworkMessageInfo 파라메터를 사용한 예제

@RPC

function PrintText (text : String, info : NetworkMessageInfo)

{

    Debug.Log(text + " from " + info.sender);

}

이미 언급했다시피, 스크립트 안에서 RPC 함수 호출 동작 순서는 네트워크뷰가 반드시 게임오브젝트에 붙어 있어야 하고 해당 스크립트를 포함하고 있어야 합니다.

RPC Buffer

RPC 호출은 버퍼(Buffer)에 저장될 수 있습니다. 버퍼화된 RPC호출은 저장되고 새로 연결된 클라이언트의 설정 순서에 맞추어서 차례차례 실행이 됩니다이것은 이미 존재하는 게임에 새로운 유저의 로그인 기능을 쉽게 만들 수 있습니다. 또한 로그인하는 모든 유저에게 특정 레벨을 바로 로딩하도록 할 수도 있습니다. RPC호출은 연결된 모든 클라이언트에게 전송이 되면서 버퍼에 세팅했다가 새로 들어오는 클라이언트에게 똑같은 과정을 거치도록 할 수 있는 것입니다. 또한 새로 들어오는 클라이언트에게 기존의 다른 클라이언트들을 자동적으로 보내어지게 되어 있어서 , 새로 들어온 클라이언트에게 기존의 연결 전체를 보내야 하는 과정이 필요 없습니다.

유니티는 또한 RPC버퍼로 부터의 호출을 제거할 수 있는 기능을 제공합니다. 예를들어 레벨을 로딩한다고 할때, 만약에 게임이 여러 레벨들로 이루어져 진행이 된다고 하면, 새로 연결된 플레이어를 현재 진행중인 레벨을 로딩하도록 할 수 있습니다. 위와 같은 경우엔, 기존에 이미 로드된 레벨 로드 함수들을 RPC 버퍼에서 제거해서 현재 레벨만 로드하도록 구현하면 됩니다.

 

NetworkView RPC의 실습예제

 

그러면 이번 챕터에서는 앞에서 알아본 네트워크뷰와 RPC호출을 사용해서 실제 사용될 수 있는 유니티 네트워크 코드를 살펴보겠습니다.

아래 예제를 실행토록 할려면 먼저 main, Scene stage1 Scene을 만들어야 합니다. Main Scene에서는 먼저 UnityNetwork.js(C# 버전으론 UnityNetworkCS.cs)스크립트 파일을 만들어서 아래의 소스코드를 입력합니다. 그리고 MainCamera에 만든 스크립트 파일(자바스크립트와 C#스크립트 둘 중 하나만)을 붙여 놓습니다. 그리고 캐릭터로 쓸 Prefab을 하나 만들어서 DummyKnight.js(C#버전으론 DummyKnightCS.cs) Prefab에 붙여 놓고 만든 Prefab Main SceneMainCamera UnityNetwork 스크립트 파일의 Inspector 창에서 Knight Player Prefab 이라는 변수에 앞에서 만든 Prefab을 드래그앤드롭으로 붙여 놓아야 합니다.

 

[그림] 4. Main Scene의 세팅

[그림] 5. DummyKnight (임시 캐릭터 프리팝) 세팅

 

 

 

[그림] 7.실행화면

[그림] 8.Console Window 출력 메시지

3.  상용 유니티 네트워크 엔진

 

1.   상용 네트워크 엔진?!

유니티에서 네트워크를 제공하긴 하지만 역시 대규모 게임 서버로썬 부족한 부분이 있습니다. 향후 유니티엔진의 추가되는 기능들의 개발 스케쥴을 보아도 많은 지원을 하진 않는다는 아쉬운 점이 있습니다. 그리고 대형 네트워크 시스템엔 어쩔수 없는 게임마다의 독특한 네트워크 레이어를 로우 레이어(Low Layer) 부터 커스터마이징해서 개발하고자 하는 게임에 맞춤형 시스템을 만들수 밖에 없는데 유니티 엔진에서는 네트워크 레이어정도의 커스터마이징이 불가능합니다.

그래서 유니티 엔진을 선택한 많은 온라인 게임회사들이 네트워크 시스템과 게임 서버를 자체 개발 또는 이미 만들어져서 라이선스를 유료로 판매하는 좋은 상용 네트워크 엔진들을 사용합니다.

 

이 단원에서는 상용 네트워크 엔진엔 어떤 것들이 있는지 알아보기 위해 현존하는 네트워크 엔진중 가장 각광을 받고 있는 두가지 네트워크 엔진인 스마트폭스(SmartFox)와 포톤(Photon)을 소개하고, 그중 포톤이라는 서버 엔진을 직접 사용해본 후기등을 이야기해 보려 합니다.

 

2.   스마트폭스서버(SmartFoxServer)

[그림] 9. 스마트폭스서버

 

스마트폭스서버는 다양한 플랫폼의 서버로써 유명한 네트워크엔진입니다. 최근에 유니티3D .Net 등을 지원하면서 유니티의 네트워크 엔진으로써 주목을 받고 있습니다.

 

특징

스마트폭스서버는 Adobe Flash and Flex 2용 네트워크 엔진으로써 디자인되었고, 또한 자바(Java)와 쇼크웨이브(Shockwave)와 닷넷(.Net)까지 지원합니다.

 

스마트폭스서버는 큰규모의 MMO 게임서버부터 간단한 채팅과 턴베이스(turn-based) 게임까지 커버가 가능하도록 해줍니다. 쉬운 사용법과 더불어 게임에 필요한 고급적인 기능들이 제공되어 네트워크 엔진으로써 각광을 받고 있습니다.

 

[그림] 10. 스마트폭스의 라이선스 형태

스마트폭스서버의 The BASIC edition은 멀티플레이어 응용어플리케이션 개발이 가능하지만 서버측면에서의 코딩은 불가능합니다. 간단한 채팅 게임이나 버디 메신저, 아바타 채팅등을 개발목적으로 만들시에 좋습니다. 서버측면에서의 코딩은 불가능 하다는 것은 제공되는 기능만 사용하고 추가적인 서버 기능은 만들어 넣을 수 없는 것을 의미합니다.

 

-       스마트폭스서버의 The PRO edition 은 대부분의 리얼타임 네트워크 엔진에 사용되며 큰규모의 게임의 개발 목적시에 사용됩니다..

 

-       200유로에 31만원(2011.08기준)정도라고 볼 때 네트워크 엔진을 사용하는 개발사입장에선 충분히 고려한 후에 최종 사용결정을 내리는 것이 중요합니다.

 

-       서버사이드의 기능확장은 액션스크립트(Actionscript),자바스크립트(Javascript),파이썬(Python)과 자바(Java)라는 언어를 사용합니다.

 

-       데이터베이스(DB) 엔진으로 접근이 쉬운 편입니다.( MySQL, Access, MS SQL) . 그리고, 관계형DB시스템 서비스를 서버측면에서 높은 성능으로 지원하고 있습니다.

 

-       웹서버의 기능을 사용해 파일 업로드와 서버의 컨텐츠들을 쉽게 제공할 수 있습니다.

 

-       The BlueBox 라는 모듈이 있는데, 이것은 방화벽과 프록시들에 상관없이 모든 클라이언트들과 연결을 하도록 도와줍니다.

 

-       서버의 프레임워크(framework)는 복잡한 상호 작용을 만드는 하이레벨(high-level) API와 확장 기능을 제공하고 고급 서버 기능에 대한 모든 액세스 권한을 제공합니다

 

-       제이썬(JSON) Raw 문자열(Raw string) 프로토콜을 사용함으로써 빠른 데이터 전송과 실시간 동작에 따른 패킷 전송량을 줄일 수 있습니다.

 

-       친구 목록 매니저는 친구 등록등에 필요한 기능 및 모든 주요 인스턴트 메신저와 같은 기능을 제공합니다

 

스마트폭스의 유니티엔진에서의 기본 사용법 소개

 

먼저, 스마트폭스서버의 홈페이지(http://www.smartfoxserver.com/)중 다운로드 페이지에서 해당하는 플랫폼의 스마트폭스서버의 설치파일을 다운 받습니다. (http://www.smartfoxserver.com/2X/download.php) 그리고

API 페이지(http://smartfoxserver.com/labs/API/)에서 유니티용 다운로드 .Net API를 다운로드 합니다.

다운 받을 파일을 설치한 후에  .Net API 압축파일을 풀은 폴더를 살펴보면 예제폴더(SFS_CSharp_1.2.6\SFS_CSharp_1.2.6\Examples)가 있습니다 그 중 01_SimpleConnect 를 살펴보도록 하겠습니다.

 

설치가 끝났다면 스마트폭스 설치폴더(C:\Program Files\SmartFoxServerPRO_1.6.6)에 가셔서 start 라는 윈도우용 배치파일을 실행시키면 아래와 같은 화면을 보실 수 있습니다.

[그림] 11. 스마트폭스 서버의 콘솔창

스마트폭스서버가 실행이 되었습니다~!

 

자 이제 예제 폴더 SFS_CSharp_1.2.6\SFS_CSharp_1.2.6\Examples\01_SimpleConnect 를 유니티에서 열어보면 프로젝트 폴더 구성은 아래와 같습니다.

[그림] 12. SimpleConnect 예제의 프로젝트 창

 

살펴보시면 Plugins라는 폴더 안에 SmartFoxClient라는 DLL 파일이 들어 있는 것을 확인하실 수 있습니다. 유니티에서 외부 라이브러리(DLL)을 사용할 경우엔 Plugins 폴더 안에 넣어야 합니다.

이렇게 DLL 파일이 Plugins 폴더안에 들어가게 되면 유니티프로젝트에서 SmartFoxClient의 기능을 사용할 수가 있습니다.

 

실행을 해보면 아래와 같은 메시지를 확인하실 수 있으실 겁니다.

[그림] 13. 실행 화면

연결이 성공됐다는 메시지가 화면에 출력이 됩니다.

 

그럼 이제 마지막으로 연결 소스 코드를 한번 살펴 보도록 하겠습니다.

[코드] 15. ConnectionGUI.cs – C# 스크립트 버전

 

using UnityEngine;

using System;

using SmartFoxClientAPI;

 

public class ConnectionGUI : MonoBehaviour

{

             //----------------------------------------------------------

             // Setup variables

             //----------------------------------------------------------

             private string ip = "127.0.0.1";

             private int port = 9339;

             private string statusMessage = "";

 

             //----------------------------------------------------------

             // Called when program starts

             //----------------------------------------------------------

             void Start()

             {

                           SmartFoxClient smartFox = new SmartFoxClient();

                           SFSEvent.onConnection += HandleConnection;

                           smartFox.Connect(ip, port);

             }

               

             //----------------------------------------------------------

             // Draw GUI every frame

             //----------------------------------------------------------

             void OnGUI()

             {

                           GUI.Label(new Rect(10, 10, 500, 100), "Status: " + statusMessage);

             }

 

             //----------------------------------------------------------

             // Handle connection response from server

             //----------------------------------------------------------

             void HandleConnection(bool success, string error)

             {

                           if (success)

                           {

                                        statusMessage = "Connection succesfull!";

                           }

                           else

                           {

                                        statusMessage = "Can't connect!";

                           }

             }           

}

 

 

 

Start()함수를 살펴보시면 SmartFoxClient라는 클래스를 인스턴스화해서 바로 사용하는 것을 보실 수 있고 소스 상단측에 보시면 using SmartFoxClientAPI 라는 구문을 통해서 스마트폭스 클라이언트 DLL을 사용할 수 있게됩니다.

 

만약, 여러분이 스마트폭스서버를 이용하여 네트워크게임의 서버를 개발하실려고 한다면 스마트폭스서버에서 제공하는 50개가 넘는 예제들을 살펴보시고 홈페이지의 문서와 WhitePaper 페이지를 꼭 숙지하는 것이 좋다는 어드바이스를 해드리고 싶습니다. 제공되는 API가 정확히 어떤식으로 돌아가는지를 알아야 서버측에서 생기는 여러가지의 문제점들을 유용하게 대처할 수 있기 때문입니다.

 

3.   포톤(Photon) 네트워크 엔진

.

[그림] 14. 포톤 네트워크 엔진

포톤 네트워크 엔진은 MMO장르에 개발하기 적합한 유니티를 위해 최적화된 소켓 네트워크 엔진입니다. MMO에만 적합한 것은 아니지만 그만큼 대형 서버 네트워크에 어울리는 성능을 가진 엔진입니다. 포톤 역시 스마트폭스서버와 함께 온라인 게임 개발사들이 많이 사용하는 네트워크 엔진이면서 지금도 게임 개발에 쓰기 적합한 많은 기능들이 추가되고 있는 엔진입니다.

 

특징

[그림] 15. 포톤 구성도

위 그림은 포톤의 구성도입니다. C/C++로 네트워크 코어 시스템의 성능을 끌어 올린후 .Net 프레임워크를 얹어서 여러 언어로 만든 개발자의 로직용 소스코드를 강력하게 서버 사이드에서 지원하고 있습니다. 클라이언트 사이드에선 SDK(SoftwareDevelopmentKit)을 사용해서 서버 사이드와 Binary Protocol을 사용해서 통신을 하게 됩니다.

 

서버 게임 로직은 C# 언어를 사용합니다. 이는 유니티에서 C#스크립트를 사용할 경우 코드를 공유해서 쓸 수 있다는 장점이 있습니다.

 

-       간단하고 유연한 RPC호출을 사용할 수 있습니다.

-       Fiber를 통한 메시지 전송은 많은 쓰레드 관련 문제를 해결했습니다.

-       Lite는 간단한 룸지향(Room Based) 게임을 위해 제공됩니다. 이는 고스톱이나 맞고와 같은 게임등을 제작할 시에 유용합니다.

-       클라우드 시스템을 간단히 개발, 추가할 수 있습니다.

-       가벼운 Binary 프로토콜은 전송대역폭 부하와 패킷의 크기를 최적화 하는 데 좋습니다.

-       플랫폼과 상관없이 통신할 수 있도록 하는 기능이 있습니다.

 

개발 시스템 요구사항

 

서버 운영을 위한 추천 사양

Windows Server 2008 - 64 bit

Microsoft .NET Framework 4.0

 

개발에 필요한 추천 사양

Windows XP, Windows Vista or Windows 7

Microsoft .NET SDK 3.5 SP1

Microsoft Visual Studio 2008

 

기본 사용 포트 리스트들

UDP: 5055

TCP: 4530

TCP: 843 (유니티 웹플레이어와 플래쉬 크로스도메인을 위해 사용)

TCP: 943 (실버라이트 크로스도메인을 위해 사용)

 

포톤의 유니티엔진에서의 기본 사용법 소개

 

먼저 포톤의 홈페이지(http://www.exitgames.com/)에서 포톤 서버를 다운받은 후(가입해서 로그인해야 합니다.) 홈페이지 하단부에 유니티 관련 SDK를 받을 수 있습니다. 포톤을 사용할 때  주의해야할 점중 하나가 라이센스(.license)파일입니다. 라이센스 파일에 따라 동시에 접속가능한 유저수와 기능이 제한되기 때문에 free license 파일을 같이 받으셔야 합니다.

 

포톤 서버의 압축을 푼 폴더를 살펴보면 Deploy폴더가 있습니다. 그 중 현재 사용하고 계신 플랫폼에 따라 Window OS 32비트 운영체제를 쓰는지 64비트 운영체제를 쓰는지, XP OS를 사용하고 계신지에 따라 폴더를 선택합니다.

 

폴더 안을 살펴보면 PhotonControl.exe라는 파일이 있습니다. 이파일을 실행시키기 전에 다운받은 라이센스파일을 PhotonControl.exe파일이 있는 폴더 안에 같이 복사해 넣으셔서 덮어씌우셔야 합니다.

이제 PhotonControl.exe를 실행시킵니다.

실행이 되었다면 아래와 같이 작업표시줄에 나타나게 됩니다.

 

[그림] 16. PhotonControl.exe 실행

박스 모양()의 포톤의 로고를 마우스 오른쪽 클릭을 하시면 아래와 같은 메뉴를 보실수있습니다.

[그림] 17. 포톤 아이콘을 마우스 오른쪽 클릭한 메뉴 화면

 

이중 Photon Start as application을 선택하시면

[그림] 18. 툴바 메뉴중 Start as application 을 선택

 

약간의 시간이 흐른 후 포톤이 실행이 된 것을 아래와 같이 확인하실 수 있습니다.

[그림] 19. Photon Application이 정상 동작함

박스모양()의 포톤의 로고에 파란 불이 들어온 것을 확인하셨으면 포톤 서버 실행에 성공하신 겁니다.

 

자 이제 유니티 SDK를 다운받아 압축을 푼 폴더을 살펴보면 샘플 프로젝트가 들어있는 걸 확인하실 수 있습니다. 유니티로 샘플 프로젝트를 열어보겠습니다.

먼저 Project 창을 살펴보시면 아래와 같은 파일들이 프로젝트 Asset폴더안에 들어 있는 것을 확인하실 수 있는데 Photon이라는 폴더 안에 library 폴더로 PhotonUnity3D 외부 라이브러리 파일이 들어 있는 것을 확인할 수 있고 같은 파일이름의 XML파일이 들어 있는 것을 보실 수있는데 이  XML파일안에  라이브러리에 포함된 포톤 클래스의 함수들과 함수 동작에 관련한 자세한 설명을 보실 수 있습니다.

 

[그림] 20. Demo-LiteLobby-ChatRoom 샘플의 프로젝트 창

 

그럼 이제demo-litelobby-chatroom 샘플 데모 프로젝트를 실행 시켜 보겠습니다.

유니티가 실행이 되어서 Project창의 ChatScene 이라는 Scene파일을 더블 클릭하면 아래와 같은 화면을 확인하실 수 있습니다.

[그림] 21. Demo-LiteLobby-ChatRoom 샘플의 Game

 

이제 유니티의 실행 버튼을 누르면 포톤 로비&채팅 데모 프로젝트가 정상동작하시는 것을 확인 하실 수 있습니다.

[그림] 22. Demo-LiteLobby-ChatRoom 샘플의 실행화면

참고로, 포톤의 경우 서버의 설정을 PhotonServer.XML파일을 통해 IP주소라던가 서버에서 실행 할 게임목록등을 정의 할 수 있습니다. 외부파일에서 정의하므로 설정 변경으로 인한 서버의 재컴파일을 안해도 되는 장점이 있습니다.

 

포톤서버에 대한 상세 설명

 

포톤의 Operation Events

 

Operations

Operation RPC(Remote Procedure Call)호출의 포톤만의 형식입니다. 이것은 서버에 구현된 함수를 클라이언트에서 호출하기 위해 사용되며, 클라이언트 측에선 특정 파라메터(위치값등이나 자신의 아이디) Operation 함수의 인자로써 서버에 구현된 함수를 호출하게 되면 전달된 함수의 인자를 갖고 서버에서 처리된 결과값을 해당 클라이언트에 돌려주게 되며 그때 OperationResult()함수가 호출됩니다.

 

Events

Operation과 달리 event는 항상 일어나는 일은 아니고 특정 이벤트나 트리거 등에 의하여 생성되어 클라이언트가 받게되는 일조의 메세지입니다. 이벤트는 서버나 다른 클라이언트에서 전송되어 질수 있습니다.

예를들어 다른 유저가 내가 접속해 있는 방에 접속해 왔을때 나한테 Event 메세지 날라와서 EventAction()함수가 호출 됩니다.

 

포톤의 작업순서

포톤 서버에 접속하고 Room에 참가하는 간단한 클라이언트를 구현한다고 해보겠습니다.

먼저 ,포톤서버의 lite 서버를 사용해서 포톤 lite 서버에서 제공하는 Room Join이나 Exit 기능을 사용하고 클라이언트가 Room join요청시 Room이 하나 생성되고 클라이언트는 특정 ID를 부여 받게 됩니다.

위와 같은 것을 포톤서버를 이용해 구현한다고 한다면

 

-       LiteLobbyPeer 인스턴스를 생성합니다.

 

-       정기적으로 Service()함수를 불러주어서 Event들을 받아오고 Command들을 보내 줍니다. (1초에 10번 정도면 적당합니다.)

 

-       Connect()함수를 호출해서 서버에 연결합니다.

 

-       IPhotonPeerListener.PeerStatusCallBack의 호출이 있을때 까지 기다립니다.

잠시후에, Status.Connect()와 같은 status가 반환되어 옵니다.

 

-       OPJoin()함수를 호출해 게임 접속을 요청하면 잠시후에, LiteOpCode.Join라는 OpCode와 함께 OperationResult()함수가 호출됩니다.

 

-       참고로, 이벤트는 evcode라는 타입으로 IPhotonPeerListener.EventAction() 함수에서 호출됩니다.

 

-      게임에서 나갈려고 한다면, LitePeer.OpLeave()를 호출하면 게임에서 나가게 되고 잠시후에 OperationResult()함수에서 LiteOpCode.Leave 메세지가 반환되어 돌아온다면 Room을 나오는 것에 성공한 것입니다.

 

자세한 구현은 아래 코드를 참고 하시면 됩니다.

 

[코드] 16. PhotonClient.cs

using System;

using System.Collections;

using System.Text;

using ExitGames.Client.Photon;

using UnityEngine;

public class PhotonClient : MonoBehaviour, IPhotonPeerListener

{

    protected LiteLobbyPeer Peer;

    public string ServerAddress = "localhost:5055";

    protected string ServerApplication = "LiteLobby";

public int SendIntervalMs = 100;

    private int NextSendTickCount = Environment.TickCount;

    public PeerStateValue LitePeerState { get { return this.Peer.PeerState; } }

    public ClientState State = ClientState.Disconnected;

    public int ActorNumber;

private StringBuilder DebugBuffer = new StringBuilder();

public string DebugOutput { get { return DebugBuffer.ToString(); } }

  

    public bool DebugOutputToConsole = true;

    public string OfflineReason = String.Empty;

    public enum ClientState : byte

    {

        Disconnected, Connected, InRoom

    }

    public virtual void Start()

    {

        this.Peer = new LiteLobbyPeer(this);

        this.Connect();

    }

    public virtual void Update()

    {

        if (Environment.TickCount > this.NextSendTickCount)

        {

            this.Peer.Service();

            this.NextSendTickCount = Environment.TickCount + this.SendIntervalMs;

        }

    }

    public virtual void OnApplicationQuit()

    {

        this.Peer.Disconnect();

    }

    internal virtual void Connect()

    {

        this.OfflineReason = String.Empty;

        // PhotonPeer.Connect() is described in the client reference doc: Photon-DotNet-Client-Documentation_v6-1-0.pdf

        this.Peer.Connect(this.ServerAddress, this.ServerApplication);

    }

    public void DebugReturn(DebugLevel level, string message)

    {

        this.DebugReturn(message);

    }

 

    public void DebugReturn(string message)

    {

        this.DebugBuffer.AppendLine(message);

        if (this.DebugOutputToConsole)

        {

            Debug.Log(message);

        }

    }

    public virtual void OperationResult(byte opCode, int returnCode, Hashtable returnValues, short invocID)

    {

        this.DebugReturn(String.Format("OperationResult: {0}={1}", opCode, returnCode));

        switch (opCode)

        {

            case (byte)LiteOpCode.Join:

                this.State = ClientState.InRoom;

                this.ActorNumber = (int)returnValues[(byte)LiteOpKey.ActorNr];

                break;

            case (byte)LiteOpCode.Leave:

                this.State = ClientState.Connected;

                break;

        }

    }

    public virtual void PeerStatusCallback(StatusCode statusCode)

    {

        this.DebugReturn(String.Format("PeerStatusCallback: {0}", statusCode));

        switch (statusCode)

        {

            case StatusCode.Connect:

                this.State = ClientState.Connected;

                break;

            case StatusCode.Disconnect:

                this.State = ClientState.Disconnected;

                this.ActorNumber = 0;

                break;

            case StatusCode.ExceptionOnConnect:

                this.OfflineReason = "Connection failed.\nIs the server online? Firewall open?";

                break;

            case StatusCode.SecurityExceptionOnConnect:

                this.OfflineReason = "Security Exception on connect.\nMost likely, the policy request failed.\nIs Photon and the Policy App running?";

                break;

            case StatusCode.Exception:

                this.OfflineReason = "Communication terminated by Exception.\nProbably the server shutdown locally.\nOr the network connection terminated.";

                break;

            case StatusCode.TimeoutDisconnect:

                this.OfflineReason = "Disconnect due to timeout.\nProbably the server shutdown locally.\nOr the network connection terminated.";

                break;

            case StatusCode.DisconnectByServer:

                this.OfflineReason = "Timeout Disconnect by server.\nThe server did not get responses in time.";

                break;

            case StatusCode.DisconnectByServerLogic:

                this.OfflineReason = "Disconnect by server.\nThe servers logic (application) disconnected this client for some reason.";

                break;

            case StatusCode.DisconnectByServerUserLimit:

                this.OfflineReason = "Server reached it's user limit.\nThe server is currently not accepting connections.\nThe license does not allow it.";

                break;

            default:

                this.DebugReturn("StatusCode not handled: " + statusCode);

                break;

        }

    }

    public virtual void EventAction(byte eventCode, Hashtable photonEvent)

    {

        this.DebugReturn(String.Format("EventAction: {0}", eventCode));

    }

}

 

포톤은 클라이언트와 서버간의 패킷을 3가지 Level로 나눠놨습니다. 그래서 각 레벨마다 중요도와 우선순위를 두어서 작동됩니다. 3가지 Level을 살펴보면 아래와 같습니다.

 

Low Level : 서비스, 연결, 끊김등과 같은 네트워크상태에 따른 서버와의 상태에 대한 Level입니다. 이 레벨은 UDP/TCP패킷을 모두 사용해서 명령을 전송합니다. 이것은 연결유지와 RPC 호출 등이 레벨에 속합니다.

 

Logic Level : Operation과 그 결과값, Event와 그 결과값등이 이 레벨에 속합니다. 포톤의 로직 처리에 필요한 레벨이며 Operation은 결과값(리턴값)이 있는 패킷이며 Event는 클라이언트의 특정 상태 업데이트등에 쓰이는 패킷 입니다.

 

Application Level : 특정 응용프로그램에 필요한 것과 특징적인 패킷들의 레벨입니다. 주로 Room이나 Actor들에 대한 패킷등을 이 레벨로 분류합니다.

 

반드시 Low Level이 항상 중요한 것만은 아닙니다만, Low Level 패킷 , Status Command등을 사용하면 내부적으로 연결을 유지시켜주고 패킷 손실이 일어났을 경우 재전송등이 이루어지게 되므로 중요한 패킷은 Low Level에서 이루어지는 것이 좋습니다.

 

모든 Operation 패킷(RPC호출) Prefix로써 Op가 붙습니다.

다른 서버에만 있는 함수를 호출할 경우 다른 파라메터와 다른 값을 돌려 받을수 있습니다. Operation 패킷은 클라이언트 라이브러리엔 포함되어 있진 않지만 OpCustom()함수를 호출해서 구현할 수 있습니다.

 

, IPhotonPeerListener 인터페이스들은 콜백들을 위하여 반드시 따로 구현을 해야합니다.

IPhotonPeerListener 인터페이스의 종류는 아래와 같습니다.

-       PeerStatusCallback() Peer 상태가 변할때(연결,끊김,에러,상태코드 변경) 사용되는 콜백 함수입니다.

-       OperationResult() Operation을 위한 콜백 함수입니다(Join,Leave,등등)

-       EventAction()은 이벤트를 위한 콜백 함수입니다.

-       DebugReturn()함수는 디버그 출력을 위한 콜백 함수입니다.

 

4.   포톤의 실제 서버 개발 후기

[그림]23. P 프로젝트의 Screen Shot

 

위의 스크린샷은 필자가 2010년 여름에 만든 게임 프로젝트로써 아이폰용 MMORPG게임프로젝트 입니다. 유니티엔진과 포톤서버를 사용해서 온라인게임을 제작했고 그 게임을 멀티 플레이 테스트하는 화면입니다.

 

비록 프로토타입 화면이지만 클라이언트와 서버를 모두 담당해서 개발했기 때문에 개발하면서 느낀점이나 노하우등을 소개해보려 합니다.

 

포톤으로의 서버 개발

C#으로 만든 서버?

포톤으로 서버를 개발하게 된다면 C#으로 프로그래밍을 하게 됩니다. 아마 기존에 서버프로그래밍을 전문적으로 담당하셨던 분이라면 C# 서버에 대해서 부정적인 생각이 있을 수 있습니다. C/C++보단 느리고 메모리 관련 문제나 패킷 최적화등을 어떻게 해결할 것인지, 수많은 데이터와 엄청나게 빠른 로직 처리를 해야 하는 게임 서버로썬 프로그래밍 언어에서 오는 네이티브 코드(C/C++)보다 느리다라는 제약까지 업고 개발하는 것은 어떻게 보면 서버프로그래밍의 기본 철칙을 어기는 것이라고 할 수 있기 때문입니다.

하지만 실제 개발을 해보니 C# 게임서버의 성능(Performance)은 서버시스템의 하드웨어의 많은 발전으로 인해 C/C++로 서버를 만들었을 때와 크게 다르지 않은 성능을 내는 것을 확인할 수 있었습니다. (포톤의 경우엔 소켓 통신 Core System C/C++로 구축이 되어 있습니다.)

그리고 성능이 약간 느리더라도 단점을 커버할 수 있는 많은 장점이 있었습니다.

장점 중 하나는 닷넷(.Net)이 제공하는 수많은 편리한 라이브러리를 가져다 쓸 수 있습니다. 쉽고 간단하면서도 빠른 DB 관리 라이브러리들과 LINQ등을 사용할 수 있습니다.

LINQ(Language Integrated Query )는 통합언어 쿼리로 .NET Framework 3.5에 포함되어 있습니다. LINQ to Object, LINQ to SQL, LINQ to XML로 크게 나눌 수 있는데 이는 데이터베이스, XML 등등 개체화하는 데이터 소스에 대해서 전반적으로 쉽게 질의 할 수 있게 제공합니다.

LINQ는 집계 연산을 수행하는 함수를 기본적으로 제공합니다. 기본함수에는 Count, Sum, Min/Max, Average, Aggregate 등등이 있습니다.

아무튼 이 LINQ를 이용해 게임 서버의 필수불가결인 수많은 게임데이터들의 Asset 파일인 XML, EXCEL 등의 파일에 쿼리문을 날려서 원하는 데이터만 추출, 저장, 가공이 쉽게 가능해 집니다. 이는 복잡한 게임 서버일수록 사용하기에 따라 엄청난 효용성을 가지고 있습니다.물론 Asset파일뿐만 아니라 DB SQL도 마찬가지입니다.

또 장점중 하나는 Window OS에서 제공하는 기능들을 쉽게 사용할 수 있다는 것입니다.

포톤의 예를 들면 아래와 같이 Window OS에서 제공하는 성능 모니터를 사용해서 서버 시스템의 성능에 대한 것을 자세히 쉽게 확인할 수 있습니다.

[그림] 24. 포톤의 성능모니터 어플을 실행

 

포톤 서버에서 성능 모니터를 실행시킨후

 

[그림] 25. 윈도우의 성능 모니터 실행 화면

 

위와 같이 현재 서버시스템의 성능을 쉽게 확인 할 수 있습니다.

 

이는 기존의 서버 프로그래밍에서 서버시스템의 운영툴이나 서버 시스템 관리툴을 별도로 제작하지 않아도 되는 이점이 있어, 서버 개발시에 많은 개발시간 단축이나 프로파일링을 통해 성능향상을 시킬 수도 있습니다.

이뿐만 아니라 .Net에서 제공하는 SMTP (simple mail transfer protocol)를 사용해서 서버 시스템의 문제가 있거나 특정 서버 시스템에 과부하가 걸렸을 경우 운영자의 메일이나 운영팀 메일로 현재 상황을 전송하여 원격으로 떨어져 있는 서버 운영팀들이 서버의 상태등을 쉽게 받아 볼수 있는 시스템을 쉽게 구축할 수 있습니다.

 

위와 같은 내용은 사실 C/C++서버에서도 불가능 한 것은 아니고 기존에 많은 사람들이 개발해 놓은 공개되어있는 좋은 라이브러리들을 가져다 써서 개발이 가능한 부분입니다, 하지만 그런 라이브러리들을 가져다 쓸 경우 기능 하나하나를 테스트 해야 하고 검증 단계를 거쳐야 하므로 쉽게 공짜로 된다고는 말할 수 없습니다. 그래서 .Net Framework의 기능들을 사용할 경우 이미 1억이 넘는 수많은 개발자들의 검증/수정 단계를 거친 라이브러리들이므로 최소한 커다란 문제는 없을 거라는 확신을 갖고 게임 서버 개발에 사용할 수 있습니다.

 

유니티와 포톤?

앞에 말씀 드렸다시피 포톤의 경우 C#으로 코딩을 하게 됩니다. 만약 유니티에서 C#스크립트를 사용하셨다면 클라이언트와 서버의 코드 공유가 가능하게 됩니다. 저 같은 경우에도 유니티에선 C#스크립트를 사용했고 서버의 로직의 경우 같은 언어(C#)를 사용함으로 인해 클라이언트와 서버에서의 똑같은 코드 공유가 가능했습니다. 뿐만 아니라 유니티의 네트워크뷰(NetworkView)와 포톤의 서버를 같이 사용했습니다. 같은 던전안에 있는 플레이어들끼리의 위치정보는 클라이언트들끼리의 P2P로써 네트워크뷰를 사용했고 만약 P2P연결이 불가능할시엔 포톤으로 만든 게임 서버를 통해 위치 정보를 공유했습니다. 아이템 구매나 캐릭터의 성장과 같은 중요한 데이터는 포톤서버를 사용했고 , 이벤트 연출이나 애니메이션 변경등은 유니티의 네트워크뷰를 사용했습니다. DB 읽고 쓰기는 포톤서버를 통해서, 이벤트 트리거나 RPC등은 유니티 네트워크뷰를 통해서 활용했습니다.

이렇게 개발을 해보니 생각보다 많은 게임서버의 개발시간을 단축할 수 있었습니다. 맨 처음 보여드렸던 스크린샷 이미지의 프로토타입의 클라이언트+서버 개발에 들어간 시간이 총 6주 입니다. 수정/변경 시간을 뺀 순수 개발시간은 3~4주 정도밖에 걸리지 않았다면 얼마나 많은 개발시간을 단축 할 수 있었는지 가늠하실 수 있을 거라 여깁니다.

 

결론

서두에 말씀 드렸다시피 향후 게임 개발에 있어서 네트워크와 멀티플레이는 필수불가결이 될 것입니다. 이는 수많은 콘솔,패키지 개발사들이 네트워크 시스템 개발에 관심을 갖게 된 배경이기도 합니다.

비록 이곳에선 리뷰와 사용법 소개 등에 그쳤지만 인터넷에 존재하는 수많은 정보와 소스코드들을 참고하신다면 좋은 네트워크 게임을 만드실 수 있을 거라 생각됩니다.

집필을 시작할때만 해도 유니티에서의 네트워크를 소개하고자 큰 포부를 갖고 시작했으나 책의 페이지 구성상 깊은 내용을 다룰 수 없는 점이 있었습니다. 그리고 지금 시점에도 빠르게 변화하고 있는 네트워크 기술들이다 보니, 세세한 설명을 할 수 없었습니다, 그래도 유니티에서 네트워크 프로그래밍을 처음 시작하는 사람들에게 미약하나마 도움이 되었으면 좋겠습니다.

 

주세영

babochuse@nate.com

 

참고 문서나 링크

http://unity3d.com/support/documentation/ScriptReference/index.html

http://www.smartfoxserver.com/

http://www.exitgames.com/

http://www.sqler.com/390183?WT.mc_id=soc-c-kr-loc-w-dw

http://100.naver.com/100.nhn?docid=719227

 






실전 Unity3D Engine Programing 과정 7일차 (2013.07.30 (화))



- 어제 했던 작업 환경에서 추가한다.


오늘은 몬스터를 잡아 보자.

몬스터

충돌

기사에게 달려 오는 것


몬스터와 기사의 거리를 찾고 노말라이제이션 해서 방향 백터로 변경하고 기사에게 달려온다.

기사는 칼로 몬스터를 치는 것이다.


카메라의 파란색이 포워드 백터이다.

포워드 방향의 수직의 직교하는 백터를 구해서 좌우 앞뒤로 이동하게 했다.




캐릭터 이동

- 무브

  이동하면서 충돌까지 채크한다.

- 심플무브

   충돌 없이 이동만 한다.



카메라를 중심으로 좌우 이동한다.


캐릭터를 애니메이션 시킨다.

- 애니메이션의 변환시 블랜딩 처리해서 자연스럽게 한다.

- 애니메이션 클립파일을 변경하기에 코드 변경없이 처리 할 수 있다.


캐릭터를 돌게한다.

- 트랜스폼의 포워드 백터로 움직임까지 변경한다.



## Gravity처리

- 땅에 떨어져 있으면 떨어 뜨린다.

- 터랜의 콜라이더가 없으면 무한정 떨어질 것이다.



using UnityEngine;

using System.Collections;


public class Character_Control : MonoBehaviour {

public float MoveSpeed = 5.0f;

public float RotateSpeed = 500.0f;

public float VerticalSpeed = 0.0f;

private float gravity = 9.8f;

private CharacterController charactercontroller;

private Vector3 MoveDirection = Vector3.zero;

private CollisionFlags collisionflags;

public AnimationClip idleAnim;

public AnimationClip walkAnim;

public AnimationClip attackAnim;

public enum CharacterState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

SKILL = 3,

SIZE

}

private CharacterState state = CharacterState.IDLE;

// Use this for initialization

void Start () {

charactercontroller = GetComponent<CharacterController>();

animation.wrapMode = WrapMode.Loop;

animation.Stop();

}

// Update is called once per frame

void Update () {

//아래내용은순서대로한것이다.

Move ();

CheckState();

AnimationControl();

BodyDirection();

ApplyGravity();

}

bool isGrounded()

{

//기본제공함수도있지만 성능이좋지않다.

return (collisionflags & CollisionFlags.CollidedBelow) != 0;  //앤드연산.

}

// 중력에의해서떨어지게한다.

void ApplyGravity() 

{

if(isGrounded() == true)

{

VerticalSpeed = 0.0f;

} else 

{

//on air 떨어뜨리겠다는것이다.

VerticalSpeed -= gravity * Time.deltaTime;

}

}

void BodyDirection()

{

//벨로시티는속도이면서방향이다.

Vector3 horizontalVelocity = charactercontroller.velocity;

horizontalVelocity.y = 0.0f;

if(horizontalVelocity.magnitude > 0.0f)

{

Vector3 trans = horizontalVelocity.normalized;

Vector3 wantedVector = Vector3.Lerp(transform.forward, trans, 0.5f);

if(wantedVector != Vector3.zero)

{

transform.forward = wantedVector;

}

}

}

void AnimationControl()

{

switch(state)

{

case CharacterState.IDLE:

animation.CrossFade(idleAnim.name);

break;

case CharacterState.WALK:

animation.CrossFade(walkAnim.name);

break;

case CharacterState.ATTACK:

break;

}

}

void CheckState()

{

if(charactercontroller.velocity.sqrMagnitude > 0.1f)

{

//move

state = CharacterState.WALK;

}else

{

//stand

state = CharacterState.IDLE;

}

}

void Move()

{

Transform cameraTransform = Camera.mainCamera.transform;

Vector3 forward = cameraTransform.

TransformDirection(Vector3.forward);

forward = forward.normalized; //normal vector 1 vector

Vector3 right = new Vector3(forward.z ,0.0f,-forward.x);

float v = Input.GetAxisRaw("Vertical");

float h = Input.GetAxisRaw("Horizontal");

Vector3 targetVector = v *forward + h * right;

targetVector = targetVector.normalized; //normal vector

MoveDirection = Vector3.RotateTowards(MoveDirection,

targetVector,RotateSpeed*Mathf.Deg2Rad*Time.deltaTime,500.0f);

MoveDirection = MoveDirection.normalized;

//중력값적용.

Vector3 grav = new Vector3(0.0f, VerticalSpeed, 0.0f);

Vector3 movementAmt = MoveDirection * MoveSpeed * Time.deltaTime + grav;

collisionflags = charactercontroller.Move(movementAmt);

}

void OnGUI()

{

GUI.color = Color.red;

GUI.Label(new Rect(10,10,100,20),"state : "+state.ToString());

GUI.Label(new Rect(10,30,100,20),"flag :"

+collisionflags.ToString());

}

}


## 어택

- 마우스왼쪽 버튼으로 정의 한다.

- 공격애니메이션 다시 아이들

- 한번만 사용

- 공격 -> 아이들로 이동시 보간

- 애니메이션이 끝나면 아이들로 돌아가야 하낟.
  애니메이션은 노마라이제이션타임이 변화된다. 90%면 변경한다.

- 이동중에 공격을 하려면, 애니메이션이 복합 동작을 하기에 이상하다.

   이런 부분은 디자이너와 함께 상의해야 한다.



using UnityEngine;

using System.Collections;


public class Character_Control : MonoBehaviour {

public float MoveSpeed = 5.0f;

public float RotateSpeed = 500.0f;

public float VerticalSpeed = 0.0f;

private float gravity = 9.8f;

private CharacterController charactercontroller;

private Vector3 MoveDirection = Vector3.zero;

private CollisionFlags collisionflags;

public AnimationClip idleAnim;

public AnimationClip walkAnim;

public AnimationClip attackAnim;

public enum CharacterState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

SKILL = 3,

SIZE

}

private CharacterState state = CharacterState.IDLE;

// Use this for initialization

void Start () {

charactercontroller = GetComponent<CharacterController>();

animation.wrapMode = WrapMode.Loop;

animation.Stop();

animation[attackAnim.name].wrapMode = WrapMode.Once; //재생모드는1회만하게한다.

animation[attackAnim.name].layer = 1;  //  복합동작시 이레이어설정으로동작의비중이높다.

}

// Update is called once per frame

void Update () {

//아래내용은순서대로한것이다.

Move ();

CheckState();

AnimationControl();

BodyDirection();

ApplyGravity();

}

bool isGrounded()

{

//기본제공함수도있지만 성능이좋지않다.

return (collisionflags & CollisionFlags.CollidedBelow) != 0;  //앤드연산.

}

// 중력에의해서떨어지게한다.

void ApplyGravity() 

{

if(isGrounded() == true)

{

VerticalSpeed = 0.0f;

} else 

{

//on air 떨어뜨리겠다는것이다.

VerticalSpeed -= gravity * Time.deltaTime;

}

}

void BodyDirection()

{

//벨로시티는속도이면서방향이다.

Vector3 horizontalVelocity = charactercontroller.velocity;

horizontalVelocity.y = 0.0f;

if(horizontalVelocity.magnitude > 0.0f)

{

Vector3 trans = horizontalVelocity.normalized;

Vector3 wantedVector = Vector3.Lerp(transform.forward, trans, 0.5f);

if(wantedVector != Vector3.zero)

{

transform.forward = wantedVector;

}

}

}

void AnimationControl()

{

switch(state)

{

case CharacterState.IDLE:

animation.CrossFade(idleAnim.name);

break;

case CharacterState.WALK:

animation.CrossFade(walkAnim.name);

break;

case CharacterState.ATTACK:

if(animation[attackAnim.name].normalizedTime > 0.9f)

{

// 어택애니메이션이거의완료된시점.

animation[attackAnim.name].normalizedTime = 0.0f;

state = CharacterState.IDLE;

} else 

{

animation.CrossFade(attackAnim.name);

}

break;

}

}

void CheckState()

{

if(state == CharacterState.ATTACK)

{

return;

}

if(charactercontroller.velocity.sqrMagnitude > 0.1f)

{

//move

state = CharacterState.WALK;

}else

{

//stand

state = CharacterState.IDLE;

}

if(Input.GetMouseButtonDown(0)) //마우스 0:왼쪽 1:오른쪽 2:휠. 

{

state = CharacterState.ATTACK;

}

}

void Move()

{

Transform cameraTransform = Camera.mainCamera.transform;

Vector3 forward = cameraTransform.

TransformDirection(Vector3.forward);

forward = forward.normalized; //normal vector 1 vector

Vector3 right = new Vector3(forward.z ,0.0f,-forward.x);

float v = Input.GetAxisRaw("Vertical");

float h = Input.GetAxisRaw("Horizontal");

Vector3 targetVector = v *forward + h * right;

targetVector = targetVector.normalized; //normal vector

MoveDirection = Vector3.RotateTowards(MoveDirection,

targetVector,RotateSpeed*Mathf.Deg2Rad*Time.deltaTime,500.0f);

MoveDirection = MoveDirection.normalized;

//중력값적용.

Vector3 grav = new Vector3(0.0f, VerticalSpeed, 0.0f);

Vector3 movementAmt = MoveDirection * MoveSpeed * Time.deltaTime + grav;

collisionflags = charactercontroller.Move(movementAmt);

}

void OnGUI()

{

GUI.color = Color.red;

GUI.Label(new Rect(10,10,100,20),"state : "+state.ToString());

GUI.Label(new Rect(10,30,100,20),"flag :"

+collisionflags.ToString());

}

}


## 기사의 콜라이더 적용

하이어아키에서 kinght를 선택한다. 

   - 인스팩토에서 Tag를 player Tag를 지정한다.

- menu > GameObject > Create other >  Share를 추가한다. 

   - 구하나가 생기는데 이것을 통해 충도 체크 한다.

   - 포지션은 0.0.0

   - Mesh Renderer는 제거한다. 

   - 신창에서 보면, 

- Share 선택하고 Tag에서 Add Tag하고 Element는 sword명으로 추가하고 이것으로 지정한다.

   - 콜라이더는 트리거 체크한다. 

- Bone_R_weapon 를 선택하고 여기에 share를 차일드로 붙인다.

- Sphere  이름은 AttacCol

   포지션 0..0.0

   스케일 1.1.1

   라디오스 0.05

- 신화면에서 AttackCol을 마우스로 잡아서 창끝에 붙여 주도록 한다.

- 자료로 제공한 이팩트 패키지를 추가한다.

- 추가가 완료되면 AttackCol를 선택하고 menu > Component > Effects > Trail Renderer를 선택한다. 

   - AttackCol 에 붙은 Trail Renderer 값을 수정한다.  Time: 0.25, Start Width 1, End Width 0.1

   - Meterials > Elements 0의 오른쪽 끝의 동그라면 버튼을 누르고 BPShork 이팩트를 선택한다.




이팩트가 마음에 안들면 스토어에서 Melee Weaphon을 찾아서 설치해 봐라.



칼끝에 AttackCol을 붙이기가 쉽지 않다.

이럴때는 포지션을 0.0.0  으로 하고 Attackcol의 파란색 방향타를 잡고 이동시킨다.

그래도 약간 틀릴 수가 있다. 로테이션값도 0.0.0으로 조절하고 다시 방향타를 잡고 이동하면서 맞추면 가능하다.






@@TIP

유니티 IDE를 두개 띄우는 방법

menu > edit > preference > Always Show Project Wizard 를 체크한다. / 대신 폴더를 구분해야 한다. 



## 몬스터를 만들자

- Kinght의 Tag는 Player로 설정한다.

- AttactCol의 리지디바디를 추가한다.

   - Use Gravity를 언체크한다.


- 아셋에 poison_beetle을 꺼내 다.

- poison_beetle 의 정보를 변경한다.

   - 포지션 3.0.-5

   - 스케일 10,10,10

- 비틀에게 콜라이더를 붙이자.

  menu > component > Phisics > Box Collider

  -값을 Size : 0.1, 0.1, 0.1

    Center 0, 0.1, 0

- 스크립트 만든다. Beetle_Control



using UnityEngine;

using System.Collections;


public class Beetle_Control : MonoBehaviour {

public Vector3 targetPos = Vector3.zero;

public float MoveSpeed = 5.0f;

public GameObject HitEffect;

public GameObject DeadEffect;

//아래코드는공통코드라서한곳에모아서사용한다.

public enum BeetleState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

HIT = 3,

SIZE

}

private BeetleState state = BeetleState.IDLE;


// Use this for initialization

void Start () {

animation.wrapMode = WrapMode.Loop;  //반복.

//다짜고짜실행무브.

animation.Play("move");

}

// Update is called once per frame

void Update () {

SearchTarget();

//거리를구하고방향을구하고이동시킨다.

Vector3 currentPos = transform.position;

Vector3 diffPos = targetPos - currentPos;

transform.Translate(diffPos * Time.deltaTime * MoveSpeed, Space.World); //전체월드에서구한다.

}

void SearchTarget()

{

//player의포지션을얻어와서쫒아다니도록한다.

GameObject target = GameObject.FindWithTag("Player");

targetPos = target.transform.position;

}

}

- Beetle에 스크립트를 붙인다.

- DynamicElements_Effects 패키지를 임포트한다.

- Beetle를 선택하고

   아셋에서 SimpleHitEffect을 찾아서 Beetle의 스크립트 Hit Effect에 붙인다.

   아셋에서 FireExplosion을 찾아서  Beetle의 스크립트 Hit Effect에 붙인다.


- 다시 스크립트로 가자. 칼에 부딧히면 이팩트를 발생시키고 목숨이 달면 터치는 이팩트를 발생시킨다.

using UnityEngine;

using System.Collections;


public class Beetle_Control : MonoBehaviour {

public Vector3 targetPos = Vector3.zero;

public float MoveSpeed = 5.0f;

public GameObject HitEffect;

public GameObject DeadEffect;

//아래코드는공통코드라서한곳에모아서사용한다.

public enum BeetleState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

HIT = 3,

DEATH = 4,

SIZE

}

private BeetleState state = BeetleState.IDLE;


// Use this for initialization

void Start () {

animation.wrapMode = WrapMode.Loop;  //반복.

//다짜고짜실행무브.

animation.Play("move");

}

// Update is called once per frame

void Update () {

SearchTarget();

//거리를구하고방향을구하고이동시킨다.

Vector3 currentPos = transform.position;

Vector3 diffPos = targetPos - currentPos;

//길이가짧으면.

if(diffPos.magnitude < 2.0f)

{

return;

}

diffPos = diffPos.normalized;

transform.Translate(diffPos * Time.deltaTime * MoveSpeed, Space.World); //전체월드에서구한다.

//Player를바라보게한다.

transform.LookAt(targetPos);

}

void SearchTarget()

{

//player의포지션을얻어와서쫒아다니도록한다.

GameObject target = GameObject.FindWithTag("Player");

targetPos = target.transform.position;

}

private int BeetleLife = 10;

void OnTriggerEnter(Collider other)

{

Debug.Log(">> OnTriggerEnter()");

if(other.gameObject.tag == "sword")

{

Debug.Log("++ OnTriggerEnter() Hit BeetleLife"+ BeetleLife);

state = BeetleState.HIT;

//Instantiate(HitEffect, transform.position, transform.rotation);

Instantiate(HitEffect, other.transform.position, transform.rotation);

BeetleLife--;

if(BeetleLife == 0) 

{

Debug.Log("++ OnTriggerEnter() Death" );

state = BeetleState.DEATH;

Instantiate(DeadEffect, other.transform.position, transform.rotation);


Destroy(gameObject);

}

}

}

}



@@TIP

public 으로 선언하게되면...

1. 선언.

2. 인스팩트값

3. start() 코드에 선언한다.

* 따라서 스테이트값은 private로 하는게 좋다.



## 카메라 처리를 한다.

- 3인칭 카메라.. 댐핑카메라, 와우카메라 같은 것...

- 방법

  - player에 뒤로 우리는 원하는 거리만큼 빼고 회전하는 만큼 회전하고 원하는 만큼 높여 준다.

  - 이렇게 하면 케릭터를 바라본다.

-  스크립트 Camera_Control을 만든다. (3인칭)

using UnityEngine;

using System.Collections;


public class Camera_Control : MonoBehaviour {

//Third view point var

public float distance = 10.0f;

public float height = 5.0f;

public float heightDamping = 2.0f;

public float distanceDamping = 3.0f;

public GameObject target; //player

public enum CameraViewPoint {FIRST =0, SECOND =1, THIRD =2, SIZE};

public CameraViewPoint current = CameraViewPoint.THIRD;


// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}



- 한박자 늦은 업데이트.. 캐릭터 움직이고 나서 카메라가 움직이게 한다. 

  업데이트가 모두 끝난 후에 호출되는 함수 

void LastUpdate()

{

switch(current)

{

case CameraViewPoint.THIRD:

ThirdView();

break;

case CameraViewPoint.SECOND:

break;

case CameraViewPoint.FIRST:

break;

}

}

void ThirdView()

{

}

}


이제 타켓을 찾고 플레이어의 방향을 회전한다.


public class Camera_Control : MonoBehaviour {

//Third view point var

public float distance = 10.0f;

public float height = 5.0f;

public float heightDamping = 2.0f;

public float rotationDamping = 3.0f;

public GameObject target; //player

public enum CameraViewPoint {FIRST =0, SECOND =1, THIRD =2, SIZE};

public CameraViewPoint current = CameraViewPoint.THIRD;

void LastUpdate()

{

switch(current)

{

case CameraViewPoint.THIRD:

ThirdView();

break;

case CameraViewPoint.SECOND:

break;

case CameraViewPoint.FIRST:

break;

}

}

void ThirdView()

{

if(target == null)

{

//타켓이없으면찾는다.

target = GameObject.FindWithTag("Player");

} else

{

float wantedRoationAngle = target.transform.eulerAngles.y;

float wantedHeight = target.transform.position.y + height;

//현재내각도와높이는연결해준다. 카메라의 값이다. 보관한다.

float currentRotationAngle = transform.eulerAngles.y;

float currentHeight = transform.position.y;

//LerpAngle를통해서보간한다.

currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRoationAngle, rotationDamping * Time.deltaTime);

currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);

//각도.

Quaternion currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);

//player position

transform.position = target.transform.position;

//move back

transform.position -= currentRotation * Vector3.forward * distance;

transform.position = new Vector3(transform.position.x, currentHeight, transform.position.z);

transform.LookAt(target.transform);

}

}

}


플래이어



- 엠프티오브젝트 만들고 Spawner를 만든다

- 아셋에 Prefab폴더 만들고 Prefabs 만든다. 그리고 Beelte를 여기에 담는다.

- 스크립트 Spawner_Control를 만든다.

using UnityEngine;

using System.Collections;


public class Spawner_Control : MonoBehaviour {

public float SpawnTime = 1.0f;

public float LastSpawnTime;

public GameObject monster;


// Use this for initialization

void Start () {

LastSpawnTime = Time.time;

}

// Update is called once per frame

void Update () {

if(Time.time > LastSpawnTime + SpawnTime)

{

LastSpawnTime = Time.time;

Vector3 pos = new Vector3(transform.position.x + Random.Range(-5.0f, 5.0f), transform.position.y, transform.position.z + Random.Range(-5.0f, 5.0f));

Instantiate(monster, pos, transform.rotation);

}

}

}


Spawner오프젝트에 스크립트를 붙이고 Monster에 Prefab을 붙인다.


벌래가 계속 나오는 것을 볼 수 있다.



실전 Unity3D Engine Programing 과정 6일차 (2013.07.29(월))


Animation.pptx



금일

- 물리

- 세이더.

- 등등..



아틀라스 : 여러개의 이미지를 하나의 이미지로 모은다. 2048x2048, 안전하게 1024x1024가 좋다
드로우컬은 아틀라스의 갯수로 파악이 가능하다.

스크린 사이즈는 절대값을 넣으면 안된다.
화면의 꼭지점의 기준으로 앵커 형태로 개발해야 한다.

핫트윈, 알트윈을 돌려 보았다.

EGGUI - NGUI - TOOLGUI


## 애니메이션에 대해서
- 사용방법, 캐릭터 적용시 사용방법..

The Animation System supports
animation blending,
블랜딩: 또는 믹싱. 서서이 맞추어준다.
mixing,
믹싱: 해당 트랜스폼에 특정 애니메이션을 넣어주는 것이다. 하나만 만들면 다른 곳에도 붙인다.
additive animations,
애디팅 : .. 사용치 않는다.
walk cycle time synchronization,
애니메이션마다 재생 시간이 다르다. 이것을 노말라이즈하여서 동기화 해서 플레이 할 수 있다.
animation layers,
레이어 : 웨이트와 레이어의 비중을 많이 넣어 주면 애니메이션 썩을 때 비중으로 사용한다.
control over all aspects of the animation playback (time, speed, blend-weights),
mesh skinning with 1, 2 or 4 bones per vertex as well as supporting physically based rag-dolls and procedural animation. 
  손목.. 등 다 따라 올라 온다. 




3D애니메이션은 각 타임라인별로 포지션, 로테이션, 스케일까지 하나하나 숫자 값이 바뀌는 것이다.
숫자의 나열을 시간별로 노출 시킨다.
숫자만 뿌리면 느릴 이유가 없지만, 시작과 끝만 나열하고 나머지는 보간 처리(앞뒤것을)하기 때문이다.
CPU를 많이 잡아 먹는다. 움직이는 연산이다. 
실전에서는 많이 사용하지 않는다.



Clip < = 많이 사용한다. 
Stores keyframe based animations.
length Animation length in seconds (Read Only)
frameRate Frame rate at which keyframes are sampled (Read Only)
wrapMode Sets the default wrap mode used in the animation state.


Once
When time reaches the end of the animation clip, the clip will automatically stop playing.
한번재생
Loop
When time reaches the end of the animation clip, time will continue at the beginning.
반복재생
When time reaches the end of the animation clip, time will ping pong back between beginning and end.
왕복
Reads the default repeat mode set higher up.
Plays back the animation. When it reaches the end, it will keep playing the last frame and never stop playing
끝까지 간다.




State <== wrap 과 쌍벽

In most cases the Animation interface is sufficient and easier to use.

Variables
enabled Enables / disables the animation.
weight The weight of animation
wrapMode Wrapping mode of the animation.
time The current time of the animation
normalizedTime The normalized time of the animation.
speed The playback speed of the animation. 1 is normal playback speed.
normalizedSpeed The normalized playback speed.
length The length of the animation clip in seconds.
layer The layer of the animation. When calculating the final blend weights,   animations in higher layers will get their weights
clip The clip that is being played by this animation state.
name The name of the animation
blendMode Which blend mode should be used?





두개가 썩인다.
Void Update()
{

If(Input.GetAxis(“Vertical”) > 0.2f)

{

  animation.CrossFade(“walk”);

}else

{

  animation.CrossFade(“idle”);

}

}




필히 셋팅해야 한다. 
기본으로 Loop을 사용한다. 나머지는 하나하나 꺼내서 세팅한다. 
Void Start () {

  animation.wrapMode = WrapMode.Loop;

  animation["shoot"].wrapMode = WrapMode.Once;

  animation["shoot"].layer = 1;

  animation.Stop();  //무조건 한번 호출

}



실슴..
void Start () {

    Transform mixTransform;

    mixTransform = transform.Find("root/upper_body/left_shoulder"); animation["wave_hand"].AddMixingTransform(mixTransform);

}



애니메이션 이벤트 <== 특정함수에 특정 이벤트 호출

function PrintFloat (theValue : float) {

  Debug.Log ("PrintFloat is called with a value of " + theValue);

}


최적화

Use one Skinned Mesh Renderer
Your character should use only a single skinned mesh renderer.
한개의 스킨을 사용해야 한다. 차이들을 포함해서
Don't Use Many Materials
You also want to keep the number of materials on that mesh as low as possible.
여러개 사용하지 않는게 좋다.
Reduce Amount of Bones
Medium Desktop games use bone hierarchies with 15-60 bones. The fewer bones you use the faster; with 30 bones you can achieve very good quality on Desktop platforms and fairly good quality on Mobile Platforms..
30개의 본을 사용해라 37이상이면 성능 저하 심하게 발생 최대 35개 좋다.
발 4개 캐릭터는 매우 힘들다... 
Polygon Count
How many polygons you should use depends on the quality you require and the platform you are targeting. Anything between 300-1500 triangles on Mobile Platforms and 500-6000 triangles on Desktop Platforms is reasonable.
옛날 이야기이다. 6000개 이상 넘지 말아라.. 1500 ~ 6000개 이내
Separate Out IK and FK
Separate out inverse kinematics (IK) and forward kinematics (FK). When animations are imported, the IK nodes are baked into FK, thus Unity doesn't need the IK nodes at all.
FK는 지원하지 않는다. 계단 올라가는 뼈대... 지원하지 않는다.
Use Reusable Rigs
Create a rig which you can reuse. This allows you to share animations between different characters.
본 세팅만 같으면 남의 애니메이션을 재 사용할 수 있다. 
Name Bones Correctly

Name the bones correctly (left hip, left ankle, left foot etc.). Especially with characters, naming your bones correctly is very important.
BIP001 등 .. 이름을 바꾸지 않는다. 바이패드 이름 수정하지 마라..




퀄리티..

- Quality Settings.. 간단히 셋팅되어 있다.

- Blend Weights가 중요한 것이다.




## 애니메이션 실습을 해 보자 

1 New Project  > AnimationTest

2. 신 : test

3. 폴더: Scripts

4. AnimationExample 스크립트 만든다.

   - 함수 추가

     void EventTest(int i) 

{

Debug.Log("Event Test Click~ "+i.ToString());

}

5. 큐브 추가 

   포지션은 0.0.0  . 검개 나온다.

6. 빛 추가

   

7. Menu > Windows > Andimation 창을 연다.

- Cube를 선택하고 한다.






8. 애니메이션 창에서 Cube를 선택하고 NewAnimation를 만든다.

9. 레코드 버튼을 누르고 값을 변환해 보고 실행해 본다.


10. 아까 만든 AnimationExample 스크립틀 추가 한다. 




11. 애니메이션 창을 새로 열고 10번째 선택후 이벤트를 추가한다. (키 이벤트 삽입 버튼)

아래 이미지중 3번째 언더바가 키 이벤트 삽입 버튼 4번째 언더바가 해당 이벤트



12. 10번 프레임에서 이벤트를 클릭하면 아까지 만든 메소드명이 뜨고 그곳에 5값을 넣는다.

상기 이미지에서 4번째 언더바를 클릭하면 아래와 같은 창이 나온다.




13. 플레이 해 보면 콘솔에 다음 내용이 출력된다.

Event Test Click~ 5

UnityEngine.Debug:Log(Object)

AnimationExample:EventTest(Int32) (at Assets/Scripts/AnimationExample.cs:18)


이렇게 트랜스폼 중심으로 애니메이션과 스크립트를 만들 수 있다.


## 인터넷 유니티 사이트의 애니메이션 설명을 보자

http://docs.unity3d.com/Documentation/ScriptReference/index.html





## 캐릭터에 애니메이션 

1. New Scen > test 2

2. 오늘자 KnightandBeetle 리소스 패키지를 추가한다. 

- 기사

- 비틀이 있다.

3. 추가한 것에 Knight를 히어알키에 추가한다.

4. 스케일 값을 키우다. 10.10.10, 로테이트는 0.180.0 포지션은 0.0.-5

5. 라이트 추가

6. knight를 선택하고 menu > windodw > animation를 선택한다.

7. 각 동작별로 변경해 보며 플레이 해봐라. 애니메이션 클립 내용을 볼 수 있다.

- 32 프레임이다. 유니티는 .. 맥스랑 다르고, 언리얼은 60프레임 따라서 이창에서 확인해 보는게 좋다.


## 에니메이션과 손을 믹싱과 애드해 보자.

1. AnimationTest 스크립트 추가

using UnityEngine;

using System.Collections;


public class AnimationTest : MonoBehaviour {

public AnimationClip idleAnim;

public AnimationClip attackAnim;

public Transform mixingTransform;

// Use this for initialization

void Start () {


//기본적인것2개추가한다.

animation.wrapMode = WrapMode.Loop;

animation.Stop();

//mixing  해쉬맵에키값을넣으면스테이트값이나오고이것을믹싱한다.

animation[attackAnim.name].AddMixingTransform(mixingTransform);

//= animation["attack_01"] = AnimationState 

animation.CrossFade(attackAnim.name);

}

}


2.  knight에 스크립트를 추가한다.
3. knight의 애니메이션 항목을 펼치고 idle를 선택하면 좌측에 리소스가 노란색으로 노출된다. 
4. 해당 리소스를 선택해서 스크립트 해당 변수에 드래그드롭으로 붙여 준다.



5. kinght을 열어 보면 Bip001 Pelvis가 있고 이것을 knight 스크립트 믹싱 트랜스폼에 붙인다.




6. play해 본다.

7. 다른 것도 미싱트랜스폼에 넣어서 플래이 해봐라.



## Additive를 해보자

1. 스크립트를 수정하자. 이전에 코딩은 주석하고 추가한다.

using UnityEngine;

using System.Collections;


public class AnimationTest : MonoBehaviour {

public AnimationClip idleAnim;

public AnimationClip attackAnim;

public Transform mixingTransform;

// Use this for initialization

void Start () {


//기본적인것2개추가한다.

animation.wrapMode = WrapMode.Loop;

animation.Stop();

//mixing  해쉬맵에키값을넣으면스테이트값이나오고이것을믹싱한다.

//animation[attackAnim.name].AddMixingTransform(mixingTransform);

////= animation["attack_01"] = AnimationState 

//animation.CrossFade(attackAnim.name);

//additive

animation[attackAnim.name].blendMode = AnimationBlendMode.Additive;

animation[attackAnim.name].layer = 10;

animation[attackAnim.name].weight = 1.0f;

animation[attackAnim.name].enabled = true;

animation[idleAnim.name].wrapMode = WrapMode.Loop;

animation.Play(idleAnim.name);

}

}


2. knight의 Idle anim을 move 리소스를 붙여 본다.

3. play해 보면.. 뛰면서 칼을 휘두른다.

layer와 weight을 바꿔 보면 다르게 동작이 될 것이다.

save secen , save project



## RPG를 만들어 보자.

사전지식

- 지형 만들고

- 캐릭터 세우고

- 캐릭터 이동하면서 애니메이션 된다.



1. New Project > RPG

2. 신저장 > main
3. Direciton light 추가 한다.
4. menu > Terrain > create Terrain
5.menu > Terrain > set Resolution의 값을 반띵한다.
6. 포지션은 -500,0,-500
7. 앗셋에서 임포트패키지에서 터레인을 임포트한다.
8. 하이어키에서 터레인 선택하고 인스패트에서 붓모양의 아이콘을 클릭한다.
9. 애드 텍스쳐를 누른다. 그리고 셀랙트를 누른다. GoodDuct를 추가한다. 그리고  add한다.
10. 애드 텍스쳐를 누른다. 그리고 셀랙트를 누른다. Glass를 추가한다. 그리고  add한다.
11. 해당 클라스를 선택하고 신창에서 글을 써봐라. 그러면 잔디밭이 추가된다.
12. 테란을 선택하고 인스패트에서 첫번째 아이콘으로 언덕을 만든다. shit 아이콘 누르면 낮아 진다.

 


13. 터레인에서 5번째 아이콘으로 나무를 심어 보자.


Add 버튼으로 추가한후 땅을 클릭해 보면 나무가 추가된다. 



윈드로 설정할 수 있는데 


다른 아이콘은 잔디도 심을 수 있다.



##

1. KnightandBeetle 패키지 추가

2. Knight 를 추가 하낟. 포시션 0, 0, -5, 로테이션 0, 180, 0, 스케일 10,10,10

3. Kinght 선택 menu > component > phisice > charactor controler



값은  center 0, 0.1, 0  각도 0.05, 높이 0.2

4. 스크립트 추가 Charictor_control

캐릭터와 카메라의 관계에서 카메라가 바라보는 위치에 따라서 캐릭터의 방향이 달라진다.

카메라중심으로 캐릭터를 움직여야 한다.

카메라에서부터 시작해야 한다.

카메라가 어디에 있냐. 카메라의 앞방향의 캐릭터의 앞뒤 방향.. 좌우가 .. 

이것을 정확히 90를 구분하면 좌우가 된다.

백터는 방향을 갖는 두 힘이다. 이렇게 축이 된다.

카메라 포워드 (트랜스.포워드) 를 이용해서 캐릭터의 앞뒤, 직교하는 좌우를 구한다. 

두백터를 더하면 캐릭터의 이동이 나온다.

시간에 따라 방향만 구하면 된다.

이것을 백터의 노멀라이즈라고 한다.

길이가 있는 것을 방향을 구해 준다.




void Move() 

{

//카메라를구한다.

Transform cameraTransform = Camera.mainCamera.transform;

Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);

forward = forward.normalized; //normal vector 1 vector

Vector3 right = new Vector3(forward.z, 0.0f, -forward.x);

//좌우백터.

//움직인다.

}


여기서

Vector3 right = new Vector3(forward.z, 0.0f, -forward.x); // 이것의 의미는 


A.B = 0 일려면, 

AxBx + AyBy = 0;



완성된 스크립트

public float RotateSpeed = 500.0f;

public float VerticalSpeed = 0.0f;

public float gravity = 9.8f;

private CharacterController charactercontroller;

private Vector3 MoveDirection = Vector3.zero;

private CollisionFlags collisionflags;

// Use this for initialization

void Start () {

charactercontroller = GetComponent<CharacterController>();

}

// Update is called once per frame

void Update () {

Move();

}

void Move() 

{

//카메라를구한다.

Transform cameraTransform = Camera.mainCamera.transform;

//좌우백터. 축을구한다.

Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);

forward = forward.normalized; //normal vector 1 vector

//방향을바꿔져서직교이다.

Vector3 right = new Vector3(forward.z, 0.0f, -forward.x);

//움직인다.

float v = Input.GetAxisRaw("Vertical");

float h = Input.GetAxisRaw("Horizontal");

Vector3 targetVector = v * forward + h * right;

targetVector = targetVector.normalized; // normal vector

//벡터를 방향으로옮기기 위해서 방향을바꿔준다.

MoveDirection = Vector3.RotateTowards(MoveDirection, targetVector, RotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 500.0f);

MoveDirection = MoveDirection.normalized;

Vector3 movementAmt = MoveDirection * MoveSpeed * Time.deltaTime;

collisionflags = charactercontroller.Move(movementAmt);

}

}




이동시키는 코드를 스크립트로 짠것이다.

5. knight에 방금 만든 스크립트를 추가한다.



캐릭터를 캐릭터콘트롤을 쓰는게 좋다. 그렇지만 리니지바디보다는 무겁다. 

캐릭터일 때만 사용해라..

collisionflags를 통해서 이 값이 어디에 있는지 알 수 있다.

캐릭터콘터롤러는 기본적으로 그라비티가 없기에 추가해 주어야 한다.



## 애니메이션 시켜 보자


1. 스크립트에 아래 내용을 추가한다.

//애니메이션클립을가져오기위해서퍼블릭으로했다.

public AnimationClip idleAnim;

public AnimationClip walkAnim;

public AnimationClip attackAnim;

public enum CharacterState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

SKILL = 3,

SIZE

}

상태를 바꿔가면서 애니메이션을 바꿔준다.

스크립트를 빼는 기준은 중복된 코드는 스크립트로 뺀다.

// Update is called once per frame

void Update () {

Move();

CheckState();

AnimationControl();

}

void AnimationControl() 

{

//상태별로애니메이션을변경한다.

switch(state)

{

case CharacterState.IDLE:

animation.CrossFade(idleAnim.name);

break;

case CharacterState.WALK:

animation.CrossFade(walkAnim.name);

break;

case CharacterState.ATTACK:

break;

}

}

void CheckState()

{

//캐릭터콘트롤러는움직이면속도를알려준다

//길이 루크길이.

if(charactercontroller.velocity.sqrMagnitude > 0.1f)

{

//move

state = CharacterState.WALK; 

} else 

{

// stand

state = CharacterState.IDLE;

}

}


총..

using UnityEngine;

using System.Collections;


public class Charictor_control : MonoBehaviour {

public float MoveSpeed = 5.0f;

public float RotateSpeed = 500.0f;

public float VerticalSpeed = 0.0f;

public float gravity = 9.8f;

private CharacterController charactercontroller;

private Vector3 MoveDirection = Vector3.zero;

private CollisionFlags collisionflags;

//애니메이션클립을가져오기위해서퍼블릭으로했다.

public AnimationClip idleAnim;

public AnimationClip walkAnim;

public AnimationClip attackAnim;

public enum CharacterState

{

IDLE = 0,

WALK = 1,

ATTACK = 2,

SKILL = 3,

SIZE

}

private CharacterState state = CharacterState.IDLE;

// Use this for initialization

void Start () {

charactercontroller = GetComponent<CharacterController>();

animation.wrapMode = WrapMode.Loop;

animation.Stop();

}

// Update is called once per frame

void Update () {

Move();

CheckState();

AnimationControl();

}

void AnimationControl() 

{

//상태별로애니메이션을변경한다.

switch(state)

{

case CharacterState.IDLE:

animation.CrossFade(idleAnim.name);

break;

case CharacterState.WALK:

animation.CrossFade(walkAnim.name);

break;

case CharacterState.ATTACK:

break;

}

}

void CheckState()

{

//캐릭터콘트롤러는움직이면속도를알려준다

//길이 루크길이.

if(charactercontroller.velocity.sqrMagnitude > 0.1f)

{

//move

state = CharacterState.WALK; 

} else 

{

// stand

state = CharacterState.IDLE;

}

}

void Move() 

{

//카메라를구한다.

Transform cameraTransform = Camera.mainCamera.transform;

//좌우백터. 축을구한다.

Vector3 forward = cameraTransform.TransformDirection(Vector3.forward);

forward = forward.normalized; //normal vector 1 vector

//방향을바꿔져서직교이다.

Vector3 right = new Vector3(forward.z, 0.0f, -forward.x);

//움직인다.

float v = Input.GetAxisRaw("Vertical");

float h = Input.GetAxisRaw("Horizontal");

Vector3 targetVector = v * forward + h * right;

targetVector = targetVector.normalized; // normal vector

//벡터를 방향으로옮기기 위해서 방향을바꿔준다.

MoveDirection = Vector3.RotateTowards(MoveDirection, targetVector, RotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 500.0f);

MoveDirection = MoveDirection.normalized;

Vector3 movementAmt = MoveDirection * MoveSpeed * Time.deltaTime;

collisionflags = charactercontroller.Move(movementAmt);

}

void OnGUI()

{

GUI.color = Color.red;

GUI.Label(new Rect(10,10,100,20),"state : "+state.ToString());

GUI.Label(new Rect(10,30,100,20),"flag : "+collisionflags.ToString());

}

}



그리고 knight의 각각의 스크립트의 animation에 물리항목을 붙이도록 한다.

완성된 화면이다.


오른쪽에 state는 방향이고 flag는 땅의 상태를 나타낸다.


## 캐릭터의 움직임의 변환 처리
바디디랙션
1. 스크립트에서 
// Update is called once per frame
void Update () {
//아래내용은순서대로한것이다.
Move ();
CheckState();
AnimationControl();
BodyDirection();
}
void BodyDirection()
{
//벨로시티는속도이면서방향이다.
Vector3 horizontalVelocity = charactercontroller.velocity;
horizontalVelocity.y = 0.0f;
if(horizontalVelocity.magnitude > 0.0f)
{
Vector3 trans = horizontalVelocity.normalized;
Vector3 wantedVector = Vector3.Lerp(transform.forward, trans, 0.5f);
if(wantedVector != Vector3.zero)
{
transform.forward = wantedVector;
}
}
}

이렇게 추가하면 방향으로 속도로 애니메이션을 보간한 것이다.
캐릭터가 확돌다가 천천히 돌것이다. move디렉션으로 바디디렉션으로 처리한 것이다. 

120줄에 이동하면서 애니메이션 되는 것은 유니티만 가능한 것이다.

오늘 복습
클립을 가져와서 블랜딩한다.

캐릭터를 띄우기 위해서 카메라

카메라 값을 가져와서 캐릭터를 돌렸다.



내일 할일...
와우카메라. 스프링 카메라..
몬스터 띄우서 몬스터 잡아 보자.



실전 Unity3D Engine Programing 과정 5일차 (2013.07.26 (금)) 


자료 모음: C:\Users\Mac\Documents

한글사이트: www.unity3dkorea.com 



##리뷰..

Component Centric 엔진


## 오늘 할일

- NGUI 

- 애니메이션


## NGUI 적용하기

- 어제 하던 것에서 NGUI 을 임포트한다.

- menu > Assets > Refresh 한다. 그러면 메뉴에 NGUI가 추가된다.


[설정]

GUI 는 그리팩 유저 인터페이스이다.

화면 해상도가 달라지면 대응하는 방법 안내

- 화면의 사각형의 모서리 엥커를 파악해 두고 거기서 이미지를 배치한다.

  총 9개의 앵커 (좌상단, 좌중단, 좌하단, 중상단, 중중단, 중하단, 우상단, 우중단, 우하단)

  총 9개의 앵커가 존재하며, 이 엥커를 기준으로 이미지를 배치한다.

- One Unit = 2/화면세로폭

- Root / 스크린의 높이

  - 자동으로 늘어나는 것을 언체크해 둔다. 해상도는 동일한 UI를 갖게된다.

  - 이 Root(불변 사이즈)를 기준으로 One Unit을 정의한다.

- Camera..  카메라 밑에 앵커를 둔다.

   - 앵커 밑에 판넬을 붙이다.

   - 판넬은 이미지, 라벨, 드로콜정보, 매트리얼 사용량 정보글 관리한다. 



[이제 작업해 보자]

어제 작업한 것중에 Camera의 Flare Layer, GUILayer, Audio Listener를 언체크한다. (오디오 리스너는 유일하게 하나이어야 한다.)



목표

- 상단에 점수, 10000M

- 하단에 목숨, 폭탄

 

이렇게 구성해 보자.


menu > NGUI > Create a New UI


창이 뜬다. Layer 선택하고 Add Layer하고  GUI 추가한다. 




다시 Layer를 선택하면 만든 GUI가 나온다. GUI를 선택한다.

그리고 create .. 선택하면 


하이어아키에 UI Root(2D)가 생긴다.

- Camera, 

   - Anchor

     - Panel

       -clipping을 설명. (화면에 보이는 것만 나온다.

이렇게 나온다. 이 각항목을  클릭해서 오른쪽 상세 내용을 보라..





Anchor를 복제해서 4개를 만든다.

그리고 앵커 이름을 각각 바꿔주다. 그리고 내부에 side를 좌우상하로 배치시킨다.

Anchor_TopLeft

Anchor_TopRight


판넬을 선택해서 menu > NGUI > create widget를 선택해서 팝업 화면이 나온다.



Atlas와 Font를 추가하고 Template는 Label로 한다. Add To한다.



아틀라스 만들자. (스프라이트 이미지를 하나의 파일로 재배치해서관리한다.)

- 텍스쳐 폴더에 모든 텍스쳐를 선택하고

- 상단에 menu > NGUI > Altras Make를 한다.

- 팝업에 Replace에 이름을 지정한다. 그러면 create가 나온다. 이 크리에이트 버튼을 누르면 

  아셋에 3개의 파일이 생성되는 것을 볼 수 있다.

  - 이미지를 선택하면 모두 아틀라스를 만들 수 있다. (최대 80개까지 포함시킬 수 있다.)




포트로 아틀라스로 만들 수 있다. BM폰트....


각 포지션으로 만든다.


버튼은  Achor_bottomRight에 추가하는데..

- 엠프티오브젝트를 만들고 이 것에 판넬을 추가한다.

- 이름 Button으로 한다.

- 이 버튼의 상세 내용중 Layer를 GUI로 변경한다.


그리고 이버튼에 menu > NGUI > Attatch Collider 를 추가한다.

그리고 Component > NGUI > interation > 버튼메시지, 버튼사운드, 버튼스켈일을 추가한다.



그리고 버튼을 눌러보면 아래와 같이 붙어 있다.



## 버튼을 누르면 메시지를 출력해 본다.

메인스크립트에 아래 내용을 추가한다.

using UnityEngine;

using System.Collections;


public class MainScript : MonoBehaviour {

public GameObject SkyGamera;

public float CameraRotateSpeed = 0.1f;


// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

//카메라를Y축으로돌리자. 축을 중심으로회전한다.

SkyGamera.transform.RotateAround(Vector3.up, Time.deltaTime * CameraRotateSpeed);

}

// 버튼누르면호출된다.

void Bomb() 

{

Debug.Log("Bomb Button Pressed");

}

}


메인오브젝트를 버튼에 넣어 주소
펑션은 상기 Bomb로 지정해 준다.
아래 그림을 참고 한다.



실행해서 버튼을 눌러보라 그러면 consol에서 메시지가 출력된다.



##이어서 하자.

보톰 라벨을 life로 하자.


모노라이브 > menu > search > go to file > uiLabel

맨밑에 아래 내용 추가후 저장 > 컨버터

void SetText(string s)

{

this.mText = s;

this.hasChanged = true;

}


메인스크립트에 아래 내용 추가

using UnityEngine;

using System.Collections;


public class MainScript : MonoBehaviour {

public GameObject SkyGamera;

public float CameraRotateSpeed = 0.1f;

public GameObject Distance;

public GameObject Life;

public GameObject Point;

private int playerLife  =3;

private int playerPoint = 0;

private float AccelSpeed = 100.0f;

 


// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

//카메라를Y축으로돌리자. 축을 중심으로회전한다.

SkyGamera.transform.RotateAround(Vector3.up, Time.deltaTime * CameraRotateSpeed);

Life.SendMessage("SetText", "X "+playerLife.ToString());

Point.SendMessage("SetText", "Point: "+playerPoint.ToString());

//N0(zero)를 넣어주면소숫점은절삭된다.

Distance.SendMessage("SetText", (Time.time*AccelSpeed).ToString("N0")+" m");

}

//총알이적군이랑충돌되면호출한다.

void Hit(int p)

{

this.playerPoint += p;

}

// 버튼누르면호출된다.

void Bomb() 

{

Debug.Log("Bomb Button Pressed");

}

}



에너미

public class EnemySprite :  SpriteBase {
public float Speed  = 10.0f;
public GameObject expolosion;
// Use this for initialization
void Start () {
gameObject.AddComponent<BoxCollider>();
}
// Update is called once per frame
void Update () {
float moveAmt = Speed * Time.deltaTime;
transform.Translate(Vector3.down * moveAmt);
//많이 떨어지면파괴한다.
if(transform.position.y < -4.0f)
{
InitPosition();
}
}
    void InitPosition()
{
transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 5.0f, 0.0f);
}
//충돌되면 충돌된 것에 Tag를비교한다 Tag가가장빠르다.
void OnTriggerEnter(Collider other) 
{
if(other.tag == "bullet")
{
Instantiate(expolosion, transform.position, transform.rotation);
InitPosition();
//히어아키에서main을찾아온다.
GameObject main = GameObject.Find("Main");
main.SendMessage("Hit", 50);
}
}
}

메인에서 추가된 변수에 파넬에 각각 라벨을 붙여 준다.


assets store로 가서 로그인 하고 itween으로 찾는다.




hotween을 찾아 다운로드 설치한다.



붙인 것 샘플을 돌려 보자




## 클리어신을 만들어 보자

1. new scene , 이름은 win

2. menu > NGUI > create new ui > layer는 GUI로 변경한다.




   크리에이트 한다.

3. 위제만든다.

    아틀라스는 판타지 아틀라스붙인다.

4. 패널에 라벨3개, 스프라이트 하나 만든다.    

5. 라벨을 적당히 채운다.

6. 엠프티 오브젝트 만들고

-버튼

- 레이어는 GUI

- 그리고 판넬로 이동

- 위치 제조정

7. 버튼에 라벨과 스크립트 넣는다.

8. 스크립트 만든다.

using UnityEngine;

using System.Collections;


public class ResultUI : MonoBehaviour {

public Transform TitleLeft;

public Transform TitleRight;

public Transform Button;

IEnumerable Start() 

{

Debug.Log("Start Time "+Time.time);

//yield는양보로 기다렸다가 해당시간후다음라인이 동작한다.

yield return new WaitForSeconds(1.0f);

Debug.Log("Next Time "+Time.time);

}

}


아래 것은 양보를 한다. 인공지능에 사용할 수 있고, 그러나 초는 정확하지 않다.

yield 




UIRootdp 스크립트를 추가한다.

그리고 각각 연결한다.



다시 스크립트에 

using Holoville.HOTween; 추가한다.


스타트함수에 다시 코딩


라벨스테이션 포지션 -1050, 40, 0

라벨클리어 푖션 1050, 40, 0

으로 한다. 


그래서 1000을 빼주고 더해주면 화면들로 들어 온다.

실행해 본다.



상세에서

버튼도 스케일도 100, 100, 1로 변경해 본다.

버튼은 언체크 한다. 









using Holoville.HOTween;
using UnityEngine;
using System.Collections;

public class ResultUI : MonoBehaviour {
public Transform TitleLeft;
public Transform TitleRight;
public Transform Button;
IEnumerable Start() 
{
Debug.Log("Start Time "+Time.time);
yield return new WaitForSeconds(1.0f);
Debug.Log("Next Time "+Time.time);
HOTween.Init(true, true, true);
//stage
HOTween.To(TitleLeft, 0.5f, "localPosition", new Vector3(1000, 0, 0),true);
//clear
TweenParms tweenParams = new TweenParms().Prop("localPosition", new Vector3(-1000, 0, 0), true).Ease(EaseType.Linear).Loops(-1,LoopType.Yoyo);
HOTween.To(TitleRight, 0.5f, tweenParams);
yield return new WaitForSeconds(1.0f);
//button enable
Button.gameObject.SetActive(true);
// on Child widget
for(int i=0; i<Button.transform.childCount;i++)
{
Button.transform.GetChild(i).gameObject.SetActive(true);
}
//button animation
Sequence sequence = new Sequence(new SequenceParms().Loops(1, LoopType.Yoyo));
sequence.Append(HOTween.To(Button, 0.5f, new TweenParms().Prop("localScale", new Vector3(-110, -110, 0),true)));
sequence.Append(HOTween.To(Button, 0.5f, new TweenParms().Prop("localScale", new Vector3(20, 20, 0), true)));
sequence.Append(HOTween.To(Button, 0.5f, new TweenParms().Prop("localScale", new Vector3(-9, -9, 0), true)));
sequence.Append(HOTween.To(Button, 0.5f, new TweenParms().Prop("localEulerAngles", new Vector3(720,0,0),true)));
sequence.Play();
}
}





http://www.unity3dacademy.com/index.php?mid=EduInfo


실전 Unity3D Engine Programing 과정 4일차 (2013.07.25 (목)) 

남들이 알아 주지 않더라도 나의 일을 묵묵히 하다 보면
결국 남 좋은 일만 하게 된다.



## [폴리곤 줄이는 방법을 배워보자.]  <== 국내에 유일한 팁으로 스크립트를 모두 사용해라


메뉴 > 윈도우 >  Assets Store 계정을 만들어라.

다음 클라우드에서 오늘자 리소스를 받아라.

skybox_objects.unitypackage

shooting2D_resources

skybox_objects

NGUI <== 상용 플러그인다. 출처를 밝히지 말라. 550$ 상당의 가치이다.


뉴프로젝트한다. : 2DShooting

Assets창에서 패키지를 로딩한다: shooting2D_resources

Consol창에서 Error pause를 켜 놓아야 한다. <== 일반적으로 켜 놓고 사용해라. 

콘솔창이 없으면 Menu > Window > Consol을 선택해서 아무곳에 붙이면 된다.


아셋 > 텍스쳐를 봐라.

- 비행기

- 해골

- 미사일

- plane 

있고 이것들의 size가 나온다. 유니티는 스스로 2승수로 바꾼다. 

이것을 변경하는 것을 갔다 사용한다.

인터넷 > unity3d.com에 접속

http://wiki.unity3d.com/index.php/Main_Page 

http://wiki.unity3d.com/index.php/TextureImportSettings

Adds under the menu Custom→Texture a way to change for multiple selected textures the import settings in one step. Idea was to have the same choices for multiple texture files as you would have if you open the import settings of a single texture. Currently the most often used import settings are editable: Texture Format (same amount and order as in Unity), enable/disable MipMap and changing the maximum texture size.

Usage

You must place the script in a folder named Editor in your project's Assets folder for it to work properly.

Select some textures in the project window and select from the Custom→Texture menu the modification you want to apply to the selected textures.

Screenshot

Textureimportsettings.jpg

내용 보면 두개의 스크립트가 있는데 첫번째것이 4버전이고 두번째것이 3버전이다. 




ChangeTextureImportSettings 이 파일명으로 스크립트를 만들어 사용한다.

내용은 아래 것으로 채운다. (상기 인터넷 사이트 것)

using UnityEngine;
using UnityEditor;
 
// /////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Batch Texture import settings modifier.
//
// Modifies all selected textures in the project window and applies the requested modification on the 
// textures. Idea was to have the same choices for multiple files as you would have if you open the 
// import settings of a single texture. Put this into Assets/Editor and once compiled by Unity you find
// the new functionality in Custom -> Texture. Enjoy! :-)
// 
// Based on the great work of benblo in this thread: 
// http://forum.unity3d.com/viewtopic.php?t=16079&start=0&postdays=0&postorder=asc&highlight=textureimporter
// 
// Developed by Martin Schultz, Decane in August 2009
// e-mail: ms@decane.net
//
// /////////////////////////////////////////////////////////////////////////////////////////////////////////
public class ChangeTextureImportSettings : ScriptableObject {
 
	[MenuItem ("Custom/Texture/Change Texture Format/Auto")]
    static void ChangeTextureFormat_Auto() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.Automatic);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGB Compressed DXT1")]
    static void ChangeTextureFormat_RGB_DXT1() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.DXT1);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGB Compressed DXT5")]
    static void ChangeTextureFormat_RGB_DXT5() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.DXT5);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGB 16 bit")]
    static void ChangeTextureFormat_RGB_16bit() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.RGB16);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGB 24 bit")]
    static void ChangeTextureFormat_RGB_24bit() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.RGB24);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/Alpha 8 bit")]
    static void ChangeTextureFormat_Alpha_8bit() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.Alpha8);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGBA 16 bit")]
    static void ChangeTextureFormat_RGBA_16bit() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.ARGB16);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Format/RGBA 32 bit")]
    static void ChangeTextureFormat_RGBA_32bit() { 
		SelectedChangeTextureFormatSettings(TextureImporterFormat.ARGB32);
	}
 
	// ----------------------------------------------------------------------------
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/32")]
    static void ChangeTextureSize_32() { 
		SelectedChangeMaxTextureSize(32);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/64")]
    static void ChangeTextureSize_64() { 
		SelectedChangeMaxTextureSize(64);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/128")]
    static void ChangeTextureSize_128() { 
		SelectedChangeMaxTextureSize(128);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/256")]
    static void ChangeTextureSize_256() { 
		SelectedChangeMaxTextureSize(256);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/512")]
    static void ChangeTextureSize_512() { 
		SelectedChangeMaxTextureSize(512);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/1024")]
    static void ChangeTextureSize_1024() { 
		SelectedChangeMaxTextureSize(1024);
	}
 
	[MenuItem ("Custom/Texture/Change Texture Size/Change Max Texture Size/2048")]
    static void ChangeTextureSize_2048() { 
		SelectedChangeMaxTextureSize(2048);
	}
 
	// ----------------------------------------------------------------------------
 
	[MenuItem ("Custom/Texture/Change MipMap/Enable MipMap")]
    static void ChangeMipMap_On() { 
		SelectedChangeMimMap(true);
	}
 
	[MenuItem ("Custom/Texture/Change MipMap/Disable MipMap")]
    static void ChangeMipMap_Off() { 
		SelectedChangeMimMap(false);
	}
 
	// ----------------------------------------------------------------------------
 
	static void SelectedChangeMimMap(bool enabled) { 
 
		Object[] textures = GetSelectedTextures(); 
		Selection.objects = new Object[0];
		foreach (Texture2D texture in textures)  {
			string path = AssetDatabase.GetAssetPath(texture); 
			TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; 
			textureImporter.mipmapEnabled = enabled;	
			AssetDatabase.ImportAsset(path); 
		}
	}
 
	static void SelectedChangeMaxTextureSize(int size) { 
 
		Object[] textures = GetSelectedTextures(); 
		Selection.objects = new Object[0];
		foreach (Texture2D texture in textures)  {
			string path = AssetDatabase.GetAssetPath(texture); 
			TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; 
			textureImporter.maxTextureSize = size;	
			AssetDatabase.ImportAsset(path); 
		}
	}
 
	static void SelectedChangeTextureFormatSettings(TextureImporterFormat newFormat) { 
 
		Object[] textures = GetSelectedTextures(); 
		Selection.objects = new Object[0];
		foreach (Texture2D texture in textures)  {
			string path = AssetDatabase.GetAssetPath(texture); 
			//Debug.Log("path: " + path);
			TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; 
			textureImporter.textureFormat = newFormat;	
			AssetDatabase.ImportAsset(path); 
		}
	}
 
	static Object[] GetSelectedTextures() 
	{ 
		return Selection.GetFiltered(typeof(Texture2D), SelectionMode.DeepAssets); 
	}
}



 menu > assets > refresh 하면 상당 menu에 custom이란 항목이 나오면 성공



텍스쳐를 선택해서 사이즈를 변경한다.

menu > custom > texture > 



성공되면 해당 텍스쳐선택히 우측 하단에... NOPT라고 나오면 된다.



현재까지 한 것은  Game 신으로 저장한다.



[폴리곤을 바꿔 보자]

카메라는 이차원 카메라로 바꾼다.

포지션 0, 0, 0

프로적션은 오도고날 (2차원)

사이즈 3.5

클립핑 -2, 2

라이트 추가



스크립트추가 SpriteBase

using UnityEngine;

using System.Collections;


public class SpriteBase : MonoBehaviour {

 

public Material mat;

void Awake()

{

gameObject.AddComponent("MeshFilter");

gameObject.AddComponent("MeshRenderer");

Mesh mesh = GetComponent<MeshFilter>().mesh;

mesh.Clear(); //remove

// 새로만든자 - xy 좌표에서 사각형좌표를찍었다

mesh.vertices = new Vector3[]

{new Vector3(0,0,0), new Vector3(0,1,0),new Vector3(1,1,0), new Vector3(1,0,0)};

// 밝아지는반경

mesh.uv = new Vector2[] {new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0)}; 

//사각형을두개의삼각형으로붙인다

mesh.triangles = new int[]{0,1,2,0,2,3};


//노말을생성한다 빗의반사색상

mesh.RecalculateNormals();


if(mat != null)

{

gameObject.renderer.material = mat;

}

}

}


위에것으로 하면 컴파일 안된다.
아래처럼 주석에 .을 붙여야 하다.
using UnityEngine;
using System.Collections;

public class SpriteBase : MonoBehaviour {
 
public Material mat;
void Awake()
{
gameObject.AddComponent("MeshFilter");
gameObject.AddComponent("MeshRenderer");
Mesh mesh = GetComponent<MeshFilter>().mesh;
mesh.Clear(); //remove
// 새로만든자 - xy 좌표에서 사각형좌표를찍었다.
mesh.vertices = new Vector3[]
{new Vector3(0,0,0), new Vector3(0,1,0),new Vector3(1,1,0), new Vector3(1,0,0)};
// 밝아지는반경.
mesh.uv = new Vector2[] {new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0)}; 
//사각형을두개의삼각형으로붙인다.
mesh.triangles = new int[]{0,1,2,0,2,3};
//노말을생성한다 빗의반사색상.
mesh.RecalculateNormals();
if(mat != null)
{
// 대소문자구분 소문자game은자체.
gameObject.renderer.material = mat;
}
}
}






빛과 텍스쳐가 각이 맞다면 색상이 달라진다. 이것을 노말이라고 한다. 

//노말을생성한다 빗의반사색상

mesh.RecalculateNormals();



아셋에서 Material 폴더를 추가한다.

<== 폴더를 구분하는 것은 매우 중요하다. 아셋에 들어갈게 많아서 구분하는게 좋다.

메트리얼 추가한다.

메트리어에 비행기 텍스쳐을 잡소 오른쪽에 붙이다.





엠프티오브젝트를 추가하고 포지션은 0,0,0으로 한다.

아까만든 SpriteBase 스크립트를 엠프티오브젝트에 추가한다.

그리고 메터리어을 스트립트의 mat에 붙인다.


그리고 실행해 보면 4각형의 비행기가 나온다.

실제 비행기 이미지 보다 작게 나온다.



<== 이 스크립트를 사용하면 폴리곤을 수를 업청 줄일 수 있다. 이것이 노하우다 이 스크립트를 사용해라


tris수가 2로 매우 작게 나온다 . 

이렇게 하지 않았을 경우는 200 까지 나온 것이다.



이렇듯 실제 사이즈가 다르면 안되니 이것을 맞추도록 하자.

사이즈 수정하자..

스크립트 아래 내용 추가하자.

using UnityEngine;

using System.Collections;


public class SpriteBase : MonoBehaviour {

 

public Material mat;

void Awake()

{

gameObject.AddComponent("MeshFilter");

gameObject.AddComponent("MeshRenderer");

Mesh mesh = GetComponent<MeshFilter>().mesh;

mesh.Clear(); //remove

// 새로만든자 - xy 좌표에서 사각형좌표를찍었다.

mesh.vertices = new Vector3[]

{new Vector3(0,0,0), new Vector3(0,1,0),new Vector3(1,1,0), new Vector3(1,0,0)};

// 밝아지는반경.

mesh.uv = new Vector2[] {new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0)}; 

//사각형을두개의삼각형으로붙인다.

mesh.triangles = new int[]{0,1,2,0,2,3};

//노말을생성한다 빗의반사색상.

mesh.RecalculateNormals();

if(mat != null)

{

// 대소문자구분 소문자game은자체.

gameObject.renderer.material = mat;

}


//사이즈변경하는것호출한다.

MakePerfectSize();

}

void MakePerfectSize() 

{

Texture tex = mat.mainTexture;

float one_unit = 2.0f / Screen.height;

float x = one_unit * tex.width;

float y = one_unit * tex.height;

Vector3 newScale = new Vector3(x, y, one_unit);

transform.localScale = newScale;

}

}


<== 여기까지가 이제 2D의 시작이다.

이제부터 텍스쳐 다르게 넣으면 다르게 나온다.

계속 이 스크립트는 복제해서 사용하자.



## [드라곤 플라이트 게임 만든자]

새롭게 시작하다. 기존 게임오브젝트는 지운다.

PlayerSprite 스크립트 새로 추가한다.

상속은 1단계만 된다. SpriteBase을 상속한다.


using UnityEngine;

using System.Collections;


public class PlayerSprite : SpriteBase {

public float Speed = 10.0f;

private float LastShootTime;

public float ShootDelay = 0.2f;

// Use this for initialization

void Start () {

LastShootTime = Time.time;

}

// Update is called once per frame

void Update () {

float moveAmt = Input.GetAxis("Horizontal") * Speed  * Time.deltaTime;

transform.Translate(Vector3.right * moveAmt);

}

}



엠프티오브젝트추가
이름 player
포지션 0, -3, 0
스크립트 추가
메트리얼 추가



비행기의 기준점이 없어 불편하다. 디버그 포인트를 추가하자.

모눈종이 모양의 선을 그려보다.


PlayerBase에 추가하자.

using UnityEngine;

using System.Collections;


public class SpriteBase : MonoBehaviour {

 

public Material mat;

void Awake()

{

gameObject.AddComponent("MeshFilter");

gameObject.AddComponent("MeshRenderer");

Mesh mesh = GetComponent<MeshFilter>().mesh;

mesh.Clear(); //remove

// 새로만든자 - xy 좌표에서 사각형좌표를찍었다.

mesh.vertices = new Vector3[]

{new Vector3(0,0,0), new Vector3(0,1,0),new Vector3(1,1,0), new Vector3(1,0,0)};

// 밝아지는반경.

mesh.uv = new Vector2[] {new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0)}; 

//사각형을두개의삼각형으로붙인다.

mesh.triangles = new int[]{0,1,2,0,2,3};

//노말을생성한다 빗의반사색상.

mesh.RecalculateNormals();

if(mat != null)

{

// 대소문자구분 소문자game은자체.

gameObject.renderer.material = mat;

}


//사이즈변경하는것호출한다.

MakePerfectSize();

}

void MakePerfectSize() 

{

Texture tex = mat.mainTexture;

float one_unit = 2.0f / Screen.height;

float x = one_unit * tex.width;

float y = one_unit * tex.height;

Vector3 newScale = new Vector3(x, y, one_unit);

transform.localScale = newScale;

}

void OnDrawGizmosSelected()

{

Gizmos.color = Color.white;

Gizmos.DrawWireSphere(transform.position, 1.0f);

}

void OnDrawGizmos()

{

Gizmos.color = Color.red;

Gizmos.DrawWireCube(transform.position, Vector3.one);

}

}


신을 보라..


이렇게 위치가 빨갔게 나오고 스프라이트가 선택되면 힌색이 나온다.

스프라이트로만 처리하면 신화면에 나오지 않으니 디버깅용으로 사용할 수 있다.

실제 화면에서는 나오지 않으니 디버깅용으로 매우 좋다.


## [총탄을 만들자]

메트리얼 만들자.  그리고 미사일 텍스쳐를 붙인다.


그리고 BulletSprite 스프라이트 만든다.

리니즈바디 추가 그라비티 를 언체크하는 것을 스크립트로 제어한다.


using UnityEngine;

using System.Collections;


public class BulletSprite : SpriteBase {

public float Speed = 15.0f;

// Use this for initialization

void Start () {

//리니즈바디 추가 그라비티 를 언체크하는 것을 스크립트로 제어한다.

gameObject.AddComponent<Rigidbody>();

gameObject.rigidbody.useGravity = false;

gameObject.AddComponent<BoxCollider>();

gameObject.collider.isTrigger = true;

}

// Update is called once per frame

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.up *moveAmt);

if(transform.position.y > 10.0f)

{

Destroy(gameObject);

}

}

}


아셋에 Prefab 폴더 만들고  BulletPrefab을  만든다.


엠프티오브젝트 만들고

 블랫스프라이트 추가

 블랫메트리얼 추가

 Tag  bullet으로 선언




그리고 BulletPrefab에 만든 엠프티오브젝트를 추가한다. 지운다.

(정상적으로 추가되면 아래 BulletPrefab 처럼 아이콘이 유색으로 변한다. 추가되지 않았을 때에는 회색)






## [ 플레이어의 총알이 자동발사하는 것을 만들자.]


1. 플레이 경과 시간파악

2. 지금 시간이 마지막 총을 쏜 시간  지연되어으면 ......

//Time.time은play 버튼이누른이후계속재시는시간이다 42일max까지이다.

if(Time.time > LastShootTime + ShootDelay)

{

LastShootTime = Time.time;

Instantiate(bullet, transform.position, transform.rotation);

}

<= 이것 많이 사용한다. 


Player  오브젝트에 보면 bullet이 추가되어 있다. 여기에 BulletPrefab을 추가 한다. 



Player오브젝트를 열어보면 리니지바디 중력은 언체크되어 있고 박스콜라이더는 트리거체크되어 화면에 녹색네모가 보이게 된다.

이렇게 코드로 추가 가능하다. 이렇게 해야 충돌 체크가 가능해진다.


[팁] 스크립트로 오브젝트를 add하면 메모리 누스를 줄일 수 있다.

[팁] 부모 함수를 자식이 호출 할 수 있다. base.. 그러나 호출 순서는 장담할 수 없다고 합니다. 스크립트라서... 



##[적군만들자]

EnemySprite 스프라이트 만들자

using UnityEngine;

using System.Collections;


public class EnemySprite : MonoBehaviour {

public float Speed  = 10.0f;

public GameObject expolosion;

// Use this for initialization

void Start () {

gameObject.AddComponent<BoxCollider>();

}

// Update is called once per frame

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.down * moveAmt);

//많이 떨어지면파괴한다.

if(transform.position.y < -4.0f)

{

InitPosition();

}

}

    void InitPosition()

{

transform.position = new Vector3(Random.Range(-3.0f, 3.0f), 5,0f, 0.0f);

}

}


신 저장


에너미 오브젝트를 만들고

스크립트추가

이팩트를 추가


이팩트는 추가한 다이나믹을 추가한다.




##[배경을 흐르는 것을 만들자]

스카이박스 패키지를 추가한다.  <== 출처는 비밀

sky_box_ring을선택해서 히어아키창에 둔다.  (아셋 > 오브젝트 > 스카이 )

이것을 Y값을 1000으로 둔다. 노출되지 않게

배경을 줄이자...



카메라를 추가한다.

포지션 0, 1670, 0

필더뷰 : 35

클립 0.3, 5000

Depth : -2 (낮은 값이 먼저 찍는다.)


엠프트오브젝트 Main

스크립트 MainScript 

추가..

using UnityEngine;

using System.Collections;


public class MainScript : MonoBehaviour {

public GameObject SkyGamera;

public float CameraRotateSpeed = 0.1f;


// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

//카메라를Y축으로돌리자. 축을 중심으로회전한다.

SkyGamera.transform.RotateAround(Vector3.up, Time.deltaTime * CameraRotateSpeed);

}

}



메인카메라에서 Clear Flags를 Depth only로 한다. 
= 뒷배경 그리도 덥어 쒸운다는 것.



아까만든 Main 오브제트에 스크립트 추가한다.

스크립트에 카메라는 하이어라키의 카메라를 붙인다.



카메라의 Rotation 0, 0, 2780 으로 변경한다.

그리고 Field of View 25로 한다





배경 이미지를 바꿔보자.



가로 형태는 카메라 축을 변경하면 충분히 처리한다.


내일은 GUI를 붙인다..





실전 Unity3D Engine Programing 과정 3일차 (2013.07.24 (수)) 


어제 만든 것을 3D로 변경 한다.


asset에 새로운 리소스 임포트한다.





1.신만든다. 이름은 NewGame으로 한다.

2. 메인 카메라 값을 변경한다.




3. 라이트 추가한다.

4. Assets _Object에 있는 SpaceCraftBox 하이어라키에 추가한다

- 에니메이션은 리무브 컴포넌트한다.

- PlayControl 스크립트를 추가한다.

- PlayControl 스크립트 드래그해서 오른쪽에 추가한다.



PlayControl스크립트 수정

void Update () {

float moveAmt = Input.GetAxis ("Horizontal") * Speed * Time.deltaTime;

float moveAmt2 = Input.GetAxis("Vertical") * Speed * Time.deltaTime;

Vector3 moveVector = Vector3.right * moveAmt + Vector3.up * moveAmt2;

transform.Translate(moveVector);

if(Input.GetKeyDown(KeyCode.Space)){

audio.Play();

Instantiate(bullet, transform.position, transform.rotation);

}

}


5. Assets _Object에 있는 missileFBX를 하이어라키에 추가한다.

- 에니메이션 제거

-   scale 은 5, 5, 2변경한다.

- 충돌을 위해서 리디지바디를 추가한다.

   그라비티  언체크

- 충돌시 캡슐 콜리더를 추가한다. Capsul Collider

  -  센터는 0 0 0

  - 트리거 체크

   - 각도.. 0.06 높이 0.42 축 z-Axis

-  BulletScript를 추가한다.

- 스크립트를 다음과 같이 수정

using UnityEngine;

using System.Collections;


public class BulletControl : MonoBehaviour {


public float Speed = 20f;

// Update is called once per frame

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.forward * moveAmt);

if(transform.position.z > 35.0f){

Destroy(gameObject);

}

}

- 파티클을 추가하자.

   Standard Assets, Particles , small flames 하이어키에 추가하자.

   - Particle animator의 force값 수정 0.0.-5

   - Ellipsoid Particle Emitter Local velocity, Rnd Velocity를 모두 0으로 준다.

                                        사이즈를 모두 줄인다 1/2


미사일를 비행체 자식으로 바꿔준다.

하이얼리키에서 옮긴다.



그리고 불을 미사일 꽁무니에 옮겨준다. 신화면에서





Prefabs에서 MeshBulletfabs를 만들고 미사일FBX을 끌어다 놓는다. 그리고 미사일 FBX는 제거한다.




[적군을 만들자]

새로 asset에 있는 오브젝트에서 sunRingFBX  하이어라키에 추가한다.

- 에니메이션 리무브

- 메슈콜라이더삭제

- 메뉴 콤포넌트 피직스 shhere collider추가 (크기가 비스하게 들어간다.)

- EnemyControl 스크립트 드래그 드랍으로 추가

   - 

- 오디오 소스 추가. way on awake 언체크, 히트오디오 추가



- EnemyControl 수정

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.forward * moveAmt);

if(transform.position.z < -7.0f)

{

InitPosition();

}

}

void InitPosition()

{

transform.position = new Vector3(

Random.Range(-10.0f,10.0f),

   Random.Range(-10.0f, 10.0f),

35.0f);

}



- sunRingFBX에서 포지션 0, 0, 35, Rotation 0, 180, 0 수정

sunRingFBX 추가로 2개 복재한다.  x 값을 수정해서 나란히 한다.


- prefabs의 MeshgBulletPrefab Tag를 bullet으로 수정한다.




[배경을 넣자]

assets -> import package -> skybox를 추가한다.


 내용을 놓쳤다...  ㅜ ㅜ





#스크립트에 대해서 배우자. Scripting Overview 파일을 연다.

- www.unity3d.com > community > Document / Go to document  > 우측의 script탭을 누른다. ( Wiki 열어보면 스크립트가 많이 있다. )

  (유니티 IDE에 메뉴에 있는 script help는 옛날 것이다.)

  상기에 스크립트 최신 정보를 볼 수 있다.

  독일 쪽에 스크립트 최신 정보를 찾아 볼 수있고, 구글에서 검색해 본다.

  에러 발생시 서치해 본다.

  - 서치에서 monobehaviour를 친다.  



  


MonoBehaviour

Description

MonoBehaviour is the base class every script derives from.

Using Javascript every script automatically derives from MonoBehaviour. When using C# or Boo you have to explicitly derive from MonoBehaviour.

Note: The checkbox for disabling a MonoBehavior (on the editor) will only prevent Start(), Awake(), Update(), FixedUpdate(), and OnGUI() from executing. If none of these functions are present, the checkbox is not displayed.

See Also: The chapter on scripting in the manual.
Variables

useGUILayout

Disabling this lets you skip the GUI layout phase.
Functions

CancelInvoke

Cancels all Invoke calls on this MonoBehaviour.

Invoke

Invokes the method methodName in time seconds.

InvokeRepeating

Invokes the method methodName in time seconds.

IsInvoking

Is any invoke on methodName pending?

StartCoroutine

Starts a coroutine.

StopAllCoroutines

Stops all coroutines running on this behaviour.

StopCoroutine

Stops all coroutines named methodName running on this behaviour.
Static Functions
printLogs message to the Unity Console. This function is identical to Debug.Log.
Messages

Awake

Awake is called when the script instance is being loaded.

FixedUpdate

This function is called every fixed framerate frame, if the MonoBehaviour is enabled.

LateUpdate

LateUpdate is called every frame, if the Behaviour is enabled.

OnAnimatorIK

Callback for setting up animation IK (inverse kinematics).

OnAnimatorMove

Callback for processing animation movements for modifying root motion.

OnApplicationFocus

Sent to all game objects when the player gets or loses focus.

OnApplicationPause

Sent to all game objects when the player pauses.

OnApplicationQuit

Sent to all game objects before the application is quit.

OnAudioFilterRead

If OnAudioFilterRead is implemented, Unity will insert a custom filter into the audio DSP chain.

OnBecameInvisible

OnBecameInvisible is called when the renderer is no longer visible by any camera.

OnBecameVisible

OnBecameVisible is called when the renderer became visible by any camera.

OnCollisionEnter

OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider.

OnCollisionExit

OnCollisionExit is called when this collider/rigidbody has stopped touching another rigidbody/collider.

OnCollisionStay

OnCollisionStay is called once per frame for every collider/rigidbody that is touching rigidbody/collider.

OnConnectedToServer

Called on the client when you have successfully connected to a server.

OnControllerColliderHit

OnControllerColliderHit is called when the controller hits a collider while performing a Move.

OnDestroy

This function is called when the MonoBehaviour will be destroyed.

OnDisable

This function is called when the behaviour becomes disabled () or inactive.

OnDisconnectedFromServer

Called on the client when the connection was lost or you disconnected from the server.

OnDrawGizmos

Implement this OnDrawGizmos if you want to draw gizmos that are also pickable and always drawn.

OnDrawGizmosSelected

Implement this OnDrawGizmosSelected if you want to draw gizmos only if the object is selected.

OnEnable

This function is called when the object becomes enabled and active.

OnFailedToConnect

Called on the client when a connection attempt fails for some reason.

OnFailedToConnectToMasterServer

Called on clients or servers when there is a problem connecting to the MasterServer.

OnGUI

OnGUI is called for rendering and handling GUI events.

OnJointBreak

Called when a joint attached to the same game object broke.

OnLevelWasLoaded

This function is called after a new level was loaded.

OnMasterServerEvent

Called on clients or servers when reporting events from the MasterServer.

OnMouseDown

OnMouseDown is called when the user has pressed the mouse button while over the GUIElement or Collider.

OnMouseDrag

OnMouseDrag is called when the user has clicked on a GUIElement or Collider and is still holding down the mouse.

OnMouseEnter

OnMouseEnter is called when the mouse entered the GUIElement or Collider.

OnMouseExit

OnMouseExit is called when the mouse is not any longer over the GUIElement or Collider.

OnMouseOver

OnMouseOver is called every frame while the mouse is over the GUIElement or Collider.

OnMouseUp

OnMouseUp is called when the user has released the mouse button.

OnMouseUpAsButton

OnMouseUpAsButton is only called when the mouse is released over the same GUIElement or Collider as it was pressed.

OnNetworkInstantiate

Called on objects which have been network instantiated with Network.Instantiate.

OnParticleCollision

OnParticleCollision is called when a particle hits a collider.

OnPlayerConnected

Called on the server whenever a new player has successfully connected.

OnPlayerDisconnected

Called on the server whenever a player disconnected from the server.

OnPostRender

OnPostRender is called after a camera finished rendering the scene.

OnPreCull

OnPreCull is called before a camera culls the scene.

OnPreRender

OnPreRender is called before a camera starts rendering the scene.

OnRenderImage

OnRenderImage is called after all rendering is complete to render image.

OnRenderObject

OnRenderObject is called after camera has rendered the scene.

OnSerializeNetworkView

Used to customize synchronization of variables in a script watched by a network view.

OnServerInitialized

Called on the server whenever a Network.InitializeServer was invoked and has completed.

OnTriggerEnter

OnTriggerEnter is called when the Collider other enters the trigger.

OnTriggerExit

OnTriggerExit is called when the Collider other has stopped touching the trigger.

OnTriggerStay

OnTriggerStay is called once per frame for every Collider other that is touching the trigger.

OnValidate

This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).

OnWillRenderObject

OnWillRenderObject is called once for each camera if the object is visible.

Reset

Reset to default values.

Start

Start is called just before any of the Update methods is called the first time.

Update

Update is called every frame, if the MonoBehaviour is enabled.
Inherited members
Variables

enabled

Enabled Behaviours are Updated, disabled Behaviours are not.

animation

The Animation attached to this GameObject (null if there is none attached).

audio

The AudioSource attached to this GameObject (null if there is none attached).

camera

The Camera attached to this GameObject (null if there is none attached).

collider

The Collider attached to this GameObject (null if there is none attached).

constantForce

The ConstantForce attached to this GameObject (null if there is none attached).

gameObject

The game object this component is attached to. A component is always attached to a game object.

guiText

The GUIText attached to this GameObject (null if there is none attached).

guiTexture

The GUITexture attached to this GameObject (Read Only). (null if there is none attached).

hingeJoint

The HingeJoint attached to this GameObject (null if there is none attached).

light

The Light attached to this GameObject (null if there is none attached).

networkView

The NetworkView attached to this GameObject (Read Only). (null if there is none attached).

particleEmitter

The ParticleEmitter attached to this GameObject (null if there is none attached).

particleSystem

The ParticleSystem attached to this GameObject (null if there is none attached).

renderer

The Renderer attached to this GameObject (null if there is none attached).

rigidbody

The Rigidbody attached to this GameObject (null if there is none attached).

tag

The tag of this game object.

transform

The Transform attached to this GameObject (null if there is none attached).

hideFlags

Should the object be hidden, saved with the scene or modifiable by the user?

name

The name of the object.
Functions

BroadcastMessage

Calls the method named methodName on every MonoBehaviour in this game object or any of its children.

CompareTag

Is this game object tagged with tag?

GetComponent

Returns the component of Type type if the game object has one attached, null if it doesn't.

GetComponentInChildren

Returns the component of Type type in the GameObject or any of its children using depth first search.

GetComponents

Returns all components of Type type in the GameObject.

GetComponentsInChildren

Returns all components of Type type in the GameObject or any of its children.

SendMessage

Calls the method named methodName on every MonoBehaviour in this game object.

SendMessageUpwards

Calls the method named methodName on every MonoBehaviour in this game object and on every ancestor of the behaviour.

GetInstanceID

Returns the instance id of the object.

ToString

Returns the name of the game object.
Static Functions

Destroy

Removes a gameobject, component or asset.

DestroyImmediate

Destroys the object obj immediately. You are strongly recommended to use Destroy instead.

DontDestroyOnLoad

Makes the object target not be destroyed automatically when loading a new scene.

FindObjectOfType

Returns the first active loaded object of Type type.

FindObjectsOfType

Returns a list of all active loaded objects of Type type.

Instantiate

Clones the object original and returns the clone.
Operators

bool

Does the object exist?

operator !=

Compares if two objects refer to a different object.

operator ==

Compares if two objects refer to the same.


이들 내용중에 on으로 시작하는 것은 꼭 달달 외워야 한다.

transform와 Translate를 찾아보라..


Scripting Overview 파일은 아래 내용을 붙였다..


많이 물어보는 내용으로 설명해 본다.


##. GameObject를 찾는 방법

Find  (찾는 것 최상에 것이 찾아진다.) 빠름2

FindWithTag    빠름 1

FindGameObjectWithTag (전부 다찾는것) 빠름3

FindObjectOfType 빠름 4  <== 쓰지 마라 느리다.. 

FindObjhectsOfType 빠름 5 <== 절대 코드에 적용하지 마라 매우 느리다.


GameObject로  찾아보라..



빠르고 느린지를 확인하는 방법...

Play해보고 아래 것을 열어 보면  overview을 알 수 있다. 

메뉴 > 윈도우 > 프로파일러

CPU, Render등이 나온다. 


이제 문서를 보자.

## Update...


Unity 한 Frame의 의미는?

GameObject의 upate가 동작하

~~~~~~~~~~~~~ <= update시작

스크립트의 내용

 - preupdate update lateUdate

 - 이것을 모든 scene에서 돈다.

update 호출 

<== 이것은 눈에 보이지 않고 느린 주범이다. 코더의 수정 부분 코딩은 잘해야 한다.


그리고 preRender Render PostRender가 있다.

  Render는 카메라에 붙어 있는 모든 Render를 호출한다. pre와 postRender로 한다. 

<== 이미지 개선이 가능한 부분 디자이너의 개선 부분

~~~~~~~~~~~~<- 여까지가 한프레임이...(폴리콘.. 카운트 )


따라서 코더는 스크립트의 단위 테스트로 문제점 시간 등을 확인해야 한다.


** 스크립트는 80line을 넘지말다 (최대 120line) / 스크립트는 하나의 함수라고 생각해야 한다.

** 스크립트가 많아지면 언떻게 해야 파라메터 차이로 스크립트 늘어나는 것을 줄일 수 있다.

** 간결하게 쓰고 이중 포문 쓰지 마라, 

** 펑션콜을  줄인다...


update는 빠른 컴퓨터는 빠르고, 느리다.

- deltaTime

fixedUpdate 는 정해진 시간에 업데트된다. 

- TimeManger를 가봐라..

- 이것은 물리에만 사용해야 하낟.

- fixedDeltaTime


##숏컷



##get하는 방법

-함수를 불러오는 방법

  playercontrol 스크립트 안에 있는 함수를 찾는다.

  GameObject.Find("player").GetComponet<playercontrol>().함수명();


   스크립트내에서 찾을 때 소문자. 소문자는 자기것을 의미함

   gameObjct.GetComponent<playControl>().함수명();


   tranform.FindChild("player").gameObject.

- 중복된 스크립트는 어떻게 가져 오나?  더비부모를 만들고 거기에 넣어서 더미부모를 가져 오게 한다. 이때 응답은 배열이다. 또는 Tag검색하라.


## 스크립트의 동작순서

1) Awake <== 딱 한번 호출 FInd 함수 되도록이면 쓰지마라. 참조해서 사용해라

2) Start <== 딱 한번 호출 FInd 함수 되도록이면 쓰지마라. 참조해서 사용해라

    생성하고 꺼놓으다가 다시 호출해도 호출되지 않는다. Inspector에서 스크립트 체크박스를 언체크...하면 끄는 것이다. 

3) update() == OnGUI()

    업데이트도 껐다 키면 호출 안된다. 

4) lateUpdate


## 인스턴스

as는 타입케스팅이다.

인스턴스를 통해서 원하는 컨포넌트를 불러올 수 있다.



## Corote..

직접예제...


## 상속

초기화 Awake, 

클래스와 파일이름은 동일하게


## 

namespace는 사용마라..


## 생성자는 못쓴다. 모노비해뷰어에 상속받은 놈은



## Use STatic Typing

자바스크립트보다 C# 스크립트가 훨 빠르다..

Var 타입때문에 매우 드리다.

이것은 모든 타입을 모두 담을 수 있다. 따라서 동적으로 타입케스팅된다. 



## 스크립트를 빠르게 쓸 수 있는 방법

ArrayList.add(모든타입)...

따라서 배열크기가 일정하지 않아서 검색시 느리다.

타입이 하나라면,

Vector[] 형태로 배열로 사용하는 것이 훨씬 빠르다. 17.5배 빠르다.

이렇게 하면 메모리 각 크기가 동일하기에 빠른 것이다.



## 컴포넌트 캐싱

미리 담아 둔다는 의미..

최신 숏컷으로 대신하기에 별로 사용하지 않는다...

소문자 transform으로 자신의 것을 지정할 수 있다.



## 이팩트 사용시 화면이 멈칫하는 것 막는 방법

1) 생성후 저멀리 위치 옮겨 놓을 것을 위치를 옮기를 형태로 한다. 

2) 스크립트에서 원하는 해당 콤포넌트를 하나씩 AddComponet하는 방법이 있다.

    - 한꺼번에 넣어 놓으면 느리다. 코드로 단계별로 호출하는 방식이 빠르다.

3) 화면에 보이지 않으면 업데이트 하지 마라..



## 카메라 들어올때만 업데이트하라

- 스크립트를 언체크한다. (끈다)



## 컴파일 순서

Assets 내용에 있는것을 본다.

- Startd Assets을 1차 

- Editor 폴더

- Plugin 폴더 (외부 것.. C++, DLL)

* 상기 3가지는 다른 것에서 불러 올수 없다. (컴파일시)


[Scripting Overview 파일]

번역/수정/추가 주세영

 

Scripting Overview  

유니티 내부의 스크립팅은 Behaviours(동작들)라고 불리는 스크립트 개체를 게임 오브젝트에 붙임 으로써 시작된다고 할 수 있습니다. 스크립트 개체의 내부 함수들은 특정 이벤트 상에서 호출이 되는데 가장 많이 사용되는 함수는 아래와 같습니다.

Update

이함수는 하나의 프레임이 렌더링되기 전에 호출된다. 물리코드를 제외한 대부분의 게임 동작 코드가 실행되는 곳입니다.

FixedUpdate

물리 동작의 매 스텝마다 호출됩니다. 물리학 기반의 게임 동작들을 실행 할 수 있는 곳입니다.

Code outside any function

오브젝트들이 로드될 때 함수 외부의 코드들이 실행 됩니다. 이것은 스크립트의 상태를 초기화 하는데 사용될 수 있습니다.

 

일반적인 동작들

대부분의 게임 오브젝트에 대한 조작은 게임오브젝트의 Transform 이나 Rigidbody를 통해 이루어 집니다. 이것은 Monobehaviour를 상속받은 스크립트 안에서 접근 할 수 있는데, 사용법은 대충 아래와 같습니다.

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        transform.Rotate(0, 5, 0);
    }
}

만약 오브젝트를 움직이고 싶다면

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        transform.Translate(0, 0, 2);
    }
}

 

Overview: Keeping Track of Time  

Time 클래스는 deltaTime이라 불리는 매우 중요한 클래스 변수를 갖고 있다. 이 변수는 Update또는 FixedUpdate 함수가 마지막으로 호출된 이후 경과한 시간의 양을 저장한다. (Update 함수 내부와 FixedUpdate 함수 내부랑은 다르다)

그래서 앞의 예제를 일정한 속도로 회전하도록 만들기 위해 다음 처럼 수정한다.

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        transform.Rotate(0, 5 *
Time.deltaTime, 0);
    }
}

오브젝트 이동:

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        transform.Translate(0, 0, 2 *
Time.deltaTime);
    }
}

만약 당신이 프레임마다 값을 더하거나 뺀다면 Time.delTaTime 곱해야 한다.

다른 예로 만일 당신이 시간에 따라 빛의 범위를 증가시키고 싶다면 아래와 같이 변경하면 되는데 아래 구문은 초당 2유닛 만큼씩 반경을 변경시킬 것이다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        light.range += 2.0F *
Time.deltaTime;
    }
}

Forces를 통해 Rigidbody를 조작할때는, 엔진이 이미 그것을 처리해 주므로 일반적으로 Time.deltaTime을 곱할 필요는 없다.

Overview: Accessing Other Components  

 

컴포넌트들은 게임 오브젝트에 Attack 된다. 하나의 Renderer 컴포넌트를 게임 오브젝트에 붙이는 것은 그것을 스크린 상에 그려지도록 만드는 것이고, Camera를 붙이는 것은 그것을 카메라 오브젝트로 변환하는 것이다. 모든 스크립트들은 컴포넌트들이므로 그것들은 게임 오브젝트들에 붙일수 있다(Monobehaviour를 상속받았다면)

대부분의 일반 컴포넌트들은 간단한 멤버변수들로 액세스 할 수 있다.

Component

Accessible as

Transform

transform

Rigidbody

rigidbody

Renderer

renderer

Camera

camera (only on camera objects)

Light

light (only on light objects)

Animation

animation

Collider

collider

... etc.

 

만일 게임오브젝트가 당신이 얻고자 하는 컴포넌트를 갖고 있지 않다면, 변수들은 null 셋팅된다.

게임 오브젝트에 attach 어떤 컴포넌트나 스크립트는 GetComponent 통해서 access 있다.

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Awake() {
        transform.Translate(0, 1, 0);
        GetComponent<
Transform>().Translate(0, 1, 0);
    }
}

 

transform Transform 사이의  /소문자 차이에 주의해야한다. 전자는 변수이고 후자는 하나의 클래스 똔느 스크립트의 이름이다. 이 대/소문자는 클래스/스크립트이름과 변수이름을 구별하게 해준다.

우리가 배운 것을 적용해 이제 우리는 GetComponent를 사용해 동일한 게임오브젝트에 붙은 다른 스크립트 또는 builtin 컴포넌트들을 찾을 수 있다. 다음의 예제를 작동시키려면 DoSomething함수를 가진 OtherScript 라는 스크립트가 필요하다. OtherScript는 게임오브젝트에 attach되어야 한다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        OtherScript otherScript = GetComponent<OtherScript>();
        otherScript.DoSomething();
    }
}

 

Overview: Accessing Other Game Objects 

 

대부분의 게임코드는 단 하나의 오브젝트만 다루지 않는다. 유니티 스크립팅 인터페이스는 다른 게임 오브젝트들과 그 내부의 컴포넌트들을 찾아서 접근하기 위한 다양한 방법들을 제공한다.

다음의 코드에서 우리는 Scene내에 게임 오브젝트들에 붙여진 OtherScript 라는 이름의 스크립트가 있다고 가정한다.

.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Update() {
        OtherScript otherScript = GetComponent<OtherScript>();
        otherScript.DoSomething();
    }
}

1.     Inspector를 통한 참조

Inspector를 통해서 변수에 할당된 컴포넌트에 접근할 수 있다.

:

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Transform target;
    void Update() {
        target.Translate(0, 1, 0);
    }
}

 

또한 다른 오브젝트에 대한 참조들을 inspector에 노출 시킬 수 있는데 Inspector의 타겟 슬롯위로 다른 스크립트를 가지고 있는 게임 오브젝트를 드래그 할 수 있다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public OtherScript target;
    void Update() {
        target.foo = 2;
        target.DoSomething("Hello");
    }
}

2.     오브젝트 계층을 통한 위치 찾기

오브젝트의 Transform 컴포넌트를 통해 기존 오브젝트의 Child Parent 오브젝트를 찾을 수 있다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Awake() {
        transform.Find("Hand").Translate(0, 1, 0);
    }
}

일단 Hierarchy에서 transform을 찾으면, 타 스크립트들에 접근해서 GetComponent함수를 사용할 수 있다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Awake() {
        transform.Find("Hand").GetComponent<OtherScript>().foo = 2;
        transform.Find("Hand").GetComponent<OtherScript>().DoSomething("Hello");
        transform.Find("Hand").rigidbody.AddForce(0, 10, 0);
    }
}

모든 Child 들을 순환하면서 접근할 있다.

:

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Awake() {
        foreach (
Transform child in transform) {
            child.Translate(0, 10, 0);
        }
    }
}

3.     이름과 Tag를 통해 접근하기

GameObject.FindWithTag GameObject.FindGameObjectsWithTag 사용해서 어떤 tag들로 게임 오브젝트들을 찾을 있다GameObject.Find  이용해서 이름으로 찾을수 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Start() {
       
GameObject go = GameObject.Find("SomeGuy");
        go.transform.Translate(0, 1, 0);
       
GameObject player = GameObject.FindWithTag("Player");
        player.transform.Translate(0, 1, 0);
    }
}

찾은 게임 오브젝트를 통해 GetComponent들을 사용할 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Start() {
       
GameObject go = GameObject.Find("SomeGuy");
        go.GetComponent<OtherScript>().DoSomething();
       
GameObject player = GameObject.FindWithTag("Player");
        player.GetComponent<OtherScript>().DoSomething();
    }
}

메인 카메라 같은 특별한 오브젝트들은  Camera.main. 같은 단축어를 있다.

4.     파라미터를 통해서 위치 찾기

어떤 이벤트 메시지들은 이벤트에 대한 자세한 정보를 담는다. 예를 들어 Trigger 이벤트들은 핸들러 함수에 충돌 오브젝트의 Collider 컴포넌트를 넘겨 준다. OnTriggerStay 함수는 Collider 대한 참조를 제공해 준다. Collider로부터 그것에 Attach rigidbody 얻을 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void OnTriggerStay(
Collider other) {
        if (other.rigidbody)
            other.rigidbody.AddForce(0, 2, 0);
       
    }
}

또는 Collider 동일한 게임오브젝트에 붙여진 다른 컴포넌트들을 얻을 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void OnTriggerStay(
Collider other) {
        if (other.GetComponent<OtherScript>())
            other.GetComponent<OtherScript>().DoSomething();
       
    }
}

샘플에서 Other 변수 뒤에 접미사를 붙임으로써 충돌 오브젝트 안의 다른 컴포넌트를 액세스 있다.

 

5. 특정 타입의 스크립트들

 Object.FindObjectsOfType 사용해 특정 타입의 어떤 오브젝트나 스크립트들을 찾거나 Object.FindObjectOfType. 사용해 특정 타입의 첫번째 오브젝트를 찾을 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Start() {
        OtherScript other = FindObjectOfType(typeof(OtherScript));
        other.DoSomething();
    }
}

Overview: Vectors

유니티는 모든 3차원 벡터를 표현하기 위해 Vector3 클래스를 사용한다. 3차원 벡터의 성분들은 x,y,z 멤버 변수들을 통해 접근 있다.

 

Vector3 aPosition;

aPosition.x = 1;

aPosition.y = 1;

aPosition.z = 1;

 

벡터의 모든 성분들을 한번에 초기화하기 위해 Vector3 생성자를 사용할 있다..

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Vector3 aPosition = new Vector3(1, 1, 1);
}

Vector3  또한 공통적으로 많이 사용하는 값들을 정의해놓은 공통 변수들을 통해 셋팅할 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Vector3 direction = Vector3.up;
}

벡터 상에서의 연산들은 아래와 같은 방식으로 접근 된다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    void Awake() {
        SomeVector.Normalize();
    }
}

또한 여러 개의 벡터들에 대한 연산들은 Vector3 클래스 함수들을 사용한다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Vector3 oneVector = new Vector3(0, 0, 0);
    public
Vector3 otherVector = new Vector3(1, 1, 1);
    public float theDistance =
Vector3.Distance(oneVector, otherVector);
}

또한 벡터들을 다루기 위해 일반 수학 연산자들을 사용할 있다.

vectors: combined = vector1 + vector2;

사용 가능한 연산들과 속성들의 전체 리스트는 Vector3 클래스 참조 문서를 참고하기 바란다.

 

Overview: Member Variables & Global Variables

 

함수 외부에서 정의된 변수는 하나의 멤버 변수로 정의된다. 멤버 변수들은 유니티 내의 Inspector를 통해 접근 될 수 있다. 또한 멤버 변수에 저장되는 어떤 값은 프로젝트와 함께 자동으로 저장된다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public float memberVariable = 0.0F;
}

 

위의 변수는 inspector 안에서 Member Variable 이라는 하나의 수치 속성으로서 보여지게 된다.

만일 변수 타입을 컴포넌트 타입(Transform, RigidBody, Collider,스크립트이름)으로 셋팅한다면, Inspector안에서 게임 오브젝트들을 위로 드래깅함으로써 그것들을 셋팅할 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Transform enemy;
    void Update() {
        if (
Vector3.Distance(enemy.position, transform.position) < 10)
            print("I sense the enemy is near!");
       
    }
}

또한 Private 멤버 변수들을 생성할 있다. Private 멤버 변수들은 스크립트 외부에서는 보이지 않아야 하는 State 저장하는 유용하다. Private 멤버 변수들은Disk 저장되지 않으며 Inspector에서 Edit 없다. 그것들은 Debug 모드일 Inspector 안에서 있다. 이것은 실시간 Updating Debugger 처럼 Private 변수들을 사용할 있게 해준다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    private
Collider lastCollider;
    void OnCollisionEnter(
Collision collisionInfo) {
        lastCollider = collisionInfo.collider;
    }
}

전역변수

Static 키워드를 사용해 전역 변수를 생성 있다.

아래 스크립트는 someGlobal 이라는 전역 변수를 생성한다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public static int someGlobal = 5;
    void Awake() {
        print(someGlobal);
        someGlobal = 1;
    }
}

다른 스크립트에서 그것을 액세스 하기 위해서는 스크립트명.전역변수명을 사용할 필요가 있다.
TheScriptName.someGlobal = 10;

Overview: Instantiate  

Instantiate는 한 오브젝트를 복제(Duplicate)한다. 모든 Attach 된 스크립트들과 전체 Hierarchy를 포함한다. 그것은 예상되는 그대로의 Reference들을 유지한다. 복제된 Hierarchy의 외부 오브젝트들에 대한 참조들은 Untouched 상태로 되고 , 내부 오브젝트들에 대한 참조들은 복제된 오브젝트에 매핑될 것이다.

 

Instantiate는 믿기지 않을 만큼 빠르고 용도도 매우 많다. 유니티를 최대로 활용하기 위해서는 필수적이다.

 

아래의 간단한 예제 스크립트는 Collider를 가진 rigidbody attach 되었을때 자신을 Destroy하고 대신 explosion 오브젝트를 Spawn할 것이다.

 

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    public
Transform explosion;
    void OnCollisionEnter() {
        Destroy(gameObject);
       
Transform theClonedExplosion;
        theClonedExplosion = Instantiate(explosion, transform.position, transform.rotation) as
Transform;
    }
}

Instantiate 흔히 Prefab.들과 결합되어 사용된다.

Overview: Coroutines & Yield  

 

게임 코드를 작성할 , 하나의 이벤트 시퀀스를 스크립트해 필요를 자주 느끼게 된다. 그것은 다음 코드와 같을 것이다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    private int state = 0;
    void Update() {
        if (state == 0) {
            state = 1;
            return;
        }
        if (state == 1) {
            state = 2;
            return;
        }
    }
}

yield문을 사용하는 것이 좀더 편리할 때가 종종있다. Yield 문은 return 특수한 종류이고, next time 호출될 yield 다음 라인으로부터 함수가 계속됨을 보장해 준다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    IEnumerator Awake() {
        while (true) {
            yield return null;
            yield return null;
        }
    }
}

 

또한 어떤 이벤트가 발생할 때까지 Update 함수의 실행을 지연시키기 위해 특별한 값들을 Yield 문에 전달 있다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    IEnumerator Awake() {
        yield return new
WaitForSeconds(5.0F);
    }
}

 

아래 예제는 Do 실행하지만 호출한 즉시 계속해서 다음이 실행될 것이다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    IEnumerator Do() {
        print("Do now");
        yield return new
WaitForSeconds(2);
        print("Do 2 seconds later");
    }
    void Awake() {
        Do();
        print("This is printed immediately");
    }
}

예제는 Do 실행하고, 실행을 계속하기 전에 Do 끝날 때까지 기다릴것이다.

C#

using UnityEngine;
using System.Collections;

public class example :
MonoBehaviour {
    IEnumerator Do() {
        print("Do now");
        yield return new
WaitForSeconds(2);
        print("Do 2 seconds later");
    }
    IEnumerator Awake() {
        yield return StartCoroutine("Do");
        print("Also after 2 seconds");
        print("This is after the Do coroutine has finished execution");
    }
}

이벤트 핸들러들 역시 coroutine 있다.

 

Update FixedUpdate 함수 안에서는 Yield 사용할 없다는 것에 주의하라, 하지만 당신은 StartCoroutine 하나의 함수를 시작하기 위해 사용할 있다.

 

많은 정보는  YieldInstructionWaitForSecondsWaitForFixedUpdateCoroutine and MonoBehaviour.StartCoroutine  참조하기 바란다.

Overview: Writing Scripts in C# & Boo

 

Syntax 별개로 하고 C#이나 Boo 스크립트를 작성할 몇가지 차이점이 있다.

1. MonoBehaviour 부터 상속

모든 게임오브젝트 동작 스크립트들은 Monobehaviour로부터 상속을 받아야 한다. (직접또는 간접적으로) 이것은 자바스크립트 안에서는 자동적으로 일어나지만 C#이나 Boo 스크립트들에서는 명시적이어야 한다. 만일 유니티에서 Asset -> Create -> C Sharp/Boo Script  통해 스크립트를 생성한다면, 생성된 템플릿은 이미 필요한 정의를 포함하고 있을 것이다.

public class NewBehaviourScript : MonoBehaviour {...} // C#

class NewBehaviourScript (
MonoBehaviour):  ... # Boo

2. 초기화는  Awake 또는 Start 함수에서 하라

자바스크립트에서는 함수들 외부에 놓는 것들을, C#이나 BOO 에서는 Awake 함수나 Start함수에 놓는다.

Awake Start 차이점은 Awake 씬이 로드될 실행되고 , Start Update FixedUpdate 처음 호출되기 직전에 호출된다. 모든 Awake함수들은 Start 함수들이 호출되기 이전에 호출된다.

3. 클래스 이름은 파일이름과 일치해야 한다..

클래스 이름과 파일이름이 다르면 GameObject에서 제대로 스크립트 컴포넌트로써 인식을 못한다.

4. C# 에서 Coroutines 들은 다른 syntax 갖는다..

Coroutines  IEnumerator 타입을 리턴해야하고  yield  대신  yield return ... ;  사용해야한다.

using System.Collections;
using UnityEngine;
public class NewBehaviourScript :
MonoBehaviour {
   
// C# coroutine
    IEnumerator SomeCoroutine () {
       
// Wait for one frame
        yield return 0;
       
       
// Wait for two seconds
        yield return new WaitForSeconds (2);
    }
}

5. namespaces 사용하지 말라.

유니티는 아직 namespace안에 당신의 스크립트들을 놓는 것을 지원하지 않는다. 제한은 차후 버전에서는 제거 것이다.

6. 오직 멤버 변수들만 serialized  되고 Inspector 에서 보여진다.

Private protected 멤버 변수는 Debug Mode에서만 보일것이다.  Properties 들은 serialized 되지 않거나, 또는 inspector에서 보이지 않을 것이다.

7. 생성자 사용을 피하라.

어떤값도 생성자에서 초기화 하지 말라. 대신 Awake Start 사용해야 한다. 유니티는 Edit Mode에서 조차도 생성자를 자동으로 Invoke한다. 이것은 종종 스크립트를 컴파일한 즉시 발생하는데, 생성자는 스크립트의 Default Value들을 Retrieve하기 위해서 Invoked 필요가 있기 때문이다. 생성자는 예기치 않은 순간에 호출될 수도 있을 뿐만 아니라, prefabs Inactive 게임오브젝트들을 위해서 호출될 수도 있다.

예를 들어 생성자를 사용하는 singleton 패턴의 경우에 위함한 결과를 야기할 있고, random Null reference Exception 에러로 이어질 있다.

 

그러므로 만일 SingleTon 패턴을 구현하고 싶다면 생성자 대신 Awake 사용하라. 실제로 Monobehavior로부터 상속받은 클래스의 생성자 안에 어떤 코드를 넣을 일은 없다.

Overview: The most important classes  

Global functions accessable in javascript or the base class in C#

Moving / Rotating objects around

Animation System

Rigid bodies

FPS or Third person character controller

 

Overview: Performance Optimization  

1. Use Static Typing

자바스크립트를 사용할 때 가장 중요한 최적화는 동적 타입 대신에 정적 타입을 사용하는 것이다. 유니티는 별도의 작업없이 자바스크립트 구조체들을 정적 타입 코드로 변환해주는 자바스크립트 타입 참조 라는 테크닉을 사용한다.

C#

*JavaScript

*C#

*Boo

var foo = 5;

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
public int foo =5;
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

public foo as int = 5

Var foo = 5;

위의 예제에서 foo 자동으로 integer 값으로 추론이 된다. 그러므로 유니티는 비싼 동적으로 변수 찾기없이 많은 컴파일 타임 최적화를 적용할 있다. 이것이 유니티의 자바스크립트가 다른 자바스크립트 구현들 보다 20 빠른 이유이다.

유일한 문제는 때때로 타입의 추론이 모두 적용되지 않아서 유니티가 동적 타입으로 되돌아 가는 것이다. 동적 타이핑으로 돌아가면 자바스크립트 코드 작성은 더 간단하다. 하지만 또한 코드가 더 느리게 실행되게 된다.

Let's see some examples.

C#

*JavaScript

*C#

*Boo

function Start () {
var foo =

GetComponent(MyScript);
foo.DoSomething();
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void Start()

{
MyScript foo = GetComponent<MyScript>();
foo.DoSomething();
}
}

JavaScript

Function Start()

{

Var foo = GetComponent(MyScript);

foo.Dosomething();

}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def Start():
foo as MyScript = GetComponent[of

MyScript]()
foo.DoSomething()

여기서 foo 동적으로 타입이 정해지므로 DoSomething 함수 호출은 필요 이상으로 오래 걸린다. 이유는 foo 타입은 var , unknown 이기 때문에 그것이 DoSomething 함수를 지원하는지를 확인해야 하고 만일 그렇다면 함수를 호출한다.

C#

*JavaScript

*C#

*Boo

function Start () {
var foo :

MyScript = GetComponent(MyScript);
foo.DoSomething();
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void Start()

{
MyScript foo =

GetComponent<MyScript>();
foo.DoSomething();
}
}

JavaScript

function StarT()

{

Var foo : MyScript = GetComponent(MyScript);

Foo.DoSomething();
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def Start():
foo as MyScript = GetComponent[of

MyScript]()
foo.DoSomething()

여기서 우리는 foo 강제로 특정 타입으로 만들었다, 이렇게 하면 좀더 나은 성능을 얻을 있는 것이다.

2. Use #pragma strict

문제는 당신이 동적타이핑을 사용할 때 보통 알아 차리지 못하고 있다는 것인데, 이것을 알려주는 것이 #pragma strict 이다 . 간단히 스크립트의 맨위에 #pragma strict 를 추가하면 유니티는 그 스트립트에서 동적 타이핑기능을 꺼버리고, 정적 타이핑만 사용하도록 강제할 것이다. 타입을 알지 못할 때마다, 유니티가 컴파일 에러를 리포트를 할 것이다. 그래서 이 경우에서는 컴파일시 foo에서 에러가 날 것이다.

C#

*JavaScript

*C#

*Boo

#pragma strict
function Start ()

{
var foo : MyScript = GetComponent(MyScript)

as MyScript;
foo.DoSomething();
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void Start()

{
MyScript foo = GetComponent<MyScript>() as

MyScript;
foo.DoSomething();
}
}

JavaScript

#pragma strict

{

var foo = GetComponent(MyScript);

foo.DoSomething();

}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def Start():
foo as MyScript = GetComponent[of

MyScript]()
foo.DoSomething()

3. component 캐싱

또 다른 최적화는 컴포넌트의 캐싱이다. 이 최적화는 약간의 코딩이 요구되고 항상 가치가 있는 것은 아니다. 하지만 정말 자주 사용되는 스크립트에서 최적화의 마지막 방법으로 유용하다.

GetComponent 나 액세스 변수를 통해 하나의 컴포넌트를 액세스할 때 마다, 유니티는 게임 오브젝트로부터 올바른 컴포넌트를 찾아야 하는데 private 변수에 컴포넌트에 대한 참조를 캐싱함으로써 쉽게 저장해서 찾는 시간을 줄일 수 있다.

간단히 다음을:

C#

*JavaScript

*C#

*Boo

function Update () {
transform.Translate(0, 0, 5);
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void Update()

{
transform.Translate(0, 0, 5);
}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def Update():
transform.Translate(0, 0, 5)

아래와 같이

C#

*JavaScript

*C#

*Boo

private var myTransform : Transform;
function

Awake () {
myTransform =

transform;
}

function Update () {
myTransform.Translate(0, 0, 5);
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
private
Transform myTransform;
void Awake()

{
myTransform = transform;
}
void Update() {
myTransform.Translate(0, 0,

5);
}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

private myTransform as
Transform

def Awake():
myTransform = transform

def Update():
myTransform.Translate(0, 0, 5)

후자의 코드는 유니티가 매프레임 게임오브젝트에서 transform 컴포넌트를 찾을 필요가 없기 때문에 상당히 더 빠르게 실행될 것이다.transform이나 다른 단축어 속성 대신 GetComponent를 사용하는 스크립트된 컴포넌트들에 모두 똑같이 적용된다.

4. 고유의 배열들 사용

고유 배열들은 빠르다, 매우 빠르다~그러니 그것들을 쓰자.

비록 ArrayList Array 클래스들은 쉽게 성분들을 추가할 수 있어서 사용하기 쉽지만 거의 비슷한 속도를 갖고 있지 않다. 고유 배열들은 고정된 사이즈를 가지지만 대부분의 경우 사전에 최대 사이즈를 알고 있기에 나중에 쉽게 채울 수가 있다. 고유 배열의 가장 좋은 점은 어떤 외부 타입 정보나 오버헤드 없이 하나의 꽉차게 묶여진 버퍼에 구조체 데이터 타입들을 직접 가져온다는 것이다. 그러므로 모든 것이 메모리에서 선형적으로 존재하기 때문에 캐쉬상에서 반복순환 하기가 매우 쉽다.

C#

*JavaScript

*C#

*Boo

private var positions : Vector3[];
function

Awake () {
positions = new
Vector3[100];
for

(var i = 0; i < 100; i++)
positions[i] =
Vector3.zero;
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
private
Vector3[] positions;
void Awake()

{
positions = new
Vector3[100];
int i =

0;
while (i < 100) {
positions[i] =
Vector3.zero;
i++;
}
}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

private positions as (
Vector3)

def Awake():
positions = array[of
Vector3](100)
i as int

= 0
while i < 100:
positions[i] =
Vector3.zero
i++

5. 불필요한 함수 호출은 피해라

모든 최적화에서 가장 간단하면서도 최선의 방법은 동작을 최소로 하는 것이다. 예를 들면 , 멀리 떨어져 있는 적의 경우 잠들게 하는 것은 성능 향상에 좋은 기법이다. 잠들게 한다는 것은 플레이어가 가까이 오기 전까지는 잠든 듯이 아무것도 하지 않는 것이다. 이런 상황을 느리게 조종하는 방법은 아래와 같을 것이다.

C#

*JavaScript

*C#

*Boo

var target : Transform;

function Update ()

{
// Early out if the player is too far

away.
if

(Vector3.Distance(transform.position,

target.position) > 100)
return;
// perform real work work...
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
public
Transform target;
void Update() {
if (
Vector3.Distance(transform.position,

target.position) > 100)
return;

}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

public target as
Transform

def Update():
if
Vector3.Distance(transform.position,

target.position) > 100:
return

이것은 좋은 방법이 아니다. 왜냐하면 유니티는 매 프레임 Update 함수를 호출해야하기 때문이다. 좀더 나은 해결법은 플레이어가 가까이 올 때 까지 스크립트를 꺼두는 것(disable)시키는 것이다. 이것에는 3가지 방법이 있다.

1. OnBecameVisible OnBecameInvisible 을 사용하는 법. 이 콜백함수 들은 렌더링 시스템에 들어 있다. 어떤 카메라가 그 오브젝트를 볼수 있는 순간 , OnBecameVisible이 호출되고, 어떤 카메라도 그것을 더 이상 보지 않을 때 OnBecameInvisible 이 호출 될 것이다. 이것은 몇가지 경우에 유용하지만 종종 AI상에서는 유용하지 않다. 왜냐하면 카메라를 적들로부터 돌리자마자 꺼지기 때문이다.

C#

*JavaScript

*C#

*Boo

function OnBecameVisible () {
enabled =

true;
}

function OnBecameInvisible () {
enabled = false;
}

using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void OnBecameVisible()

{
enabled = true;
}
void OnBecameInvisible() {
enabled = false;
}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def OnBecameVisible():
enabled = true

def OnBecameInvisible():
enabled = false

2. triggers 사용 , 하나의 간단한 구형 트리거로 놀라운 일을 할수 있는데 , 원하는 영향 범위에서 벗어날 때 OnTriggerEnter/Exit 함수가 호출된다.

C#

*JavaScript

*C#

*Boo

function OnTriggerEnter (c : Collider) {
if

(c.CompareTag("Player"))
enabled =

true;
}

function OnTriggerExit (c :
Collider) {
if

(c.CompareTag("Player"))
enabled = false;
}


using UnityEngine;
using

System.Collections;

public class example :
MonoBehaviour {
void OnTriggerEnter(
Collider c) {
if (c.CompareTag("Player"))
enabled = true;

}
void OnTriggerExit(
Collider c) {
if (c.CompareTag("Player"))
enabled = false;

}
}

import

UnityEngine
import

System.Collections

class example(
MonoBehaviour):

def OnTriggerEnter(c as
Collider):
if

c.CompareTag('Player'):
enabled = true

def OnTriggerExit(c as
Collider):
if

c.CompareTag('Player'):
enabled = false

3. Coroutines을 사용. Update함수에서의 문제는 매프레임 호출된다는 것이다. 플레이어와의 거리 체크는 5초마다 수행할 수도 있을 것이다. 이것은 처리 시간을 많이 Save해준다.

Overview: Script compilation (Advanced)  

유니티는 모든 스크립트들을 .NET DLL 파일들로 컴파일 한다. DLL파일들은 런타임에 JIT(Just In Time) 컴파일 될 것이다.

이것은 믿기 어려울 만큼 빠르게 스크립트를 실행 할수있게 해준다. 그것은 전통적인 자바 스크립트보다 20배 더 빠르고 native C++코드보다 약 50% 더 느리다 . 스크립트를 저장할 때 유니티는 모든 스크립트를 컴파일하고 잠깐의 시간이 걸린다. 만일 유니티가 여전히 컴파일 중이라면 유니티의 메인 윈도우의 오른쪽 구석에서 프로그레스바가 도는 것을 볼수 있다.

스크립트 컴파일은 4단계로 수행된다.

1. "Standard Assets", "Pro Standard Assets" 또는 "Plugins" 안의 모든 스크립트들이 먼저 컴파일된다.

이 폴더들 안의 스크립트 들은 폴더 밖의 다른 스크립트들을 직접 액세스 할 수 없다.

클래스나 변수들을 참조할 수 없지만, GameObject.SendMessage를 통해서 통신은 할 수 있다.

2. "Standard Assets/Editor", "Pro Standard Assets/Editor" 또는 "Plugins/Editor" 안의 모든 스크립트들이 먼저 컴파일 된다.

만약에 UnityEditor 네임스페이스를 사용하고 싶다면 당신의 스크립트들을 이 폴더들에 넣어야 한다. 예를 들면 메뉴 아이템 추가나 커스텀 창들을 만들기 위해서는 스크립트들을 이 폴더들 안에 넣어야 한다. 이 스크립트들은 앞의 그룹의 스크립트들을 액세스 할 수 있다.

 

3. "Editor" 밖의 모든 스크립트들이 그 다음 컴파일 된다.

“Editor”폴더와 그 서브 폴더를 제외한 모든 스크립트들이 컴파일 된다.

이 단계의 스크립트들은 이전 단계의 스크립트들을 액세스 할 수 있다. 이것은  다른 언어로 짜여진 스크립트들을 얻을수 있도록 해준다. 예를 들면, C#스크립트 를 사용하는 자바스크립트를 만들길 원한다면 , C#스크립트는 Standard Assets 폴더에 놓고 자바스크립트는 Standard Assets 폴더 바깥에 놓는다면 그 자바스크립트는 C#스크립트를 직접 참조 할수 있다.

첫번째 그룹안에 놓인 스크립트들이 컴파일되면 세번째 그룹도 재컴파일 되어야 하므로 컴파일에 시간이 더 걸릴 것이다. 따라서 컴파일 시간을 단축시키고 싶다면 덜 바뀌는 스크립트들을 첫번째 그룹으로 옮기고 많이 바뀌는 것은 3번째 그룹으로  옮겨라

 

4. "Editor" 안의 스크립트들이 가장 마지막에 컴파일된다.

만약 UnityEditor 네임스페이스를 쓰고 싶다면 이 폴더에 넣어야 한다. 예를 들면 menu 아이템들을 추가하거나 custom wizards를 작성하기 위해서는 스크립트들을 이 폴더 안에 넣어야 한다.

이 스크립트들은 앞 그룹의 모든 스크립트들을 접근할 수 있다.

 

또한 WebPlayerTemplates이라고 불리는 폴더는 컴파일 되지 않는다..

조건부 컴파일은 유니티 버전에 의존한다.

유니티 2.6버전부터 C# 전처리기 정의가 추가되었다. 이 정의는 사용중인 유니티의 버전을 정의하고 특정 특징들을 조건적으로 액세스할 수 있게 해준다. 예를들면,


// Specific version define including

the minor revision
#if

UNITY_2_6_0
// Use Unity 2.6.0 specific

feature
#endif

// Specific version define not including the minor

revision
#if UNITY_2_6
// Use Unity 2.6.x specific feature
#endif

이 코드는 특정 버전에서만 가능한 game feature들을 활성화 시키는데 사용될 수 있다. 이 정의는 오직 2.6버전부터 제공된다는 것에 주의해라. 앞으로 나올 유니티 버전들은 스크립트 버전을 분별하기 위한 적절한 정의들이 제공될 것이다.

알림 : 플랫폼별 조건부 컴파일에 대해서 좀더 정보가 필요하다면 Platform Dependent Compilation.을 참조해라

Overview: Unity and Mono compatibility  

유니티를 위하여 당신의 스크립트 안에서 닷넷 라이브러리를 사용할 수있다. 당신 프로젝트 안에서 닷넷 호환성 레벨을 선택한 것에 따라 달라지고, 유니티를 풀클래스 라이브러리들보다 좀더 많거나 적게 제공하고 있다. 어떤 특정한 세팅들과 함수 호환성을 체크하고 싶다면 .Net Compatibility page.

 








3_MonoBehaviourAndUnityScripting20111215 파일을 연다.

Object <- Componet <- Behaviour  형태로 상속 받았다. 함수를 모두 사용할 수 있다.

이렇게 상속받아서 솟컷이 가능하다..



## 해당 이벤트 발생 처리


## 객체가 접근

## Component  접근

## Debugging  <== 실습하자..



## Send Message 방법을 알아보자.

GO.SendMessage("함수이름");

GO.SendMessage("함수이름, 인자");

이것은 프라이빗 함수라도 호출 가능하다. (인자도 특정한 것으로 정해져 있다.) 

펑션콜보다 센드메시지가 훨 빠를 때가 있다.


이것을 쓰면 메신저에 쌓아두고 나중에 여유가 되면 겟해서 실행한다.

- 시간에 관계없이 여유될 때 사용하게 하려면 이렇게 해서 처리해 두면 좋다.

- RPC로 다른 함수를 호출시 이해가 쉽다.

  이것은 패킷 필요없고 함수 호출로 동작이 가능하다. 

  이것의 시작점이 sendmessage이다. 통신도 편하게 사용할 수 있다.

  이것은 퍼블릭함수만 호출가능하다.





[delegate를 설명하자.]

- 활용하기에 유용하다. C# 스럽다.

새로 new Scene

스크립트 DelegateTest 스크립트 추가. 


using UnityEngine;

using System.Collections;

using System;

using System.Collections.Generic;

using System.Text;


delegate void dele1();

delegate int dele2(int a, int b);

public class DelegateScript : MonoBehaviour {

public class MathClass

{

public void Intor()

{

Debug.Log("Start the Intro~");

}

public int Sum(int a, int b)

{

return a+b;

}

}

// Use this for initialization

void Start () {

MathClass mclass = new MathClass();



dele1 intro = new dele1(mclass.Intor);

dele2 sum = new dele2(mclass.Sum);

intro();

int result = sum(-10, 90);

Debug.Log("result : " + result.ToString());

}


}


그리고 메인카메라에 스크립트를 추가한다.




using UnityEngine;

using System.Collections;

using System;

using System.Collections.Generic;

using System.Text;


//사용법


delegate void deleMath(int Value);



public class MathClass

{

public int number;

public void plus(int Value)

{

number = number + Value;

}

public void Minus(int Value)

{

number = number - Value;

}

public void Multiply(int Value)

{

number = number * Value;

}

}


public class DelegateTest : MonoBehaviour {

void Start() 

{

MathClass MClass = new MathClass();

deleMath Math = new deleMath(MClass.plus);

Math += new deleMath(MClass.Minus);

Math += new deleMath(MClass.Multiply);

//result

MClass.number = 10;

Math(10);

Debug.Log("result : "+MClass.number.ToString());

// delegate remove

Math -= new deleMath(MClass.Minus);

//result

MClass.number = 10;

Math(10);

Debug.Log("result : "+MClass.number.ToString());

}

}


/*

delegate void dele1();

delegate int dele2(int a, int b);

public class DelegateScript : MonoBehaviour {

public class MathClass

{

public void Intor()

{

Debug.Log("Start the Intro~");

}

public int Sum(int a, int b)

{

return a+b;

}

}

// Use this for initialization

void Start () {

MathClass mclass = new MathClass();

dele1 intro = new dele1(mclass.Intor);

dele2 sum = new dele2(mclass.Sum);

intro();

int result = sum(-10, 90);

Debug.Log("result : " + result.ToString());

}

// Update is called once per frame

void Update () {

}

}

*/



c#은 모두 콜백이.. 레퍼런스로 사용가능하다.
delegate는 한사람만 사용해야 한다. 또는 자기것만 사용한다.
결과는 run해서 consol에서 확인 가능하다.
result : 100
UnityEngine.Debug:Log(Object)
DelegateTest:Start() (at Assets/Scripts/DelegateTest.cs:41)
result : 200
UnityEngine.Debug:Log(Object)
DelegateTest:Start() (at Assets/Scripts/DelegateTest.cs:50)




내일은 슈팅게임을 다시 만든다..
2D슈팅게임 한다.
마무리하고 보강한다..

캐릭터한다.

오늘은 3D로 움직인다.라는 것.











 실전 Unity3D Engine Programing 과정 2일차 (2013.07.23 (화))


금일 만든 파일: 

shooting.unitypackage




툴에대해서

히어락키

카메라

인스팩터 : 설명, 폴더 (중요하지 않다.)

플레이/좌우 버튼.


창을 새로 끄낼경우.. 우측 상단에 Layers 버튼으로 추가할 수 있다.

Layers는  WIde로 하자..



슈팅게임 만들자..

프로젝젝트 하단 폴더가 비어 있습니다. 오른쪽 클릭, 옵션 메뉴 

크리에터 / 폴더

- Scripts 만든다.

- Materials

- Prefabs




포커싱의 의미

 히어아키에서 카메라를 선택해서 카메라가 중앙에 오도록 한다.

- 카메라가 중앙에 있으면 이상태에서 개발하면 좋다.

- 0 1 1.0

- 이상태 Menu-> GameObject -> Create Empty하면 카메라 것과 동일하게 생긴다.

- 이 GameObject 클릭해 보면 카메라와 동일하다. 포지션이


카메라 선택하고 포지션은 0.0.0으로 한다.





프로젝션은 Othographic를 선택한다.

신창  Alt + 마우스로 카메라를 옯길 수 있다. 휠키로 줌 처리



카메라 신창의 박스를 조절하자.

Size : 5

ClippingPlanes

Near -2, Far 2




Menu GameObject Create Other Cube를 선택한다. 




히얼아키에 Cube가 생기고

신탭 옆에 Game탭을 눌러보면 검은색이 생겨 있을 것이다.

검은색 박스가 안보이면  포지션을 0.0.0 으로 한다.

그리고 이름은 Cube -> Player로 변경한다.




-5   5  5  -5 크기의 박스에서 중앙에 플레이어가 있게 한다.

플레이어 중앙으로 옮기기 위해서 -4한다.



Menu GameObject Create Other Direct Light






Assets에서  Meterial -> Create -> Meterial 이름은 PlayerMat 로 지정한다.

이것을 선택하면 좌측에 Main Color를 녹색으로 바꾼다.






Player를 선택하고 

프로젝트에 PlayerMat를 끌어다가 오른쪽 인스팩토리리에 옮겨 놓으면 Player는 녹색된다.





코딩해 보자

프로젝트의 scripts > create > C# script > 이름은 PlayerControl





이 PlayerControl을 누르면 모노디벨롭이 뜬다. 여기서 코딩한다.






파일 이름과 클래스 이름이 동일하다. <== 매우 중요 틀리면 컴파일 안됨

클래스이름이 스크립의 해더가 된다. 


using UnityEngine;

using System.Collections;


public class PlayerControl : MonoBehaviour {


// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}


열어 보면 MonoBehaviour를 상속 받는다.

start: 이니셜

update: 여기서 그려진다.



using UnityEngine;

using System.Collections;


public class PlayerControl : MonoBehaviour {

public float Speed = 1.0f;

// Update is called once per frame

void Update () {

}

}


이렇게 수정하고 


하이어라키에서 Player를 선택하고 프로젝트에서 PlayerControl를 잡아서 오른쪽으로 옮겨 놓는다.

그러면 speed가 생긴다. Public으로 해서 나오는 것이다. (디자이너에게 수정할 때 이렇게 해 주면 좋다.) 


코드를 아래와 같이 수정해 보고

void Update () {

float moveAmt = Input.GetAxis ("Horizontal") * Speed * Time.deltaTime;

transform.Translate(Vector3.right * moveAmt);

}



플레이 버튼을 누르고 키보드로 좌우를 눌러보면 녹색박스가 좌우로 움직인다.

그리고 인스팩트에서 스피드를 1 -> 10으로 변경해 본다. 그리고 움직이면 움직인다.

플레이중에 스피드를 변경하면 저장안된다. 



저장


File Save Scene 

File Save Project

한다. (play는 스톱해야 한다.)


Edit -> Project Setting -> Input 를 누르면 우측 인스팩터에 Axes가 나온다..

이중 Negative Button left, Positive Button은 Right

따라서 좌우 키를 누르면 코드상에 음수 값 양수값이 GatAxis에서 값이 들어오게 되는 것이다.

경과시간을 곱한다..

업데이트 타임은 deltaTime을 사용한다. (타임의 종류도 많다.)

float moveAmt = Input.GetAxis ("Horizontal") * Speed * Time.deltaTime;


transform.Translate(Vector3.right * moveAmt);

인스팩트에 있는 것으로 숏컷 개념으로 바로 사용하고 

transform 해당 항목을 정의 하면 해당 항목과 링크를 걸어 주는 것이다.


Vector3 는 아래와 같은 방향을 제공한다.

방향은 

업다운

레프트라이트

프론트백




이제 총을 쏴보자..

GameObject create other Shpere

positon은 0.0.0

이름은 Bullet이다.

그리고 메터리얼 만들고 색깔 바꾸고 Bullet에 선택하고 드래그 드럽한다.


스크립터 한다.

Bulletcontrol을 만든다.

내용은

using UnityEngine;

using System.Collections;


public class BulletControl : MonoBehaviour {


public float Speed = 20f;

// Update is called once per frame

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.up * moveAmt);

}

}




콘솔창을 띄우다.

Window >  console

진행과정을 볼 수 있다.


하이얼라키에서 Bullet을 선택하고 BulletControl을 마우스로 잡아서 오른쪽으로 드래그 드랍한다.

플레이 한다. 


총알 올라가는 것을 제약해야 한다. 

if(transform.position.y > 7.0f){

Destroy(gameObject);

}

gameObject와 같이 소문자는 자기로써.. 삭제된다는 것이 GC전까지..


Assets > Prefabs > Create Prefa  이름 BulletPrefab

만든 Bullet 오브젝트를 끌어서 여기에 저장한다. 

그리고  Bullet는 삭제한다. 



스페이스바에서 총을 쏴보자.

PlayerControl에서 아래 것을 추가한다.


public GameObject bullet;

public GameObject bullet;

void Update () {

float moveAmt = Input.GetAxis ("Horizontal") * Speed * Time.deltaTime;

transform.Translate(Vector3.right * moveAmt);

if(Input.GetKeyDown(KeyCode.Space)){

Instantiate(bullet, transform.position, transform.rotation);

}

}



Player

None 한번도 링크를 건적이 없다.

Missing 링크 걸고 지운것


Prefabs에서 BulletPrefab을 선택해서 마우스로 끌어서 오른쪽에 스크립의 Bullet 항목에  드랍한다.


그리고 플레이 시켜서 스페이바를 눌러 본다.









[적군을 만들자]

적군 만들기: GameObject > Create Other > Cube 이름은 Enemy

적군 색상바꾸기: Materials  Create > Metrial 이름은 EnemyMat 색은 빨간색

Enemy선택해서 EnemyMat를 잡아서 오른쪽에 둔다.

적군 코드를 넣다. : Script Create > C# script 이후 코딩한다.


using UnityEngine;

using System.Collections;


public class EnemyConrol : MonoBehaviour {


public float Speed = 8.0f;

// Update is called once per frame

void Update () {

float moveAmt = Speed * Time.deltaTime;

transform.Translate(Vector3.down * moveAmt);

if(transform.position.y < -7.0f){

// 하단까지떨어지면  다시 상단으로간다 

InitPosition();

}

}

void InitPosition()

{

// 죽으면 상단에 x값을랜덤하게새로생성된다 

transform.position = new Vector3 (Random.Range(-5.0f, 5.0f), 7.0f, 0.0f);

}

}




Prefabs를 선택해서 우측 상단에 Tag를 추가한다. 

선택하면 TagManager가 생긴다.

* 총알은 계속 발사하면, 또는 벽 등으로 구분하기 위해서는 클론된 것들을 Tag로 구분한다.


Tags  선택하고 Element에 bullet를 입력한다.



[충돌체크를 넣는다.]

다시 Prefabs에서 BulletPrefab을 선택하고 오른쪽에


BulletPrefab에 RidgBody를 붙이고 중력값을 큰다.

Menu > Componet > Physics > RigidyBody를 선택한다.

그리고 Sphere Colliderd의 Is Trigger를 체크한다.

<== 이제 물리엔진에 대한 속성을 갖게 된다.


다시 EnemyControl로 이동한다.

//인자로  충돌된 것이날라온다 충돌시자동으로온다 in trigger를체크할경우

void OntriggerEnter(Collider other)

{

if(other.gameObject.tag == "bullet")

{

// 충돌 오브젝트의 테그를보고 총알인지   벽인지 구분한다 

InitPosition();

}

}



[이팩트]

이제 충돌시 이팩트를 넣어 보자.. 

간단한 제공하는 것으로 레가시로 하자.


Assets > Import Package > Particle > 팝업에서 Import버튼을 누른다. 그러면 Assets에 Standard Assets이 추가된다.

여기서 Legacy Particle > Small explosion이 있다. 

이것을 끌어서 히얼아키창에 드레그 드랍한다.


이것을 One Shot을 체크한다.

- 한번 노출 되었다 사라진다.


파티클 사이즈와 에니메이션 몇개 노출될 것인가를 지정ㅎ낟.

Emit

MiknSize

Max Size

..

Max Emission


Rnd Velocity를 3.3.3d으로 변경


다시  Prefabs Explosion을 만들고

Small expiosion을 드레그 드랍으로 끌어 넣고 지운다. 



[사운드 만든다.]

인터넷을 열어서 구글 에서 

http://www.drpetter.se/project_sfxr.html 이동한다.

http://www.drpetter.se/files/sfxr.zip

sfxr.zip을 다운받는다. 

압축을 풀고 실행해서 원하는 사운드를 골라서 저장한다. (붉은색 박스의 버튼으로 저장할 수 있다.)



Assets  에서  Audios폴더 선택하고 오른쪽 마우스 > Import New Asset 으로 파일하나씩 불러온다.

꼭 이 방식으로 한다. 같은 이름을 임포트하지 않기에 프로젝트 뻑나지 않게 한다.

파일 익스플로러로 해당 파일에 옮기지 말것 <=== 매우 중요함

audio show in folder로 익스플로러를 열어 볼 수 있다. 여기다 직접 넣으면 안됨.. ** 


player 오브젝트를 선택하고  Menu > Componet > Audio > Audio source를 선택한다. 

오른쪽을 보면 Audio Source생긴다

audo clip에 audios  파일을  여기다 끌어 놓는다.

play on awake를 체크를 언체크한다. 생성시 계속 출력하기에



스크립트에 코드 추가

//인자로  충돌된 것이날라온다 충돌시자동으로온다 in trigger를체크할경우

void OntriggerEnter(Collider other)

{

if(other.gameObject.tag == "bullet")

{

audio.Play();

Instantiate(explosion, transform.position, transform.rotation);

// 충돌 오브젝트의 테그를보고 총알인지   벽인지 구분한다 

InitPosition();

}

}



prefabs에서 explosion을 선택하고 audio source를 추가한다.

오디오 파이를 오디오 클립에 넣어 준다.

그리고 play on awake를 체크한다. 한번만 출력하게..



Enemy에도 오디오 소스를 추가하고 오디오를 오디오클립에 넣어 준다.


유니티는 3D 사운드를 제공해서 카메라 위치에 따라서 다르게 들린다.

Main Camera에 Audio Listener로 한개의 리스너만 있다. 카메라를 추가해도 메인카메라에 리스너 하나만 있어야 한다.

한신에 오디오리스너는 한개만 존재해야 한다. 


[스코어 추가하자.]

메인하나 추가하고 스코어를 추가한다.

히어아키 GameObject > Create GameObject 이름은 main game object


스크립트 추가한다. Main

using UnityEngine;

using System.Collections;


public class Main : MonoBehaviour {


static public int Score = 0;

static public int Lives = 3;

// Update is called once per frame

void Update () {

}

// 느려터치 유니티 GUI 거의안씀

void OnGUI() 

{

GUI.Label(new Rect(10.0f, 10.0f, 200.0f, 20.0f),"Score : ", Main.Score.ToString());

GUI.Label(new Rect(10.0f, 10.0f, 200.0f, 20.0f),"Lives : ", Main.Lives.ToString());

}


MainGameObject에 Main 스크립트 추가한다.


그리로 블랫에 코드를 추가해서 변경


New Scene을 만든다.

엠프티 오브젝트 추가 WinGameObject를 만든다. 

스크립트 단다. WIn

using UnityEngine;

using System.Collections;


public class Win : MonoBehaviour {



void OnGUI()

{

if(GUI.Button(new Rect(Screen.width/2-100.0f, Screen.height/2-100.0f, 200.0f, 200.0f),"YouWin!"))

{

//버튼눌러지면이곳으로들어온다

Application.loadedLevel("game");  // main scene이노출된다

}

}

}


winGameObject에  win  스크립트를 추가하고

menu save scene을 눌러서 win 으로 저장한다.

Assets을 보면 추가된 것을 볼 수 있다. 


Menu > File > Game Setting > 두개의 신을 추가하다. game, win  순서대로



using UnityEngine;

using System.Collections;


public class Main : MonoBehaviour {


static public int Score = 0;

static public int Lives = 3;

public int FinishScore = 1000;

// Update is called once per frame

void Update () {

if(Score >= FinishScore)

{

Score = 0;

Application.loadedLevel("win");

}

}

// 느려터치 유니티 GUI 거의안씀

void OnGUI() 

{

GUI.Label(new Rect(10.0f, 10.0f, 200.0f, 20.0f),"Score : "+ Main.Score.ToString());

GUI.Label(new Rect(10.0f, 30.0f, 200.0f, 50.0f),"Lives : "+ Main.Lives.ToString());

}

}





내일은 모델, 폴리곤, UI를 붙인다. 드레곤플라이트를 만든다. 배경 만든다.

케릭터, 전투, 이팩트, 채팅, 슈팅,  네트워크해서 드레곤플라이트를 만든다.



에너미를 복사해서 위치를 바꿔주면 여러개의 적이 나온다.




숙제..

1. 플레이어도 죽자. <= 리니지 바디를 붙인다. 

2. 아이템 박스 (스코어를 올려준다.) <= 큐브 만들면 된다. 

3. 아이템 박스 먹으면 다양한 삼지창 방향으로 총이 나가게 한다. <= 방향을 바꿔주면 된다.

4. 스테이지 늘리기 <= 옵션

5. 보스 <= 옵션





실전 Unity3D Engine Programing 과정 1일차 (2013.07.22 (월))


[강사 소개]

국민대학교 게임 교육원 주세영교수  


프리랙 책 집필중

주간에는 게임회사팀장

야간과 주말은 교육장


HG Entertainment -  캐쥬얼슈팅

엔사 - 소셜온라인

CCR - MMORPG

현 GameLoft - Web 3D온라인

현 국민은행 게임 교육원 교수



 GameLoft / 4000명 1006개국.. 급여는 국내 테이블

- 시니어스튜디오 유명한 게임을 많이 만든 스튜디오를 지칭한다.



만든 게임은

페이스북 게임

MMORPG

데이트 온라인 /  SNS 게임

싱크 건담 / 200개 팀대팀전..


서버클라이언 6주 개발

캐릭터 폴리곤 500개.. 

RocketRacer


게임 난이도

온라인 FPS

대전게임

레이싱게임


노리공방

10개월 




[강의 순서]

1. 게임 개발에 대해서

- 패키지, 온라인, 외국계회사 등등의 개발 패턴

- 웹게임과 3D 게임개발

- Unity3D로 만드는 게임

2. Unity3D

- Unity3D

- Unity3D로 만들어진 게임

3. 실무에서의 Unity3D

4. Project - Unity3D

5. 실무에서 유용한 팁과 노하우들



1. 게임 개발 패턴

게임은 어떻게 개발하나


패키지 게임의 게임 개발 패턴

- 스크럼 방식을 주로 사용

- 기획을 중간에 바꾸지 않는다. (수정사항은 다음 게임에)

- 마감은 정확하게 하지만 여유 있는게 포인트

- 업무부하 협동해결제도


-패치는 최후의 수단, 반드시 버그 없는...


온라인 게임의 게임 개발 패터

1. 컨셉기획

2차 

3차


4차 CBT

 5차 검증 및 수정

6차..



제작기간이 길다.


외국계회사의 게임 개발 패턴

GameLoft - 모방과 신속개발이 모토

T모다 - 창의성과 재미있는 게임이 모토


웹게임젲가의 노하우


...


3D rpdla ..


2.Unity3D 

쉽고 빠른 게임 개발 엔진......

엔진은 개발에 필요한 것을 모아둔것...


Componet Centric 엔진 <== 가장 중요한 용어 굉장히 중요한 개념이다.

그레고리  - 게임 엔진 아키텍쳐

유니티는 기존엔진과 많이 다르다.

C++은 탈것 하나만 정의 하면 된다...

- 한번 객체로 설계된 이후 변경이 발생되면 수정이 어렵게 된다.


컨포넌트 센트릭은

어떤 오브젝트의 속성을 정의한다.. 

각 오브젝트의 조합을 해서 기능을 만든수 잇다..

아무것도 오브젝트 하나 만들고 아무 기능이 없다..

이 것이 사람이라 정의 하면, 밤이란 환경을 만나면 잔다... 

어떤 특정 환경에 만나게 되면 기능이 생긴다. 

특정 지역에서 특정 기능을 갖게 된다.

이렇게 하면 확장성이 무긍무진하다.


특성을 모아둔것...


따라서 기획...이 바꿔야 한다..

기능정의를 하고 이 기능이 모여진 것이 사람이 될 수 있고, 적군이 될 수 있다.. 등...

기능만 합치면 무긍무진하다...



 Actor centric (언리얼 게임 엔진)

<- 컨포넌트 센트릭 의 반대 개념..

<- 기능을 다 만들어 놓고 On/Off처리..


컨포넌트센트릭은

- 기능중심의..........


슈팅, 모델링, NGUI, 2D, 네트워크, 에니메이션 케릭터,  3개월과정

슈팅, 디펜스, RPG, LOL 6개월과정



컨포넌트로 자유롭게 게임 오브젝트를 구성 가능하다...


유니티에서 컴포넌트?

트렌스콤포넌트



3D모델


게임 오브젝트.

- 컴포넌트를 담는 그룻


게임환경


유니는 쉽게 접근이 가능하다. 시간화, 실시간 확인 가능

멀티플랫폼...




Programmer -> GraphicArtist

자신이 만든 산출물이 게임에서 어떻게 나올지, 우리 엔진에서

어떻게 보이는 지 궁금한 것이 당연한 아티스트들

그래픽 아티스트가 쉽게 확인가능하기에 책임을 진다.


큰프로젝트일수록 어마어마한 데이터 데이틀(XML, Excel, Orginal-Format)

프로젝트 후반일 수록 개발에 큰 영향을 미치게 된다.


데이터 관리툴...

사고 방지를 위해 데이터 검증 및 관리 툴을 제작

하지만 검증툴 개발 우선순위느..



유니티는

엑셀 -> XML -> 서버, 클라이언트 데이터로 생성된다.



툴을 이용하면 레벨생성기를 제공한다.



Project Issue

- Engleish Spelling Issue

- Debugging

- Edit


리소스에 스펠링이 달라서 문제가 생길 수 있다.

Missile = mesail meesail, misail mysail


PrePop

리소스 값의 모음으로 프리팝을 통해서 사용하면 좋다. 권장한다.



Proejct Editor / 


유니티는 디버깅이 안된다..

브레이크가 안된다.. 메모리가 세고 프로그램 뻣는다.

로그 메시지는 알기 쉽게..

Debug.Log, LogWarning, LogError

왜냐 하면 코어엔진은 C++이지만, 나머지는 닷넷으로 되어 있다..


버전관리툴은 사지도 마라 문제가 많다.

토토즈, 포포즈 사용해라.


Edit and Edit, Again Edit

값을 외부로 뽑아서 지정할 수 있게 해 준다.


기타..

리팩토링, 스크립트는 80라인을 이내로 해야 한다. 

Simple is Best


여기서 정의 하는 클래스는 함수라고 생각해야 한다.

함수하나가 스크립터이다. 기능하나이다.


3.5.7F가 가장 안정적이다. (4.0은 최신 버전으로 써라)


10년된 엔진이지만 최근 5년에 발전했다.


5. Tip

GUI가 매우 느리다.

GUI가 하나 하나 오브젝트로써 이벤트를 갖는다.

이GUI, NGUI 등을 추가로 판다.

멀티스크린UI





www.unifycommunity.com/wiki/index.php?title=main_page



어셋에 좋은 것 판다.


스케일


스케일 팩터 0.01 로 변경된다. 스케일은 변경하면 안된다.

익스포트..것


PSD  파일을 지원한다. 

지원하지만 레이어를 제공하기에 메모리용량이 크기에 게임에서는 쓰지 마라..



메타파일

기획, 디자인, 개발 모두 유니티3D를 사용해야 한다.

한프로젝트르 모두 사용하기에 오히려 위험할 수 있다..

리소스에 메타파일을 기록되어 있다.

메타파일은 수정 이력을 기록되어 있어 리소스와 함께 save해야 한다.


4시간 30분 인포트하는 상황이 생길 수 있다.


리소스 추가 시 메타파일을 같이 추가해야 한다. 


save하고 메타파일과 같이 올려줘야 한다.


메타파일 정말 중요하다... (꼬리면 2일 이상 리로드할 수 있다.)




텍스쳐 형식변경

2의 승수이어야 한다.

change Non Power of 2 (NPOT Gsize)

이미지 넣으면 깨진다.....



라이트맵...

다이렉트 스포트... 파일

다이렉트 텍스쳐이다. 맥에는 이 파일을 못읽는다.

멀티플랫폼시 필히.. 라이트맵은 맥에서 빌드해야 한다. 맥용 버쳘 텍스쳐 포맷으로 나온다. (윈도우 것으로 하면 깨진다.)


폰트..

볼드등은 지원하지 않았지만 4.0에서 지원한다 느리다..



프리로딩..

프리팝.. 그때 그때 화면에서 읽는다. 이러면 화면이 뚝뚝 끈긴다..

만들어서 우주밖으로 보내 둔다...


유니티는 생성은 하고 삭제는 하지 않고 GC가 삭제한다. 

GC가 호출될 때 게임이 끈기는 현상이 생길 수 있다..

GC를 임의로 호출하는 방법도 있다..

프로파일과 사용하는 방법을 제공할 것이다...



게임 개발사 입장에서 유니티 한계


Low Layer 커스트마이징 안된다.

프로젝트만의 툴 커스터마이징이 안된다.

멀티 플랫폼이지만 지원 플랫폼에 따라 복잡도가 증가하는 건 진리

그지 같은 디버깅...



멀티플랫폼 제공하지만 각 플랫폼에 맞게 적용해야 하는 어려움이 있다.


리소스 최적화 방법

플랫폼 별 텍스쳐 크기 지정

텍스쳐 파일 포맷은 최대한 동일하게

공유 매트리얼을 쓰면 좋다.

폴리곤은 압축해서

애니메이션은 적을 수록 좋다.



2033

2048 아이폰 한번에 읽는 량은 50MB만 읽는다..

1024 x 1024  안드리이드





멀티 플랫폼 개발

다른 엔진의 멀티 플랫폼

유니티 엔진에서 멀티 플랫폼

각 플랫폼별 아셋을 변경하기에 오래 걸린다.. 

3개의 버전별로 PC를 준비해서 빌드한다...


N스킬ㄴ 대응

UI윛 보정

마우스 터치스크린 

아이폰은 5개..  아이패드는 11개도 터치 되낟. 넥서스원 2개 최근 안드로이드는 8개..


가속도계중력..


프레임 확보를 위해...(최소프레임은 21 프레임 이상 나와야 한다.)

LOD는 동적 생성보다 GameObject On/Off활용 

Culling은 적극적으로 활용해라 / 카메라 컬링을 해 주며 좋다. 

Shader의 남용 금지

Mesh Merging / 캐릭터의 커스터마이징하다보면 문제가 많은데 메쉬 머지로 하면 드로컬이 하나로 된다.

충돌체크 간략화  / 충돌 위치 파악을 위해서 레일을 쓰면 느리다.. 7프레임 정도 겹치는 형태로 해야 하면 좋다.

Light는 최소화 / 모바일에서는 동작 라이트는 절대 사용하지 마라. 생성해서 사용하지 마라..라이트수만큼 드로컬이 2배씩 증가된다.

Mipmap반드시 켜라 / 

UI리소스는 2의 승수로 만드는 것이 좋다.

애니메이션 최소화

폴리곤 최소화

빌보드는 Shader로 할 것....



웹기만 

구글 웹스토는 


유니티는 다양곳에 사용한다.

- 교육용 40%

- 영화 20%

- 3D화면 제어 


3D GPS, SI솔루션 AR소프트웨어 매장관리 프로그램 키오스크 용 소프트웨어 3D아바타 메신저