본문 바로가기
유니티 개발

3D 캐릭터 팔, 주먹 동작 구현

by L3m0n S0ju 2021. 8. 17.

 

 

이번에는 팔과 팔동작을 추가시켜봤습니다. 상대 객체를 공격하면 아래 콘솔 창에 정보가 출력되도록 설계하였으나 아직 객체는 땅바닥 밖에 없어서 땅을 공격하면 콘솔에 객체 이름이 출력됩니다. 노트북으로 작업해서 영상이 많이 끊깁니다.

 

 

 

 

 

 

추가된 코드 Hand.cs, HandController.cs

 

Hand.cs


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

public class Hand : MonoBehaviour
{
    public string handName; // 맨손, 너클 구분
    public float range; // 공격범위
    public int damage; // 공격력
    public float workSpeed; // 작업속도
    public float attackDelay; // 공격 딜레이
    public float attackDelayA; // 공격 활성화 시점
    public float attackDelayB; // 공격 비활성화 시점


    public Animator anim; // 애니메이션 가져오기
}

 

 

 

 

 

 

 

 

 

 

 

 

 

HandController.cs


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

public class HandController : MonoBehaviour
{
    // 현재 장착된 Hand형 타입 무기 
    [SerializeField]
    private Hand currentHand; // Hand 변수 모두 SerializeField 적용

    // 공격중?
    private bool isAttack=false;
    private bool isSwing = false;
    private RaycastHit hitInfo; // 닿은 대상 정보 획득

 
    // Update is called once per frame
    void Update()
    {
        TryAttack();
    }

    private void TryAttack()
    {
        if(Input.GetButton("Fire1"))
        {
            if(!isAttack)
            {
                StartCoroutine(AttackCoroutine()); // 딜레이에 적합하므로 코루틴 실행
            }
        }
    }

    IEnumerator AttackCoroutine()
    {
        isAttack = true;
        currentHand.anim.SetTrigger("Attack"); // Attack 트리거 실행

        yield return new WaitForSeconds(currentHand.attackDelayA); // 동작 시작 후 attackDelayA 시간 딜레이
        isSwing = true; // 현재 시점부터 공격이 먹힘

        // 공격 활성화 시점
        StartCoroutine(HitCoroutine());
        yield return new WaitForSeconds(currentHand.attackDelayB); // 공격이 먹히는 시간 attackDelayB 만큼 설정

        isSwing = false; // 현재 시점부터 공격이 안먹힘

        yield return new WaitForSeconds(currentHand.attackDelay- currentHand.attackDelayA- currentHand.attackDelayB); // 공격 루틴 종료까지 딜레이 -> 공격 불가

        isAttack = false; // 공격이 끝났으므로 다시 공격 가능
    }

    IEnumerator HitCoroutine()
    {
        while(isSwing)
        {
            if(CheckObject()) // 충돌함
            {
                isSwing = false;
                Debug.Log(hitInfo.transform.name); // 충돌하는 대상 이름 출력
            }
            yield return null;
        }
    }

    private bool CheckObject()
    {
        if(Physics.Raycast(transform.position, transform.forward,out hitInfo,currentHand.range)) // 캐릭터 정면으로 range 안에 충돌하는 대상이 있다면 true 반환
        {
            return true;
        }
        return false;
    }
}

 

 

 

 

 

 

 

기존 코드 

PlayerController.cs


 

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

public class PlayerController : MonoBehaviour
{
    // 스피드 조정 변수
    [SerializeField]
    private float walkSpeed; // 걸음속도
    [SerializeField]
    private float runSpeed; // 달리기속도
    [SerializeField]
    private float crouchSpeed; // 앉기상태에서의 속도
    private float applySpeed; // 임시속도 저장변수

    [SerializeField]
    private float jumpForce; // 점프

    // 상태 변수
    private bool isCrouch = false; // 앉아있는가?
    private bool isRun = false; // 달리고 있는가?
    private bool isGround = true; // 땅에 붙어있는가?

    // 앉기상태, 서있는상태 높이변수
    [SerializeField]
    private float crouchPosY; //앉기상태 시선
    private float originPosY; //서있는상태 시선
    private float applycrouchPosY;

    // 땅 착지 여부
    private CapsuleCollider capsuleCollider;

    // 민감도
    [SerializeField]
    private float lookSensitivity; // 마우스 민감도

    // 카메라 한계
    [SerializeField]
    private float cameraRotationLimit; // 위아래 시선 각도 제한
    private float currentCameraRotationX = 0;

    // 필요한 컴포넌트
    [SerializeField]
    private Camera theCamera;
    private Rigidbody myRigid; 

    void Start() // 시작
    {
        capsuleCollider = GetComponent<CapsuleCollider>(); // 캡슐 로드
        myRigid = GetComponent<Rigidbody>(); // Rigidbody 속성 가지고 있는 컴포넌트 로드
        applySpeed = walkSpeed;

        // 초기화
        originPosY = theCamera.transform.localPosition.y; //서있는상태 카메라 상대위치 저장
        applycrouchPosY = originPosY;
    }

