Unity/수업내용

Unity 3D 21.04.15.수업내용

HappyFrog 2021. 4. 15. 14:49

어제 복습하던 것을 이어한다.

============================================================================

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

public class PlayerMovement : MonoBehaviour
{
    Animator animator;
    AudioSource playerAudio;
    Rigidbody playerRigid;

    private float rotateSpeed = 1000f;

    // Start is called before the first frame update
    void Start()
    {
        this.animator = GetComponent<Animator>();
        this.playerAudio = GetComponent<AudioSource>();
        this.playerRigid = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        if (!GameManager.instance.isOver && GameManager.instance.isRotate)
        {
            Rotate();
            Debug.LogFormat("Rotate Activated");
        }
    }

    public void Rotate()
    {
        float turn = this.playerRigid.rotation.y * this.rotateSpeed * Time.deltaTime;
        
        if(GameManager.instance.isRight && GameManager.instance.isLeft)
        {
            this.playerRigid.rotation = this.playerRigid.rotation;            
        }

        if (GameManager.instance.isRight)
        {
            this.transform.Rotate(0, rotateSpeed * Time.deltaTime, 0f);
            
            Debug.LogFormat("Right");
        }
        if (GameManager.instance.isLeft)
        {
            this.transform.Rotate(0, -rotateSpeed * Time.deltaTime, 0f);
            Debug.LogFormat("Left");
        }

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

public class GameManager : MonoBehaviour
{
    public bool isRotate;
    public bool isOver;
    public bool isRight;
    public bool isLeft;

    GameObject player;
    PlayerMovement playerMove;

    public static GameManager instance
    {
        get
        {
            if (m_instance == null)
                m_instance = FindObjectOfType<GameManager>();
            return m_instance;
        }
    }
        

    private static GameManager m_instance;

    private void Awake()
    {
        if(instance != this)
        {
            Destroy(this.gameObject);
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        this.player = GameObject.Find("Player");
        this.playerMove = this.player.GetComponent<PlayerMovement>();
    }

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

    public void RightRotate()
    {
        this.isRight = true;
        this.playerMove.Rotate();
    }
    public void RightOff()
    {
        this.isRight = false;
    }

    public void LeftRotate()
    {
        this.isLeft = true;
        this.playerMove.Rotate();
    }
    public void LeftOff()
    {
        this.isLeft = false;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class LRotateButton : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventTrigger eventTriggers = gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry entry_PointerDown = new EventTrigger.Entry();
        entry_PointerDown.eventID = EventTriggerType.PointerDown;
        entry_PointerDown.callback.AddListener((data) => {
            GameManager.instance.isRotate = true;
            GameManager.instance.isLeft = true;
            Debug.LogFormat("LeftBtn Pressed");
        });
        eventTriggers.triggers.Add(entry_PointerDown);

        EventTrigger.Entry entry_PointerUp = new EventTrigger.Entry();
        entry_PointerUp.eventID = EventTriggerType.PointerUp;
        entry_PointerUp.callback.AddListener((data) =>
        {
            GameManager.instance.isRotate = false;
            GameManager.instance.isLeft = false;
            Debug.LogFormat("LeftBtn Released");
        });
        eventTriggers.triggers.Add(entry_PointerUp);
    }

    // Update is called once per frame
    void Update()
    {

    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI : MonoBehaviour
{
    public Button rotateButton;
    public Text ammoText;
    public Text scoreText;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class RRotateButton : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventTrigger eventTriggers = gameObject.AddComponent<EventTrigger>();

        EventTrigger.Entry entry_PointerDown = new EventTrigger.Entry();
        entry_PointerDown.eventID = EventTriggerType.PointerDown;
        entry_PointerDown.callback.AddListener((data) => {
            GameManager.instance.isRotate = true;
            GameManager.instance.isRight = true;
            Debug.LogFormat("RightBtn Pressed");
            });
        eventTriggers.triggers.Add(entry_PointerDown);

        EventTrigger.Entry entry_PointerUp = new EventTrigger.Entry();
        entry_PointerUp.eventID = EventTriggerType.PointerUp;
        entry_PointerUp.callback.AddListener((data) => {
            GameManager.instance.isRotate = false;
            GameManager.instance.isRight = false;
            Debug.LogFormat("RightBtn Released");
        });
        eventTriggers.triggers.Add(entry_PointerUp);
    }

    // Update is called once per frame
    void Update()
    {

    }
}

1.인게임에서 버튼이 안 눌렸는데 버튼에 들어가있는 Text가 ray를 받으면 button은 가려져서 인식을 못한다고 한다. Text의 RaycastTarget을 주의하자

 

2.PlayerMovement에서 Rotate을 this.playerRigid.rotate *= Quaternion.Euler(0, turn, 0); 이런식으로 했는데

this.transform.Ratate(0, turn*Time.deltaTime, 0)하니 깔끔하게 해결됐다.

============================================================================

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

public class PlayerShoot : MonoBehaviour
{
    public Gun gun;
    public Transform gunPivot;
    public Transform leftHandMount;
    public Transform rightHandMount;
    Animator playerAnimator;
    // Start is called before the first frame update
    void Start()
    {
        this.playerAnimator = GetComponent<Animator>();
        if (Input.GetMouseButtonDown(0))
        {
            Fire();
        }
    }

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

    private void Fire()
    {

    }

    private void OnAnimatorIK(int layerIndex)
    {
        this.gunPivot.position = this.playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, this.rightHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, this.rightHandMount.rotation);                
    }
}

Gunpivot은 EmptyObject로 만들어서 대충 플레이어 손 위치쯤에 맞게 일단 조정해둔다.

어차피 코드로 IK를 붙여 위치를 맞출것이기 때문이다.

Gunpivot에 Gun을 넣어서 pivot내에서 gun의 위치를 갖게 만들고 IK코드로 GunPivot의 위치를 아바타 IK의 오른쪽 팔꿈치로 걸어둔 뒤에 그 아래에서 애니메이션의 각도와 위치를 정해주고

정해둔 위치와 각도들을 leftHandMount와 rightHandMount포지션, 로테이션에 각각 연결해준다

leftHandMount와 rightHandMount는 Gun 내의 위치값을 나타내는 빈 오브젝트 들이다.

또한 아바타의 IKPass버튼을 체크해주어야 하며 IK를 1로 만들어줘야 온전히 아바타의 애니메이션이 오브젝트와 연동되어 실행된다.

유니티 - 매뉴얼: 역운동학(Inverse Kinematics) (unity3d.com)

 

유니티 - 매뉴얼: 역운동학(Inverse Kinematics)

타겟 매칭(Target Matching) 루트 모션 - 작업 방법 역운동학(Inverse Kinematics) 대부분의 애니메이션은 스켈레톤에서 미리 정해진 값에 조인트를 변경하여 회전하는 것으로 실현됩니다. 자식 조인트의

docs.unity3d.com

============================================================================

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

public class Gun : MonoBehaviour
{
    public LineRenderer shootEffect;
    public ParticleSystem muzzleEffect;
    public ParticleSystem shellEjection;
    public Transform firePosition;

    float cycle = 0.8f;
    float timeBetFire;
    float lastTimeFire;
    int magAmmo;
    int magCapacity = 100;
    int remainAmmo;
    float attackRange;
    bool isReload;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    void OnEnable()
    {
        this.magAmmo = this.magCapacity;
    }

    // Update is called once per frame
    void Update()
    {

    }

    public void Fire()
    {
        StartCoroutine(OnFire());
        Debug.LogFormat("Gun.Fire() 작동함");
    }

    private IEnumerator OnFire()
    {
        while (true)
        {
            this.muzzleEffect.Play();
            this.shellEjection.Play();

            yield return new WaitForSeconds(this.cycle);
        }        
    }

    public void Shot()
    {
        /*this.shootEffect.SetPosition()*/
    }

    public bool Reload()
    {
        if (this.magAmmo < this.magCapacity || this.magAmmo <= 0 || this.remainAmmo > 0)
        {
            this.isReload = true;
        }

        return false;
    }

    public IEnumerator ReloadRoutine()
    {

        if (this.isReload)
        {
            yield return new WaitForSeconds(0.8f);
            int ammoToFill = this.magCapacity - this.magAmmo;

            if (this.remainAmmo < ammoToFill)
            {
                ammoToFill = this.remainAmmo;
                this.remainAmmo -= ammoToFill;
            }

            this.magAmmo += ammoToFill;
        }
    }

    public void OnDamage()
    {

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

public class PlayerShoot : MonoBehaviour
{
    public Gun gun;
    public Transform gunPivot;
    public Transform leftHandMount;
    public Transform rightHandMount;
    Animator playerAnimator;
    

    // Start is called before the first frame update
    void Start()
    {
        this.playerAnimator = GetComponent<Animator>();
    }

    private void FixedUpdate()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            Fire();
            Debug.LogFormat("PlayerShoot 작동함");
        }
    }

    private void Fire()
    {
        this.gun.Fire();
        Debug.LogFormat("PlayerShoot.Fire() 작동함");
    }

    public void Reload()
    {
        if (this.gun.Reload())
        {
            this.playerAnimator.SetTrigger("Reload");
        }
    }

    private void OnAnimatorIK(int layerIndex)
    {
        this.gunPivot.position = this.playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, this.rightHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, this.rightHandMount.rotation);
    }
}

가져온 소스로 총구에서 화염이 나오게 하고 탄피가 튀게 하는 파티클을 작동시켰다. Coroutine을 사용했는데, 중간에 이펙트가 안나오는 경우가 있어서 원인을 찾아봤더니 Coroutine메서드를 작성하고 StartCoroutine으로 불러오지 않아서 작동하지 않았던 것이었다.

============================================================================

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

public class Gun : MonoBehaviour
{
    public LineRenderer line;
    public ParticleSystem muzzleEffect;
    public ParticleSystem shellEjection;
    public Transform fireTransform;

    float cycle = 0.8f;
    float timeBetFire;
    float lastTimeFire;
    int magAmmo;
    int magCapacity = 100;
    int remainAmmo;
    float attackRange = 20f;
    bool isReload;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    void OnEnable()
    {
        this.magAmmo = this.magCapacity;
    }

    private void Awake()
    {
        this.line.positionCount = 2;
    }

    // Update is called once per frame
    void Update()
    {

    }

    public void Fire()
    {
        
        StartCoroutine(ShotEffect());

        Debug.LogFormat("Gun.Fire() 작동함");
    }

    private IEnumerator ShotEffect()
    {

        this.muzzleEffect.Play();
        this.shellEjection.Play();

        this.line.SetPosition(0, this.fireTransform.position);
        var pos = this.fireTransform.position;
        pos.z = this.fireTransform.forward.z * this.attackRange;
        this.line.SetPosition(1, pos);
        
        this.line.enabled = true;
        yield return new WaitForSeconds(0.03f);
        this.line.enabled = false;
    }

    public void Shot()
    {
        /*this.shootEffect.SetPosition()*/
    }

    public bool Reload()
    {
        if (this.magAmmo < this.magCapacity || this.magAmmo <= 0 || this.remainAmmo > 0)
        {
            this.isReload = true;
        }

        return false;
    }

    public IEnumerator ReloadRoutine()
    {

        if (this.isReload)
        {
            yield return new WaitForSeconds(0.8f);
            int ammoToFill = this.magCapacity - this.magAmmo;

            if (this.remainAmmo < ammoToFill)
            {
                ammoToFill = this.remainAmmo;
                this.remainAmmo -= ammoToFill;
            }

            this.magAmmo += ammoToFill;
        }
    }

    public void OnDamage()
    {

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

public class PlayerShoot : MonoBehaviour
{
    public Gun gun;
    public Transform gunPivot;
    public Transform leftHandMount;
    public Transform rightHandMount;
    Animator playerAnimator;
    

    // Start is called before the first frame update
    void Start()
    {
        this.playerAnimator = GetComponent<Animator>();
      
    }

    private void FixedUpdate()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(1))
        {
            Fire();
            Debug.LogFormat("PlayerShoot 작동함");
        }
    }

    private void Fire()
    {
        this.gun.Fire();
        Debug.LogFormat("PlayerShoot.Fire() 작동함");
    }

    public void Reload()
    {
        if (this.gun.Reload())
        {
            this.playerAnimator.SetTrigger("Reload");
        }
    }

    private void OnAnimatorIK(int layerIndex)
    {
        this.gunPivot.position = this.playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, this.rightHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, this.rightHandMount.rotation);
    }
}

공격버튼이 좌클릭일때 버튼을 누르면 회전과 사격이 동시에 발생해서 우클릭으로 바꿨다.

총알의 LineRenderer가 총구에서 시작해서 자꾸 바닥으로 고꾸라지는 선을 그렸는데 그 원인이 var pos를 정할때 fireTransform의 localPosition을 가져와서 그런것이었다. 월드좌표를 받기위해 fireTransform.position으로 고쳐쓰니 정상적으로 작동했다.

++그리고 LineRenderer의 size는 배열의 크기를 나타낸다.

============================================================================

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

public class Gun : MonoBehaviour
{
    public UI ui;
    public LineRenderer line;
    public ParticleSystem muzzleEffect;
    public ParticleSystem shellEjection;
    public Transform fireTransform;
    public int magAmmo;    
        
