ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TopDown Shooting Game / 화살 쏘기 기능 만들기
    스파르타 게임 개발 2024. 5. 22. 14:55

    마우스 클릭 하면 화살이 나가게 해준다. 

     

    마우스 버튼을 누르면 화살이 생성이 되고, 그 화살은 클릭 된 방향으로 나아간다. 

     

    먼저 OnFire를 만들어 주자. 우린 bool 자료형을 하나 만들고 버튼이 클릭 돼있으면 true가 되게 하고 공격쿨타임이 아닐 때 화살이 생성 되게 해준다. 

     

        public void OnFire(InputValue value)
        {
            IsAttacking = value.isPressed;
        }

     

    이렇게 코드를 작성 해주면 마우스가 눌리고 있거나 눌렸을 때 IsAttacking 변수가 true가 된다. 변수는 TopDownController에 선언 돼있음.

     

    그리고 TopDownController 에서 화살을 생성 하는 코드를 작성 한다. 

     

       private void HandleAttackDelay()
       {
           if(timeSinceLastAttack <= stats.CurrentStat.attackSO.delay)
           {
               timeSinceLastAttack += Time.deltaTime;
           }
           if(IsAttacking && timeSinceLastAttack > stats.CurrentStat.attackSO.delay)
           {
               timeSinceLastAttack = 0;
               CallAttackEvent(stats.CurrentStat.attackSO);
           }
       }

     

    라는 함수를 생성 해서 update 함수에 실행 시키게 한다. 

     

    여기서 timeSinceLastAttack 변수는 float.MaxValue를 해둠으로써 시작 했을 때는 바로 공격이 가능한 상태이고, 공격을 한 후에 값이 0이 되어서 timeSinceLastAttack += Time.deltaTime;으로 다시 delay값만큼 추가가 되어야 공격이 가능해지게 한 것이다.

     

    그리고 IsAttacking true가 돼야 하는데 이건 클릭을 할 때 true가 된다. 

     

    이 두가지 조건이 충족하면 화살을 생성 하는 함수를 호출 하게 한다. 

    이렇게 입력을 처리하는 클래스를 작성 했으니 이제 Move와 Look 처럼 실행을 하는 클래스를 만들어 준다.

     

    TopDownShooting 클래스를 만들어 준다. 

     

    먼저 똑같이 TopDOwnController를 겟컴포넌트 해준다. 그리고 화살이 생성 됐을 때 클릭 한 좌표로 나아가게 해야하기 때문에 Vector2 aimDirection이라는 변수도 하나 만들어 주고 controller.OnLookEvent += OnAim; 라는 것을 추가 시켜준다. 

     

      private void OnAim(Vector2 newAimDirection)
      {
          aimDirection = newAimDirection;
      }

    (OnAim함수) OnLookEvent는 우리가 클릭 한 곳에 좌표를 월드포지션으로 변경한 x,y의 값으로 반환 시켜주는 작업이 돼있다. 그래서 aimDirection은 계산이 완료 된 x,y의 값을 매개변수로 받아서 값이 채워지게 된다. 

     

    그리고 OnAttackEvent도 채워준다. 

     

        private void Start()
        {
            controller.OnAttackEvent += OnShoot;
        }

     

    이제 OnShoot 함수를 만들어 주면 되는데 이 함수는 화살을 생성 시키는 함수이다. 

     

        private void OnShoot(AttackSO attackSO)
        {
            RangedAttackSO rangedAttackSO = attackSO as RangedAttackSO;
            if (rangedAttackSO == null) return;

            float projectilesAngleSpace = rangedAttackSO.multipleProjectilesAngel;
            int numberOfProjectilesPerShot = rangedAttackSO.numberOfProjectilesPerShot;

            float minAngle =  - (numberOfProjectilesPerShot / 2f) * projectilesAngleSpace + 0.5f * rangedAttackSO.multipleProjectilesAngel;


            for (int i = 0; i < numberOfProjectilesPerShot; i++)
            {
                float angle = minAngle + i * projectilesAngleSpace;
                float randomSpread = Random.Range(-rangedAttackSO.spread, rangedAttackSO.spread);
                angle += randomSpread;
                CreateProjectile(rangedAttackSO, angle);
            }
        }

     

    이렇게 코드를 작성 해주는데 여기서  RangedAttackSO rangedAttackSO = attackSO as RangedAttackSO; 코드는 매개변수로 받아온 AttackSO가 원거리공격이 가능한지, 화살을 쏘는 게 가능한지 여부에 대해서 확인 하는 코드이다. 

     

    만약 화살을 쏠 수 없는 근거리유닛이면 OnShoot을 바로 return 해서 실행이 안 되게 한다. 

     

    as를 이용한 형변환을 통해서 원거리유닛인지 확인 할 수 있게 한다. RangedAttackSo는 AttackSO를 상속 받아서 가능하다. 이 때 형변환이 실패 할 경우 null값을 반환 하게 되고 이는 오류가 생길 수 있어서 if (rangedAttackSO == null) return; 라는 코드로 보완을 해줘야 한다. 

     

    그리고 다음으로는 우리는 화살을 쏠 때 멀티샷 부분도 고안을 해서 화살이 생성 되게 해줄 것이다. 

    화살을 동시에 여러 발 쏠 때 한 곳으로 나가는 것이 아니라 애쉬 W처럼 여러 각도로 나가게 해줄 거다. 

     

    그러기 위해서는 각 발당 각도의 차이를 주기 위해 각도를 하나 정해줘야 하고, 몇 발 쏠 것인지에 대한 값도 있어야 한다. 

     float projectilesAngleSpace = rangedAttackSO.multipleProjectilesAngel;
     int numberOfProjectilesPerShot = rangedAttackSO.numberOfProjectilesPerShot;

     

    이거를 계산을  float minAngle =  - (numberOfProjectilesPerShot / 2f) * projectilesAngleSpace + 0.5f * rangedAttackSO.multipleProjectilesAngel; 

     

    이렇게 해줘서 minAngle이라는 최초의 화살의 각도를 정해주고 

     

     for (int i = 0; i < numberOfProjectilesPerShot; i++)
            {
                float angle = minAngle + i * projectilesAngleSpace;

    이 코드를 통해서 최초화살 각도에 미리 정해둔 각도를 더해줌으로써 여러발을 쏠 때 애쉬 W처럼 보이게 나가게 할 수가 있는 것이다. 

     

    그리고 여기서

    float randomSpread = Random.Range(-rangedAttackSO.spread, rangedAttackSO.spread);
    angle += randomSpread;

     

    이 코드를 추가 시켜서 각 각도마다 랜덤한 각도의 차이를 넣어서 화살이 나갈 때 좀 더 다양하게 나가게 했다.

     

    이 OnShoot 함수를 통해서 angle 계산을 하고 화살을 생성 하는 함수를 실행 하면 된다.

     

    private void CreateProjectile(RangedAttackSO rangedAttackSO, float angle)
    {
    GameObject obj = Instantiate(testPrefab); 

    obj.transform.position = projectileSpawnPosition.position;

    ProjectileController attackController = obj.GetComponent<ProjectileController>(); attackController.InitializeAttack(RotateVector2(aimDirection, angle), RangedAttackSO);
    }

    private static Vector2 RotateVector2(Vector2 v, float angle)
    {
        return Quaternion.Euler(0f, 0f, angle) * v;
    }

     

    GameObject obj = Instantiate(testPrefab);  로 만들어둔 프리펩을 생성 시키고 그 포지션은 오브젝트를 미리 생성 해둔 그 오브젝트의 위치에 생성 되게 한다. obj.transform.position = projectileSpawnPosition.position; 

     

    그리고 ProjectileController라는 클래스는 프리펩으로 만들어둔 화살에 들어가있는 컴포넌트로 거기에 있는 InitializeAttack함수를 사용 하기 위해 객체를 생성 해주고 attackController.InitializeAttack(RotateVector2(aimDirection, angle), RangedAttackSO); 라는 코드로 함수를 사용 해준다. 

     

    RotateVector2는 우린 화살들의 각도를 정해주는 걸 OnShoot 함수로 구했고 게임엔진에서는 쿼터니언 각도를 요구로 한다. 그래서 우리가 구한 각도를 쿼터니언으로 계산 해주는 함수를 사용 해야 한다. 거기에 Vector를 곱해줘서 Vector가 회전이 되게끔 해준다.  return Quaternion.Euler(0f, 0f, angle) * v;

    그러니까 각도가 적용 된 Vector2에 값이 반환 된다. 

    여기서 aimDirection은 우리가 클릭을 한 곳을 월드좌표계로 계산한 x,y의 값.

     

    InitializeAttack 은 이러한 매개변수를 이어받고 함수를 실행 한다. 

     

    이제 프리펩, 화살에 대한 코드를 작성 해서 그 화살이 클릭한 곳으로 나아가고, 부딪히면 효과를 적용 하는 코드를 작성 해주면 된다.

Designed by Tistory.