※ 유니티 상에서 Read Only 파일에 어떠한 수정을 가해도 껏다키면 사라진다. 주의하자
※ 애니메이션 파일에서 Inspector - Anmation 에서 클립 추가하고, 몇프레임에서 몇프레임까지 주는 식으로 클립 주기 가능
※ 애니메이션은 하나의 파일에서프레임 단위로 찢어서 여러 모션들을 넣는경우가 있고, 각 모션마다 모두 따로만들어서 관리하는 케이스가 있다.
※ 후자의 치명적 단점 : 모델이 각각의 파일마다 필요하므로, 코스트가 굉장히 크다 ;;
해결 : 클립파일만 뽑아내서 사용하고 모델링 파일을 하나만 남겨둔다.
※ 전자의 치명적 단점 : 중간에 어떤 애니메이션의 프레임이 증가하거나 감소할 경우 나머지 애니메이션에도 모든 영향을 끼침
※ 애니메이션 이름 줄때 @ 를 사용하자, 모델명@클립이름 이 규칙을 사용하면 자동으로 포함을 한다.
※ Transform 혹은 GameObject 에 접근 할수 있다면 모든 컴퍼넌트에 접근할 수 있다.
※ GameObject.Contains( " " ) 로 느슨한 체크 가능
※ Destroy ( ) 안에 this 등 다른 class나 컴퍼넌트를 넣는 경우가 있는데 이렇게 할 경우 해당 class 혹은 컴퍼넌트만 사라지게 된다. 그런데 나중에 이 컴퍼넌트 등이 필요해지면 또 AddCompoent 를 해서 추가해줘야하는데, 좋지않은 케이스다.
※ GetComponentInChildren< > 은 얼마나 하위에있건 상관없이 다 찾아서 컴퍼넌트를 가져오는데 가까운 순으로 탐색한다.
※ 계층 구조 어디에있던 해당 컴퍼넌트를 가져올수 있다.
바로 Find 함수 사용법은 GameObject.Fine("이름").원하는컴퍼넌트
// 하지만 Find 는 굉장히 느리다 사용하지마!
※ 더욱더 좋은 방법이 있으니 바로 태그를 사용하는 것!
GameObject.FindGameObjectWithTag("태그명").컴퍼넌트 태그를 씁시다 태그를 !
※ 이런 실시간 링크가 필요한 이유는 서로 링크시켜야할 대상이 처음엔 Scene 에 존재하지 않을경우
직접링크가 불가능하기 때문이다.
※ .magnitude 벡터의 성질중 하나로 한점에서 한점을 빼면 벡터가 나오는데, 그 거리를 구하는 방식으로 두 점의
실제 거리를 반환 시켜준다. 같은 방법으로 Vector3.Distance ( 포지션, 포지션 ) 이 있다.
※ 벡터의 성질을 이해하자 A 에서 B 를 빼면 B에서 A를 향하는 벡터가 나온다.
※ Normalize 정규화 그 벡터의 크기를 1로 만들어 주는것
※ Quaternion.LookRotation ( 벡터 ) 벡터값을 기준으로 사원소값을 만들어준다 !
※ SimpleMove : 심플무브는 y값에 대한 모든 값을 무시해 버리고 이동을 하는데, 경사면오르기나 계단 등은 잘 오르지만점프만 안하는 것이다.
충돌 대상 구분하기
충돌한 대상이 무엇이냐에 따라서 서로 다른일이 일어나게 하고싶다라면 충돌이 발생했을때,구분하는 방법은 두가지가 있다.
하나는 충돌 대상의 Layer 을 구분하는 방법이고 하나는, 그냥 오브젝트의 이름을 비교하는 방법이다.
Layer 를 이용하여 비교하는 코드
if( other.gameObject.layer == LayerMask.NameToLayer("Player"))
{
other.gameObject.transform.position = new Vector3(0, 50, 0);
}
혹은
if( other.gameObject.layer == 12 )
{
other.gameObject.transform.position = new Vector3(0, 50, 0);
}
슬슬 입질이 오고있다. Layer 의 형태를 기본적으로 int 형이다. Edit - ProjectSetting 에 들어가서 Layer 들을 보면,
숫자로 구분이 되어 있는 것을 알수있다. 그렇다면 위의 LayerMask.NameToLayer( "String" ) 이라는 것이 있다.
이는 저 스트링에 해당하는 친구의 레이어를 찾아서, int 값으로 반환해준다.
반대로 LayerToName 을 하면 인트값을 주면 해당 Layer 의 String 값을 반환해준다.
LayerMask.NameToLayer 을 이용하도록 하자, 왜냐하면 나주에 Layer 의 위치를 변경해서 기존 12가 아닌 15가 되어도
스트링을 넘겨줬었다면 코드를 수정하지 않아도 문제가 일어나지 않기 때문이다.
이름으로 구분하는 방법은
other.gameObject.name.Contatins( "String" ) 이라는 방법이 있다. == 을 쓰는 것보다 Contains 를 쓰는 이유는,
새로 만들어지는 경우 이름뒤에 Clone 이 붙기도 하고 해서 인식이 안되는 경우가 있으므로 Contains 를 통해서,
true 조건을 조금 느슨하게 해주는 것이다.
if( other.gameObject.name.contains( "Player" )
{
other.gameObject.transform.position = new Vector3(0, 50, 0);
}
Tag 를 이용하여 비교하는 코드
if (other.gameObject.tag == "Wall")
태그는 gameObject 에서 불러올수 있으며 그냥 스트링 비교로 바로 체크가 가능하다. 굉장히 깔끔한것 같다.
※ 레이어는 10번부터 사용하도록 하자 < 권장 사항 >
Trigger 트리거
is Trigger 이라는 것이 Collider 안에 있고 체크가 해제되어있다. 트리거 라는 것은 방아쇠 라는 뜻으로 총을 한번쏘면 되돌릴수 없듯이 트리거는 이벤트를 발생시키는 일종의 방아쇠 역할을 한다. 그럼 Collider 과 같은 역할을 하는거 아닌가 라고 생각할수도 있지만, 큰 차이가 있다. 바로 물리적인 힘이 존재하지 않다는것이다.
일상생활에서 예로 자동문을 생가해볼수 있다. 분명 우리가 어떤 부분의 트리거를 건드렸기 때문에 자동문이 열리는 것일텐데 우리가 뭔가에 부딪히거나 하는 느낌은 전혀 들지 않는다.
이 트리거에 대한 함수로는 OnTriggerEnter( Collider other ) 과 같은 식으로 사용한다.
Animations
Anim Compression 애니메이션 압축할거냐 ? Keyframe Reduction : 지워도 큰문제 없을것 같은 값들을 지워서 용량 절약
Clips 일종의 배열로 관리 / Start End 로 시작프레임과 끝프레임 정해줄수 있음 ( 실수 값 ! )
Add Loop Frame 루프가 아닌 것을 루프인것처럼 만들어 주는 것 / 첫프레임과 끝프레임이 같아야하는것
Wrap Mode : 애니메이션을 어떻게 재생 할것인가.
Default : Once 와 같음
Once : 한번만 수행함
Loop : 무한 반복
Ping Pong : 끝 찍고, 다시 처음으로 돌아오서 다시 수행
Clamp Forever : 마지막 프레임을 계속 반복함 -> 부드럽게 모션을 만들기 위해서 ( 애니메이션 블랜딩을 위해 )
블랜딩 -> 두 애니메이션이 섞여서 부드럽게 이어지게 만드는것
모든 모션에 다 블랜딩이 무조건 좋다 ? 아니다 -> 블랜디을 안줌으로서 역동성고 속도감을 줄수 있다.
어디에 넣고, 어디에는 뺄꺼야 ? 눈으로 보고 판단하자.
Clip 미리보기 에서 No model is 어쩌고 나오면 드래그 해서 될만한 애를 넣고 재생시켜봄으로서 잘 되는건지 알수 있다.
※ Shift , 원하는 클립까지 클릭하고 그대로 누른상태로 끌어서 Animations 에 넣으면 순서대로 들어간다.
※ 재생 전에 속도를 조절해줘야, 애니메이션에 적용된다.
※ 애니메이션에 Play 는 그냥 재생시키고 Cossfade 는 애니메이션 블랜딩을 하면서 플레이 시킨다.
Lerp 함수
일종의 보관함수로서 인자는 From, To, Percent 라고 생각하면 된다.
ex ) transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(dir),
rotationSpeed * Time.deltaTime);
어떤 값을 퍼센트로 받아오기때문에 확확 움직이지않고 천천히 돌거나 하는 등에 사용 가능 하다 !
데드존 코드분석
public class DeadZone : MonoBehaviour {
void OnTriggerEnter(Collider other) ※ 트리거에 들어간 순간 발동
{
if( other.gameObject.layer == LayerMask.NameToLayer("Player")) ※ 레이어가 Player 면
{
other.gameObject.transform.position = new Vector3(0, 50, 0); ※ 위치를 옮겨버린다.
}
else if(other.gameObject.layer == LayerMask.NameToLayer("Bomb")) ※ 레어어가 Bomb 이면
{
Destroy(other.gameObject); ※ 부딪힌 대상을 부숴버린다!
// Debug.Log("BOOM");
}
}
}
Enemy 코드분석
public class Enemy : MonoBehaviour {
enum ENEMYSTATE ※열거형으로 이제 적의 상태를 관리
{
IDLE = 0,
MOVE,
ATTACK,
DAMAGE,
DEAD
}
ENEMYSTATE enemyState = ENEMYSTATE.IDLE; ※ 열거형 변수선언
float stateTime = 0.0f; ※ 상태에 대한 시간체크를 위해
public float idleStateMaxTime = 2.0f; ※ 시작 대기값
public Animation anim; ※ 애니메이션을 컨트롤 하기 위함, public 이므로 직접 링크 필요!
public Transform target = null;
CharacterController characterController = null;
public float moveSpeed = 5.0f;
public float rotationSpeed = 10.0f;
public float attackRandge = 5f;
public float attackStateMaxTime = 2.0f;
void Start() {
// target = GameObject.Find("Player").transform;
target = GameObject.FindGameObjectWithTag("Player").transform;
※ 왜 public 인데 직접 링크를 안했는지 알수 있는 부분이다. 만약 게임 타이틀 화면등에 플레이어가
존재안하면? 프리팹에 직접링크를 시켜려해도 플레이어는 Scene에서 존재하지않으므로 링크가 불가능이다.
따라서 이런식으로 실시간 링크를 시켜주어야 하는 것이다.
characterController = GetComponent<CharacterController>();
}
void Awake() { ※ Awake 함수가 일단은 Start 보다 빠르다고 알고는 있다. 중요한건 단 한번 수행된다는것
Init();
}
void Update()
{
switch (enemyState) ※ enemyState 함수에 의해 매 프레임 스위치 문을 수행한다.
{
case ENEMYSTATE.IDLE:
Idle();
break;
case ENEMYSTATE.MOVE:
Move();
break;
case ENEMYSTATE.ATTACK:
Attack();
break;
case ENEMYSTATE.DAMAGE:
Damage();
break;
case ENEMYSTATE.DEAD:
Dead();
break;
}
} // END UPDATE
void Idle()
{
stateTime += Time.deltaTime; ※ 매 프레임과 프레임 사이 시간을 더해주는 것으로 시간체크가가능하다.
if(stateTime > idleStateMaxTime) ※ 위에서 설정해줬던 시작대기시간을 넘어가면
{
stateTime = 0.0f;
enemyState = ENEMYSTATE.MOVE; ※ 움직임으로 상태변환 !
}
}
void Move() {
anim["Run"].speed = 1.5f; ※ Run 애니메이션의 수행속도를 1.5 배 해준다.
anim.CrossFade("Run"); ※ 이전 단계의 애니메이션에서 블랜딩하면서 Run 을 실행 시켜준다 !
float distance = (target.position - transform.position).magnitude; ※ target(Player) 와 자기자신의 거리값 구하기
if( distance < attackRandge) ※ 만약 현재 타겟과의 거리가 공격범위보다 작다면
{
enemyState = ENEMYSTATE.ATTACK; ※ 공격 상태로 넘어간다.
stateTime = 2.0f; ※ 넘어가서는 바보같이 멀뚱멀뚱 보지않고 바로때리기위한 설정
}
else
{
Vector3 dir = target.position - transform.position; ※ 만약 공격범위 안에 없다면 ?
dir.y = 0.0f;
dir.Normalize(); ※ 거리를 정규화 시켜준다 아니면 멀때는 빨리다가오고 가까우면 천천히 다가올것이다.
characterController.SimpleMove(dir * moveSpeed); ※ 심플 무드 : 점프 빼고 다됨
transform.rotation = Quaternion.Lerp(transform.rotation, ※ Lerp함수 위의 참조
Quaternion.LookRotation(dir),
rotationSpeed * Time.deltaTime);
}
}
void Attack() {
stateTime += Time.deltaTime;
float distance = (target.position - transform.position).magnitude;
if (stateTime > attackStateMaxTime && distance < attackRandge)
※ 공격범위 안에 있으면 공격하되, 쿨타임이 존재
{
stateTime = 0.0f;
anim.Play("Attack");
anim.PlayQueued("Idle", QueueMode.CompleteOthers);
}
else
{
enemyState = ENEMYSTATE.MOVE;
}
}
void Damage() { }
void Dead() { }
void Init()
{
enemyState = ENEMYSTATE.IDLE;
anim["Idle"].speed = 1.5f;
anim.Play("Idle");
}
}