나 개발자 진짜 되냐?

[ Unity 뱀서라이크 게임을 만들어보자 10 ] 애니메이션 컨트롤 + 싱글톤이란? 본문

유니티를 공부해봐요!/초급이에요!

[ Unity 뱀서라이크 게임을 만들어보자 10 ] 애니메이션 컨트롤 + 싱글톤이란?

Snow Rabbit 2024. 10. 11. 19:00

 

몬스터를 만들어주어야 하다니..

몬스터가 가장 어려운 거 같다

그래도 플레이했던 부분을 다시 하는 거다 보니

복습하는 느낌으로 공부해 보자!

 


그전에!

싱글턴이라는 친구를 공부해 보자

 

싱글턴 패턴은

말 그대로 싱글

소프트웨어 디자인 패턴 중 하나로,

특정 클래스의 인스턴스가 하나만 존재하도록 보장하고,

이를 전역적으로 접근할 수 있는

전역 접근을 제공하는 패턴

 

" 클래스의 인스턴스가 하나만 존재하도록 보장하는 디자인 패턴 "

 

단점은.. 모든 친구들이

이 전능한 싱글턴객체를 만지기 때문에

잘못 건드리면

유지보수가 굉장히 어렵고 코드가 꼬일 수도 있다.

 

 

그리고

🌟 FindGameObjectWithTag 🌟

이 친구는

지정된 태그와 일치하는 게임오브젝트를 반환한다.

 

그렇기 때문에

특정태그를 가진 오브젝트를 왕 빠르게 찾을 수 있다.

 

앗.. 대신

아주 비싼 연산자이다.

무슨 뜻이냐

cpu를 굉장히 많이 쓰기 때문에 이 친구를

너무 많이 쓰면

게임 성능에 큰 문제가 생긴다.

 

그래서!

start나 awake에 한 번만 사용되어야 좋다.

 

🌟 Physics2D.Raycast 🌟

Raycasting은

콜라이더와 교차하는지를 감지하는 데 사용되는 기술이다.

 

이 메서드는

시작점, 방향, 최대거리를 매개변수로 받는다

필요하다면 레이어마스크도 받을 수 있다.

 

이 친구는

hit 정보를 반환한다.

이 정보에는 충돌한 객체, 충돌 지점, 충돌 지점의

정규화된 벡터 등이 포함됩니다.

 

이 친구도 좀 비싼 친구라

남용하는 것은 좋지 않다!

 

이 친구는

Debug.DrawRay를 통해서

이게 지금 어디 쏘나..

디버그 해볼 수 있다!


자!

이제 진짜 게임매니저를 만들어보자!

먼저 플레이어 태그를 플레이어로 바꾼 다음에 시작!

 

< GameManager.cs >

using UnityEngine;

public class GameManager : MonoBehaviour
{
    //싱글톤 특징은 public static로 시작된다.
    public static GameManager Instance;

    public Transform Player { get; private set; }
    public ObjectPool ObjectPool { get; private set; }
    [SerializeField] private string playerTag = "Player";

    private void Awake()
    {
        Instance = this; //지금 이 오브젝트가 이 친구입니다.
        Player = GameObject.FindGameObjectWithTag(playerTag).transform;

        ObjectPool = GetComponent<ObjectPool>();
    }
}

여기서 ObjectPool이나 오는데

이 친구를 우리는 예전에 TopDownShooting 밑에 Awake에 넣었었다.

그 선언만 빼서 여기로 가져온다.

 

다음에 우리는

게임오브젝트에

게임매니저를 넣고 태그를 player이라고 적어준다.

 

자,

코드를 구현해 보자

 

< TopDownEnemyController.cs >

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.RuleTile.TilingRuleOutput;

public class TopDownEnemyController : TopDownController
{
    GameManager gameManager;

    protected Transform ClosestTarget { get; private set; }

    protected override void Awake()
    {
        base.Awake();
    }

    protected virtual void Start()
    {
        gameManager = GameManager.instance;
        ClosestTarget = gameManager.Player;
    }

    protected virtual void FixedUpdate()
    {
    }

    protected float DistanceToTarget()
    {
        return Vector3.Distance(transform.position, ClosestTarget.position);
    }

    protected Vector2 DirectionToTarget()
    {
        return (ClosestTarget.position - transform.position).normalized;
    }
}

 

점점 머리가 복잡해진다.

여기서는 적들의 위치 및 거리 정보가 표시되는 곳이다.

 

다음은 방금 만든 클래스를 상속받는 친구를 만들어보자.

 

 

