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

05. 배틀그라운드 - 01, 02 번을 진행합니다.


04.배틀로얄에서 05.배틀그라운드 라서 다른 게임인 줄 알았더니 같은 게임의 연속이네요 ^^;




복습과 전체 리뷰를 합니다.

유니티를 실행하여 우선 전체 이동하는 것과 소리 등을 확인합니다.



이제 무기를 집는 위치에 가서 설명합니다.

무기에는 Collider가 2개가 있고, 집는 동작을 확인하는 자체 Collider와 "Pick Rifle"이 뜨고 집는 반경을 확인하기 위한 Trigger Collider가 존재합니다.

무기를 집고 조준도 해보고.. 카메라 회전을 하고나서 캐릭터가 약간 딜레이를 주어 따라 회전하는 것을 확인할 수 있습니다.

총을 조준하고 쏴보고 소리도 듣고 바닥에도 쏴봅니다.




Quad GameObject에 BulletHole Material을 씌워서 만든 내용이었습니다.. 라지만...
R 키를 눌러서 재장전도 확인합니다.
Animator에 대한 설정이나 교육 등은 Youtube에서 검색해서 보면 된다고 합니다!?




PlayerCharacter의 Animator 설정어니 Rigidbody 등을 했었습니다.
BehaviourController에 대한 설명..
힘드셨죠... 에 대한 계속 반복적인 설명...






프로젝트에 들어가기 전에 Enemy 적군이 어떻게 움직이게 될지 미리 예습을 하는 시간입니다.

또 계속 코딩이 이어질 것이라.. 우선 Enemy가 어떻게 동작하는지를 보여준다고 합니다. 무지막지한 코딩이 계속되면 실행화면을 볼 시간이 없을 것이라고 합니다 ^^;




적의 Patrol 기능 설명입니다. 특정 위치를 왔다 갔다.. 하다가.. 캐릭터의 발자국 소리가 감지되면 장애물 뒤로 숨어서 공격을 시작하는 것에 대한 설명입니다.




실제 플레이를 해보면 적 캐릭터가 패트롤을 하다가.. 캐릭터의 소리가 들리면 장애물 뒤로 숨어서 사격을 시작하는 등을 확인할 수 있습니다.




적군의 State 상태에 대한 설명입니다.

어떤 조건 Decision을 맞족하면 특정 상태로 트랜지션된다..이지요...

 



Attack 상태에서의 트랜지션 가능한 모든 상태에 대한 설명입니다.
State 표는 좋은데.. 너무 당연한 게임이 어떻게 동작하는지에 대해서는 왜이리 설명이 긴 것일까요 ^^;

장애물찾기(FindCover) State에 대한 설명도 이어집니다.

NavigationMesh를 사용한다고 합니다.

찾기(Search) State에 대한 설명도 이어집니다.

공격 가능 지점(GotoShotSpot)으로 이동에 대한 State 설명도 이어집니다..

State가 꽤 많습니다. 머 당연하겠지요. ㅎㅎ 배틀그라운드의 상태가 그만큼 복잡한 상태를 왔다갔다하며 구동되도록 되어 있습니다.

장애물로 복귀(ReturnToCover) State에 대한 설명도 이어집니다..

장애물로 이동(TakeCover)에 대한 설명도 이어집니다... ㅎㅎ 구현시작하면 계속 코딩이 이어질 것 같네요.

강제이동, 강제대기 등에 대한 State 설명도 이어집니다.


한 State에서 사용가능한 Action은 여러개.. Transition이 일어나기 위한 조건을 Decision으로 하여 진행하겠습니다.

여러가지 Behaviour Tree 방법이 있는데 그 중에서 양갈래식 Tree 구조를 가지는 시스템을 구현한다고 합니다.

다음 시간부터 본격적으로 만들기 시작한다고 합니다.

기능들을 잘 쪼개놓으면 여러 기능을 재활용하는 것들도 해보게 됩니다.

플레이어 캐릭터에서 사용한 시스템에서 그치지 않고 업그레이드된 방식으로 진행을 합니다.

다음 영상에서는 위에서 설명한 것들을 하나씩 만들어보는 시간이 됩니다.






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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 41, 42 번을 진행합니다.




Shoot Behaviour 테스트를 위한 Unity 세팅을 진행해 줍니다.

 


Interactive Weapon 관련 세팅도 진행해 줍니다.




그리고 Unity를 플레이하면 무기 집고 조준 및 총 쏘기, 장전 등 잘 동작하는 것을 확인할 수 있습니다.




자 이제 생명력 처리를 하는 과정의 시작입니다.




Player/PlayerHealth 스크립트를 생성하고 코딩을 시작합니다.

/// 플레이어의 생명력을 담당
/// 피격시 피격 이팩트를 표시하거나 UI 업데이트를 함.
/// 죽었을 경우 모든 동작 스크립트 동작을 멈춤.
public class PlayerHealth : HealthBase
{
    public float health = 100f;
    public float criticalHealth = 30f;
    public Transform healthHUD;
    public SoundList deathSound;
    public SoundList hitSound;
    public GameObject hurtPrefab;
    public float decayFactor = 0.8f;
    
    private float totalHealth;
    private RectTransform healthBar, placeHolderBar;
    private Text healthLabel;
    private float originalBarScale;
    private bool critical;
    
    private void Awake() {
        myAnimator = GetComponent<Animator>();
        totalHealth = health;
        
        healthBar = healthHUD.Find("HealthBar/Bar").GetComponent<RectTransform>();
        placeHolderBar = healthHUD.Find("HealthBar/Placeholder").GetComponent<RectTransform>();
        healthLabel = healthHUD.Find("HealthBar/Label").GetComponent<Text>();
        originalBarScale = healthBar.sizeDelta.x;
        healthLabel.text = "" + (int)health;
    }
    
