본문 바로가기

IndianPoker

[11] Battle Scene

턴 페이즈에서 사용할 TMP-Button들. 각 기능은 아직 만들지 않았다. 우선은 버튼만 만들고, 나중에 배치하자.

그 다음은 덱과 핸드, 사용할 Hand[0]의 카드 위치를 지정해놓자. 또, 카드들의 숫자와 이미지(Sprite)를 [CreateAssetMenu(fileName = "NewCardData")], ScriptableObject를 통해 데이터베이스처럼 만들어준다.

헷갈린 부분. Class 전체를 에디터에서 입력할 수 있게 받아오려면 SerializeField가 아니라 Serializable로 선언해야한다.

 

[Serializable] 특성은 인스턴스를 직렬화하는 기능으로, MonoBehaviour로 선언한 클래스를 에디터에서 값을 수정할 수 있는 것처럼 아래 인스턴스 변수에 접근할 수 있게 해준다. 

[SerializeField] 특성은 변수나 필드에 붙이는 것으로 클래스 자체에 적용되지 않고, 변수에 직접 적용할 때 사용된다.

 

※특수폴더 Resources

 

    private void Awake()
    {
        cardImage = GetComponent<SpriteRenderer>();
        if (CardData == null)
        {
            CardData = Resources.Load<CardDataBase>(DataBasePath);
        }
        //전부 뒷면으로 초기화
        cardImage.sprite = CardData.Cards[0].cardImage;
    }

Resources.Load 함수는 Assets 폴더 하위에 만든 Resources라는 특수 폴더에 넣은 데이터를 불러올 수 있는 유니티의 함수이다. 여러 카드가 만들어지더라도 데이터베이스를 한 번만 불러와도 되도록 static을 사용했고, 그 경우 Resources.Load를 통해 불러와주자.

 

이왕 카드 스크립트를 작성하는 김에 카드가 가지고 있으면 좋을 기능을 하나 만들어보자. 

 

인디언포커는 다른 카드게임처럼 카드 자체의 기능이 있지 않다. 그러나 온라인에서 하는 카드게임이라면 응당 있어야 하는 기능 중 하나가 바로 마우스 반응이다. 마우스를 카드에 올리면 움직이면서 어떤 카드를 고르고 있는 상태인지 확인시켜주는 기능이다. OnMouseOver와 OnMouseExit함수를 써서 진행하면 될 것이다.

    public void OnMouseOver() //마우스가 올려지면, 카드를 자세히 볼 수 있도록 중심을 향해 움직이는 것
    {
        //위에 있다면 아래로, 아래에 있다면 위로.
        Vector2 targetPosition = originalPosition.y > 0 ? originalPosition + Vector2.down * moveDistance : originalPosition + Vector2.up * moveDistance;
        transform.position = Vector2.Lerp(transform.position, targetPosition, Time.deltaTime * moveSpeed);
    }
    private void OnMouseExit()
    {
        transform.position = originalPosition;
    }

카드가 움직이는 방향은 항상 화면 중앙을 기준으로 위면 아래, 아래면 위. 처음 카드가 세팅된 장소인 originalPosition을 기준으로 판단한다.

중앙 아래 카드에 마우스를 올려놓기 전과 올려놓은 후. Lerp를 통해 카드 애니메이션처럼 부드럽게 움직인다.

 

이제 버튼, 카드를 했으니 칩 이미지도 넣을 시간이다. 에셋스토어에서 무료로 사용가능한 Chip 에셋을 가져오자.

덱 카드는 어차피 게임이 시작하면 카드를 나눠지며 사라질 장소. TableChip을 놓을 장소로 사용하자.

버튼 위치는 칩과 카드를 방해하지 않는 선에서 잘 배치해보자. 꼭 한 번에 모든 버튼이 보이지는 않으니, 몇 개는 겹쳐도 될 것이다. 일단은 비활성화해놓자. 

또, 플레이어가 가진 칩의 개수와 남은 턴의 시간. 이 둘은 TMP로 변경하자.

위의 칩 개체와 시간 개체가 가져야 할 것은 수치변동. 그렇지만 칩의 수치 변동은 쉬운 편이지만, 시간의 수치변동은 매 초 해주어야 한다. 코루틴을 통해 작성해보자.

    public TextMeshProUGUI remainingTurnTimeText;
    private float turnTime = 30.0f;

    public TextMeshProUGUI player1ChipText;
    public TextMeshProUGUI player2ChipText;

    private Coroutine turnTimeCoroutine;
    private void StartTurn()
    {
        if(turnTimeCoroutine != null)
        {
            StopCoroutine(turnTimeCoroutine);
        }
        turnTimeCoroutine = StartCoroutine(UpdateTurnTime());
    }
    IEnumerator UpdateTurnTime()
    {
        turnTime = 30.0f;
        while (turnTime >= 0f)
        {
            remainingTurnTimeText.text = $"Player : {turnTime.ToString("00")}s";
            turnTime--;
            yield return new WaitForSeconds(1);
        }
    }

여기서 참고할 것은 Coroutine을 하나 선언해서 계속 돌려쓴다는 점. 이것은 남은 턴의 시간을 알려주는 UI가 딱 하나면 존재하면 되기에 선언한 것이다. 이렇게 하나를 선언해서 계속 사용한다면 무분별한 코루틴 생성을 막을 수 있다. 또, 턴이 시간이 다 되기 전에 바뀌어도 기존의 코루틴을 손쉽게 중지할 수 있다.

 

시간이 지나면서 줄어들고, 다시 작동시키면 30초로 돌아가는 것 확인.

 

이렇게 각각의 기능들이 얼추 완성되어가는 참이다. 그렇지만 이런 경우 문제가 남아있다.

 

각각의 스크립트에서... 그러니까 스크립트 1의 A함수와 스크립트 2의 B함수가 동시에 실행된다고 할 때, 어떻게 그것을 알릴 것인가? 이벤트?

 

각 오브젝트 단위로 스크립트와 함수를 작성하고, 메인 스크립트인 SceneBattle에서 한 번에 컨트롤 하기로 했다. 다음 [12]에서는 SceneBattle을 재정렬하고, 각 오브젝트를 불러와보자.