๐ mixamo
characters ๐ ybot ๊ฒ์ ๐ download ๐ format : FBX for Unity
animations ๐ idle, walk ๋ฑ ๋ค์ด๋ก๋
- with skin : ์บ๋ฆญํฐ ๋ชจ๋ธ๋ ๊ฐ์ด
- without skin : ์ ๋๋ฉ์ด์ ๋ง
๐ฉ ์บ๋ฆญํฐ ๋ชจ๋ธ
- Rig
- animation type
- humanoid : 3d max์์ ์ฌ๋ ํํ์ ์บ๋ฆญํฐ ๋ชจ๋ธ์ ๊ตฌ์ฑํ๋ ๋ผ๋๋ก ์ค์ ๋์ด ์๋ ๊ฒ๋ค์ ๊ฐ์ ธ์ค๋ ๊ฒ
- generic : ์ฌ๋์ ํํ๊ฐ ์๋ ๊ฒ
- animation type
๊ธฐ๋ณธ์ ์ผ๋ก Animator๊ฐ ๋ฌ๋ ค์์
๊ธฐ๋ณธ ์ ๋๋ฉ์ด์ ์ ๋๋๊ทธ ์ค ๋๋ํด์ฃผ๋ฉด controller์ ์๋์ผ๋ก ์ฝ์ ๋๋ค.
using UnityEngine;
using UnityEngine.AI;
namespace FastCampus.Characters {
[RequireComponent(typeof(NavMeshAgent)), RequireComponent(typeof(CharacterController)), RequireComponent(typeof(Animator))]
public class PlayerCharacter : MonoBehaviour {
#region Variables
[SerializeField] private LayerMask groundLayerMask;
[SerializeField] private Animator animator;
private CharacterController controller;
private NavMeshAgent agent;
private Camera camera;
readonly int moveHash = Animator.StringToHash("Move");
readonly int fallingHash = Animator.StringToHash("Falling");
#endregion
#region Main Methods
private void Start() {
controller = GetComponent<CharacterController>();
agent = GetComponent<NavMeshAgent>();
agent.updatePosition = false;
agent.updateRotation = true;
camera = Camera.main;
}
private void Update() {
// Process mouse left button input
if (Input.GetMouseButtonDown(0)) {
// Make ray from screen to world
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
// Check hit from ray
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100, groundLayerMask)) {
Debug.Log("We hit " + hit.collider.name + " " + hit.point);
// Move our player to what we hit
agent.SetDestination(hit.point);
}
}
if (agent.remainingDistance > agent.stoppingDistance) {
controller.Move(agent.velocity * Time.deltaTime);
animator.SetBool(moveHash, true);
}
else {
controller.Move(Vector3.zero);
animator.SetBool(moveHash, false);
}
if (agent.isOnOffMeshLink) {
animator.SetBool(fallingHash, agent.velocity.y != 0.0f);
}
else {
animator.SetBool(fallingHash, false);
}
}
private void OnAnimatorMove() {
Vector3 position = agent.nextPosition;
animator.rootPosition = agent.nextPosition;
transform.position = position;
}
#endregion Main Methods
}
}
๐ฉ Animator
idle ์ ๋๋ฉ์ด์ ์ ๋ฐ๋ก ์ฒ๋ฆฌํ๋ค.
sub-state๋ฅผ ๋๋ธ ํด๋ฆญํ๋ฉด ์ผ๋ฐ animator์ ๋์ผํ ๋ก์ง์ด ๋์จ๋ค. ๊ทธ ์์์ ์ ๋๋ฉ์ด์ ์์ ์งํ
has exit time : ์ ๋๋ฉ์ด์ ์ด ์ด์ ๋ ๋ ์ด๋ ์ ๋์ ์ ๋๋ฉ์ด์ ์ด ์งํ๋ ์ดํ์ ๋ค์ ์ํ๋ก ๋์ด๊ฐ ๊ฒ
๋ฐ๋ก ๋์ด๊ฐ๊ฑฐ๋ฉด ๊บผ์ฃผ๋ฉด ๋๋ค.
์ด๋ค ์ ๋๋ฉ์ด์ ์ด ์คํ๋ ์ง ์ ํํ๋ behaviour ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค๋ค.
using UnityEngine;
namespace FastCampus.Characters {
public class RandomStateMachineBehaviour : StateMachineBehaviour {
public int numberOfStates = 2;
public float minNormTime = 0f;
public float maxNormTime = 5f;
protected float randomNormalTime;
readonly int hashRandomIdle = Animator.StringToHash("RandomIdle");
// state ์ง์
์ ๋ฐ์ํ๋ ํจ์
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
// Randomly decide a time at which to transition.
randomNormalTime = Random.Range(minNormTime, maxNormTime);
}
// state ์ง์
์ดํ ์
๋ฐ์ดํธ๋ ๋ ๋ฐ์ํ๋ ํจ์
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
// If transitioning away from this state reset the random idle parameter to -1.
if (animator.IsInTransition(0) && animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash) {
animator.SetInteger(hashRandomIdle, -1);
}
// If the state is beyond the randomly decided normalised time and not yet transitioning then set a random idle.
if (stateInfo.normalizedTime > randomNormalTime && !animator.IsInTransition(0)) {
animator.SetInteger(hashRandomIdle, Random.Range(0, numberOfStates));
}
}
}
}