    private void Update() {
        if (placeHolderBar.sizeDelta.x > healthBar.sizeDelta.x) {
            placeHolderBar.sizeDelta = Vector2.Lerp(placeHolderBar.sizeDelta, healthBar.sizeDelta, 2f * Time.deltaTime);
        }
    }
    
    public bool IsFullLife() {
        return Mathf.Abs(health - totalHealth) < float.Epsilon;
    }
    
    private void UpdateHealthBar() {
        healthLabel.text = "" + (int)health;
        float scaleFactor = health / totalHealth;
        healthBar.sizeDelta = new Vector2(scaleFactor * originalBarScale, healthBar.sizeDelta.y);
    }
    
    private void Kill() {
        IsDead = true;
        gameObject.layer = TagAndLayer.GetLayerByName(TagAndLayer.LayerName.Default);
        gameObject.tag = TagAndLayer.TagName.Untagged;
        healthHUD.gameObject.SetActive(false);
        healthHUD.parent.Find("WeaponHUD").gameObject.SetActive(false);
        myAnimator.SetBool(AnimatorKey.Aim, false);
        myAnimator.SetBool(AnimatorKey.Cover, false);
        myAnimator.SetFloat(AnimatorKey.Speed, 0);
        foreach (GenericBehaviour behaviour in GetComponentsInChildren<GenericBehaviour>()) {
            behaviour.enabled = false;
        }
        SoundManager.Instance.PlayOneShotEffect((int)deathSound, transform.position, 5f);
    }
    
    public override void TakeDamage(Vector3 location, Vector3 direction, float damage, Collider bodyPart = null, GameObject origin = null) {
        health -= damage;
        UpdateHealthBar();
        if (health <= 0) Kill();
        else if (health <= criticalHealth && !critical) critical = true;
        SoundManager.Instance.PlayOneShotEffect((int)hitSound, location, 1f);
    }
}



PlayerHealth Script를 Attach하고 관련 설정을 하여 줍니다.



그리고 Unity 플레이를 진행해 보면 좌하단에 HealthBar 100 이 표시되는 것을 확인할 수 있습니다.



 

 

 

 



이제 플레이어 발소리 컴포넌트 제작을 시작합니다.




PlayerFootStep 스크립트를 생성하고 코딩을 시작합니다.

/// <summary>
/// 발자국 소리를 출력.
/// </summary>
public class PlayerFootStep : MonoBehaviour
{
    public SoundList[] stepSounds;
    private Animator myAnimator;
    private int index;
    private Transform leftFoot, rightFoot;
    private float dist;
    private int groundedBool, coverBool, aimBool, crouchFloat;
    private bool grounded;
    
    public enum Foot { LEFT, RIGHT, }
    private Foot step = Foot.LEFT;
    private float oldDist, maxDist = 0;
    
    private void Awake() {
        myAnimator = GetComponent<Animator>();
        leftFoot = myAnimator.GetBonTransform(HumanBodyBones.LeftFoot);
        rightFoot = myAnimator.GetBonTransform(HumanBodyBones.RightFoot);
        groundedBool = Animator.StringToHash(AnimatorKey.Grounded);
        coverBool = Animator.StringToHash(AnimatorKey.Cover);
        aimBool = Animator.StringToHash(AnimatorKey.Aim);
        crouchFloat = Animator.StringToHash(AnimatorKey.Crouch);
    }
    
    private void PlayFootStep() {
        if (oldDist < maxDist) return;
        oldDist = maxDist = 0;
        int oldIndex = index;
        while (oldIndex == index) {
            index = Random.Range(0, stepSounds.Length - 1);
        }
        SoundManager.Instance.PlayOneShotEffect((int)stepSounds[index], transform.position, 0.2f);
    }
    
    private void Update() {
        if (!grounded && myAnimator.GetBool(groundedBool)) PlayFootStep();
        grounded = myAnimator.GetBool(groundedBool);
        float factor = 0.15;
        if (grounded && myAnimator.velocity.magnitude > 1.6f) { // 움직이고 있다면..
            oldDist = maxDist;
            switch( step) {
                case Foot.LEFT:
                    dist = leftFoot.position.y - transform.position.y;
                    maxDist = dist > maxDist ? dist : maxDist;
                    if (dist <= factor) {
                        PlayFootStep();
                        step = Foot.RIGHT;
                    }
                    break;
                case Foot.RIGHT:
                    dist = rightFoot.position.y - transform.position.y;
                    maxDist = dist > maxDist ? dist : maxDist;
                    if (dist <= factor) {
                        PlayFootStep();
                        step = Foot.LEFT;
                    }
                    break;
            }
        }
    }
}

 



PlayFootStep 스크립트를 Attach하고 StepSound를 설정해 줍니다.




Unity 플레이를 하여 재생하여 캐릭터를 움직여 보면 걸을 때마다 소리가 재생되는 것을 확인할 수 있습니다.




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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 39, 40 번을 진행합니다.




저번에 이어 사격 동작 만들기 2가 이어집니다.

코드를 따라 치는 형태를 진행하지만 Unity 실행화면도 모르고 결과를 모르니 타이핑만 치게 되는게 맞다고 하시네요..
동작하는 것을 보면 코드가 이해될 것이라고 합니다...
하지만 그랬다면 완성된 코드로 구동을 보여주며 주요 코드를 설명해주시고, 주요 코드가 빠진 프로젝트에서 주요 코드를 타이핑 해보는 방법으로 진행하셨다면 어땠을까.. 또는 기본옵션으로 동작하는 간단한 구동 코드를 먼저 하고 다양한 옵션을 하나씩 추가해본다거나 했으면 어땠을까.. 하는 마음이 드네요 ^^


public class ShootBehaviour : MonoBehaviour
{
    // 이전 코드..
    
