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

 

+ Recent posts