유니티/게임 프로젝트

2D RPG - (14) 분신술 스킬 구현

monstro 2025. 1. 7. 21:23
728x90
반응형

이번 포스트에서는 첫번째 스킬 중 분신술을 구현해보겠습니다.

분신술을 위한 키를 따로 설정하지 않아 대쉬에 연동하여 발동하는 스킬로서,

분신은 적을 발견하면 적을 공격하게 됩니다.

 

분신술은 다음의절차에 의해 수행됩니다.

  • 플레이어가 입력하면 SkillManager에 의해 분신술 수행
  • 수행된 분신술에서 이전에 만들고 설정해놓은 분신 Prefab을 생성
  • 분신 Prefab에 설정된 컴포넌트에 의해 로직을 수행

위의 절차에 맞춰 코드를 알아보겠습니다.

 

1) SkillCloning

public class SkillCloning : SkillTemplate
{
    [Header("Clone Info")]
    [SerializeField]
    private GameObject _clonePrefab;
    [SerializeField]
    private float _cloneDuration;

    // SkillManager에 의해 분신술을 수행
    public void DoCreateClone(Transform clonePosition, bool canAttack)
    {
        GameObject newClone = Instantiate(_clonePrefab);

        newClone.GetComponent<SkillCloningController>().DoSetupClone(clonePosition, _cloneDuration, canAttack);
    }
}

 

분신술 스킬은 위와 같이 구성되어 있습니다.

분신 prefab을 생성하고 이후의 로직은 분신 prefab이 수행합니다.

 

2) SkillManager

public class SkillManager : MonoBehaviour
{
    public static SkillManager _skillManagerInstance;

    // SkillManager는 스킬을 수행하는 주체이므로, 각 스킬에 대해 알고 있어야 함
	.
	.
	.
    public SkillCloning _skillCloning { get; private set; }

    
	.
	.
	.

    // 스킬을 가져와서 설정
    private void Start()
    {
		.
		.
		.
        _skillCloning = GetComponent<SkillCloning>();
    }
}

 

SkillManager에서 분신술을 호출할 수 있도록 위와 같이 설정하였습니다.

 

3) PlayerController

public class PlayerController : BaseCharacterController
{
	.
	.
	.

    // Skill Info
    public SkillManager _skillManager { get; private set; }

	.
	.
	.

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

        _skillManager = SkillManager._skillManagerInstance;

        _stateMachine.Init(_idleState);
    }
    
	.
	.
	.
}

 

플레이어의 입력을 처리하는 PlayerController에서 스킬을 수행할 수 있도록

SkillManager를 캐싱해놓도록 하겠습니다.

 

4) PlayerStateDash

public class PlayerStateDash : PlayerState
{
	.
	.
	.

    // State에 돌입
    public override void Enter()
    {
        base.Enter();

        _controller._skillManager._skillCloning.DoCreateClone(_controller.transform, _controller.DoDetectIsGrounded());

        _stateTimer = _controller._dashDuration;
    }
    
	.
	.
	.
}

 

일단은 대쉬 상태에서 분신술을 수행할 수 있도록 위와 같이 설정하였습니다.

 

5) SkillCloningController

public class SkillCloningController : MonoBehaviour
{
    [SerializeField]
    private float _colorLoosingSpeed;
    private SpriteRenderer _spriteRenderer;
    private Animator _animator;

    private float _cloneTimer;

    [SerializeField]
    private Transform _attackCheck;
    [SerializeField]
    private float _attackCheckRadius = 0.8f;
    private Transform _closestEnemy;

    private void Awake()
    {
        _spriteRenderer = GetComponent<SpriteRenderer>();
        _animator = GetComponent<Animator>();
    }

    private void Update()
    {
        _cloneTimer -= Time.deltaTime;

        // 쿨타임이 충족되어 분신술 스킬을 사용가능한 경우
        if (_cloneTimer < 0)
        {
            // 분신을 생성하고, 투명도를 조금씩 낮춤
            _spriteRenderer.color = new Color(1, 1, 1, _spriteRenderer.color.a - (Time.deltaTime * _colorLoosingSpeed));

            // 투명도가 0이 되면 GameObject 파괴
            if (_spriteRenderer.color.a <= 0)
                Destroy(gameObject);
        }
    }

    // 분신을 생성
    public void DoSetupClone(Transform newTransform, float cloneDuration, bool _canAttack)
    {
        // 공격할 수 있는 경우, 분신의 Animator 변수를 랜덤하게 설정하여 공격 애니메이션 재생
        if (_canAttack)
            _animator.SetInteger("AttackNumber", Random.Range(1, 3));

        // 분신의 위치 설정 및 쿨타임을 설정
        gameObject.transform.position = newTransform.position;
        _cloneTimer = cloneDuration;

        // 분신이 적을 마주하게끔 설정
        DoFaceClosestTarget();
    }

    // 분신의 애니메이션에 사용될 이벤트 트리거 함수, 쿨타임을 -1로 설정하여 분신술 스킬을 사용가능하게 바꿈 
    private void AnimationTrigger()
    {
        _cloneTimer = -1.0f;
    }

    // 분신의 애니메이션에 사용될 이벤트 트리거 함수
    private void AttackAnimationTrigger()
    {
        // 공격 범위를 설정하고 원 모양으로 오버랩을 수행
        Collider2D[] colliders = Physics2D.OverlapCircleAll(_attackCheck.position, _attackCheckRadius);

        // 수행 결과를 순회하면서
        foreach (Collider2D hit in colliders)
        {
            // 적이라면 적의 DoGetDamage 함수 호출
            if (hit.GetComponent<EnemyController>() != null)
                hit.GetComponent<EnemyController>().DoGetDamage();
        }
    }

    private void DoFaceClosestTarget()
    {
        // 분신의 위치에서 25 만큼의 반지름으로 원으로 오버랩한후,
        Collider2D[] colliders = Physics2D.OverlapCircleAll(gameObject.transform.position, 25f);

        float closestDistance = Mathf.Infinity;

        // 오버랩 결과를 순회
        foreach (var hit in colliders)
        {
            // 오버랩 결과가 적이라면
            if (hit.GetComponent<EnemyController>() != null)
            {
                // 적과의 거리를 저장하고, 적을 기억
                float distanceToEnemy = Vector2.Distance(gameObject.transform.position, hit.transform.position);
                if (distanceToEnemy < closestDistance)
                {
                    closestDistance = distanceToEnemy;
                    _closestEnemy = hit.transform;
                }
                    
            }
        }

        // 기억된 적을 대상으로
        if (_closestEnemy != null)
        {
            // 적이 나보다 x축 기준으로 뒤에 있다면 방향 회전
            if (gameObject.transform.position.x > _closestEnemy.position.x)
                transform.Rotate(0, 180, 0);
        }
    }
}

 

분신술로 생성되는 분신을 대상으로 동작하는 로직입니다.

이제 세부 설정 사항을 알아보겠습니다.

 

6) 유니티 에디터

 

플레이어의 분신 prefab은 위와 같이 설정되었습니다.

우리가 의도한 바라면, 땅에 서 있는 상태에서 생성한 분신은 적을 공격하고, 

그렇지 않은 분신은 멀뚱히 서 있어야 합니다.

실제 실행 결과를  알아보겠습니다.

 

 

문제없이 수행되는 것을 볼 수 있습니다.

728x90
반응형