ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 유니티 AOS게임 만들기 - 플레이어 애니메이션과 공격과 스텟구현
    유니티 2022. 5. 23. 09:06

    AOS게임의 스텟은 다양하겠지만

    간단하게

    1.공격관련- 공격력,공격속도,공격력 관통력

    2.방어-마법방어력,물리방어력

    3.체력,마나- 체력, 마나 ,체력재생,마나재생

    4.이동관련- 이동속도

    5.자원관련-보유골드,현상금 등

    이정도로 간단하게 만들수 있겠네요

     

    저희는 더 간단하게 공격력,공격속도,체력,마나 만 만들어보도록하겠습니다.

    우선 애니메이션을 세팅하겠습니다.

    제가 만든 player를 공유하겠습니다 

     https://drive.google.com/drive/folders/1YqQJnTxy0KTWitxP_HM4CojttjnTO2p0?usp=sharing

     

    유니티 애니메이션만든것 - Google Drive

    이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

    drive.google.com

    이 플레이어를 저희가 만든 빈오브젝트에 넣어주시면 될것 같습니다.

    거대하네요 y값에 2.9정도주면 터레인과 닿아있는것처럼 보입니다. y값을기준으로 90도회전시켜주세요!

    삽입한 오브젝트의 컴포넌트에 에니메이션을 넣어준뒤 프로젝트의 에니메이션 컨트롤러를 더블클릭해

    줍시다.

    애니메이터 사진

    아무곳에나 오른쪽마우스를 누른뒤 상태생성 -> 블렌드 트리를 선택해줍니다.

    블렌드트리는 매끄러운 에니메이션을 만들수 있게 도와줍니다.

    저희는 블랜드 트리를 사용해 움직일때와 움직이지않을때를 구분해서 

    stand상태와 running 상태를 교체하도록 만들겠습니다.

    지금현재는 게임을 실행해서 움직인다고하여도 스크립트를 작성하지않았기떄문에 + 파라미터를 주지않았기떄문에

    달리는 모션이 실행되지는 않습니다.

    파라미터 speed를 float형으로 생성하고 파라미터를 speed로 변경해줍시다.
    stand 클릭후 오른쪽위의 edit... 클릭 루프시간체크후 적용 눌러줍시다.

    running,attack 도 stand와 마찬가지로 루프시간을 적용해줍니다. 루프시간을 적용하면

    에니메이션 재생이 끝나도 다시처음부터 애니메이션을 재생해줍니다.

    여기 까지 설정하였다면

    이제 스크립트를 만들어봅시다.

    gameobject에 player_animation 스크립트를 만들어줍니다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using  UnityEngine.AI; // navmesh 
    
    public class Player_Animation : MonoBehaviour
    {
        public Animator anim; //애니메이션 지정
        
        public UnityEngine.AI.NavMeshAgent agent; //네비게이션 지정
    
        public float speed; //속도 지정
    
        float motionSmoothTime = 0.1f; //애니메이션 스무스틱 시간 지정
        void Start()
        {
            agent = gameObject.GetComponent<NavMeshAgent>();//네비게이션 컴포넌트를 스크립트로 지정
            anim = gameObject.GetComponentInChildren<Animator>(); //자식오브젝트의 animator 컴포넌트를 스크립트로 지정
        }
        // Update is called once per frame
        void Update()
        {
            speed=agent.velocity.magnitude / agent.speed; //속도지정
            anim.SetFloat("Speed", speed); //속도
        }
    }

    이번에는 유니티상에서 agent나 에니메이션을 지정하지않고 스크립트상에서 

    지정해봤습니다.

    GetComponent는 이스크립트를가진 오브젝트에서 컴포넌트를 가져오는것이고

    GetComponetInChildren은 이스크립트를가진 오브젝트의 자식오브젝트의 컴포넌트를 가져오는것입니다.

    움직이지 않을땐 stand 애니메이션이 움직일땐 running 애니메이션이 실행되는 모습을 볼 수 있습니다.

    이제 공격모션도 추가해볼까요?

    필요한것을 생각해봅시다 생각해보지않으면 어떤것부터 시작할지 알 수 없기떄문에

    미리 생각하고 만들어보는게 시간절약에 도움을 줍니다.

    1.공격모션(이미 만듬)

    2.공격할대상 만들기

    3.공격 할 대상 지정할 수 있게 만들기

    4. 공격력와 체력을 만들어 공격했을때 체력이 깎이게 만들기

    5.dead 상태일때 마우스움직임,등 다른 컴포넌트 사용불가하게 만들기

    이정도로 나눌수 있겠네요

    우선 2번과 3번을 해결해봅시다.

    플레이어 오브젝트인 gameobject에 box 콜라이더를 추가해준뒤 모델크기만큼 늘려서 맞춰줍시다.

    게임오브젝트를 복사한뒤 player_move와 player_animation 스크립트두가지를 삭제해줍니다.

    복재한 오브젝트

    콜라이더를 추가해준 이유는 저희가 공격대상을 선택할때 마우스로 클릭해서 선택하게 하기위해선

    이동할떄 썻던 raycast를 써야하는데 콜라이더가없다면 레이저와 충돌하지않고 통과해버리기떄문에

    콜라이더를 사용하여 지정할 수 있게 해주는겁니다.

    player_move 스크립트를 살펴볼까요?

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Player_Move : MonoBehaviour
    {   
        public Camera cam;
        public GameObject Player;
        public UnityEngine.AI.NavMeshAgent agent;
        public Player_Attack p_a;
        void Start(){
            p_a=Player.GetComponent<Player_Attack>(); //player_attack 스크립트 지정
        }
        void Update(){
            if(Input.GetMouseButtonDown(0))//마우스 클릭이 됬다면
            {
            RaycastHit hit;
                if(Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hit,Mathf.Infinity))//레이케스트(마우스 클릭위치,레이케스트가 맞은곳,무한대기)
                {   if(hit.collider.tag=="땅")
                        p_a.target = null;//땅을 선택하면 이미선택되있던 타겟초기화함
                        movemont(hit.point);//플레이어의 위치를 레이케스트가 맞은곳으로 이동  
                    }
                    if(hit.collider.tag=="적")
                    {  
                        p_a.target = hit.collider.gameObject;//타겟을 적으로 지정
                    } 
                }
            }
        }
        public void movemont(Vector3 pos){
            agent.SetDestination(pos);        
        }
    
    }

    레이캐스트가 반환한 오브젝트의 tag가 땅일경우 움직이게했다면

    오브젝트(적)일경우 어떻게해야할까요?

    1번.마우스로 적을 클릭한다

    2번.적과 나의 위치가 내가 공격할 수 있는 거리가 되는지확인한다

    if.거리가된다면 공격한다

    else.거리가 닿지않는다면 사거리가 될때까지 움직인다

    3.공격속도에따라2번으로 돌아간다

    if.다른대상을 클릭하면 공격을 중단하고 이동하거나 다른타겟을 공격한다.

    일것같네요

    우선 적의 태그를 만들어봅시다.

    적으로 만들었으니 move 스크립트의 tag=="오브젝트"부분을

    tag=="적"으로 바꿔줄 필요가 있겟네요

    그렇게되면 적을 클릭했을때 무언가 할 수 있겠습니다

    2번 공격할대상 만들기는 이렇게 끝났네요

    3번 도 거의다 했다고 봅니다.

    이제부터는

    player를 player로 이름을변경하겠습니다 

    적은 enemy로 바꾸도록할게요.

    이제 공격하기위해 필요한 스텟들을 저장할 스크립트를 만들어줍시다.

    stats스크립트 를 만들어볼게요

    stats 스크립트는 상호작용가능한 모든 오브젝트에 필요합니다.

    포탑에도 hp,미니언에도 hp, 넥서스에도 hp는 존재하기 때문입니다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Stats : MonoBehaviour
    {
        public float hp;
        public float maxHp;
        public float mp;
        public float maxMp;
        #region 공격관련 변수
        public float atk;
        public float atk_speed;
        public float atk_range;
        
    
        #endregion 공격관련 변수
        public float def;
        public float spd;
        
    }

    정말간단하게 저희에게 필요한 함수만 대충만들어줍시다. 나중에 추가해도 되니까요

    저는 이런식으로 체력을 100 공격력을 10 주고 공격사거리는 5로 attack 스크립트를 만들어보겠습니다.

    적에게도 똑같이 스텟을 만들어주고 값을 넣어줍시다.

    Player_Attack이라는 스크립트를 player오브젝트에 만들어봅시다.

    블랜더트리에서 attack으로 이동하기위해서 bool값이 true인지확인하고 speed가 0인지

    확인하면 이동하면서 공격하는것을 막을수 있습니다.

    전환만들기

    전환만들기를 통해 attack과 이어줍시다.

    attack에서 blend tree로 2번 blend tree에서 attack으로 1번 이어줍니다

     

    종료시간있음 체크해제해줍니다.

    attack ->blend tree1번에 

    conditions을 추가합니다

    attack(bool) false로 설정한뒤

    attack ->blend tree2번에

    conditions을 추가합니다

    blend tree->attack

    종료시간을 체크해제합니다

    speed를 greater(더큰) > 0.1 보다 크다면 (움직인다고볼수있으니)

    공격을중단하고 blend tree로 돌아갑니다.

    반대로 bool attack이 true가 되면 attack애니메이션이 재생됩니다.

     

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.AI; // navmesh
    
    public class Player_Attack : MonoBehaviour
    {   
        public float rotateVelocity;//회전속도
        public float rotateSpeedForAttack;//공격회전속도
        public NavMeshAgent agent; //네비게이션 지정
        public Animator anim;
        public GameObject target;
        public Stats stats;
    
        void Start()
        {
            agent = gameObject.GetComponent<NavMeshAgent>();//네비게이션 컴포넌트를 스크립트로 지정
            anim = gameObject.GetComponentInChildren<Animator>(); //자식오브젝트의 animator 컴포넌트를 스크립트로 지정
            stats = gameObject.GetComponent<Stats>();//스텟 스크립트 지정
        }
        void Update(){
            if(target!=null){
                if(Vector3.Distance(transform.position,target.transform.position)>stats.atk_range){//target의 위치와 플레이어의 위치의 거리가 공격범위보다 크면
                    agent.SetDestination(target.transform.position);//목표지점을 타겟의 위치 로 지정
                    agent.stoppingDistance = stats.atk_range;//공격범위를 넘어가면 멈춤
                }
                else{
                        Quaternion rotationToLookAt = Quaternion.LookRotation(target.transform.position - transform.position);//타겟의 위치와 플레이어의 위치의 거리를 계산하여 회전값을 계산
                        float rotationY = Mathf.SmoothDampAngle(transform.eulerAngles.y,rotationToLookAt.eulerAngles.y,ref rotateVelocity,rotateSpeedForAttack * (Time.deltaTime * 5));
                        transform.eulerAngles = new Vector3(0, rotationY, 0); //위의 계산을 통해 회전함 (타겟을 쳐다봄)
                        agent.SetDestination(transform.position);
                        if(target != null){
                            anim.SetBool("attack",true);//어택 시작
                        }    
                }
            }
            else{//타겟이 null이면 (없을때)
                anim.SetBool("attack",false);//어택종료
            }
        }
    }

    공격하는 모습

    이제 데미지가 실제로 계산이 되어야겠죠? 

    그리고 체력이 0이된다면 더이상 공격하지않아야하며 또한 타겟지정또한풀려야합니다

    애니메이션모션을 더블클릭해줍니다.

    더내려보면 이벤트 부분이있습니다 주먹을 뻗는 모습이 최고조일때 상대를 때릴때 frame 을 찾아 이벤트를 추가합니다

    함수 NewEvent를 attack_dmg으로 바꿔준뒤 적용을 눌러줍니다

    player의 자식오브젝트인 box_man_stand의 컴포넌트에 attack_cheak 스크립트를 만들어줍시다.

    애니메이션내의 이벤트함수를 실행 시키기위해서는 애니메이터컴포넌트가 존재하는 오브젝트에있는 

    함수가 실행되는것이기 때문에 

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class attack_cheak : MonoBehaviour
    {   public Player_Attack P_A;
        void Start()=>P_A=this.GetComponentInParent<Player_Attack>();
        public void attack_dmg()=>P_A.attack_dmg();
            
    }

     

    이코드로 GetComponetInParent 부모오브젝트의 컴포넌트를 가져와서

    실행하는 코드를 추가합니다.

    player_attack에 이코드를 추가합니다

        public void attack_dmg(){
            target.gameObject.GetComponent<Stats>().hp -= stats.atk;//타겟의 stats에서 hp 값을 가져와서 공격자의 공격력만큼뺀값으로 설정함
        }

    체력을 깍는것까지 만들어보았습니다.

    공격속도 구현을 위해 멀티플라이어 체크

    애니메이터에서 파라미터 attackspeed를 추가하고

    stats에서 attackspeed를 원하는값으로 계산한뒤

    애니메이션 재생속도를 바꿔주는형태로 진행합시다.

    저는 atk_speed 의값을 그대로 넣는형태로 구현했습니다

    그리고 공격할때 딜레이가 필요합니다

    이처럼 바꿔주시면됩니다

    stand->attack attack이 true라면

    attack->stand 조건x

    stand->blend tree speed가 0보다 크다면

    이렇게 넣어준다면 공격을 한뒤 stand 애니메이션 프레임만큼 딜레이가 발생하게됩니다.

    stand는 30프레임으로 이루어져 있습니다. 

    24프레임이 1초이니 stand의 speed를 0.8로 바꾸어주면 1초에 한번 딜레이가 발생합니다.

    이를전제로 공격속도를 2배로 하고싶다면 1.6으로 설정해주면될것입니다. 

    파라미터 float attackdely를 만든뒤 멀티플라이어를 셋팅해줍시다.

    전환을 클릭해 이런식으로 에니메이션이 끝나면 전환되도록 옮겨줍니다.

    player_animation.cs파일을 열어준뒤 작성해줍시다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using  UnityEngine.AI; // navmesh 
    
    public class Player_Animation : MonoBehaviour
    {
        public Animator anim; //애니메이션 지정
        
        public UnityEngine.AI.NavMeshAgent agent; //네비게이션 지정
        public Stats stats;
    
        public float speed; //속도 지정
    
        float motionSmoothTime = 0.1f; //애니메이션 스무스틱 시간 지정
        void Start()
        {   stats = gameObject.GetComponent<Stats>(); //스텟 스크립트 지정
            agent = gameObject.GetComponent<NavMeshAgent>();//네비게이션 컴포넌트를 스크립트로 지정
            anim = gameObject.GetComponentInChildren<Animator>(); //자식오브젝트의 animator 컴포넌트를 스크립트로 지정
        }
        // Update is called once per frame
        void Update()
        {
            speed = agent.velocity.magnitude / agent.speed; //속도지정
            anim.SetFloat("speed", speed); //에니메이션 파라미터 speed를 변경함
            anim.SetFloat("atk_speed", stats.atk_speed); //에니메이션 재생속도 atk_speed를 변경함
            anim.SetFloat("attackdely", stats.atk_speed*0.8f); //에니메이션 재생속도 atk_delay를 변경함
        }
    }

     

    이것으로 공격속도도 구현완료하였습니다.

    댓글

Designed by Tistory.