    float timeBetFire = 0.08f;
    float lastTimeFire;
    int magCapacity = 50;
    int remainAmmo = 250;
    float attackRange = 20f;
    bool isReady = true;
    bool isReloading = false;
    
    
    // Start is called before the first frame update
    void Start()
    {

    }

    void OnEnable()
    {
        this.magAmmo = this.magCapacity;
    }

    private void Awake()
    {
        this.line.positionCount = 2;
        
    }

    // Update is called once per frame
    void Update()
    {
        this.ui.ammoText.text = this.magAmmo + "/" + this.remainAmmo;
    }

    public void Fire()
    {
        if (this.magAmmo > 0)
        {
            if (Time.time > this.lastTimeFire + this.timeBetFire)
            {
                this.lastTimeFire = Time.time;
                StartCoroutine(ShotEffect());

                this.magAmmo--;
            }
        }
    }

    private IEnumerator ShotEffect()
    {
        this.muzzleEffect.Play();
        this.shellEjection.Play();

        this.line.SetPosition(0, this.fireTransform.position);
        var pos = this.fireTransform.position;
        pos.z = this.fireTransform.forward.z * this.attackRange;
        this.line.SetPosition(1, pos);

        this.line.enabled = true;
        yield return new WaitForSeconds(0.03f);
        this.line.enabled = false;
    }

