1일1알

유니티로 언리얼의 GAS 시스템 따라하기 본문

유니티/Project Nammo

유니티로 언리얼의 GAS 시스템 따라하기

영춘권의달인 2024. 7. 31. 15:57

언리얼 엔진의 GAS(Gameplay Ability System)은 소유하고 발동할 수 있는 능력을 어빌리티라는 개념으로 정의하고 다른 어빌리티 및 액터간의 인터랙션 기능을 제공하는 프레임워크이다.

 

GAS시스템을 학습할 때 감동을 많이 받았고 굉장히 세련된 시스템이라고 느껴서 전부는 아니지만 조금이라도 비슷하게 구현해 놓는다면 앞으로 콘텐츠를 개발할 때 편리하게 할 수 있을 것이라고 생각되었다.

 

실제 언리얼의 GAS시스템에는 많은 요소들이 존재하지만 일단

Gameplay Tag, Gameplay Ability, Ability System Component 이렇게 세가지만 고려하여 구현하였다.

 

Gameplay Ability(GA)는 행동이나 스킬들을 Ability라는 개념으로 묶어놓은 것이다.

GA를 상속받아서 점프, 공격등의 Ability들을 설계하고 이 Ability를 Gameplay Tag를 통해 발동시키거나 취소시킬 수 있도록 구현하였다.

public enum GameplayTag
{
    Player_Action_Jump,
    Player_Action_Attack,
    Player_Action_Parrying,
    Player_Action_Wave,
    Player_Action_UseItem,
    Player_Action_Interaction,
    Player_Action_Dash,
    Player_Action_AirAttack,
    Player_Action_DownJump,
    Player_Action_Block,

    MaxCount
}
public enum GameplayAbility
{
    GA_Jump,
    GA_Dash,
    GA_Attack,
    GA_WaveDetect,
    GA_AirAttack,
    GA_DownJump,
    GA_Block,
    GA_Parrying,
}

언리얼에서 Gameplay Tag는 계층적으로 설계할 수 있는 Enum같은 것이고 유니티에서는 그냥 Enum으로 사용하였다.  Ability마다 대응되는 Tag, Ability가 발동됐을 때 부착되는 상태를 나타내는 Tag 2가지로 분류하여 사용하였다.

// 발동될때 추가될 태그, 발동이 종료될때 삭제될 태그
[SerializeField] protected List<Define.GameplayTag> _tagsToAdd;
// 발동에 필요한 태그
[SerializeField] protected List<Define.GameplayTag> _needTags;
// 발동할 때 있으면 안되는 태그
[SerializeField] protected List<Define.GameplayTag> _blockTags;

Ability에는 해당 Ability에 대응되는 Tag가 하나 있고, 이 태그를 통해 대응되는 Ability를 발동시키거나 취소시킬 수 있다. 그리고 3가지 상황에 대한 태그들을 등록하여 다른 Ability들과의 상호작용을 설정할 수 있다.

3가지 상황은 각각 해당 Ability가 발동됐을 때 부착될 태그, 해당 Ability가 발동되기 위해 사전에 부착되어 있어야 할 태그, 해당 Ability가 발동되개 위해 부착되어있으면 안되는 태그이다.

Dictionary<Define.GameplayTag, int> _tagContainer = new Dictionary<Define.GameplayTag, int>();
Dictionary<Define.GameplayAbility, GameAbility> _abilities = new Dictionary<Define.GameplayAbility, GameAbility>();

Tag를 통해 Ability를 발동시킬 수 있는데 해당 Ability가 현재 부여되어 있는지, 발동을 막는 태그나 발동에 필요한 태그를 관리하는 관리자 역할을 하는 것이 AbilitySystemComponent(ASC)이다.

ASC의 _tagContainer에서는 현재 부착되어있는 태그들을 관리하고 _abilities에서는 현재 부여된 ability들을 관리한다.

public void GiveAbility(Define.GameplayAbility tag)
{
    GameAbility ga = null;
    if(_abilities.TryGetValue(tag, out ga))
    {
        Debug.Log("Already Exsist Ability");
        return;
    }
    ga = CreateAbility(tag);
    _abilities.Add(tag, ga);
}

private GameAbility CreateAbility(Define.GameplayAbility tag)
{
    GameObject go = Managers.Resource.Instantiate("GameAbility/" + Enum.GetName(typeof(Define.GameplayAbility), tag));
    GameAbility ga = go.GetComponent<GameAbility>();
    go.transform.SetParent(transform);
    ga.SetASC(this);
    return ga;
}

ASC의 GiveAbility함수를 통해 Ability를 부여할 수 있다. Ability들은 프리팹으로 관리되고 있고, 부여가 된다면 ASC가 부착된 GameObject의 자식 오브젝트의 위치에 부착되고 _abilities에 추가된다.

public void TryActivateAbilityByTag(Define.GameplayAbility tag)
{
    GameAbility ga = null;
    if(_abilities.TryGetValue(tag, out ga))
    {
        ga.TryActivateAbility();
    }
    else
    {
        Debug.Log("Not Exsist Ability");
    }
}

ASC의 TryActivateAbilityByTag를 통해 Ability를 발동시키려는 시도를 할 수 있다.

만약 _abilities에 해당 Tag에 대응되는 Ability가 없다면 (Ability가 부여가 되지 않았다면) 그냥 넘어가고, 만약 부여가 되어있는 상태라면 GA의 TryActivateAbility를 통해 Ability 발동을 시도하게 된다.

public void TryActivateAbility()
{
    if (CanActivateAbility() == false) return;
    foreach(Define.GameplayTag tag in _tagsToAdd)
    {
        _asc.AddTag(tag);
    }
    ActivateAbility();
    return;
}
protected virtual bool CanActivateAbility()
{
    foreach(Define.GameplayTag tag in _needTags)
    {
        if (_asc.IsExsistTag(tag) == false) return false;
    }
    foreach(Define.GameplayTag tag in _blockTags)
    {
        if (_asc.IsExsistTag(tag)) return false;
    }
    if (_canOverlapAbility == false)
    {
        if (_isActivated) return false;
    }
    return true;
}

GA의 TryActivateAbility함수에서는 먼저 CanActivateAbility함수로 현재 Ability가 발동될 수 있는 상태인지를 검사하고 아니라면 그냥 return해준다. CanActivateAbility에서는 앞서 설정한 태그들을 검사하여 현재 Ability가 발동될 수 있는 상황인지를 검사해준다. 하위 클래스에서 오버라이드하여 추가적인 조건을 검사하도록 할 수 있다.

CanActivateAbility를 통과하면 앞서 설정한 부착될 태그들을 ASC의 태그 컨테이너에 부착해주고 ActivateAbility를 실행해준다.

 

실제 콘텐츠 관련 코드는 이 ActivateAbility에서 구현해주면 된다.

Ability가 종료될때는 EndAbility가 발동되게 해주면 되고, 여기서는 이 Ability로 인해 ASC에 부착된 태그들을 탈착해준다.

 

 

 

 

'유니티 > Project Nammo' 카테고리의 다른 글

적 쉐이더(2D)  (0) 2024.08.09
지형지물에 따라 메쉬 모양 변형시키기  (0) 2024.08.09