C# 인터페이스

클래스와 비슷하게 인터페이스는 메서드, 속성, 이벤트, 인덱서 등을 갖지만, 인터페이스는 이를 직접 구현하지 않고 단지 정의(prototype definition)만을 갖는다. 즉, 인터페이스는 추상 멤버(abstract member)로만 구성된 추상 Base 클래스(abstract base class)와 개념적으로 유사하다. 클래스가 인터페이스를 가지는 경우 해당 인터페이스의 모든 멤버에 대한 구현(implementation)을 제공해야 한다.

한 클래스는 하나의 Base 클래스만을 가질 수 있지만, 인터페이스는 여러 개를 가질 수 있다. 아래의 예를 보면, MyConnection 이라는 클래스는 Component 라는 하나의 Base 클래스와 IDbConnection, IDisposable이라는 2개의 인터페이스를 가지고 있음을 알 수 있다.
public class MyConnection : Component, IDbConnection, IDisposable
{
   // IDbConnection을 구현
   // IDisposable을 구현
}

C# 인터페이스의 정의

인터페이스는 C# 키워드 interface를 사용하여 정의한다. 인터페이스 정의 시에는 (메서드와 같은) 내부 멤버들에 대해 public과 같은 접근 제한자를 사용하지 않는다.
예를 들어, 아래 예제에서 CompareTo() 메서드 앞에 public 을 쓸 수 없다.


public interface IComparable
{
   // 멤버 앞에 접근제한자 사용 안함
   int CompareTo(object obj);
}
 

C# 인터페이스의 구현

C# 클래스가 인터페이스를 갖는 경우 인터페이스의 모든 멤버에 대한 구현을 제공해야 한다. C# 에서는 인터페이스로부터 직접 new를 사용하여 객체를 생성할 수 없다. 아래의 클래스는 IComparable이라는 인터페이스를 갖는 경우로서 IComparable.CompareTo() 메서드를 구현한 예이다.

public class MyClass : IComparable
{
   private int key;
   private int value;

   // IComparable 의 CompareTo 메서드 구현
   public int CompareTo(object obj)
   {
      MyClass target = (MyClass)obj;
      return this.key.CompareTo(target.key);
   }
}
 

C# 인터페이스의 사용

C# 실무에서 클래스와 인터페이스를 잘 정의하고 사용하는 것은 매우 중요하다. 비지니스를 객체지향 프로그래밍으로 디자인하고 구현하는데 가장 중요한 핵심이기 때문이다. 자연스럽게 .NET Framework도 상당히 많은 인터페이스를 구현했으며, 거의 모든 영역에서 흔히 사용되고 있다.

다음 코드는 IDbConnection이라는 인터페이스를 사용하는 예제이다. 이 코드에서 GetDbConnection() 메서드는 시스템의 구성파일로부터 DB타입과 Connection String을 받아와 해당 DB타입에 맞는 데이타베이스 Connection을 리턴한다. GetDbConnection()가 DB connection을 리턴할 때 IDbConnection을 리턴하고 있는데, 이 때문에 이 메서드를 사용하는 클라이언트에서는 어떤 DB를 사용하든지 상관없이 모든 DB 클래스에 공통적으로 구현된 IDbConnection 멤버들을(메서드, 속성등) 사용할 수 있게 된다.


public void Run()
{
   // 인터페이스 사용하기 때문에
   // 특정 DB Connection을 신경 쓸 필요가 없다
   IDbConnection dbCon = GetDbConnection();
   dbCon.Open();
   if (dbCon.State == ConnectionState.Open)
   {
      dbCon.Close();
   }
}

// IDbConnection 인터페이스를 리턴
public IDbConnection GetDbConnection()
{
   IDbConnection dbConn = null;
   string cn = ConfigurationManager.AppSettings["Connection"];
   switch (ConfigurationManager.AppSettings["DbType"])
   {
      case "SQLServer":
         dbConn = new SqlConnection(cn);
         break;
      case "Oracle":
         dbConn = new OracleConnection(cn);
         break;
      case "OleDB":
         dbConn = new OleDbConnection(cn);
         break;         
   }
   return dbConn;
}