    void Update() // 대략 1초에 60프레임 반복
    {
        IsGround();
        TryJump();
        TryRun(); // 달리기
        TryCrouch(); // 앉기
        Move(); // 캐릭터 움직임
        CameraRotation(); // 카메라 상하각도
        CharacterRotation(); // 캐릭터 좌우각도
    }

    // 앉기 시도
    private void TryCrouch()
    {
        if(Input.GetKeyDown(KeyCode.LeftControl))
        {
            Crouch();
        }
    }

    // 앉기 동작
    private void Crouch()
    {
        isCrouch = !isCrouch; // 앉기상태 변환

        if(isCrouch)
        {
            applySpeed = crouchSpeed;
            applycrouchPosY = crouchPosY;
        }
        else
        {
            applySpeed = walkSpeed;
            applycrouchPosY = originPosY;
        }

        StartCoroutine(CrouchCoroutine()); //카메라 위치변경
    }

    // 부드러운 앉기 동작
    IEnumerator CrouchCoroutine() // 카메라 위치변경
    {
        float _posY=theCamera.transform.localPosition.y;
        int count = 0; // 무한히 실행되므로 카운터를 통해 종료

        while(_posY!=applycrouchPosY) //_posY가 applycrouchPosY와 같을 때 까지 반복
        {
            count++;
            _posY = Mathf.Lerp(_posY, applycrouchPosY, 0.3f); // 0.3 속도로 서서히 변경
            theCamera.transform.localPosition = new Vector3(0, _posY, 0); // 좌표 수정
            if (count > 15) // 15번 반복 후 종료
                break;
            yield return null;
        }
        theCamera.transform.localPosition = new Vector3(0, applycrouchPosY, 0); // 좌표 완전히 수정
    }

    // 지면 체크
    private void IsGround()
    {
        isGround = Physics.Raycast(transform.position, Vector3.down, capsuleCollider.bounds.extents.y+0.1f); // 캡슐 중간 높이에서 아래로 레이저를 쏴서 땅에 닿으면 true
    }

    // 점프 시도
    private void TryJump()
    {
        if(Input.GetKeyDown(KeyCode.Space) && isGround) // 스페이스 키를 누르고 땅에 붙어있는 경우
        {
            Jump();
        }
    }

    // 점프 동작
    private void Jump()
    {
        if (isCrouch) // 앉기상태에서 점프할 경우 앉기상태 해제
            Crouch();
        myRigid.velocity = transform.up * jumpForce;
    }

    // 달리기 시도
    private void TryRun()
    {
        if(Input.GetKey(KeyCode.LeftShift)) // 왼쪽 쉬프트를 누를때
        {
            Running(); // 달리기
        }
        if(Input.GetKeyUp(KeyCode.LeftShift)) // 왼쪽 쉬프트를 안누를때
        {
            RunningCancle(); // 달리기 취소
        }
    }

    // 달리기 동작
    private void Running()
    {
        if (isCrouch) // 앉은상태에서 달리기 누르면 일어서서 달림
            Crouch();
        isRun = true; 
        applySpeed = runSpeed; // 달리기 속도 적용
    }

    private void RunningCancle()
    {
        isRun = false;
        applySpeed = walkSpeed; // 걷기 속도 적용
    }

    // 움직임 실행
    private void Move()
    {
        float _moveDirX = Input.GetAxisRaw("Horizontal"); // 좌우 키
        float _moveDirZ = Input.GetAxisRaw("Vertical"); // 상하 키

        Vector3 _moveHorizontal = transform.right * _moveDirX; // 좌우 좌표
        Vector3 _moveVertical = transform.forward * _moveDirZ; // 상하 좌표

        Vector3 _velocity = (_moveHorizontal + _moveVertical).normalized * applySpeed; // 속도

        myRigid.MovePosition(transform.position + _velocity * Time.deltaTime); // 속도에 따른 위치이동
    }

    // 좌우 캐릭터 회전
    private void CharacterRotation() 
    {
        float _yRotation = Input.GetAxisRaw("Mouse X"); // 마우스는 좌우가 X축이고 캐릭터는 좌우가 Y축이다.
        Vector3 _characterRotationY = new Vector3(0f, _yRotation, 0f)*lookSensitivity;
        myRigid.MoveRotation(myRigid.rotation * Quaternion.Euler(_characterRotationY));
    }

    // 상하 카메라 회전
    private void CameraRotation() 
    {
        float _xRotation = Input.GetAxisRaw("Mouse Y"); // 마우스는 상하가 Y축이고 카메라는 상하가 X축이다.
        float _cameraRotationX = _xRotation * lookSensitivity; // 상하 민감도
        currentCameraRotationX -= _cameraRotationX; // 상하각도 값
        currentCameraRotationX = Mathf.Clamp(currentCameraRotationX, -cameraRotationLimit, cameraRotationLimit); // 각도 제한
     
        theCamera.transform.localEulerAngles = new Vector3(currentCameraRotationX, 0f, 0f); // 상하각도 업데이트
    }
}

'유니티 개발' 카테고리의 다른 글

지형제작 (나무, 잔디, 돌)  (0) 2021.08.17
3D 캐릭터 움직임 심화  (0) 2021.08.17
3D 캐릭터 움직임 기초  (0) 2021.08.16
2D 상하좌우 이동  (0) 2021.08.16

댓글