    private void ShootWeapon(int weapon, bool firstShot = true) {
        if (!isAiming || isAimBlocked || behaviourController.GetAnimator.GetBool(reloadBool) || !weapons[weapon].Shoot(firstShot)) return;
        else {
            burstShotCount++;
            behaviourController.GetAnimator.SetTrigger(shootingTrigger);
            aimBehaviour.crossHair = shootCrossHair;
            behaviourController.GetCamScript.BounceVertical(weapons[weapon].recoilAngle);
            
            Vector3 imprecision = Random.Range(-shootErrorRate, shootErrorRate) * behaviourController.playerCamera.forward; // 약간 흔들기
            Ray ray = new Ray(behaviourController.playerCamera.position, behaviourController.playerCamera.forward + imprecision);
            RaycastHit hit = default(RaycastHit);
            if (Physics.Raycast(ray, out hit, 500f, shotMask)) {
                if (hit.collider.transform != transform) { // 내가 아니고
                    bool isOrganic = (organicMask == (organicMask | (1 << hit.transform.root.gameObject.layer)));
                    DrawShoot(weapons[weapon].gameObject, hit.point, hit.normal, hit.collider.transform, !isOrganic, !isOrganic);
                    if (hit.collider) {
                        hit.collider.SendMessageUpwards("HitCallback", new HealthBase.DamageInfo(hit.point, ray.direction, weapons[weapon].bulletDamage, hit.collider), SendMessageOptions.DontRequireReceiver);
                    }
                }
            }
            else {
                Vector3 destination = (ray.direction * 500f) - ray.origin;
                DrawShoot(weapons[weapon].gameObject, destination, Vector3.up, null, false, false);
            }
            
            SoundManager.Instance.PlayOneShotEffect((int)weapons[weapon].shotSound, gunMuzzle.position, 5f);
            GameObject gameController = GameObject.FindGameObjectWithTag(TagAndLayer.TagName.GameController);
            gameController.SendMessage("RootAlertNearBy", ray.origin, SendMessageOptions.DontRequireReceiver);
            shotInterval = originalShotInterval;
            isShotAlive = true;
        }
    }
    
    public void EndReloadWeapon() {
        behaviourController.GetAnimator.SetBool(reloadBool, false);
        weapons[activeWeapon].EndReload();
    }
    
    private void SetWeaponCrossHair(bool armed) {
        if (armed) aimBehaviour.crossHair = aimCrossHair;
        else aimBehaviour.crossHair = originalCrossHair;
    }
    
    private void ShotProgress() {
        if (shotInterval > 0.2f) {
            shotInterval -= shootRateFactor * Time.deltaTime;
            if (shotInterval <= 0.4f) {
                SetWeaponCrossHair(activeWeapon > 0);
                muzzleFlash.SetActive(false);
                if (activeWeapon > 0) {
                

               
                }
            }
        }
        else {
            isShotAlive = false;
            behaviourController.GetCamScript.BounceVertical(0);
            burstShotCount = 0;
        }
    }
    
    private void ChangeWeapon(int oldWeapon, int newWeapon) {
        if (oldWeapon > 0) {
            weapons[oldWeapon].gameObject.SetActive(false);
            gunMuzzle = null;
            weapons[oldWeapon].Toggle(false);
        }
        
        while (weapons[newWeapon] == null && newWeapon > 0) {
            newWeapon = (newWeapon + 1) % weapons.Count;
        }
        
        if (newWeapon > 0) {
            weapons[newWeapon].gameObject.SetActive(true);
            gunMuzzle = weapons[newWeapon].transform.Find("muzzle");
            weapons[newWeapon].Toggle(true);
        }
        activeWeapon = newWeapon;
        if (oldWeapon != newWeapon) {
            behaviourController.GetAnimator.SetTrigger(changeWeaponTrigger);
            behaviourController.GetAnimator.SetInteger(weaponType, weapons[newWeapon] ? (int)weapons[newWeapon].weaponType : 0);
        }
        SetWeaponCrossHair(newWeapon > 0);
    }
    



    /// <summary>
    /// 인벤토리 역할을 하게 될 함수
    /// </summary>
    public void AddWeapon(InteractiveWeapon newWeapon) {
        newWeapon.gameObject.transform.SetParent(rightHand);
        newWeapon.transform.localPosition = newWeapon.rightHandPosition;
        newWeapon.transform.localRotation = Quaternion.Euler(newWeapon.relativeRotation);
        



    }
    
    private bool CheckforBlockedAim() {
        isAimBlocked = Physics.SphereCast(transform.position + castRelativeOrigin, 0.1f, behaviourController.GetCamScript.transform.forward, out RaycastHit hit, distToHand - 0.1f);
        isAimBlocked = isAimBlocked && hit.collider.transform != transform;
        behaviourController.GetAnimator.SetBool(blockedAimBool, isAimBlocked);
        Debug.DrawRay(transform.position + castRelativeOrigin, behaviourController.GetCamScript.transform.forward * distToHand, isAimBlocked ? Color.red : Color.cyan);
        return isAimBlocked;
    }
    
    public void OnAnimatorIK(int layerIndex) {
        if (isAiming && activeWeapon > 0) {
            if (CheckforBlockedAim()) return;
            Quaternion targetRot = Quaternion.Euler(0, transform.eulerAngles.y, 0);
            targetRot *= Quaternion.Euler(initialRootRotation);
            targetRot *= Quaternion.Euler(initialHipsRotation);
            targetRot *= Quaternion.Euler(initialSpineRotation);
            behaviourController.GetAnimator.SetBoneLocalRotation(HumanBodyBones.Spine, Quaternion.Inverse(hips.rotation) * targetRot);
            
            float xcamRot = Quaternion.LookRotation(behaviourController.playerCamera.forward).eulerAngles.x;
            targetRot = Quaternion.AngleAxis(xcamRot + armsRotation, transform.right);
            if (weapons[activeWeapon] && weapons[activeWeapon].weaponType == InteractiveWeapon.WeaponType.LONG) {
                targetRot *= Quaternion.AngleAxis(9f, transform.right);
                targetRot *= Quaternion.AngleAxis(20f, transform.up);
            }
            targetRot *= spine.rotation;
            targetRot *= Quaternion.Euler(initialCheckRotation);
            behaviourController.GetAnimator.SetBoneLocalRotation(HumanBodyBones.Chest, Quaternion.Inverse(spine.rotation) * targetRot);
        }
    }
    