인터페이스와 느슨한 커플링

게임속 플레이어는 다양한 오브젝트를 공격할 수 있습니다. 공격받은 오브젝트에 따라 반응이 서로 다릅니다. if문으로 일일히 타입을 검사하고 처리할 수 있습니다.

세련된 방법은 IDmageable 인터페이스를 사용 추상화하여 다룰수 있습니다.

인터페이스란 외부와 통신하는 공개 통로이며, 통로의 규격입니다. 인터페이스는 통로의 규격은 강제하지만 그 아래에 어떤일이 일어날지는 결정하지 않습니다.

IItem은 아이템 클래스들이 상속하는 인터페이스 입니다.(인터페이스앞에 I를 추가하는 것이 관례입니다.)

IItem.cs 스크립트를 다음과 같이 만듭니다.

using UnityEngine;

// 아이템 타입들이 반드시 구현해야하는 인터페이스
public interface IItem {
    // 입력으로 받는 target은 아이템 효과가 적용될 대상
    void Use(GameObject target);
}
Use() 메서드는 아이템을 사용하는 메서드입니다. 아이템을 사용할 대상을 GameObject타입으로 입력받습니다. 선언만 하고 구현은 자신을 상속하는 클래스에 맡깁니다. 외부에서 사용할수 있게 public으로 선언합니다.

IItem을 상속해서 만든 두 아이템을 클래스를 살펴봅시다.

public class AmmoPack : MonoBehavior, IItem {
    public int ammo = 30;
    public void Use(GameObject target) {
          Debug.Log("탄알이 증가했다 : "+ammo);
    }
}
public class HealthPack : MonoBehavior, IItem {
    public int health = 30;
    public void Use(GameObject target) {
          Debug.Log("체력을 회복했다 : "+ health);
    }
}

IItem을 상속한 클래스는 Use()메서드를 아이템의 역할에 따라 반드시 구현해야 합니다.

느슨한 커플링

IItem인터페이스를 상속한 클래스는 많아지고 충돌시 이들 모두를 검사하여 개별 처리하는 것보다

private void OnTriggerEnter(Collider other) {
    AmmoPack ammoPack = other.GetComponent<AmmoPack>();
    if(ammoPack != null) {
        ammoPack.Use();
    }
    HealthPack HealthPack = other.GetComponent<HealthPack>();
    if (HealthPack != null) {
        HealthPack.Use();
    }
}

 다음과 같이 IItem타입으로 처리가능합니다.

void OnTriggerenter(Collider other) {
    IItem item = other.GetComponent<IItem>();
    if(item != null) {
        item.Use();
    }
}

IDamageable : 공격당할 수 있는 모든 대상은 IDamageable인터페이스를 상속해야 합니다. IDamageable.cs 스크립트를 다음과 같이 만듭니다. 

using UnityEngine;
// 데미지를 입을 수 있는 타입들이 공통적으로 가져야 하는 인터페이스
public interface IDamageable {
    // 데미지를 입을 수 있는 타입들은 IDamageable을 상속하고 OnDamage 메서드를 반드시 구현해야 한다
    // OnDamage 메서드는 입력으로 데미지 크기(damage), 맞은 지점(hitPoint), 맞은 표면의 방향(hitNormal)을 받는다
    void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal);
}

'유니티좀비게임 > 총과슈터' 카테고리의 다른 글

Player Shooter 스크립  (0) 2023.04.28
GunData 스크립트  (0) 2023.04.28
총 게임오브젝트  (0) 2023.04.28

시네머신을 사용하기 위해 패키지매니저에서 설치해야한다.

하이라키의 MainCamera를 선택하고 Cinemachine을 Add Component한다.

하이라키의 +를 누르고 Cinemachin>Virtual Camera를 선택한다.

이름을 FollowCam으로 바꾼다.

하이라키뷰에서 FollowCam을 선택하고 씬뷰에서 Y축을 이동시켜 인스펙터뷰에서 Y포지션을 5정도로 맞춘다.

하이라키뷰의 Player를 선택해 FollowCam의 CinemachineVirtualCamera의 Follow, LookAt에 연결해준다.