< TopDownContactEnemyController.cs >

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

public class TopDownContactEnemyController : TopDownEnemyController
{
    //어느정도 가까워야 따라오는지..
    [SerializeField][Range(0f, 100f)] private float followRange; 
    [SerializeField] private string targetTag = "Player";
    private bool isCollidingWithTarget;
	//캐릭터에 위치에 따라 달라진다. 막 좌우반전 될 수도 있으니..
    [SerializeField] private SpriteRenderer characterRenderer;

    protected override void Start()
    {
        base.Start();
    }

    protected override void FixedUpdate()
    {
        base.FixedUpdate();

        Vector2 direction = Vector2.zero;
        if (DistanceToTarget() < followRange)
        {
            direction = DirectionToTarget();
        }

        CallMoveEvent(direction);
        Rotate(direction);
    }

    private void Rotate(Vector2 direction)
    {
        // TopDownAimRotation에서 했었죠? 
        // Atan2는 가로와 세로의 비율을 바탕으로 -파이~파이(-180도~180도에 대응, * Rad2Deg가 그 기능)하는 값을 나타내주는 함수였다는 것 기억하시죠?
        float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        characterRenderer.flipX = Mathf.Abs(rotZ) > 90f;
    }
}

 

코드를 다 짰으니

 

이제 적을 만들어보자

고블린 출격!

create Empty

sp ren 친구 만들어서

습랜ㅋㅋㅎ

 

값을 설정해 준다.

order값이 4

포지션은 그냥 그림이 잘 넣어졌나 보려고 

임의로 넣었다 ㅋㅎㅋ

 

설정해 주고

고블린은 layer를 Enemy로 설정해 주고

 

box 충돌친구 만들기

사이즈는 0.5 0.5

Offset은 y -0.15 해준다.

 

그리고 rigid

회전하면 안 되니까 Z 축은 체크 해주고

Gravity Scale은 0

 

우리는 이제 SO를 만져주러 가보자

AttackSO로 가서 추가

 

고블린 만들고

값 설정

여기서 타깃은 player이고

PlayerSO는 타깃이 Enemy이다.

 

자 이제 스크립트를 하나씩 넣어보자.

 

Character Stat Handler

기본값 주고,

 goblin SO도 주고

 

Top Down Contact Enemy Controller로

얼마큼 가까워졌을 때, 적이 날볼건지를 설정해 주고

캐릭터 랜더러는 메인 스프라이트를 드래그드롭 해주면 된다.

 

 

 

자 이제 고블린을 만들어보자.

 

전 시간에 캐릭터 움직이는 거랑 거의 비슷하다.

 

https://qua28.tistory.com/172

 

[ Unity 뱀서라이크 게임을 만들어보자 9 ] 애니메이션 컨트롤

오늘은애니메이션을 넣어보도록 하자! 예전에 잠시 유니티 만들 때 하긴 했었는데좀 더 제대로 짚고 넘어가보자!🌟 Animation 🌟 Animation 컴포넌트는 게임 오브젝트에 애니메이션을 추가하는 데

qua28.tistory.com

참고

 

일단 먼저

애니메이션 파일에

Enemy 추가하고

거 기안에 고블린 파일을 만들어준다.

그리고

애니메이션 컨트롤 만들어서

고블린이라고 지어준다.

 

다음에 메인스프라이트에 Animator을 추가해서 

컨트롤러에 

방금 만든 고블린이라는 컨트롤을 넣어준다.

 

다음에는 우리가

캐릭터 하던 대로

hit

idle

run

이렇게 3개 만들어준다.

 

그리고 해주었던 거처럼

 

이거 해주시면 됩니다!

 

자 그다음에 우리는 

스크립트를 하나 만든다.

위에서는 근거리 몬스터에 대한 코드였다면

지금은 원거리 몬스터이다.

 

using UnityEngine;

public class TopDownRangeEnemyController : TopDownEnemyController
{
    [SerializeField][Range(0f, 100f)] private float followRange = 15f;
    [SerializeField][Range(0f, 100f)] private float shootRange = 10f;
    private int layerMaskLevel;
    private int layerMaskTarget;

    protected override void Start()
    {
        base.Start();
        layerMaskLevel = LayerMask.NameToLayer("Level");
        //타겟이 변하지않아서 그대로 써도 된다.
        layerMaskTarget = stats.CurrentStat.attackSO.target;
    }

