[Go-Unity 3D] 20. Animations/ Simple Combat โญโญ
์นดํ ๊ณ ๋ฆฌ: Go Unity
Simple Combat
๊ณ ๋ฐ์ฌ์ ์ ๋ํฐ ๊ธฐ์ด๋ฅผ ์ ๋ฆฌ.
1. ๊ฒ์ ์๋, ์บ๋ฆญํฐ ๊ตฌ์ฑ
Asset
1.๊ตฌ์กฐ๋ฌผ, 2.์บ๋ฆญํฐ ๋ชจ๋ธ, 3.์บ๋ฆญํฐ ์ ํฌ ์ ๋๋ฉ์ด์
- ๊ตฌ์กฐ๋ฌผ : ์์ ์๋ฃ
- ์บ๋ฆญํฐ ๋ชจ๋ธ : Unity-chan -> Data ๋ค์ด๋ก๋ -> ๋์ํ ์ฌ์ฉ ->์บ๋ฆญํฐ๋ค
- Asset Store -> RPG Character Mecanim Animation Pack Free ๋ด๋ ค ๋ฐ๊ธฐ
- ํ์์๋ ํด๋ ์ ๋ฆฌํ์ฌ ์ต์ ํ
๊ฒ์ ์๋ ์บ๋ฆญํฐ
- ๊ฒ์์๋ ์์ฑ
- ์นด๋ฉ๋ผ ๊ฐ๋ ์กฐ์
- ์บ๋ฆญํฐ ์ธ๋ถ ์กฐ์
- Rig ํญ Optimize Game Object ์ต์ ํ, ์ค๋ฅธ์์ ๋ฌด๊ธฐ๋ค๊ธฐ๋๋ฌธ์ Right hand ์ฒดํฌ Right Hand ์์์ผ๋ก ๋ฌด๊ธฐ ์์ฑ, ์์น, ํ์ ๊ฐ ์กฐ์ , Mesh ์ Materials ์ถ๊ฐ
- ์บ๋ฆญํฐ ์์ฑ์ ๋ง์ ํ,ํํฌ์ ํด๊ฒฐ -> ์ฌ์ฉ๋ Materials Shader : Standard ๋ก ๋ณ๊ฒฝ
- player ์ค๋ธ์ ํธ Character Controller ์ค์
2. Script
CameraController.cs
์นด๋ฉ๋ผ ์ ์ด cs
CameraController.cs
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
private Transform target; // ์นด๋ฉ๋ผ๊ฐ ์ถ์ ํ๋ ๋์
[SerializeField]
private float minDistance = 3; // ์นด๋ฉ๋ผ์ target์ ์ต์ ๊ฑฐ๋ฆฌ
[SerializeField]
private float maxDistance = 30; // ์นด๋ฉ๋ผ์ target์ ์ต๋ ๊ฑฐ๋ฆฌ
[SerializeField]
private float wheelSpeed = 500; // ๋ง์ฐ์ค ํ ์คํฌ๋กค ์๋
[SerializeField]
private float xMoveSpeed = 500; // ์นด๋ฉ๋ผ์ y์ถ ํ์ ์๋
[SerializeField]
private float yMoveSpeed = 250; // ์นด๋ฉ๋ผ์ x์ถ ํ์ ์๋
private float yMinLimit = 5; // ์นด๋ฉ๋ผ x์ถ ํ์ ์ ํ ์ต์ ๊ฐ
private float yMaxLimit = 80; // ์นด๋ฉ๋ผ x์ถ ํ์ ์ ํ ์ต๋ ๊ฐ
private float x, y; // ๋ง์ฐ์ค ์ด๋ ๋ฐฉํฅ ๊ฐ
private float distance; // ์นด๋ฉ๋ผ์ target์ ๊ฑฐ๋ฆฌ
private void Awake()
{
// ์ต์ด ์ค์ ๋ target๊ณผ ์นด๋ฉ๋ผ์ ์์น๋ฅผ ๋ฐํ์ผ๋ก distance ๊ฐ ์ด๊ธฐํ
distance = Vector3.Distance(transform.position, target.position);
// ์ต์ด ์นด๋ฉ๋ผ์ ํ์ ๊ฐ์ x, y ๋ณ์์ ์ ์ฅ
Vector3 angles = transform.eulerAngles;
x = angles.y;
y = angles.x;
}
private void Update()
{
// target์ด ์กด์ฌํ์ง ์์ผ๋ฉด ์คํ ํ์ง ์๋๋ค
if ( target == null ) return;
// ๋ง์ฐ์ค๋ฅผ x, y์ถ ์์ง์ ๋ฐฉํฅ ์ ๋ณด
x += Input.GetAxis("Mouse X") * xMoveSpeed * Time.deltaTime;
y -= Input.GetAxis("Mouse Y") * yMoveSpeed * Time.deltaTime;
// ์ค๋ธ์ ํธ์ ์/์๋(x์ถ) ํ๊ณ ๋ฒ์ ์ค์
y = ClampAngle(y, yMinLimit, yMaxLimit);
// ์นด๋ฉ๋ผ์ ํ์ (Rotation) ์ ๋ณด ๊ฐฑ์
transform.rotation = Quaternion. Euler(y, x, 0);
// ๋ง์ฐ์ค ํ ์คํฌ๋กค์ ์ด์ฉํด target๊ณผ ์นด๋ฉ๋ผ์ ๊ฑฐ๋ฆฌ ๊ฐ(distance) ์กฐ์
distance -= Input.GetAxis("Mouse ScrollWheel") * wheelSpeed * Time.deltaTime;
// ๊ฑฐ๋ฆฌ๋ ์ต์, ์ต๋ ๊ฑฐ๋ฆฌ๋ฅผ ์ค์ ํด์ ๊ทธ ๊ฐ์ ๋ฒ์ด๋์ง ์๋๋ก ํ๋ค
distance = Mathf.Clamp(distance, minDistance, maxDistance);
}
private void LateUpdate()
{
// target์ด ์กด์ฌํ์ง ์์ผ๋ฉด ์คํ ํ์ง ์๋๋ค
if (target == null ) return;
// ์นด๋ฉ๋ผ์ ์์น(Position) ์ ๋ณด ๊ฐฑ์
// target์ ์์น๋ฅผ ๊ธฐ์ค์ผ๋ก distacne๋งํผ ๋จ์ด์ ธ์ ์ซ์๊ฐ๋ค
transform.position = transform.rotation * new Vector3(0, 0, -distance) + target.position;
}
private float ClampAngle(float angle, float min, float max)
{
if (angle< -360) angle += 360;
if (angle > 360) angle -= 360;
return Mathf.Clamp (angle, min, max);
}
}
- Target ์ ์ค์ฌ์ผ๋ก ํ์ ํ๋ ์นด๋ฉ๋ผ ๋ชจ์
- ๋น ์ค๋ธ์ ํธ ๋ฅผ ์บ๋ฆญํฐ ์ค์ฌ์์น์ ์์นํ๊ณ Target ์ผ๋ก ์ง์
- Clamp ์ต์ ์ต๋๊ฐ ์ง์
Movement3D.cs
Movement3D.cs
using UnityEngine;
public class Movement3D : MonoBehaviour
{
[SerializeField]
private float moveSpeed = 5; // ์ด๋ ์๋
[SerializeField]
private float gravity = -9.81f; // ์ค๋ ฅ ๊ณ์
[SerializeField]
private float jumpForce = 3.0f; // ์ ํ ํ
private Vector3 moveDirection; // ์ด๋ ๋ฐฉํฅ
private CharacterController characterController;
public float MoveSpeed
{
// ์ด๋์๋๋ 2~5 ์ฌ์ด์ ๊ฐ๋ง ์ค์ ๊ฐ๋ฅ
set => moveSpeed = Mathf.Clamp (value, 2.0f, 5.0f);
}
private void Awake()
{
characterController = GetComponent<CharacterController>();
}
private void Update()
{
// ์ค๋ ฅ ์ค์ , ํ๋ ์ด์ด๊ฐ ๋
์ ๋ฐ๊ณ ์์ง ์๋ค๋ฉด
// y์ถ ์ด๋๋ฐฉํฅ์ gravity * Time.deltaTime์ ๋ํด์ค๋ค.
if ( characterController.isGrounded == false)
{
moveDirection.y += gravity * Time.deltaTime;
}
// ์ด๋ ์ค์ . CharacterController์ Move() ํจ์๋ฅผ ์ด์ฉํ ์ด๋
characterController. Move(moveDirection * moveSpeed * Time.deltaTime);
}
public void MoveTo(Vector3 direction)
{
moveDirection = new Vector3(direction.x, moveDirection.y, direction.z);
}
public void JumpTo()
{
//์บ๋ฆญํฐ๊ฐ ๋ฐ๋ฅ์ ๋ฐ๊ณ ์์ผ๋ฉด ์ ํ
if (characterController.isGrounded == true)
{
moveDirection.y = jumpForce;
}
}
}
- MoveToํจ์๋ฅผ ์ด์ฉํด ์ด๋๋ฐฉํฅ ์ค์
- CharacterController์ Move()ํจ์๋ฅผ ์ด์ฉํด ์ด๋
- JumpTo ๋ก ํ๋ ์ด์ด ์ ํ
PlayerController.cs
PlayerController.cs
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField]
private KeyCode jumpKeyCode = KeyCode.Space;
[SerializeField]
private Transform cameraTransform;
private Movement3D movement3D;
private PlayerAnimaor playerAnimaor;
private void Awake()
{
Cursor.visible = false; // ๋ง์ฐ์ค ์ปค์๋ฅผ ๋ณด์ด์ง ์๊ฒ
Cursor.lockState = CursorLockMode.Locked; // ๋ง์ฐ์ค ์ปค์ ์์น ๊ณ ์
movement3D = GetComponent<Movement3D>();
playerAnimaor = GetComponentInChildren<PlayerAnimaor>();
}
private void Update()
{
// ๋ฐฉํฅํค๋ฅผ ๋๋ฌ ์ด๋
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
// ์ ๋๋ฉ์ด์
ํ๋ผ๋ฏธํฐ ์ค์ (horizontal, verticla)
playerAnimaor.OnMovement(x,z);
// ์ด๋ ์๋ ์ค์ (์์ผ๋ก ์ด๋ํ ๋๋ง 5, ๋๋จธ์ง๋ 2)
movement3D.MoveSpeed = z > 0? 5.0f: 2.0f;
// ์ด๋ ํจ์ ํธ์ถ (์นด๋ฉ๋ผ๊ฐ ๋ณด๊ณ ์๋ ๋ฐฉํฅ์ ๊ธฐ์ค์ผ๋ก ๋ฐฉํฅํค์ ๋ฐ๋ผ ์ด๋)
movement3D.MoveTo(cameraTransform.rotation * new Vector3(x, 0, z));
// ํ์ ์ค์ (ํญ์ ์๋ง ๋ณด๋๋ก ์บ๋ฆญํฐ์ ํ์ ์ ์นด๋ฉ๋ผ์ ๊ฐ์ ํ์ ๊ฐ์ผ๋ก ์ค์ )
transform.rotation = Quaternion.Euler(0, cameraTransform.eulerAngles.y,0);
if (Input.GetKeyDown(jumpKeyCode))
{
playerAnimaor.OnJump();
movement3D.JumpTo();
}
// ๋ง์ฐ์ค ์ขํด๋ฆญ ์ดํ
if (Input.GetMouseButtonDown(0))
{
playerAnimaor.OnAttack();
}
// ์ฐํด๋ฆญ ์ฐ๊ณ๊ณต๊ฒฉ
if (Input.GetMouseButtonDown(1))
{
playerAnimaor.OnWeaponAttack();
}
}
}
- ๋ง์ฐ์ค ์ปค์ ๊ณ ์ ,
- ์ด๋์๋ ์ค์
- ์คํ์ด์คํค ์ ๋ ฅ์ ์ ํ
3. Animation
์บ๋ฆญํฐ์ Animator Controller ์ค์
apply Root Motion(๊ฒ์ ์ค๋ธ์ ํธ์ ์์น์ ํ์ ์ ์ ๋๋ฉ์ด์
์ด ์ ์ดํ๋๋ก ํ์ฉ) ํ์ธ
2D simple Blend Tree
Jump ์ ๋๋ฉ์ด์ ์ถ๊ฐ, trigerํ์ ํ๋ผ๋ฏธํฐ๋ก Movement, Jump ์ํ์ ์ด, ์ํ์ ์ด has exit Time ์ฒดํฌ ํ์ธ
์ ๊ณต๋๋ ์ ๋๋ฉ์ด์ ์ด๋ฒคํธ ํจ์๊ฐ ํฌํจ๋์ด ์์ผ๋ฉด ์๋ฌ๋ฐ์ํจ์ผ๋ก ์ญ์ ํด์ค๋ค.(FootL,FootR)
PlayerAnimaor.cs
PlayerAnimaor.cs
using UnityEngine;
public class PlayerAnimaor : MonoBehaviour
{
[SerializeField]
private GameObject attackCollision;
private Animator animator;
private void Awake()
{
animator = GetComponent<Animator>();
}
public void OnMovement (float horizontal, float vertical)
{
animator.SetFloat("horizontal", horizontal);
animator.SetFloat("vertical", vertical);
}
public void OnJump()
{
animator.SetTrigger("onjump");
}
public void OnAttack()
{
animator.SetTrigger("onattack");
}
public void OnWeaponAttack()
{
animator.SetTrigger("onweaponattack");
}
public void OnAttackCollision()
{
attackCollision.SetActive(true);
}
}
4. ๊ณต๊ฒฉ ์ ๋๋ฉ์ด์
์ฐ๊ณ๊ณต๊ฒฉ
Sub-State Machine(์ ๋๋ฉ์ด์ ์ ๊ทธ๋ฃน ๋จ์๋ก ๋ฌถ์ด์ ์ ๋ฆฌํ๋ ๊ธฐ๋ฅ) ์์ฑ
- ์ดํ 3 - 4 - 2 -1 ์์ผ๋ก
- ๊ณต๊ฒฉ๋ชจ์ ์ด ๋๋ ํ ๋ค์ ๋ชจ์ ์ ํ ์ ์๊ฒ has exit Time์ ์ฒดํฌ
- ๋ชจ๋ ๊ณต๊ฒฉ์ ๋์ค ๋๊ธธ ์ ์๊ธฐ ๋๋ฌธ์ Base Layer์ ์ฐ๊ฒฐ
- ์ํ์ ์ด๋ณ Exit TIme, Transition duration ํ์ธ
์คํ ํ๋ฉด
5. Simple Combat System
1.์ข,์ฐํด๋ฆญ -> ๊ณต๊ฒฉ ์ ๋๋ฉ์ด์
์ฌ์
2.๊ณต๊ฒฉ ์ ๋๋ฉ์ด์
์ ํน์ ํ๋ ์์ ์ด๋ฒคํธ ํจ์ ํธ์ถ -> ์ถฉ๋ ๋ฐ์ค AttackCollision ์ค๋ธ์ ํธ ํ์ฑํ
3.๊ณต๊ฒฉ ์ถฉ๋ ๋ฐ์ค์ ์ค๋ธ์ ํธ๊ฐ ๋ถ๋ชํ๋ฉด -> Take Damage() ํจ์ ํธ์ถ (ํผ๊ฒฉ ์ ๋๋ฉ์ด์
, ์ค๋ธ์ ํธ ์์๋ณ๊ฒฝ)
Enemy
1.AnimationController ์์ฑ
2.์ถฉ๋ Capsule Collider ๋ฒ์ ์ง์ , Meterial ์ง์
๊ณต๊ฒฉ๋ฒ์
- ํ๋ ์ด์ด์ ๊ณต๊ฒฉ ๋ฒ์๋ฅผ ๋ํ๋ด๋ ๋ฐ์ค๋ฅผ ์์ฑ , ์์น,ํฌ๊ธฐ ์ค์
- ๋ชจ์ต์๋ณด์ด๊ฒ Mesh ์ฒดํฌํด์ , collier ํธ๋ฆฌ๊ฑฐ ์ฒดํฌ, rigid ์ค๋ ฅx
EnemyController.cs
EnemyController.cs
using System.Collections;
using UnityEngine;
public class EnemyController : MonoBehaviour
{
private Animator animator;
private SkinnedMeshRenderer meshRenderer;
private Color originColor;
private void Awake()
{
animator = GetComponent<Animator>();
meshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
originColor = meshRenderer.material.color;
}
public void TakeDamage(int damage)
{
// ์ฒด๋ ฅ์ด ๊ฐ์๋๊ฑฐ๋ ํผ๊ฒฉ ์ ๋๋ฉ์ด์
์ด ์ฌ์๋๋ ๋ฑ์ ์ฝ๋๋ฅผ ์์ฑ
Debug.Log(damage+"์ ์ฒด๋ ฅ์ด ๊ฐ์ํฉ๋๋ค");
// ํผ๊ฒฉ ์ ๋๋ฉ์ด์
์ฌ์
animator.SetTrigger("onhit");
// ์์ ๋ณ๊ฒฝ
StartCoroutine("OnHitColor");
}
private IEnumerator OnHitColor()
{
// ์์ ๋นจ๊ฐ์์ผ๋ก ๋ณ๊ฒฝํ ํ 0.1์ด ํ์ ์๋ ์์์ผ๋ก ๋ณ๊ฒฝ
meshRenderer.material.color = Color.red;
yield return new WaitForSeconds (0.1f);
meshRenderer.material.color = originColor;
}
}
- Enemy ํผ๊ฒฉ
PlayerAttackCollision.cs
์ถฉ๋๋ฐ์ค, ๋ฐ๋ฏธ์ง
PlayerAttackCollision.cs
using System.Collections;
using UnityEngine;
public class PlayerAttackCollision : MonoBehaviour
{
private void OnEnable()
{
StartCoroutine("AutoDisable");
}
private void OnTriggerEnter (Collider other)
{
// ํ๋ ์ด์ด๊ฐ ํ๊ฒฉํ๋ ๋์์ ํ๊ทธ, ์ปดํฌ๋ํธ, ํจ์๋ ๋ฐ๋ ์ ์๋ค
if (other.CompareTag("Enemy"))
{
other.GetComponent<EnemyController>().TakeDamage(10);
}
}
private IEnumerator AutoDisable()
{
// 0.1์ด ํ์ ์ค๋ธ์ ํธ๊ฐ ์ฌ๋ผ์ง๋๋ก ํ๋ค
yield return new WaitForSeconds(0.1f);
gameObject.SetActive(false);
}
}
- ์ถฉ๋ ํ๊ทธ๊ฐ enemy ์ด๋ฉด TakeDamage
์ ๋๋ฉ์ด์ ์ ์ด๋ฒคํธ ํจ์ ์์ฑ
๋๊ธ๋จ๊ธฐ๊ธฐ