    private void LateUpdate() {
        if (isAiming && weapons[activeWeapon] && weapons[activeWeapon].weaponType == InteractiveWeapon.WeaponType.SHORT) {
            leftArm.localEulerAngles = leftArm.localEulerAngles + leftArmShortAim;
        }
    }
}


What is the purpose of this lecture?
Ooops. BattleRoyale and BattleGround is the same project... so should I continue?





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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 37, 38 번을 진행합니다.




생명력 컴퍼넌트와 경고 체크 컴포넌트 개발을 시작합니다.



위와 같이 Manager 폴더를 하나 만들고, DataManager, SoundManager, EffectManager를 만든 후에 각각의 Manager Script를 드래그하여 연결시켜 줍니다.




설정을 잘 확인후에 Unity 플레이를 진행해 봅니다. 무기 근처에 가니 "Pick Rifle" 글자가 잘 나오긴 하는데 Console을 보니 에러가 발생하고 있습니다.




GetKeyDown() -> GetButtonDown()으로 변경해주어야 합니다.




다시 Unity 플레이를 하여 무기를 집어봅니다...
그런데 총 모양이 다르네요 ^^; 아직 버그인건지 설정 문제인지는 모르는 상황.. ㅎㅎ
Weapon Sprite에서 "rifle_HUD"로 설정해주어서 해결됩니다.

바닥의 총이 사라지고 캐릭터가 집지 않은 건 아직 ShootBehaviour를 작성하지 않았기 때문입니다.


해당 위치에 HealthBase 스크립트를 생성하여 줍니다. 그리고 코드 작업을 시작합니다.


pubic class HealthBase : MonoBehaviour
{
    public class DamageInfo {
        public Vector3 location, direction; // 맞은 위치와 방향
        public float damage;
        public Collider bodyPart;
        public GameObject origin;
        
        public DamageInfo(Vector3 location, Vector3 direction, float damage, Collider bodyPart = null, GameObject origin = null) {
            this.location = location;
            this.direction = direction;
            this.damage = damage;
            this.bodyPart = bodyPart;
            this.origin = origin;
        }
    }
    
    [HideInInspector] public bool IsDead;
    protected Animator myAnimator;
    
    public virtual void TakeDamage(Vector3 location, Vector3 direction, float damage, Collider bodyPart = null, GameObject origin = null) {
    }
    
    public void HitCallBack(DamageInfo damageInfo) {
        TakeDamage(damageInfo.location, damageInfo.direction, damageInfo.damage, damageInfo.bodyPart, damageInfo..origin);
    }
}




TagAndLayer 스크립트에 LayerMasking 클래스를 하나 작성해 줍니다. 코딩 편의를 위하여 ㅎㅎ..



AlertChecker 스크립트를 생성하고 작성을 시작합니다.


public class AlertChecker : MonoBehaviour
{
    [Range(0,50)] public float alertRadius;
    public int extraWaves = 1;
    
    public LayerMask alertmask = TagAndLayer.LayerMasking.Enemy;
    private Vector3 current;
    private bool alert;
    
    private void Start() {
        InvokeRepeating("PingAlert", 1, 1);
    }
    
    private void AlertNearBy(Vector3 origin, Vector3 target, int wave = 0) {
        if (wave > extraWaves) return;
        Collider[] targetsInViewRadius = Physics.OverlapSphere(origin, alertRadius, alertMask);
        
        foreach (Collider obj in targetsInViewRadius) {
            obj.SendMessageUpwards("AlertCallback", target, SendMessageOptions.DontRequireReceive);
            AlertNearBy(obj.transform.position, target, wave + 1);
        }
    }
    
    public void RootAlertNewBy(Vector3 origin) {
        current = origin;
        alert = true;
    }
    
    void PingAlert() {
        if (alert) {
            alert = false;
            AlertNearBy(current, current);
        }
    }
}



사격 동작 만들기 시작입니다. 대략 500 라인 정도된다고 합니다.

/// <summary>
/// 사격 기능 : 사격이 가능한지 여부를 체크하는 기능
/// 발사 키 입력받아서 애니메이션 재생, 이펙트 생성, 충돌 체크 기능
/// UI 관련해서 십자선 표시 기능
/// 발사 속도 조정
/// 캐릭터 상체를 IK를 이용해서 조준 시점에 맞춰 회전.
/// 벽이나 충돌체 총알이 피격되었을 경우 피탄 이팩트를 생성하고
/// 인벤토리 역할. 무기를 소지하고 있는지 확인용
/// 재장전과 무기 교체 기능까지 포함하고 있음.
/// </summary>
public class ShootBehaviour : GenericBehaviour
{
    public Texture2D aimCrossHair, shootCrossHair;
    public GameObject muzzleFlash, shot, sparks;
    public Material bulletHole;
    public int MaxBulletHoles = 50;
    public float shootErrorRate = 0.01f;
    public float shootRateFactor = 1f;
    pubic float armsRotation = 8f;
    