FollowCam은 Follow객체의 뒤에 따라다니고 시선은 LookAt을 본다. 비슷한것 같지만 위치와 로테이션의 타겟을 뜻한다.

사실 이 정도 효과는 Main Camera를 Player의 차일드로 하는 것으로 간단히 구현할 수 있지만. 시네머신은 Body, Aim등 다양한 컨트롤을 마련해놨지만 여기서는 설명하지 않겠습니다.

'유니티좀비게임 > 레벨아트와 플레이어' 카테고리의 다른 글

입력및 이동  (0) 2023.04.27
레벨아트및 플레이어 만들기  (0) 2023.04.27

이번게임에서는 키보드의 입력과 플레이어의 이동 로직을 분리한다. 이렇게 하는것이 업그레이드 관리에 용이하기 때문이다. 우선 입력부분부터 만들어본다.

 

Player를 선택하고 Add Component>Script add하고 이름을 PlayerInput으로 바꾼다.

Input.GetAxis()에 사용될 스트링의 선언이다.

public string moveAxisName = "Vertical"; // 앞뒤 움직임을 위한 입력축 이름
public string rotateAxisName = "Horizontal"; // 좌우 회전을 위한 입력축 이름
public string fireButtonName = "Fire1"; // 발사를 위한 입력 버튼 이름
public string reloadButtonName = "Reload"; // 재장전을 위한 입력 버튼 이름

키보드 입력을 받아올 변수의 정의 인데 잘보면 프로퍼티라고 클래스이다. 변수와 다른점을 클래스 안에서 필요에 따라 좀더 유연한 정의가 가능하다는 점이다. 

public float move { get; private set; } // 감지된 움직임 입력값
public float rotate { get; private set; } // 감지된 회전 입력값
public bool fire { get; private set; } // 감지된 발사 입력값
public bool reload { get; private set; } // 감지된 재장전 입력값

Update()함수는 Input.GetAxis()함수를 이용해 입력값을 읽어와 프로퍼티에 저장한다.

PlayerInput 전체코드

using UnityEngine;

// 플레이어 캐릭터를 조작하기 위한 사용자 입력을 감지
// 감지된 입력값을 다른 컴포넌트들이 사용할 수 있도록 제공
public class PlayerInput : MonoBehaviour {
    public string moveAxisName = "Vertical"; // 앞뒤 움직임을 위한 입력축 이름
    public string rotateAxisName = "Horizontal"; // 좌우 회전을 위한 입력축 이름
    public string fireButtonName = "Fire1"; // 발사를 위한 입력 버튼 이름
    public string reloadButtonName = "Reload"; // 재장전을 위한 입력 버튼 이름

    // 값 할당은 내부에서만 가능
    public float move { get; private set; } // 감지된 움직임 입력값
    public float rotate { get; private set; } // 감지된 회전 입력값
    public bool fire { get; private set; } // 감지된 발사 입력값
    public bool reload { get; private set; } // 감지된 재장전 입력값

    // 매프레임 사용자 입력을 감지
    private void Update() {
        // move에 관한 입력 감지
        move = Input.GetAxis(moveAxisName);
        // rotate에 관한 입력 감지
        rotate = Input.GetAxis(rotateAxisName);
        // fire에 관한 입력 감지
        fire = Input.GetButton(fireButtonName);
        // reload에 관한 입력 감지
        reload = Input.GetButtonDown(reloadButtonName);
    }
}

 

이제 움직임을 담당할 PlayerMovement를 만들어본다.

앞뒤 움직임 속도, 회전속도를 지정할 변수를 설정한다.

public float moveSpeed = 5f; // 앞뒤 움직임의 속도
public float rotateSpeed = 180f; // 좌우 회전 속도

Start()함수에서  PlayerInput(), Rigidbody()컴포넌트 참조를 가져온다. PlayerInput()도 Player오브젝트의 컴포넌트로 추가되어 있어야한다.

private void Start() {
    // 사용할 컴포넌트들의 참조를 가져오기
    playerInput = GetComponent<PlayerInput>();
    playerRigidbody = GetComponent<Rigidbody>();
}

