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

 

+ Recent posts