    public LayerMask shotMask = ~(TagAndLayer.LayerMasking.IgnoreRayCast | TagAndLayer.LayerMasking.IgnoreShot | TagAndLayer.LayerMasking..ConvertInvisible | TagAndLayer.LayerMasking.Player);
    public LayerMask organicMask = TagAndLayer.LayerMasking.Player | TagAndLayer.LayerMasking.Enemy;
    // 짧은총, 피스톨 같은 총을 들었을 때 조준시 왼팔의 위치 보정.
    public Vector3 leftArmShortAim = new Vector3(-4.0f, 0.0f, 2.0f);
    
    private int activeWeapon = 0;
    // animator value
    private int weaponType;
    private int changeWeaponTrigger;
    private int shootingTrigger;
    private int aimBool, blockedAimBool, reloadBool;
    
    private List<InteractiveWeapon> weapons; /// 소지하고 있는 무기들.
    private bool isAiming, isAimBlocked;
    private Transform gunMuzzle;
    private float distToHand;
    private Vector3 castRelativeOrigin;
    
    private Dictionary<InteractiveWeapon.WeaponType, int> slotMap;
    
    private Transform hips, spine, chest, rightHand, leftArm;
    private Vector3 initialRootRotation;
    private Vector3 initialHipsRotation;
    private Vector3 initialSpineRotation;
    private Vector3 initialCheckRotation;
    
    private float shotInterval, originalShotInterval = 0.5f;
    private List<GameObject> bulletHoles;
    private int bulletHoleSlot = 0;
    private int brustShotCount = 0;
    private AimBehaviour aimBehaviour;
    private Texture2D originalCrossHair;
    private bool isShooting = false;
    private bool isChangingWeapon = false;
    private bool isShotAlive = false;
    
    void Start() {} // 각 변수들의 초기화............ Vomiting........
    


}





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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 35, 36 번을 진행합니다.



이번시간에는 무기를 집고, 던지고 등의 무기와의 Interactive 동작들을 하는 스크립트를 작성할 예정입니다.




사용하게될 Prefabs가 위와 같이 준비되어 있습니다.



그리고 드디어 전에 만들었던 Effect Tool 창에서 Effect에 대한 설정을 해줍니다.



SoundTool 설정해보다가 문제가 있어서 확인해보니 코드 변경이 필요한 부분이 있네요.
위와 같이 수정을 해줍니다 ^^;;;


SoundTool도 마찬가지로 위와 같이 설정을 해줍니다.



Contents 폴더를 생성하고 InteractiveWeapon 스크립트를 생성합니다.
Player 폴더 밑에 ShootBehaviour 스크립트를 생성합니다.



public class ShootBehaviour : GenericBehaviour
{
    /// <summary>
    /// 인벤토리 역할을 하게 될 함수.
    /// </summary>
    public void AddWeapon(InteractiveWeapon weapon) {
    }
}



/// <summary>
/// 충돌체를 생성해서 무기를 주을 수 있도록 한다.
/// 루팅했으면 충돌체는 제거.
/// 무기를 다시 버릴 수도 있어야 하며, 충돌체를 다시 붙여줍니다.
/// 관련해서 UI도 컨트롤할 수 있어야 하고 ShootBehaviour에 주은 무기를 넣어주게 됩니다.
/// </summary>
public class InteractiveWeapon : MonoBehaviour
{
    public string label_weaponName; // 무기이름.
    public SoundList shotSound, reloadSound, pickSound, dropSound, noBulletSound;
    public Sprite weaponSprite;
    public Vector3 rightHandPosition; // 플레이어 오른손 보정 위치
    public Vector3 relativeRotation; // 플레이어 맞춘 보정을 위한 회전값.
    public float bulletDamage = 10f;
    public float recoilAngle; // 반동
    public enum WeaponType { NONE, SHORT, LONG, }
    public enum WeaponMode { SEMI, BURST, AUTO, }
    public WeaponType weaponType = WeaponType.NONE;
    public WeaponMode weaponMode = WeaponMode.SEMI;
    public int burstSize = 1;
    
    public int currentMagCapacity, totalBullets; // 현재 탄창 양과 소지하고 있는 전체 총알량
    private int fullMag, maxBullets; // 재장전시 꽉 채우는 탄의 양과 한번에 채울 수 있는 최대 총알수
    private GameObject player, gameController;
    private ShootBehaviour playerInventory;
    private BoxCollider weaponCollider;
    private SphereCollider interactiveRadius; // 무기 감지 반경.
    private Rigidbody weaponRigidbody;
    private bool pickable;
    // UI
    public GameObject screenHUD;
    public WeaponUIManager weaponHUD;
    private Transform pickHUD;
    public Text pickupHUD_Label;
    
    public Transform muzzleTransform;
    
    private void Awake() {
        gameObject.name = label_weaponName;
        gameObject.layer = LayerMask.NameToLayer(TagAndLayer.LayerName.IgnoreRayCast);
        foreach (Transform tr in transform)
            tr.gameObject.layer = LayerMask.NameToLayer(TagAndLayer.LayerName.IgnoreRayCast);
        player = GameObject.FindGameObjectWithTag(TagAndLayer.TagName.Player);
        playerInventory = player.GetComponent<ShootBehaviour>();
        gameController = GameObject.FindGameObjectWithTag(TagAndLayer.TagName.GameController);
        
        if (weaponHUD == null) {
            if (screenHUD == null) screenHUD = GameObject.Find("ScreenHUD");
            weaponHUD = screenHUD.GetComponent<WeaponUIManager>();
        }
        if (pickHUD == null) pickHUD = gameController.transform.Find("PickupHUD");
        
        // 인터랙션을 위한 충돌체 설정
        weaponCollider = transform.GetChild(0).gameObject.AddComponent<BoxCollider>(); // Child에는 Box, 본인에게는 Sphere를 붙이려고..
        CreateInteractiveRadius(weaponCollider.center);
        weaponRigidbody = gameObject.AddComponent<Rigidbody>();
        
        if (weaponType == WeaponType.NONE) weaponType = WeaponType.SHORT;
        fullMag = currentMagCapacity;
        maxBullets = totalBullets;
        pickHUD.gameObject.SetActive(false);
        if (muzzleTransform == null) muzzleTransform = transform.Find("muzzle");
    }
    