FixedUpdate()함수는 Unity에서 Rigidbody컴포넌트 처리(물리갱신주기)가 이루어지는 곳이다. 여기서 움직임을 처리한다. Update()는 화면갱신주기이다.

private void FixedUpdate() {
    // 회전 실행
    Rotate();
    // 움직임 실행
    Move();

    // 입력값에 따라 애니메이터의 Move 파라미터 값을 변경
    //playerAnimator.SetFloat("Move", playerInput.move);
}

Move()와  Rotate()는 Rigidbody.MovePosition()메써드나 position을 변경해도 된다. 주의할점은 게임오브젝트의 transform.position을 사용해도 되지만 Rigidbody컴포넌트가 추가되어 있을때는 계산의 엉킴을 방지하기위해 리지드바디에서 처리하는게 좋은듯 하다.

private void Move() {
    // 상대적으로 이동할 거리 계산
    Vector3 moveDistance =
        playerInput.move * transform.forward * moveSpeed * Time.deltaTime;
    // 리지드바디를 통해 게임 오브젝트 위치 변경
    playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
    //playerRigidbody.position += moveDistance;
}

Quaternion.rotation의 회전의 + 가 아니라 * 이다. 행렬계산을 해야하기 때문이다. 따라서 회전시키고 싶은 오일러각(Vector3)를 Quaternion.Euler()로 변경시켜 곱해줘서 회전을 하게 만들어 준다.

private void Rotate() {
    // 상대적으로 회전할 수치 계산
    float turn =
        playerInput.rotate * rotateSpeed * Time.deltaTime;
    // 리지드바디를 통해 게임 오브젝트 회전 변경
    playerRigidbody.rotation = playerRigidbody.rotation * Quaternion.Euler(0, turn, 0f);
}

PlayerMovement 전체코드, 이번 강좌에서는 Animator를 사용하지 않기 때문에 주석처리되어있다

using UnityEngine;

// 플레이어 캐릭터를 사용자 입력에 따라 움직이는 스크립트
public class PlayerMovement : MonoBehaviour {
    public float moveSpeed = 5f; // 앞뒤 움직임의 속도
    public float rotateSpeed = 180f; // 좌우 회전 속도

    //private Animator playerAnimator; // 플레이어 캐릭터의 애니메이터
    private PlayerInput playerInput; // 플레이어 입력을 알려주는 컴포넌트
    private Rigidbody playerRigidbody; // 플레이어 캐릭터의 리지드바디

    private void Start() {
        // 사용할 컴포넌트들의 참조를 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerRigidbody = GetComponent<Rigidbody>();
        //playerAnimator = GetComponent<Animator>();
    }

    // FixedUpdate는 물리 갱신 주기에 맞춰 실행됨
    private void FixedUpdate() {
        // 회전 실행
        Rotate();
        // 움직임 실행
        Move();

        // 입력값에 따라 애니메이터의 Move 파라미터 값을 변경
        //playerAnimator.SetFloat("Move", playerInput.move);
    }

    // 입력값에 따라 캐릭터를 앞뒤로 움직임
    private void Move() {
        // 상대적으로 이동할 거리 계산
        Vector3 moveDistance =
            playerInput.move * transform.forward * moveSpeed * Time.deltaTime;
        // 리지드바디를 통해 게임 오브젝트 위치 변경
        playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
    }

    // 입력값에 따라 캐릭터를 좌우로 회전
    private void Rotate() {
        // 상대적으로 회전할 수치 계산
        float turn =
            playerInput.rotate * rotateSpeed * Time.deltaTime;
        // 리지드바디를 통해 게임 오브젝트 회전 변경
        playerRigidbody.rotation = playerRigidbody.rotation * Quaternion.Euler(0, turn, 0f);
    }
}

 

 

'유니티좀비게임 > 레벨아트와 플레이어' 카테고리의 다른 글

시네머신  (0) 2023.04.27
레벨아트및 플레이어 만들기  (0) 2023.04.27

3D 프로젝트를 하나  만들자.

