실전 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을 붙인다.


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