    private void CreateInteractiveRadius(Vector3 center) {
        interactiveRadius = gameObject.AddComponent<SphereCollider>();
        interactiveRadius.center = center;
        interactiveRadius.radius = 1;
        interactiveRadius.isTrigger = true;
    }
    
    private void TogglePickHUD(bool toggle) {
        pickHUD.gameObject.SetActive(toggle);
        if (toggle) {
            pickHUD.position = transform.position + Vector3.up * 0.5f;
            Vector3 direction = player.GetComponent<BehaviourController>().playerCamera.forward;
            direction.y = 0;
            pickHUD.rotation = Quaternion.LookRotation(direction);
            pickupHUD_Label.text = "Pick " + gameObject.name;
        }
    }
    
    private void UpdateHUD() {
        weaponHUD.UpdateWeaponHUD(weaponSprite, currentMagCapacity, fullMag, totalBullets);
    }
    
    public void Toggle(bool active) {
        if (active) SoundManager.Instance.PlayOneShotEffect((int)pickSound, transform.position, 0.5f);
        weaponHUD.Toggle(active);
        UpdateHUD();
    }
    
    private void Update() {
        if (pickable && Input.GetKeyDown(ButtonName.Pick)) {
            // disable physics weapon
            weaponRigidbody.isKinematic = true;
            weaponCollider.enabled = false;
            playerInventory.AddWeapon(this);
            Destory(interactiveRadius);
            Toggle(true);
            pickable = false;
            TogglePickHUD(false);
        }
    }
    
    private void OnCollisionEnter(Collision collision) {
        if (collision.collider.gameObject != player && Vector3.Distance(transform.position, player.transform.position) <= 5f) {
            SoundManager.Instance.PlayOneShotEffect((int)dropSound, transform.position, 0.5f);
        }
    }
   
    private void OnTriggerExit(Collider other) {
        if (other.gameObject == player) {
            pickable = false;
            TogglePickHUD(false);
        }
    }
    
    private void OnTriggerStay(Collider other) {
        if (other.gameObject == player && playerInventory && playerInventory.isActiveAndEnabled) {
            pickable = true;
            TogglePickHUD(true);
        }
    }
    
    public void Drop() {
        gameObject.SetActive(true);
        transform.position += Vector3.up;
        weaponRigidbody.isKinematic = false;
        transform.parent = null;
        CreateInteractiveRadius(weaponCollider.center);
        weaponCollider.enabled = true;
        weaponHUD.Toggle(false);
    }
    
    public bool StartReload() {
        if (currentMagCapacity == fullMag || totalBullets == 0) return false;
        else if (totalBullets < fullMag - currentMagCapacity) {
            currentMagCapacity += totalBullets;
            totalBullets = 0;
        }
        else {
            totalBullets -= fullMag - currentMagCapacity;
            currentMagCapacity = fullMag;
        }
        return true;
    }
    
    public void EndReload() {
        UpdateHUD();
    }
    
    public bool Shoot(bool firstShot = true) {
        if (currentMagCapacity > 0) {
            currentMagCapacity--;
            UpdateHUD();
            return true;
        }
        if (firstShot && noBulletSound != SoundList.None) {
            SoundManager.Instance.PlayOneShotEffect((int)noBulletSound, muzzleTransform.position, 5f);
        }
        return false;
    }
    
    public void ResetBullet() {
        currentMagCapacity = fullMag;
        totalBullets = maxBullets;
    }
}




이제 Unity에서 Interactive Weapon을 위와 같이 설정해 줍니다.



Unity 실행화면 입니다. Interactive가 되지 않는 버그가!? ㅎㅎ 좀더 세팅이 필요하기에 계속 이어진다고 합니다.




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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 33, 34 번을 진행합니다.



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


public class AimBehaviour : MonoBehaviour
{
    // 변수들 및 Start()
    