3D오브젝트 플레인을 만들고 스케일을 5로한다.

프로젝트뷰에서 우클릭후 Material을 만들어 좋와하는 색깔을 만들고  끌어다 Plane에 적용한다.

플레이어 만들기

빈오브젝트를 만들고 이름을 Player로 변경한다.

3D오브젝트 Capsule을 하나 만들고 body로 이름바꾼다. Player의 차일드로 끌어놓고, Y Position을 1로 한다.

프로젝트뷰에서 우클릭후 Material을 만들어 좋와하는 색깔을 만들고  끌어다 Player에 적용한다.

리지드바디를 추가하고 Angular Drag를 20으로 하고 Constraints > Freeze Rotation x ,z 체크한다.

방향을 알기 쉽게 모자를 하나 만들어주자 Cube를 만들어 이름을 Cap으로 바꾸고 Player의 차일드로 끌어주고  사이즈를 스케일을 변경한다.

 

 

'유니티좀비게임 > 레벨아트와 플레이어' 카테고리의 다른 글

시네머신  (0) 2023.04.27
입력및 이동  (0) 2023.04.27

레트로의 좀비게임을 공부하면서 간단히 핵심만 정리해본다

https://www.hanbit.co.kr/store/books/look.php?p_code=B9351446616 

 

소문난 명강의 : 레트로의 유니티 게임 프로그래밍 에센스(개정판)

필수적인 C# 문법을 배우고, 4가지 장르별 게임을 [목표 설정]+[미션 해결] 방식으로 실습해보며 유니티의 기능을 익히는 도서

www.hanbit.co.kr

설명이 좀 엉성해서 교재의 채텁16, 17 Done을 다운 받아 실행해 보면서 분석해 보시면 좀 이해가 쉬우실듯

https://terms.naver.com/entry.naver?docId=3405101&cid=47324&categoryId=47324 

 

벡터

물체에 힘을 가할 때는 어느 방향으로 얼마만큼 세게 미는지를 같이 이야기한다. 속도라는 개념도 어느 방향으로 얼마나 빠르게 가는지를 동시에 이야기한다. 이와 같이 크기와 방향을 동시에

terms.naver.com

일반적인 벡터의 정의는 이미 대략은 알고 계실것이다.

유니티도 벡터를 많이 이용하고 툴을 제공한다.

1. 두 지점사이의 거리

현재위치 curPos와 목적지인 destPos가 있다면 현재위치에서 목적지로 가는 벡터는 delta = destPos-curPos 이다.

2지점의 거리는 delta.magnitude이다. Vector3.Distance(curpos,destPos)로도 구할 수 있다.

 

2. 단위거리 이동

목적지로 10만큼의 거리를 가고 싶을때는 정규화후 이동하면된다.

newPos = curPos;

newPos += delta.normalized * 10f

 

3. 쿼터니언

인스펙터뷰의 transform의 rotation은 Vector3로 보이지만 스크립트에서 수정하고자 하면 에러가 난다. 왜냐하면 내부적으로는 짐벌락의 문제로 쿼터니언으로 처리하기 때문이다. 

오일러각으로 읽어오기 위해서는 rotation.eulerAngles로 읽어와야하고 변경할때는

유니티는 오일러각을 Quaternion으로 변환해주는 함수를 제공한다.

rotation = Quaternion.Euler(Vector3);처럼 변경해야 한다.

 

현재 회전 상태에서 더 회전하기

현재 rotation1 = Quaternion(30,0,0)상태에서 rotation2 = Quaternion(0.60,0) 만큼 더 회전시키려면 rotation1+rotation2가 아니고 rotation1*rotation2이다. 쿼터니언 사이의 연산은 행렬을 사용하기 때문입니다.

 

4.벡터의 내적 : vector3.Dot(a,b) 벡터b를 벡터a로 투영한 길이를 구합니다. 수직일 경우 0이 됩니다.

5. 벡터의 외적 : Vector3.Cross(a,b) 두 벡터 모두에 수직인 벡터를 구합니다. 앞뒤가 있습니다.

 

'유니티스크립팅 > 게임수학' 카테고리의 다른 글