    protected override void FixedUpdate()
    {
        base.FixedUpdate();

        float distanceToTarget = DistanceToTarget();
        Vector2 directionToTarget = DirectionToTarget();

        UpdateEnemyState(distanceToTarget, directionToTarget);
    }

    private void UpdateEnemyState(float distance, Vector2 direction)
    {
        IsAttacking = false; // 기본적으로 공격 상태를 false로 설정합니다.

        if (distance <= followRange)
        {
            //따라오는 범위에서는 또 다르게 값을 줘야하기 때문에,
            CheckIfNear(distance, direction);
        }
    }

    private void CheckIfNear(float distance, Vector2 direction)
    {
        if (distance <= shootRange)
        {
            //여기 슛이 중요!
            TryShootAtTarget(direction);
        }
        else
        {
            CallMoveEvent(direction); // 사정거리 밖이지만 추적 범위 내에 있을 경우, 타겟 쪽으로 이동합니다.
        }
    }

    private void TryShootAtTarget(Vector2 direction)
    {
        // 몬스터 위치에서 direction 방향으로 레이를 발사합니다. // 위치 방향
        RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, shootRange, GetLayerMaskForRaycast());

        // 벽에 맞은게 아니라 실제 플레이어에 맞았는지 확인합니다.
        if (IsTargetHit(hit))
        {
            PerformAttackAction(direction);
        }
        else
        {
            CallMoveEvent(direction);
        }
    }

    private int GetLayerMaskForRaycast()
    {
        // "Level" 레이어와 타겟 레이어 모두를 포함하는 LayerMask를 반환합니다.
        return (1 << layerMaskLevel) | layerMaskTarget;
    }

    private bool IsTargetHit(RaycastHit2D hit)
    {
        // RaycastHit2D 결과를 바탕으로 실제 타겟을 명중했는지 확인합니다.
        return hit.collider != null && layerMaskTarget == (layerMaskTarget | (1 << hit.collider.gameObject.layer));
    }

    private void PerformAttackAction(Vector2 direction)
    {
        // 타겟을 정확히 명중했을 경우의 행동을 정의합니다.
        CallLookEvent(direction);
        CallMoveEvent(Vector2.zero); // 공격 중에는 이동을 멈춥니다.
        IsAttacking = true;
    }
}

 

코드 작성 후

오크친구를 만들어주자

고블린 복사해서 오크라고 만들어준다.

 

오른쪽 보니 SO가 없네

오크 SO를 만들러 가자

 

오크는 Range로 만들고

 

해주고

값 지정!

color에 255인 거 잊지 말자! 

 

이 친구는 넉백도 있다!

 

아까 고블린과 다른 점이라면

 

아까 말했듯 고블린은 근거리라 Contact, 얘는 원거리라 Range이다.

 

오크한테 가서 

아까랑 똑같이 애니메이션 걸어주고,

 

박스 콜라이더 값도 고쳐준다.

왜냐면 좀 더 길쭉해졌어서 사이즈를 변경해주어야 한다.

 

오크친구는

애니메이터 이외에도 할게 많다.

이 친구는 마법사라

무기도 필요하다.

 

파일을 만들어서

웨펀스프라이트에서

사진을 

red친구를 찾아준다.

이렇게!

 

그다음에 마술봉에서 공격도 나가야 하니

웨펀스프라이트 밑에

Bullet하나 추가

 

자 이제 발사체를 만들었으니

오크가 발사한다고 알려줘야 한다.

 

프로젝트타일에 넣어주면 된다!

 

자!

발사까지 해주었으니

이제 에임조절만 해주면 된다.

 

그 밑에

 

만들어준다.

 

이렇게 실행하면!!!

 

.. 흠..

무기를 뒤집어서 들고 있네

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

어디서 고쳐야 할까?!

 

발사하는 방향의 문제이니

 aim으로 가보자.

 

 

밑식을 추가하면 무기가 저렇게 뒤집히지는 않는다..ㅎㅎ

 

 

* 추가

애니메이션에도 좀 더 간편하게 만드는 방법이 있다.

우리는 오크 3개 뭐 고블린 3개 이렇게 난리를 쳤는데

그렇게 안 하고

같은 애니메이션일 경우

파일만 갈아 끼우는 방법이 있다.

 

이렇게 해서 그림만 바꿔준다면

움직임이 같은 다양한 몬스터들을

한 번에 관리할 수 있다!!

 

 

 

다음 글에는

이 친구들을 여러 개 프리팹으로 만들어보려고 한다!

이제 정말 얼마 안 남았다!!

파이팅