    // 카메라에 따라 플레이어를 올바른 방향으로 회전.
    void Rotating() {
        Vector3 forward = behaviourController.playerCamera.TransformDirection(Vector3.forward);
        forward.y = 0.0f;
        forward = forward.normalized;
        
        Quaternion targetRotation = Quaternion.Euler(0f, behaviourController.GetCamScript.GetH, 0.0f);
        float minSpeed = Quaternion.Angle(transform.rotation, targetRotation) * aimTurnSmoothing);
        if (peekCorner) {
            // 조준중 일때 플레이어 상체만 살짝 기울여 주기 위함
            myTransform.rotation = Quaternion.LookRotation(-behaviourController.GetLastDirection()); // IK(Inverse Kinematics)를 검색!
            targetRotation *= Quaternion.Euler(initialRootRotation);
            targetRotation *= Quaternion.Euler(initialHipRotation);
            targetRotation *= Quaternion.Euler(initialSpineRotation);
            Transform spine = behaviourController.GetAnimator.GetBoneTransform(HumanBodyBones.Spine);
            spine.rotation = targetRotation;
        }
        else {
            behaviourController.SetLastDirection(forward);
            myTransform.rotation = Quaternion.Slerp(myTransform.rotation, targetRotation, minSpeed * Time.deltaTime);
        }
    }
    
    // 조준중일때를 관리하는 함수
    void AimManagement() {
        Rotating();
    }
    
    private IEnumerator ToggleAimOn() {
        yield return new WaitForSeconds(0.05f);
        // 조준이 불가능한 상태일 때에 대한 예외처리.
        if (behaviourController.GetTempLockStatus(behaviourCode) || behaviourController.IsOverriding(this)) {
            yield return false;
        }
        else {
            aim = true;
            int signal = 1;
            if (peekCorner) {
                signal = (int)Mathf.Sign(behaviourController.GetH);
            }
            aimCamOffset.x = Mathf.Abs(aimCamOffset.x) * signal);
            aimPivotOffset.x = Mathf.Abs(aimPivotOffset.x) * signal);
            yield return new WaitForSeconds(0.1f);
            behaviourController.GetAnimator.SetFloat(speedFloat, 0.0f);
            behaviourController.OverrideWithBehaviour(this);
        }
    }
    
    private IEnumerator ToggleAimOff() {
        aim = false;
        yield return new WaitForSeconds(0.3f);
        behaviourController.GetCamScript.ResetTargetOffsets();
        behaviourController.GetCamScript.ResetMaxVerticalAngle();
        yield return new WaitForSeconds(0.1f);
        behaviourController.RevokeOverridingBehaviour(this);
    }
    
    public override void LocalFixedUpdate() {
        if (aim) {
            behaviourController.GetCamScript.SetTargetOffset(aimPivotOffset, aimCamOffset);
        }
    }
    
    pubic override void LocalLateUpdate() {
        AimManagement();
    }
    
    private void Update() {
        peekCorner = behaviourController.GetAnimator.GetBool(cornerBool);
        
        if (Input.GetAxisRaw(ButtonName.Aim) != 0 && !aim) {
            StartCoroutine(ToggleAimOn());
        }
        else if (aim && Input.GetAxisRaw(ButtonName.Aim) == 0) {
            StartCoroutine(ToggleAimOff());
        }
        // 조준중일때는 달리기를 하지 않도록..
        canSprint = !aim;
        if (aim && Input.GetButtonDown(ButtonName.Shoulder) && !peekCorner) {
            aimCamOffset.x = aimCamOffset.x * (-1);
            aimPivotOffset.x = aimPivotOffset.x * (-1);
        }
        behaviourController.GetAnimator.SetBool(aimBool, aim);
    }
    
    private void OnGUI() {
        if (crossHair != null) {
            float length = behaviourController.GetCamScript.GetCurrentPivotMagnitude(aimPivotOffset);
            if (length < 0.05f) {
                GUI.DrawTexture(new Rect(Screen.width * 0.5f - (crossHair.width * 0.5f), Screen.height * 0.5f - (crossHair.height * 0.5f), crossHair.width, crossHair.height), crossHair);
            }
        }
    }
}




Unity에서 "Cross Hair"를 설정해줍니다.

 

 



Unity에서 플레이를 하여 마우스 우클릭을 하면 위 화면과 같이 조준점이 나타나는 것을 확인할 수 있습니다.
물론 현재는 총을 가지고 있지 않아도 무조건 Cross hair가 나타나는 상황으로 무기를 가진 상태에서만 조준이 되도록 처리를 해주어야 하겠지요.

여기까지 캐릭터의 움직임과 조준 등에 대한 처리가 마무리되었습니다.





 



이제 무기를 줍고 총을 쏘고 등을 개발을 해야하는데 하나씩 진행을 해나아가게 됩니다.

 

 



자 우선 UI 폴더를 만들고, WeaponUIManager 스크립트를 생성하고 코드 작업을 시작합니다!!!!!


/// <summary>
/// 무기를 획득하면 획득한 무기를 UI를 통해 보여주고
/// 현재 잔탄량과 전체 소지할 수 있는 총알량을 출력
/// </summary>
public class WeaponUIManager : MonoBehaviour
{
    public Color bulletColor = Color.white;
    public Color emptyBulletColor = Color.black;
    private Color noBulletColor; // 투명하게 색깔표시.
    
    [SerializeField] private Image weaponHUD;
    [SerializeField] private GameObject bulletMag;
    [SerializeField] private Text totalBulletsHUD;
    
    void Start() {
        noBulletColor = new Color(0f, 0f, 0f, 0f);
        if (weaponHUD == null) weaponHUD = transform.Find("WeaponHUD/Weapon").GetComponent<Image>();
        if (bulletMag == null) bulletMag = transform.Find("WeaponHUD/Data/Mag").gameObject;
        if (totalBulletsHUD == null) totalBulletsHUD = transform.Find("WeaponHUD/Data/bulletAmount").GetComponent<Text>();
        Toggle(false); // 시작시 탄창 UI 숨기기.
    }
    
    public void Toggle(bool active) {
        weaponHUD.transform.parent.gameObject.SetActive(active);
    }
    
    public void UpdateWeaponHUD(Sprite weaponSprite, int bulletLeft, int fullMag, int ExtraBullets) {
        if (weaponSprite != null && weaponHUD.sprite != weaponSprite) {
            weaponHUD.sprite = weaponSprite;
            weaponHUD.type = Image.Type.Filled;
            weaponHUD.fillMethod = Image.FillMethod.Horizontal;
        }
        int bulletCount = 0;
        foreach (Transform bullet in bulletMag.transform) {
            if (bulletCount < bulletLeft) bullet.GetComponent<Image>().color = bulletColor; // 잔탄..
            else if (bulletCount >= fullMag) bullet.GetComponent<Image>().color = noBulletColor; // 넘치는탄..
            else bullet.GetComponent<Image>().color = emptyBulletColor; // 사용한탄..
            bulletCount++;
        }
        totalBulletsHUD.text = bulletLeft + "/" + ExtraBullets;
    }
}




우선 ScreenHUD에 WeaponUIManager 스크립트를 연결해주고,
각 변수에 위와 같이 드래그하여 설정해 줍니다.

다음 진행할 것에 대한 설명이 이어집니다.