게임에서 사용되는 삼각함수  (2) 2023.05.25

3D 오브젝트를 하나 만들고 Plane을 Scale(5,5,5) Player를 다음과 같이 Cube, Shphere를 이용해 만들어준다. Green, Black Material을 만들어 Plane과 Eye에 적용한다.

프로젝트뷰에 우클릭 [Create]후 Animator를 하나 만들고 PlayerAnimCtrl로 이름을 바꾸고, 다시 [Create]후 Animation을 만들고 Idel로 이름을 바꾼다.

하이라키뷰에서 Player의 Head를 선택하면 Animation뷰에서 Create하라고 하면 새로 만들고

AddProperty를 클릭하고 Position옆 + 를 클릭한다.

0:00 ◆ 클릭후 Ctrl-C한후 타임라인 0:30밑을 선택해 Ctrl-V해서 카피한다.

씬뷰탭을 누르고 하이라키뷰의 Head를 선택후 살짝 위로 올린다.

Preview 옆 빨간 ◉ 클릭해 녹화를 한후 다시 클릭해 녹화를 마친다. ▶를 눌러 머리가 위아래로 움직이는지 확인해본다   

Idel옆 ▼를 클릭해 Create New Clip을 선택한후 RunFoward를 만든다

하이라키뷰에서 Player의 Head를 선택하면 Animation뷰에서 Create하라고 하면 새로 만들고 AddProperty를 클릭하고 Position옆 + 를 클릭한다

 

Preview 옆 빨간 ◉ 클릭해 녹화를 시작하고 하이라키뷰의 PlayerHead를 선택하고 TimeLine 0:00을 클릭후 Z Position을 0.5로 변경한후 Preview 옆 빨간 ◉ 클릭해 녹화를 마친다. 혹시 0:00외 다른 타임라인이 있다면 제거해준다. Head가 이동 방향쪽으로만 향해 있으면 되므로 한프레임만 있으면 된다

같은 요령으로 RunLeft, RunRight, RunBackward를 만들오 첫프레임의 Head위치를 변경후 저장해준다. 

 

Animator탭을 선택한후 idle Animation을 끌어다 놓으면 자동으로 Entry와 연결되면서 Default State가 된다.

NewblendTree를 만든다. 이름을 Move로 바꾼다.

Move를 클릭해 들어간후 BlendTree를 클릭한후 인스펙터뷰의 BlendType 속성을 1D를 2D Simple로 변경한다.

Animator탭밑 Parameters를 선택하고 +를 눌러 float incX, float incZ, Bool isMove를 생성한다. Inspector뷰의 Parameters를 incX, incZ를 선택한다.

Motion List +를 4번 눌러 리스트를 4개 만들어 Animation을 끌어다 놓고 PosX와 PosY를 다음과 같이 설정한다.

Animator탭밑 BaseLayer탭을 선택해 다시 돌아가자

Idel과 Move사이 Transition을 만들어준다.

Idle->Move 속성은 다음과 같다. Conditions는 isMove==true, Has Exit time은 언체크 Duration은 0.1

Move->Idle 속성은 다음과 같다. Conditions는 isMove==false, Has Exit time은 언체크 Duration은 0.1

하이라키뷰에서 Player를 선택후 지금만든 PlayerAnimCtrl을 끌어다 Animator 컴포넌트의 Controller에 적용해준다.

이제 PlayerCtrl 스크립트를 만들어 Player에 적용시켜주자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerCtrl : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("isMove", isMove);
        anim.SetFloat("incX", direction.x);
        anim.SetFloat("incZ", direction.z);
    }
}

플레이 해보면 허름한 이동 애니메이션이 실행된다.

 

'유니티스크립팅' 카테고리의 다른 글

Beginner Scripting  (0) 2023.02.25

Windows - Package Manager에서 Animation Rigging 패키지를 검색하고 Install 한다.

 

패키지 설치가 끝났으면 애니메이션 리깅을 설정해서 캐릭터를 구성하는 뼈대를 나타내는 것으로 시작한다.

 

던전 스켈레톤을 누르고 상단에 Animation Rigging - Bone Renderer Setup을 클릭하면 된다.

 