    public void Shot()
    {
        /*this.shootEffect.SetPosition()*/
    }

    public bool IsReload()
    {
        if (this.magAmmo < this.magCapacity && this.remainAmmo > 0)
        {            
            return true;
        }
        else
        {
            return false;
        }        
    }

    public void Reload()
    {
        this.isReady = false;
        this.isReloading = true;
        StartCoroutine(ReloadRoutine());
    }

    public IEnumerator ReloadRoutine()
    {
        int ammoToFill = this.magCapacity - this.magAmmo;

        if (this.remainAmmo < ammoToFill)
        {
            ammoToFill = this.remainAmmo;            
        }
        this.remainAmmo -= ammoToFill;
        yield return new WaitForSeconds(0.8f);
        this.magAmmo += ammoToFill;
        this.isReady = true;
        this.isReloading = false;
    }

    public int GetAmmo()
    {
        return this.magAmmo;
    }

    public int GetRemains()
    {
        return this.remainAmmo;
    }

    public void OnDamage()
    {

    }

    public bool IsReloading()
    {
        return this.isReloading;
    }

    public bool IsReady()
    {
        return this.isReady;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerShoot : MonoBehaviour
{
    public Gun gun;
    public Transform gunPivot;
    public Transform leftHandMount;
    public Transform rightHandMount;
    Animator playerAnimator;

    // Start is called before the first frame update
    void Start()
    {
        this.playerAnimator = GetComponent<Animator>();
        
    }

    private void FixedUpdate()
    {
        
        if (this.gun.IsReady() && Input.GetMouseButton(1))
        {
            this.gun.Fire();

        }

        if (!this.gun.IsReloading() && Input.GetKeyDown(KeyCode.R))
        {            
            Reload();
        }
    }

    // Update is called once per frame
    void Update()
    {

    }   

    public void Reload()
    {
        if (this.gun.IsReload())
        {
            this.playerAnimator.SetTrigger("Reload");
            this.gun.Reload();
            
        }       
    }

    private void OnAnimatorIK(int layerIndex)
    {
        this.gunPivot.position = this.playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);

        this.playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1);
        this.playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1);