수류탄에 의한 데미지를 예로 든다면,
로컬게임에서는 수류탄 반경내 데미지 받는 GameObject를 찾아내고 각각 데미지를 받는 코드를 작성하고,
Server/Client 구조에서는 서버에서 수류탄 반경내 해당하는 Client를 찾아내어 데이터 전송을 하면 Client에서는 단순히 데미지 처리를 하는 형태로 제작합니다.
총소리가 발생하면 특정 영역내 GameObject내에 Alert를 알려주는 시스템도 진행하게 됩니다.





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

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

 

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

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

www.fastcampus.co.kr

 

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

04. 배틀로얄 - 31, 32 번을 진행합니다.




직교 함수 구하기에 대한 설명입니다.
아래 Rotating() 함수에서 right를 구할 때 사용하는 내용입니다.


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


public class MoveBehaviour : GenericBehaviour
{
    // 변수선언과 Start() 함수 구현..
    
    // 3D 캐릭터의 이동은 우선 이동방향으로 회전하고 이동..
    Vector3 Rotating(float horizontal, float vertical) {
        Vector3 forward = behaviourController.playerCamera.TransformDirection(Vector3.forward);
        forward.y = 0.0f;
        forward = forward.normalized; // Normal Vector -> 단일 Vector
        
        Vector3 right = new Vector3(forward.z, 0.0f, -forward.x);
        Vector3 targetDirection = Vector3.zero;
        targetDirection = forward * vertical + right * horizontal;
        
        if (behaviourController.IsMoving() && targetDirection != Vector3.zero) {
            Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
            Quaternion newRotation = Quaternion.Slerp(behaviourController.GetRigidbody.rotation, targetRotation, behaviourController.turnSmoothing);
            behaviourController.GetRigidbody.MoveRotation(newRotation);
            behaviourController.SetLastDirection(targetDirection);
        }
        if (!(Mathf.Abs(horizontal) > 0.9f || Mathf.Abs(vertical) > 0.9f)) {
            behaviourController.Repositioning();
        }
        return targetDirection;
    }
    
    private void RemoveVerticalVelocity() {
        Vector3 horizontalVelocity = behaviourController.GetRigidbody.velocity;
        horizontalVelocity.y = 0.0f;
        behaviourController.GetRigidbody.velocity = horizontalVelocity;
    }
    
    void MovementManagement(float horizontal, float vertical) {
        if (behaviourController.IsGrounded()) {
            behaviourController.GetRigidbody.useGravity = true;
        }
        else if (!behaviourController.GetAnimator.GetBool(jumpBool) && behaviourController.GetRigidbody.velocity.y > 0) {
            RemoveVerticalVelocity();
        }
        
        Rotating(horizontal, vertical);
        Vector2 dir = new Vector2(horizontal, vertical);
        speed = Vector2.ClampMagnitude(dir, 1f).magnitude;
        speedSeeker += Input.GetAxis("Mouse ScrollWheel");
        speedSeeker = Mathf.Clamp(speedSeeker, walkSpeed, runSpeed);
        speed *= speedSeeker;
        if (behaviourController.IsSprinting())
            speed = sprintSpeed;
        behaviourController.GetAnimator.SetFloat(speedFloat, speed, speedDampTime, Time.deltaTime);
    }
    
    private void OnCollisionStay(Collision collision) {
        isColliding = true;
        if (behaviourController.IsCurrentBehaviour(GetBehaviourCode) && collision.GetContact(0).normal.y <= 0.1f) {
            float vel = behaviourController.GetAnimator.velocity.magnitude;
            Vector3 targetMove = Vector3.ProjectOnPlane(myTransform.forward, collision.GetContact(0).normal).normalized * vel;
            behaviourController.GetRigidbody.AddForce(targetMove, ForceMove.VelocityChange);
        }
    }
    
    private void OnCollisionExit(Collision collision) {
        isColliding = false;
    }
    

타이핑 따라하기 ㅜ.,ㅡ;

    private void Update() {
        if (!jump && Input.GetButtonDown(ButtonName.Jump) && behaviourController.IsCurrentBehaviour(behaviourCode) && !behaviourCode.IsOverriding())
            jump = true;
    }
    
    public override void LocalFixedUpdate() {
        MovementManagement(behaviourController.GetH, behaviourController.GetV);
        JumpManagement();
    }
}

따라하다..토..할..뻔.. ^^; 수강타겟층이 애매한것 아닐까 싶어요.. 초중타겟으로는 설명할게 너무 많고 중급 이상 타겟으로 하자니 일일이 설명할 게 없는데 초보자 교육이 아니다보니 어쩔 수 없이 코드 따라하기가 되버리는 듯합니다..
물론 코드다 다 말해준다 하지만... 이럴거면 준비된 코드를 가지고 설명을 자세히 해주시는게 좋을 듯도 ㅠ




Freeze Rotation [v]X [v]Y [v]Z를 하여 여기저기로 이동되는 것을 막아줍니다.
유니티 플레이를 해보면 땅에서 열심히 뛰어가는 캐릭터를 볼 수가 있습니다 ^^~




Aim Behaviour 제작을 시작합니다.



우선 이제 실제로 게임을 플레이할 Scene을 불러와서 여기에서 플레이어 및 카메라를 세팅해 줍니다.
그리고 플레이를 해보면 걷고 뛰도 잘 이동하는 것을 볼 수 있습니다.




AimBehaviour 스크립트를 생성하고 코딩을 ㅠ 시작합니다.


/// <summary>
/// 마우스 오른쪽 버튼으로 조준. 다른 동작을 대체해서 동작하게 됩니다.
/// 마우스 휠 버튼으로 좌우 카메라 변경
/// 벽의 모서리에서 조준할 때 상체를 살짝 기울여주는 기능.
/// </summary>
public class AimBehaviour : MonoBehaviour
{

}



위에서 사용되는 Bone 정보를 볼 수 있는 Unity 화면입니다. (초반에 설명했지만 재확인 ㅎ)





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

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

 

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

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

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 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