Bone Renderer Setup를 클릭하면 던전 스켈레톤에 Bone Renderer라는 컴포넌트가 추가되며 동시에 Scene 뷰에 던전 스켈레톤의 뼈대가 표시되고 마우스로 쉽게 클릭도 가능해진다!

 

Bone Renderer는 던전 스켈레톤의 릭을 구성하는 뼈대 오브젝트들의 트랜스폼을 참조하고 있으며 뼈대의 크기나 색을 조정할 수도 있다.

 

여기까지 됐다면 Rig을 만들어야 한다. 참고로 애니메이션 리깅은 별도의 Rig을 만듦으로써 동작한다.

던전 스켈레톤을 클릭하고 상단의 Animation Rigging - Rig Setup을 클릭하면 두 가지 변화가 생긴다.

  1. 던전 스켈레톤에 Rig Layer를 가진 Rig Builer 컴포넌트가 추가된다.
  2. 던전 스켈레톤의 자식으로 Rig 컴포넌트를 가진 한 오브젝트가 생긴다.

Rig 1 오브젝트에 제약조건(Constraint)를 추가함으로써 애니메이션 리깅이 가능해진다.

Rig 1에 Empty 오브젝트를 하나 추가해서 이름을 HeadAim으로 변경하고 HeadAim에 Multi-Aim Constraint 컴포넌트를 추가해 보자. 

Multi-Aim Constraint에 몇 가지 정보를 넘겨줘야 한다.

  1. Constrained Object 제약조건이 걸릴 뼈대를 넘겨주면 되는데, 본 실습에선 머리에 애니메이션 리깅을 적용하고 싶으니 Constrained Object에 던전 스켈레톤의 Head 뼈대를 넘겨주고 Aim Axis를 Y로 변경해 주자.
  2. Source Object는 Constrained Object와 상호작용할 오브젝트로 하이라키뷰의 Fox를 선택해서 Head가 보이게 펼쳐준후 Head를 끌어 연결해준다. Aim Axis는 Y로 변경해 준다.

추가로 Rig 컴포넌트에 weight가 있다. 이는 타겟이 캐릭터에게 영향을 주는 정도로 weight가 줄어들수록 target과 상호작용은 약해진다. 

 

3. 머리만 움직이면 어색하니 흉부도 함께 움직이도록 Rig1에 ChestAim 빈오브젝트에 Multi-airm Constraint를 추가하고 Spine1을 연결해준다. Source는 똑같이 Fox의Head를 연결해주고 머리와 흉부에 weight 값을 다르게 줘서 자연스러운 애니메이션을 연출해 보자. Aim Axis는 Y로 변경해 준다. Min Limit과 Max Limit을 각각 -100, 100으로 지정하면 멋진 결과물이 탄생한다.

 

해보면 흉부도 함께 움직인다. Fox가 움직여서  테스트가 어려우면 Fox의 스크립트를 언체크해도 된다.

 

같은 방법으로 Fox도  Animation Rigging - Bone Renderer Setup후 Animation Rigging - Rig Setup을 클릭 Rig1에 HeadAim을 추가하고 Multi-Aim Constraint 컴포넌트를 추가후 Constrained Object를 Fox Head로 하고

Source를 +를 누르고 Skeleton 다리를 추가하면 Fox 가 고개를 움직인다. Skeleton Head는 높이가 안 맞는다  Aim Axis Y t설정및 리미트 설정을 꼭한다. 

 

 

https://assetstore.unity.com/packages/3d/characters/creatures/dungeon-skeletons-demo-71087

 

Dungeon Skeletons Demo | 3D 생물 | Unity Asset Store

Elevate your workflow with the Dungeon Skeletons Demo asset from Polygon Blacksmith. Find this & other 생물 options on the Unity Asset Store.

assetstore.unity.com

Asset Store에서 Dungeon Skeletons를 다운 받아 인포트해서 하이라키에 끌어다 놓는다.  프리팹이 없기때문에 Models폴더에서 끌어다 놓는다