        this.playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, this.rightHandMount.position);
        this.playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, this.rightHandMount.rotation);
    }
}

재장전이 중복되는 문제와 재장전 중에 발사가 가능한 문제가 있었는데 bool타입으로 장전중인 상태와 쏠 수 있는 상태를 추가하여 PlayerShoot에서 Gun의 상태에 따라 발사가 가능/불가능 하게 만들었고 장전또한 이미 장전중이면 다시 R을 눌러도 장전이 되지 않게 했다.

 

발사//장전

============================================================================

private IEnumerator ShotEffect()
    {
        this.muzzleEffect.Play();
        this.shellEjection.Play();

        this.line.SetPosition(0, this.fireTransform.position);
        var pos = this.fireTransform.position;
        pos.z = this.fireTransform.forward.z * this.attackRange;
        pos.x = this.fireTransform.forward.x * this.attackRange;
        this.line.SetPosition(1, pos);

        Debug.Log(pos.z);
        this.line.enabled = true;
        yield return new WaitForSeconds(0.03f);
        this.line.enabled = false;
    }

캐릭터를 회전시키면처음 쏘던 방향으로만 총알이 나가는 문제를 해결했다.

원인은 x축값을 정해주지 않았기 때문에 z축으로만 경로가 바뀌어서 그랬다.

============================================================================

NavMesh까진 적용했는데 유닛들이 어떻게 NavMeshAgent를 이용하여 이동하는지 잘 모르겠다.

 

--미완성 기능들

-몬스터 피격

-캐릭터 피격

-점수

-유닛 이동

ㄴ네비게이션

-캐릭터 카메라(시네머신)