본문 바로가기
GameDevelopment(개임개발)/Unity_KeyWord

Unity_Interface (인터페이스)

by (S39) 2024. 3. 12.

Interface의 정의


인터페이스란 외부와 통신하는 공개 통로이며 통로의 규격이다.

인터페이스는 규격을 정해만 줄뿐 내부의 일에는 간섭하지 못한다.

USB를 생각해 보자 USB는 A타입, C타입, 5pin등 규격만 정해줄 뿐 이를 통해 벌어지는 통신이나 충전에는 관여하지 못한다.

 

 

 

C#에서의 Interface


Class를 선언할때 특정 메서드를 만들도록 강제하는 제약이다.

Class를 선언할때 C#은 하나의 클래스만 상속 받을 수 있다. 그러나 Interface의 경우 1개 이상을 상속 받을 수 있다.

Class가 Interface를 상속받을 경우 Interface에서 정의된 모든 메서드를 구현해야 한다. -> 미 구현시 오류발생

이는 Class가 Interface가 제시한 모든 메서드(기능)을 구현했음을 보장한다.

 

 

 

Unity에서의 Interface


게임을 한다고 가정하고 생각해 보자.

게임을 하다보면 플레이어는 다양한 아이템을 획득한다. HP회복, MP회복, 탄창, 등... 이렇게 다양한 아이템을 획득하는것을 단순하게 코드로 구현하면 아마 이렇게 구현할 것이다.

<Player 스크립트>

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

public class Player : MonoBehaviour
{

    public float hp; // player의 현재 hp가 저장될 변수
    [SerializeField] private float maxHp = 100; // player의 최대 hp값

    void Start()
    {
        this.hp = this.maxHp; // 시작시 player의 hp를 maxHp로 초기화
    }

    void Update()
    {
        
    }

    // private로 제한되어있는 hp변수에 값을 amount만큼 더해주는 메서드 
    public void SetHp(float amount)
    {
        this.hp += amount;
    }

    private void OnTriggerEnter(Collider other) // 충돌시
    {
        HpPack hpPack = other.GetComponent<HpPack>(); // 충돌한 오브젝트의 HpPack컴포넌트를 HpPack변수에 저장
        if(hpPack != null) // 만약 hpPack이 null이 아니면 -> 충돌한 오브젝트가 hpPack를 가지고 있다.
        {
            hpPack.HealHP(); // hpPack변수에 저장된 컴포넌트의 HealHP()메서드 호출;
        }

        MpPack mpPack = other.GetComponent<MpPack>(); // 충돌한 오브젝트의 MpPack컴포넌트를 MpPack변수에 저장
        if(mpPack != null)
        {
            mpPack.HealMP();
        }

        AmmoPack ammoPack = other.GetComponent<AmmoPack>(); // 충돌한 오브젝트의 AmmoPack컴포넌트를 AmmoPack변수에 저장
        if(ammoPack != null)
        {
            ammoPack.chargeAmmo();
        }
    }
}

 

<HpPack 스크립트>

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


// HpPack아이템 클래스
public class HpPack : MonoBehaviour
{
    private GameObject playerGo; // Player의 게임 오브젝트를 저장할 변수
    private Player player; // Player의 게임 오브젝트에 붙어있는 Player스크립트 컴포넌트가 저장될 변수 
    private float hpAmount = 10.0f; // HP아이템 획득시 회복량
    void Start()
    {
        // Player라는 이름을 가지고 있는 GameObject를 찾아서 playerGo에 저장
        this.playerGo = GameObject.Find("Player");

        // playerGo라는 GameObject의 Player라는 스크립트 컴포넌트를 가져온다.
        this.player = this.playerGo.GetComponent<Player>();
    }

    // 회복량 만큼 Player의 Hp를 회복시켜주는 메서드
    public void HealHP()
    {
        this.player.SetHp(this.hpAmount);
    }
}

이 코드의 단점은 아래와 같이 2가지로 나눌수 있다.

  1. 모든 아이템의 타입을 매번 검사해야 한다.
  2. 새로운 아이템이 추가되면 매번 코드를 수정 해야한다.

위와 같이 구현을 가능하지만 코드가 복잡해진다.

위 코드는 아이템과 닿았을 때 해당 아이템의 효과를 사용하는 코드의 집합이다.

[아이템의 효과를 사용한다.] 라는 공통점과 [아이템의 타입에 따라 사용효과는 다르다]라는 다른점을 가지고 있다.

 

