유니티3D - 3
이번 목표
총 히트스캔 구현 , 히트스캔과 비슷한 목적지에 총알발사 , 줌자세 추가 (줌상태에서만 총알 발사)
줌자세(Aiming) 애니메이션
줌 애니메이션은 Idle 형태만 있어 상체 분리 블렌딩이 필요했다. 언리얼의 Layered Blend Per Bone 에 대응해 유니티에서는 Avatar Mask + Animation Layer 조합을 사용했다. Animator에 Upper 레이어를 추가하고, Upper Mask를 적용해 상체만 가중치로 제어한다.
일단 WeaponComponent에 L_Attack() 함수를 실행할 때 이벤트를 호출하고 Animcontroller에서 바인딩하여 사용하려고 한다.
이 방법도 유지보수를 생각해 델리게이트를 활용했다.
1
2
3
4
5
6
7
8
9
10
public class WeaponComponent : MonoBehaviour
{
public event Action OnFireEvent;
public void L_Attack()
{
CurrentWeapon.fire();
OnFireEvent.Invoke();
}
}
이것을 구현하다가 내 Player구조가 헷갈려서 정리해 보았다.
에이밍을 할 때 두번째 UpperLayer에 가중치를 1 안할 때 0으로 애니메이션을 조절을 했다. 그리고 에이밍 파라미터로 BaseLayer에서 또 앞을 응시하는 애니메이션트리로 재생하고 이동방향으로 캐릭터 회전을 꺼야하는 기능을 구현할 것이다.
- 가중치 조절 코드
1 2 3 4 5 6 7 8 9 10 11
// if(animator.GetCurrentAnimatorStateInfo(1).normalizedTime > 0.5f) 이것은 2번째레이어의 애니메이션 절반 진행이다. void OnAiming() { animator.SetLayerWeight(1, 1); } void OffAiming() { animator.SetLayerWeight(1, 0); }
바로 가중치를 0↔1로 바꾸면 끊김이 발생해, 목표치로 보간하는 방식으로 전환해 자연스럽게 만들었다.
1
2
currentWeight = Mathf.MoveTowards(currentWeight, targetWeight, Time.deltaTime * 5);
animator.SetLayerWeight(1, currentWeight);
에이밍중에는 캐릭터에 방향을 받고 그 이동방향을 Direction 변수에 넣어 애님컨트롤러에 넘겨주어 애니메이션에 반영할 것이고 카메라를 회전할 때 전방 방향으로 캐릭터도 회전시켜주는 코드를 추가해주었다.
캐릭터 이동코드에 좀 추가를 하고 바꾸었는데 코드가 한눈에 안들어와 정리를 했다 주석 설명으로 함수화 하는게 더 좋긴할 것이다.
- 구현 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
private void Update() { maxSpeed = run ? 10f : 5f; // 에이밍 중 카메라 포워드 벡터 방향으로 캐릭터를 회전 시킨다. if (weapon.IsAiming()) { Quaternion targetRotation = Quaternion.Euler(0, Camera.transform.eulerAngles.y, 0); transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSpeed * Time.deltaTime); } // 카메라 기준으로 x, z 축 이동 방향을 정한다. Vector3 moveDir = Camera.transform.right * moveInput.x + Camera.transform.forward * moveInput.y; moveDir.y = 0f; moveDir.Normalize(); // 키입력을 했을경우 if (moveDir.magnitude > 0.1f) { // 캐릭터의 이동속도에 따라 방향벡터를 짧고 길게 만들어줌 horizontalVelocity = Vector3.MoveTowards(horizontalVelocity, moveDir * maxSpeed, acceleration * Time.deltaTime); // 에이밍을 하지 않을 때 캐릭터를 이동방향으로 회전시켜줌 if (!weapon.IsAiming()) { Quaternion toRotation = Quaternion.LookRotation(moveDir, Vector3.up); transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, turnSpeed * Time.deltaTime); } } else { horizontalVelocity = Vector3.MoveTowards(horizontalVelocity, Vector3.zero, deceleration * Time.deltaTime); } // 중력 및 점프 처리 if (controller.isGrounded) { if (verticalVelocity < 0f) verticalVelocity = -1f; } else { verticalVelocity -= gravity * Time.deltaTime; } // 현재 스피드를 애니메이터에 반영할 변수에 전달 animSpeed = horizontalVelocity.magnitude; // 현재 이동 방향을 전달할 변수를 구하는 과정 { moveDir = horizontalVelocity.normalized; Vector3 cameraForward = Camera.transform.forward; cameraForward.y = 0f; cameraForward.Normalize(); direction = Vector3.SignedAngle(cameraForward, moveDir, Vector3.up); } // 최종 이동 방향 Y축 포함한 벡터를 만든다. Vector3 totalVelocity = horizontalVelocity; totalVelocity.y = verticalVelocity; // 실제 캐릭터를 벡터의 방향과 크기만큼 이동시키는 코드 controller.Move(totalVelocity * Time.deltaTime); }
상하좌우이동
음 구동은 잘된다 하지만 역시 삐걱거리는게 있다 저건아마 180도 방향회전일 때 좀더 자연스럽게 넘어가야하는 부분이 있어야할 거 같다. 아마 감속과 관련이 있어 보인다. 그리고 캐릭터 애니메이션이 각도가 정면이 아닌데 저것도 수정해야 좀 이쁠거 같다.
총알 방향과 히스트캔
일단 GunWeapon의 Fire 함수를 수정하여 Ray를 카메라의 전방방향으로 쏘고 히트 포인트 지점을 가져왔다.
그 후에 히트포인트 지점에 이펙트를 생성했다. ( 이상하긴한데 이펙트는 내가 그냥 대충 만들었다 )
히트포인트 - 총구포지션을 해서 총알이 날라갈 방향을 지정해줘 우리의 에이밍한 목표지점에 가깝게 총알이 날라가 보이게끔 만들었다.
그리고 프로젝타일에서 포워드벡터로 쭉 날라가게끔 되어있기 때문에 날라가는 방향으로 또 회전을 시켜주었다.
하지만 총알이 세워져서 나가는데 회전값을 바꾸어도 스폰할 때 바뀌기 때문에 메쉬용으로 빈오브젝트를 하나 만들어 자식오브젝트로 처리했다.
- 구현 코드 ```c# using UnityEngine;
[System.Serializable] public struct GunData { public float delayTime; public int maxBullet; public bool auto; public GameObject bullet; public Transform muzzlePoint; public ParticleSystem effect; }
public abstract class GunWeapon : MonoBehaviour { public GunData data;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
float MaxDistance = 100f;
public abstract void InitSetting();
public virtual void fire()
{
// 카메라 기준 Ray 쏘기
Camera cam = Camera.main;
Vector3 origin = cam.transform.position;
Vector3 direction = cam.transform.forward;
Vector3 hitPoint = origin + direction * MaxDistance;
if (Physics.Raycast(origin, direction, out RaycastHit hit, MaxDistance))
{
hitPoint = hit.point;
Debug.DrawRay(origin, direction * hit.distance, Color.green, 1f);
Instantiate(data.effect, hitPoint, Quaternion.identity);
// TODO: hit.collider.SendMessage("TakeDamage", damage)
}
else
{
Debug.DrawRay(origin, direction * 100f, Color.red, 1f);
}
// 총구 기준 방향 계산
Vector3 muzzlePos = data.muzzlePoint.position;
Vector3 fireDir = (hitPoint - muzzlePos).normalized;
// 총알 생성 및 방향 세팅
GameObject bulletObj = Instantiate(data.bullet, muzzlePos, Quaternion.LookRotation(fireDir));
} } ```
시현 영상
동작은 정상이나 일부 각도에서 회전 감속과 캐릭터 정면 정합이 어색하다. TODO로 감속/회전 튜닝을 남겨 후속에서 다룬다.
TODO
감속/회전 보간 값 튜닝, 정면 정렬 정밀도 보강