애니메이터가 연결 안되어 있기 때문에 Fox와 동일한 방법으로 만든다.

프로젝트뷰에서 우클릭 [Create]-[Animator Controller] 를 만든후 이름을 NewFoxAnim으로 한다.

더블클릭해서 Animator를 연다.

다음 폴더에서 idel을 끌어다 놓는다

Walk와 attack도 끌어다 놓고 transition을 연결한다.

애니메이션이 walk 밖에 없어서 브랜드트리는 안 만드는 것이다.

파라미터는 Trigger attack, bool move만 만들어준다.

idel>walk 트랜지션속성 Has Exit Time 언체크 Condition>move>false

walk>idel 트랜지션속성 Has Exit Time 언체크 Condition>move>false

 

완성된 SkeletonAnim을 DungeonSkeletonDemo  인스펙터 Animator컴포넌트의 Controller속성과 연결해준다.

SkeletonCtrl.cs를 다음과 같이 만들고 DungeonSkeletonDemo 에 연결해 준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkeletonCtrl : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("move", isMove);
    }
}

유니티를 실행해서 WASD를 눌러보면 Fox와 DungeonSkeleton이 같이 움직인다.

3D 프로젝트를 하나 만든다.

3D Object Plane을 하나 만들고 Scale을 5로 한다.

Assetstore에서 Toon Fox를 다운받고 import한다. 하이라키에 끌어다 놓는다.

https://assetstore.unity.com/packages/3d/characters/animals/toon-fox-183005

 

Toon Fox | 3D 동물 | Unity Asset Store

Elevate your workflow with the Toon Fox asset from Pxltiger. Find this & other 동물 options on the Unity Asset Store.

assetstore.unity.com

기본적으로 애니메이터가 설정되어 있어 플레이하면 모든 애니메이션을 돌아가면서 보여준다.

프로젝트뷰에서 우클릭 [Create]-[Animator Controller] 를 만든후 이름을 NewFoxAnim으로 한다.

더블클릭해서 Animator를 연다.

Fox/animations폴더에서 Fox_Idle Animation을 끌어다 놓는다.

우클릭후 New Blend Tree를 만들고 이름을 Move로 만든다

Idle과 Transition을 만들어 준다.

Idle과 Move 사이 트랜지션의 컨디션을 다음과 같이 설정하고

양쪽다 Has Exit Time은 언체크하고 Transition Duration은 0.1로 한다

Parameter

s뷰에서 float incX, float incZ, bool move를 만든다.

Move State를 클릭한다.

가운데 Blend Tree를 선택하면 인스펙터가 다음 같이 바뀌고 Blend Type을 2D Simple Directional로 바꾼다.

Parameters를 다음과 같이 바꿔준다.

그다음과 +를 눌러 Add Motion Field를 4개 넣어주고

 

같은 창이 아래 모션을 애니메이션과 연결해준후 PosX PosY값을 설정한다.

BlendTree.cs를 다음과 같이 만들고 Fox에 연결해 준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlendTree : MonoBehaviour {
    // Start is called before the first frame update
    private Animator anim;  // 애니메이터 컴포넌트를 담을 변수
    private bool isMove;  // Move State 체크용 파라메터
    void Start() {
        anim = GetComponent<Animator>();  //애니메이터 컴포넌트를 받아온다
    }

    // Update is called once per frame
    void Update() {
        float moveSpeed = 3.0f;
        float _x = Input.GetAxisRaw("Horizontal");  //키임력을 받아
        float _z = Input.GetAxisRaw("Vertical");
        Vector3 direction = new Vector3(_x, 0, _z);  //벡터를 만들고
        isMove = false;

        if (direction != Vector3.zero) {
            isMove = true;  //움직임이 있으면 Move State로 간다
            //큐브를 이동시켜주고
            this.transform.Translate(direction.normalized * moveSpeed * Time.deltaTime);
        }
        //애니메이터 파라메터를 설정해준다
        //애니메이터 파라메터를 설정해준다
        anim.SetBool("move", isMove);
        anim.SetFloat("incX", direction.x);
        anim.SetFloat("incZ", direction.z);
    }
}

 

+ Recent posts