Interface를 생각해보자.

  1. Interface는 규격을 제공할 뿐 내용에는 관여하지 않는다.
  2. Interface를 상속받으면 Interface내부의 정의된 메서드는 모두 구현해야 한다.
  3. 상속에 제한이 없다.

여기서 1번과 2번을 이용하면 위 코드를 좀더 간단하게 만들수 있을것 같다.

추가로 유니티의 컴포넌트에 대해 알면 이해가 쉬워진다.

유니티에서는 MomoBehaviour를 상속받은 스크립트는 컴포넌트로써 게임오브젝트에 붙일 수 있다.

 

이러한 점과 Interface의 특징을 이용하면 [아이템의 효과를 사용한다.] 라는 공통점과 [아이템의 타입에 따라 사용효과는 다르다]라는 다른점을 동시에 만족하면서 다양하게 사용할 수 있는 하나의 규격을 만들수 있다.

아래와 같이 스크립트를 만들어 보자

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

public interface ItemInterface // Interface 선언
{
   public void Use(); // 함수형태 정의
}

// Interface는 어디서든 접근할 수 있기에 반드시 public으로 선언한다.

 

위 스크립트는 ItemInterface라는 Interface를 선언하고 해당 인터페이스를 상속받았을 때 반드시 구현해야 하는 Use라는 메서드를 정의한 것이다.

 

이제 아이템 클래스들은 ItemInterface를 반드시 상속 받아야 하며 Use()라는 메서드를 구현해야 된다.

Use()라는 메서드는 [아이템의 효과를 사용한다.] 라는 공통적인 부분을 제공한다. 동시에 함수의 내용은 정의하지 않았기에 [아이템의 타입에 따라 사용효과는 다르다] 라는 다른점도 만족시켜준다.

또한 ItemInterface를 상속 받았기 때문에 아이템을 타입마다 각자 검사하는 것이 아닌 [ ItemInterface]를 가지고 있는지 만을 검사하고 Use()라는 메서드를 호출하기만 하면 되었다.

 

스크립트를 수정해보자

<Player 스크립트>

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

public class Player : MonoBehaviour
{

    public float hp; // player의 현재 hp가 저장될 변수
    [SerializeField] private float maxHp = 100; // player의 최대 hp값

    void Start()
    {
        this.hp = this.maxHp; // 시작시 player의 hp를 maxHp로 초기화
    }

    void Update()
    {
        
    }

    // private로 제한되어있는 hp변수에 값을 amount만큼 더해주는 메서드 
    public void SetHp(float amount)
    {
        this.hp += amount;
    }

    private void OnTriggerEnter(Collider other) // 충돌시
    {
        ItemInterface item = other.GetComponent<ItemInterface>(); // 충돌한 오브젝트가 ItemInterface을 가지고 있으면 item변수에 저장
        if(item != null) // 만약 hpPack이 null이 아니면 -> 충돌한 오브젝트가 ItemInterface를 가지고 있다. => 충돌한 것은 아이템이다.
        {
            item.Use(); // item변수에 저장된 컴포넌트의 Use()메서드 호출;
        }
    }
}

 

<HpPack 스크립트>

using System.Collections;
using System.Collections.Generic;
using Unity.Services.Analytics;
using UnityEngine;


// HpPack아이템 클래스
public class HpPack : MonoBehaviour, ItemInterface
{
    private GameObject playerGo; // Player의 게임 오브젝트를 저장할 변수
    private Player player; // Player의 게임 오브젝트에 붙어있는 Player스크립트 컴포넌트가 저장될 변수 
    private float hpAmount = 10.0f; // HP아이템 획득시 회복량
    void Start()
    {
        // Player라는 이름을 가지고 있는 GameObject를 찾아서 playerGo에 저장
        this.playerGo = GameObject.Find("Player");

        // playerGo라는 GameObject의 Player라는 스크립트 컴포넌트를 가져온다.
        this.player = this.playerGo.GetComponent<Player>();
    }

    // 회복량 만큼 Player의 Hp를 회복시켜주는 메서드
    public void Use()
    {
        this.player.SetHp(this.hpAmount);
    }
}

 

위의 스크립트는 아이템 각자의 타입을 전부 검사하는 것이 아닌 ItemInterface라는 Interface를 상속만 받았으면 묻고 따지지 않고 Use()메소드를 실행시킨다.


★결론★

Iterface를 사용하지 않아도 기능은 구현 할 수 있다. 그러나 Interface로 일정한 규격을 제공한다면 좀 더 쉽고 반복되는 작업을 줄일 수 있다.