[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 42회차 미션 시작합니다.

04. 배틀로얄 - 29, 30 번을 진행합니다.



저번 시간에 계속하여 BehaviourController 작업을 계속 이어나갑니다.

public class BehaviourController : MonoBehaviour
{
    // 지난 시간까지 소스작업들..
    
    private void LateUpdate() {
        if (behaviourLocked > 0 || overrideBehaviours.Count == 0) {
            foreach (GenericBehaviour behaviour in behaviours) {
                if (behaviour.isActiveAndEnabled && currentBehaviour == behaviour.GetBehaviourCode)
                    behaviour.LocalLateUpdate();
            }
        }
        else {
            foreach (GenericBehaviour behaviour in overrideBehaviours)
                behaviour.LocalLateUpdate();
        }
    }
    
    public void SubscribeBehaviour(GenericBehaviour behaviour) {
        behaviours.Add(behaviour);
    }
    
    public void RegisterDefaultBehaviour(int behaviourCode) {
        defaultBehaviour = behaviourCode;
        currentBehaviour = behaviourCode;
    }
    
    public void RegisterBehaviour(int behaviourCode) {
        if (currentBehaviour == defaultBehaviour) {
            currentBehaviour = behaviourCode;
        }
    }
    
    public void UnRegisterBehaviour(int behaviourCode) {
        if (currentBehaviour == behaviourCode) {
            currentBehaviour = defaultBehaviour;
        }
    }
    
    public bool OverrideWithBehaviour(GenericBehaviour behaviour) {
        if (!overrideBehaviours.Contains(behaviour)) {
            if (overrideBehaviours.Count == 0) {
                foreach (GenericBehaviour behaviour1 in behaviours) {
                    if (behaviour1.isActiveAndEnabled && currentBehaviour == behaviour1.GetBehaviourCode) {
                        behaviour1.OnOverride();
                        break;
                    }
                }
            }
            overrideBehaviours.Add(behaviour);
            return true;
        }
        return false;
    }
    
    public bool RevokeOverridingBehaviour(GenericBehaviour behaviour) {
        if (overrideBehaviours.Contains(behaviour)) {
            overrideBehaviours.Remove(behaviour);
            return true;
        }
        return false;
    }
    
    public bool IsOverriding(GenericBehaviour behaviour = null) {
        if (behaviour == null) {
            return overrideBehaviours.Count > 0;
        }
        return overrideBehaviours.Contains(behaviour);
    }
    
    public bool IsCurrentBehaviour(int behaviourCode) {
        return currentBehaviour == behaviourCode;
    }
    
    public bool GetTempLockStatus(int behaviourCode = 0) {
        return (behaviourLocked != 0 && behaviourLocked != behaviourCode);
    }
    
    public void LockTempBehaviour(int behaviourCode) {
        if (behaviourLocked == 0)
            behaviourLocked = behaviourCode;
    }
    
    public void UnLockTempBehaviour(int behaviourCode) {
        if (behaviourLocked == behaviourCode)
            behaviourLocked = 0;
    }
    
    public Vector3 GetLastDirection() {
        return lastDirection;
    }
    
    public void SetLastDirection(Vector3 direction) {
        lastDirection = direction;
    }
}




여기까지 작업하고 Unity에서 컴파일 에러가 나오는지 확인해 봅니다. 네~ 아무 에러도 발생하지 않았네요 ^^~ 굳~~~




이제 이동 동작을 만들기입니다. 모든 게임에서는 이동이 기본이라고 할 수 있겠지요.
그리고 현재 프로젝트에서는 이동에 점프를 포함하는 Behaviour로 구현합니다.

FPS라 이동과 점프를 굳이 구분하여 처리할 필요가 없는 프로젝트이기 때문입니다.


 



해당 위치에 MoveBehaviour 스크립트를 생성하고 재미있는 소스코드 작업을 시작합니다 ^^~


///<summary>
/// 이동과 점프 동작을 담당하는 컴포넌트.
/// 충돌처리에 대한 기능도 포함
/// 기본 동작으로써 작동.
///</summary>
public class MoveBehaviour : GenericBehaviour
{
    public float walkSpeed = 0.15f;
    public float runSpeed = 1.0f;
    public float sprintSpeed = 2.0f;
    public float speedDampTime = 0.1f;
    
    public float jumpHeight = 1.5f;
    public float jumpInertialForce = 10f; // 점프 관성
    public float speed, speedSeeker;
    private int jumpBool;
    private int groundedBool;
    private bool isColliding; // 충돌 체크
    private CapsuleCollider capsuleCollider;
    private Transform myTransform; // 캐싱용
    
    private void Start() {
        myTransform = transform;
        capsuleCollider = GetComponent<CapsuleCollider>();
        jumpBool = Animator.StringToHash(AnimatorKey.Jump);
        groundedBool = Animator.StringToHash(AnimatorKey.Grounded);
        behaviourController.GetAnimator.SetBool(groundedBool, true);
        
        behaviourController.SubscribeBehaviour(this);
        behaviourController.RegisterDefaultBehaviour(this.behaviourCode);
        speedSeeker = runSpeed;
    }
    
    // 3D 캐릭터의 이동은 우선 이동방향으로 회전하고 이동..
}


여기까지 변수 선언 및 초기화 작업이 완료되었습니다 ^^~

다음 시간에는 MoveBehaviour 함수들을 계속 구현합니다~




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 41회차 미션 시작합니다.

04. 배틀로얄 - 27, 28 번을 진행합니다.




플러거블 컨트롤러에 대한 설명을 하였었고, 이제 플러거블 컨트롤러 제작을 시작합니다.

우선 기본이 되는 동작 컨트롤러(Behaviour Controller)부터 제작을 시작합니다.

개념을 이해하면 어렵지 않은 내용이니 어떠한 구성과 개념을 가지고 동작하는지를 염두하고 보는 것이 좋습니다.



우선 PlayerCharacter의 Animator에 미리 다운받아 구성되어진 CharacterController라는 Animation을 연결하여 줍니다.




CharacterController에서 "Open"을 클릭하면 Animator Window가 나타나며 어떤 상태에서 어떤 상태로 Transform되는지가 미리 만들어져 있습니다.



Speed이 0일 때는 걷는 애니메이션이 구동되다가 Speed 값을 계속 올려 보면 조금씩 빨라지다가 빠르게 달려가는 것까지 구동되는 것을 테스트해볼 수 있습니다.

 



Configure Avatar를 클릭하면 캐릭터의 뼈대를 볼 수 있는 Bone 화면이 나타납니다.
게임을 진행하다가 몸을 옆으로 약간 기울이는 기능을 만든다거나 할 때 여기에 있는 뼈대를 가지고 스크립트에서 각도를 주어 기울이는 형태로 제작하기 때문에 어디에 있는지 어떤 이름을 가지고 있는지 알아야겠지요 ^^

여기까지 Unity UI 및 PlayerCharacter의 미리 작성된 구성 및 설정에 대한 설명이 끝났습니다 ^^~ 짝짝짝



 

 


Player 폴더를 만들고 BehaviourController 스크립트를 만들어서 제작을 시작합니다. ^^


/// <summary>
/// 현재 동자, 기본 동작, 오버라이딩 동작, 잠긴 동작, 마우스 이동값, 땅에 서있는지,
/// GenericBehaviour를 상속받은 동작들을 업데이트 시켜줍니다.
/// </summary>
public class BehaviourController : MonoBehaviour
{
    private List<GenericBehaviour> behaviours; // 동작들
    private List<GenericBehaviour> overrideBehaviours; // 우선시 되는 동작
    private int currentBehaviour; // 현재 동작 해시코드
    private int defaultBehaviour; // 기본 동작 해시코드
    private int behaviourLocked; // 잠긴 동작 해시코드
    
    // 캐싱
    public Transform playerCamera;
    private Animator myAnimatoor;
    private Rigidbody myRigidbody;
    private ThirdPersonOrbitCam camScript;
    private Transform myTransform;
    
    private float h; // horizontal axis
    private float v; // vertical axis
    public float turnSmoothing = 0.06f; // 카메라를 향하도록 움직일 때 회전속도
    private bool changedFOV; // 달리기 동작이 카메라 시야각이 변경되었을때 저장되었니
    public float sprintFOV = 100; // 달리기 시야각
    private Vector3 lastDirection; // 마지막 향했던 방향
    private bool sprint; // 달리기 중인가?
    private int hFloat; // 애니메이터 관련 가로축 값
    private int vFloat; // 애니메이터 관련 세로축 값
    private int groundedBool; // 애니메이터 지상에 있는가
    private Vector3 colExtents; // 땅과의 충돌체크를 위한 충돌체 영역.
    
    public float GetH { get => h; }
    public float GetV { get => v; }
    public ThirdPersonOrbitCam GetCamScript { get => camScript; }
    public Rigidbody GetRigidbody { get => myRigidbody; }
    public Animator GetAnimator { get => myAnimatoor; }
    public int GetDefaultBehaviour { get => defaultBehaviour; }
   
    private void Awake() {
        behaviours = new List<GenericBehaviour>();
        overrideBehaviours = new List<GenericBehaviour>();
        myAnimatoor = GetComponent<Animator>();
        hFloat = Animator.StringToHash(AnimatorKey.Horizontal);
        vFloat = Animator.StringToHash(AnimatorKey.Vertical);
        camScript = playerCamera.GetComponent<ThirdPersonOrbitCam>();
        myRigidbody = GetComponent<Rigidbody>();
        myTransform = transform;
        // ground??
        groundedBool = Animator.StringToHash(AnimatorKey.Grounded);
        colExtents = GetComponent<Collider>().bounds.extents;
    }
   
    public bool IsMoving() {
        //return (h != 0) || (v != 0); // 부동소숫점 문제가 발생되는 안좋은 코드!!!
        return Mathf.Abs(h) > Mathf.Epsilon || Mathf.Abs(v) > Mathf.Epsilon;
    }
    
    public bool IsHorizontalMoving() {
        return Mathf.Abs(h) > Mathf.Epsilon;
    }
    
    public bool CanSprint() {
        foreach (GenericBehaviour behaviour in behaviours)
            if (!behaviour.AllowSprint)
                return false;
        foreach (GenericBehaviour genericBehaviour in overrideBehaviours)
            if (!genericBehaviour.AllowSprint)
                return false;
        return true;
    }
    
    public bool IsSprinting() {
        return sprint && IsMoving() && CanSprint();
    }
   
    public bool IsGrounded() {
        // 레이저를 충돌체 크기만큼 아래로 쏴서 걸리는 것이 있으면 땅에 있다고 판단
        Ray ray = new Ray(myTransform.position + Vector3.up * 2 * colExtents.x, Vector3.down);
        return Physics.SphereCast(ray, colExtents.x, colExtents.x + 0.2f);
    }
    
    private void Update() {
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");
        myAnimatoor.SetFloat(hFloat, h, 0.1f, Time.deltaTime);
        myAnimatoor.SetFloat(vFloat, v, 0.1f, Time.deltaTime);
        
        sprint = Input.GetButton(ButtonName.Sprint);
        if (IsSprinting()) {
            changedFOV = true;
            camScript.SetFOV(sprintFOV);
        }
        else if (changedFOV) {
            camScript.ResetFOV();
            changedFOV = false;
        }
        
        myAnimatoor.SetBool(groundedBool, IsGrounded());
    }
   
    public void Repositioning() {
        if (lastDirection != Vector3.zero) {
            lastDirection.y = 0f; // 3인칭 캐릭터의 y이동을 없앰. 안그러면 하늘을 보고 걸어간다거나하는 이상동작.ㅋㅋ
            Quaternion targetRotation = Quaternion.LookRotation(lastDirection);
            Quaternion newRotation = Quaternion.Slerp(myRigidbody.rotation, targetRotation, turnSmoothing);
            myRigidbody.MoveRotation(newRotation);
        }
    }
    
    // Pluggable Behaviour Pattern의 핵심!!!
    private void FixedUpdate() {
        bool isAnyBehaviourActive = false;
        if (behaviourLocked > 0 || overrideBehaviours.Count == 0) {
            foreach (GenericBehaviour behaviour in behaviours) {
                if (behaviour.isActiveAndEnabled && currentBehaviour == behaviour.GetBehaviourCode) {
                    isAnyBehaviourActive = true;
                    behaviour.LocalFixedUpdate();
                }
            }
        }
        else {
            foreach (GenericBehaviour behaviour in overrideBehaviours) {
                behaviour.LocalFixedUpdate();
            }
        }
        if (!isAnyBehaviourActive && overrideBehaviours.Count == 0) {
            myRigidbody.useGravity = true;
            Repositioning();
        }
    }
    
    private void LateUpdate() {
        if (behaviourLocked > 0 || overrideBehaviours.Count == 0) {
            foreach (GenericBehaviour behaviour in behaviours) {
                if (behaviour.isActiveAndEnabled && currentBehaviour == behaviour.GetBehaviourCode)
                    behaviour.LocalLateUpdate();
            }
        }
    }
}

public abstract class GenericBehaviour : MonoBehaviour
{
    protected int speedFloat;
    protected BehaviourController behaviourController;
    protected int behaviourCode;
    protected bool canSprint; // 뛸 수 있는가 여부. 조준중일때는 못뛴다 등.
    
    private void Awake() {
        behaviourController = GetComponent<BehaviourController>();
        speedFloat = Animator.StringToHash(AnimatorKey.Speed);
        canSprint = true;
        // 동작 타입을 해시코드로 가지고 있다가 추후에 구별용으로 사용
        behaviourCode = this.GetType().GetHashCode();
    }
    
    public int GetBehaviourCode { get => behaviourCode; }
    
    public bool AllowSprint { get => canSprint; }
    
    public virtual void LocalLateUpdate() {
    }
    
    public virtual void LocalFixedUpdate() {
    }
    
    public virtual void OnOverride() {
    }
}


다음 시간에도 이어서 BehaviourController의 코드 작업이 계속됩니다.




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 40회차 미션 시작합니다.

04. 배틀로얄 - 25, 26 번을 진행합니다.




저번 시간에 이어 ThirdPersonOrbitCam 스크립트를 지속 작성합니다.

우선 Unity Script의 이벤트 함수 실행 순서는 반드시 숙지해야 하는 내용입니다.

Unity 매뉴얼 최신 URL은 다음과 같습니다.

docs.unity3d.com/kr/2020.2/Manual/ExecutionOrder.html

 

이벤트 함수의 실행 순서 - Unity 매뉴얼

Unity 스크립트를 실행하면 사전에 지정한 순서대로 여러 개의 이벤트 함수가 실행됩니다. 이 페이지에서는 이러한 이벤트 함수를 소개하고 실행 시퀀스에 어떻게 포함되는지 설명합니다.

docs.unity3d.com

 

그래도 정리된 Flowchart를 첨부합니다. 정리가 아주 죽입니다. ㅎㅎ



일단 Camera의 위치가 어떻게 위치하게 되는지 Update() 함수를 임시로 작성하여 확인해 봅니다.
위와 같이 작성후 플레이를 해보면 다음과 같이 오른쪽 어깨 위쯤에서 약간 위를 보는 듯한 시선으로 처리되는 것을 확인할 수 있습니다.



Update() 내용은 모두 지우고 ThirdPersonOrbitCam 스크립트를 계속 작성합니다.


[RequireComponent(typeof(Camera))] // Camera 컴포넌트 있어야 구동.
public class ThirdPersonOrbitCam : MonoBehaviour
{
    // 저번 시간 작성한 변수들..
    
    private void Awake() {
        // 캐싱
        cameraTransform = transform;
        myCamera = cameraTransform.GetComponent<Camera>();
        // 카메라기본 포지션 세팅
        cameraTransform.position = player.position + Quaternion.identity * pivotOffset + Quaternion.identity * camOffset;
        cameraTransform.rotation = Quaternion.identity;
        
        // 카메라의 플레이어간의 상대 벡터, 충돌체크에 사용하기 위함
        relCameraPos = cameraTransform.position - player.position;
        relCameraPosMag = relCameraPos.magnitue - 0.5f; // 플레이어 충돌을 피하기 위한 Offset
        
        // 기본 세팅
        smoothPivotOffset = pivotOffset;
        smoothCamOffset = camOffset;
        defaultFOV = myCamera.fieldOfView;
        angleH = player.eulerAngles.y;
        
        ResetTargetOffsets();
        ResetFOV();
        ResetMaxVerticalAngle();
    }
    
    public void ResetTargetOffsets() {
        targetPivotOffset = pivotOffset;
        targetCamOffset = camOffset;
    }
    
    public void ResetFOV() {
        targetFOV = defaultFOV;
    }
   
    public void ResetMaxVerticalAngle() {
        targetMaxVerticalAngle = maxVerticalAngle;
    }
    
    public void BounceVertical(float degree) {
        recoilAngle = degree;
    }
    
    public void SetTargetOffset(Vector3 newPivotOffset, Vector3 newCamOffset) {
        targetPivotOffset = newPivotOffset;
        targetCamOffset = newCamOffset;
    }
    
    public void SetFOV(float customFOV) {
        targetFOV = customFOV;
    }
    
    bool ViewingPosCheck(Vector3 checkPos float deltaPlayerHeight) {
        Vector3 target = player.position + (Vector3..up * deltaPlayerHeight);
        if (Physics.SphereCast(checkPos, 0.2f, target - checkPos, out RaycastHit hit, relCameraPosMag)) {
            if (hit.transform != player && !hit.transform.GetComponent<Collider>().isTrigger)
                return false;
        }
        return true;
    }
    
    bool ReverseViewingPosCheck(Vector3 checkPos, float deltaPlayerHeight, float maxDistance) {
        Vector3 origin = player.position + (Vector3.up * deltaPlayerHeight);
        if (Physics.SphereCast(origin, 0.2f, checkPos - origin, out RaycastHit hit, maxDistance) {
            if (hit.transform != player & hit.transform != transform && !hit.transform.GetComponent<Collider>().isTrigger)
                return false;
        }
        return true;
    }
    
    bool DoubleViewingPosCheck(Vector3 checkPos, float offset) {
        float playerFocusHeight = player.GetComponent<CapsuleCollider>().height * 0.75f;
        return ViewingPosCheck(checkPos, playerFocusHeight) && ReverseViewingPosCheck(checkPos, playerFocusHeight, offset);
    }
   
    void Update() {
        // 마우스 이동 값..
        angleH += Mathf.Clamp(Input.GetAxis("Mouse X"), -1f, 1f) * horizontalAimingSpeed;
        angleV += Mathf.Clamp(Input.GetAxis("Mouse Y"), -1f, 1f) * verticalAimingSpeed;
        // 수직 이동 제한
        angleV = Mathf.Clamp(angleV, minVerticalAngle, targetMaxVerticalAngle);
        // 수직 카메라 바운스
        angleV = Mathf.LerpAngle(angleV, angleV + recoilAngle, 10f * Time.deltaTime);
        
        // 카메라 회전
        Quaternion camYRotation = Quaternion.Euler(0.0f, angleH, 0.0f);
        Quaternion aimRotation = Quaternion.Euler(-angleV, angleH, 0.0f);
        cameraTransform.rotation = aimRotation;
        
        // Set FOV
        myCamera.fieldOfView = Mathf.Lerp(myCamera.fieldOfView, targetFOV, Time.deltaTime);
        
        Vector3 baseTempPosition = player.position + camYRotation * targetPivotOffset;
        Vector3 noCollisionOffset = targetCamOffset; // 조준할 때 카메라의 오프셋값, 조준할때와 평소때가 다르다.
        for (float zOffset = targetCamOffset.z; zOffset <= 0f; zOffset += 0.5f) {
            noCollisionOffset.z = zOffset;
            if (DoubleViewingPosCheck(baseTempPosition + aimRotation * noCollisionOffset, Mathf.Abs(zOffset)) || zOffset == 0f) {
                break;
            }
        }
        
        // Reposition Camera
        smoothPivotOffset = Vector3.Lerp(smoothPivotOffset, targetPivotOffset, smooth * Time.deltaTime);
        smoothCamOffset = Vector3.Lerp(smoothCamOffset, noCollisionOffset, smooth * Time.deltaTime);
        
        cameraTransform.position = player.position + camYRotation * smoothPivotOffset + aimRotation * smoothCamOffset;
        
        if (recoilAngle > 0.0f) {
            recoilAngle -= recoilAngleBounce * Time.deltaTime;
        }
        else if (recoilAngle < 0.0f) {
            recoilAngle += recoilAngleBounce * Time.deltaTime;
        }
        
        public float GetCurrentPivotMagnitude(Vector3 finalPivotOffset) {
            return Mathf.Abs((finalPivotOffset - smoothPivotOffset).magnitue);
        }
    }
}




위와 같이 하고 컴파일 에러가 없는 것을 확인 후, 플레이를 해보면 마우스 움직임에 따라 카메라 시점이 변경되는 것을 확인할 수 있습니다.







이제 플러거블 동작 시스템을 제작합니다.
우선 플러거블 동작 시스템에 대한 설명입니다. 우선 플러거블 패턴을 사용하지 않았을 때의 문제점에 대해 간략히 설명합니다.





나쁜 코드에 대한 예입니다. 하나의 Update()에서 특정 키를 누를 때 무얼하고 무엇하고, 그런데 이럴때는 예외처리를 하고 등을 넣고 하다보면 Update()가 엄청나게 길어지고 복잡한 코드로 만들어지는 것을 볼 수 있습니다. 이러한 문제가 발생한다는 것이지요.


[7]



디아블로 시간에 설명되었던 Transition에 대한 설명입니다 ^^~
그리고 이것을 구현하는 디자인 패턴 방법이 Pluggable Pattern인 것입니다.



캐릭터를 하나 가져다 놓고, Rigidbody와 CapsuleCollider를 추가하여 대략적인 세팅을 해줍니다.


다음 시간에는 Pluggable Pattern을 하나씩 만들어 보게 되겠네요 ^^~






<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts