🐧 들어가기 앞서

랜덤 다이스의 다이스를 만드는게 이렇게 어려울지,,,

세상에 똥겜은 없다.

모든 게임 개발자에게 존경을,,,


🐧 오늘 배운 것

다이스 타워의 정보를 만들었고, 다이스 타워를 생성하는 부분까지 제작했다.

💗 클래스 다이어그램

우선 타워의 클래스 다이어그램이다.


🐧 기억할 것 & 진행

1. Scriptable Object 타워 정보 구현

using UnityEngine;

[CreateAssetMenu(menuName = "Scriptable/TowerData", fileName = "Tower Data")]
public class TowerData : ScriptableObject
{
    [Header("Dice")]
    public string towerName = "Tower";
    public float towerAtkDamage = 5f;
    public float towerAtkSpeed = 1f;
    public float upgradeAtkDamage = 1f;
    public float upgradeAtkSpeed = 1f;
    public int towerLevel = 1;
    public Sprite sprite;

    [Header("Dot")]
    public Color dotColor = Color.white;
}

이렇게 진행하면, 타워의 정보를 생성할 수 있다.


2. Tower 구현

타워의 속성에 따라 색상이 다르니, 이미지를 포토샵으로 제작하고 타워 객체를 만들어서 스크립터블 오브젝트를 사용할 수 있게 스크립트를 작성했다.

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

public class Tower : MonoBehaviour
{
    public TowerData[] towerData;
    private TowerData selectedTowerData;

    public GameObject dotPrefab;
    private List<Dot> dots = new List<Dot>();

    public void Init()
    {
        if (towerData != null && towerData.Length > 0)
        {
            int randomTowerIndex = Random.Range(0, towerData.Length);
            selectedTowerData = towerData[randomTowerIndex];

            string towerName = selectedTowerData.towerName;
            gameObject.name = towerName;

            Image towerImage = GetComponent<Image>();

            if (towerImage != null && selectedTowerData.sprite)
            {
                towerImage.sprite = selectedTowerData.sprite;
            }

            float attackDamage = selectedTowerData.towerAtkDamage;
            float attackSpeed = selectedTowerData.towerAtkSpeed;

            int towerLevel = selectedTowerData.towerLevel;
            ActivateDots(towerLevel);
        }
        else
        {
            Debug.Log("데이터 없음");
        }
    }

    private Vector2[] CalculateDotPositions(int towerLevel)
    {
        Vector2[] positions = null;

        switch (towerLevel)
        {
            case 1:
                positions = new Vector2[] { Vector2.zero };
                break;
            case 2:
                positions = new Vector2[] { new Vector2(-30f, -30f), new Vector2(30f, 30f) };
                break;
            case 3:
                positions = new Vector2[] { new Vector2(-30f, -30f), Vector2.zero, new Vector2(30f, 30f) };
                break;
            case 4:
                positions = new Vector2[] { new Vector2(-30f, -30f), new Vector2(30f, -30f), new Vector2(-30f, 30f), new Vector2(30f, 30f) };
                break;
            case 5:
                positions = new Vector2[] { new Vector2(-30f, -30f), new Vector2(30f, -30f), Vector2.zero, new Vector2(-30f, 30f), new Vector2(30f, 30f) };
                break;
            case 6:
                positions = new Vector2[] { new Vector2(-30f, -30f), new Vector2(-30f, 0f), new Vector2(-30f, 30f), new Vector2(30f, -30f), new Vector2(30f, 0f), new Vector2(30f, 30f) };
                break;
            default:
                break;
        }

        return positions;
    }
    private void ActivateDots(int towerLevel)
    {
        foreach (var dot in dots)
        {
            dot.Deactivate();
            Destroy(dot.gameObject);
        }
        dots.Clear();

        Vector2[] dotPositions = CalculateDotPositions(towerLevel);

        for (int i = 0; i < dotPositions.Length; i++)
        {
            Vector2 dotPosition = dotPositions[i];

            GameObject dotObj = Instantiate(dotPrefab, transform);
            dotObj.transform.localPosition = new Vector3(dotPosition.x, dotPosition.y, 0f);

            Dot dot = dotObj.GetComponent<Dot>();
            dot.SetColor(selectedTowerData.dotColor);
            dot.Activate();

            dots.Add(dot);
        }
    }
}

어려웠던 점

아무래도 위치를 하드코딩해서 직접 정해주다보니 어려웠다.
오브젝트를 정해진 자리에 위치시키고, 켜고 끄는 방법도 있고
일반화시키는 방법도 있는데, 아무리 생각해도 일반화가 되지 않았다...

이런 바보같은 코드도 작성해봤는데,,

private Vector2 CalculateDotPosition(int index, int level)
{
    int row = Mathf.FloorToInt(index / Mathf.Ceil(Mathf.Sqrt(level)));
    int col = index % Mathf.FloorToInt(Mathf.Sqrt(level));
    float offsetX = Mathf.Floor(Mathf.Sqrt(level)) % 2 == 0 ? 30f : 0f;
    float offsetY = Mathf.Floor(Mathf.Sqrt(level)) % 2 == 0 ? 30f : 0f;
    return new Vector2((col - Mathf.Floor(Mathf.Sqrt(level)) / 2) * 30f + offsetX, (Mathf.Floor(Mathf.Sqrt(level)) / 2 - row) * 30f + offsetY);
}

다시 생각해보니 일반화 할 루틴이 없는 것 같기도,,

수학 고수의 피드백은 적극 환영입니다.


3. Dot

이건 Tower에 붙어서 Dot를 관리하는 코드다.

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

public class Dot : MonoBehaviour
{
    public Image dotImage;
    private Color dotColor; 

    private bool isActive = false;

    public void SetPosition(Vector2 position)
    {
        transform.localPosition = new Vector3(position.x, position.y, 0f);
    }

    public void SetColor(Color color)
    {
        dotColor = color;
        GetComponent<Image>().color = dotColor;
    }
    public void Activate()
    {
        isActive = true;
        gameObject.SetActive(true);
    }
    public void Deactivate()
    {
        isActive = false;
        gameObject.SetActive(false);
    }
    public bool IsActive()
    {
        return isActive;
    }
}

4. TowerSlotBG

타워의 배경화면 아래에 빈 타워 슬롯이 있다.

가장 아래에 버튼을 클릭하면, 빈 슬롯 아래 랜덤한 타워가 생성이 되어야 한다.

리스트로 슬롯을 담아서, 랜덤한 슬롯에 타워를 배치하고

만약 활성화 되어있다면, 활성화 되어있지 않은 슬롯에 랜덤으로 배치한다.

using System.Collections.Generic;
using UnityEngine;

public class TowerSlotBG : MonoBehaviour
{
    [SerializeField] private TowerSlot[] towerSlots;

    private void Start()
    {
        towerSlots = GetComponentsInChildren<TowerSlot>();
    }

    public void ActivateRandomTowerSlot()
    {
        List<TowerSlot> inactiveSlots = new List<TowerSlot>();

        foreach (TowerSlot slot in towerSlots)
        {
            GameObject tower = slot.transform.GetChild(0).gameObject;
            if (!tower.activeSelf)
            {
                inactiveSlots.Add(slot);
            }
        }
        if (inactiveSlots.Count > 0)
        {
            int randomIndex = Random.Range(0, inactiveSlots.Count);
            TowerSlot selectedSlot = inactiveSlots[randomIndex];
            GameObject tower = selectedSlot.transform.GetChild(0).gameObject;

            tower.SetActive(true);
            Tower towerScript = tower.GetComponent<Tower>();
            if (towerScript != null)
            {
                towerScript.Init();
            }
        }
    }
}

아쉬운점

조금 걸리는건, GetChild 함수를 사용했다는 것이다.
TowerSlot 아래에, Tower 객체가 하나 붙어있는데,
요것을 가져오게 하려면 Find를 쓰거나, FindwithTag를 쓰거나 등등 방법이있다.
직접 설정할 수 있지만, 일단 써봤는데

이것도 Find처럼 오류의 주범이 될지는 잘 모르겠다.

지금처럼 Tower가 하나만 있으면 괜찮지만 아마 Child 객체가 많아질수록
직접 인덱스를 설정하는게 문제가 생길지도?!


5. CreateTowerBtn

using UnityEngine;

public class CreateTowerBtn : MonoBehaviour
{
    public TowerSlotBG towerSlotBG;

    public void OnCreateTowerButtonClick()
    {
        if (towerSlotBG != null)
        {
            towerSlotBG.ActivateRandomTowerSlot();
        }
    }
}

버튼을 누르면, 타워가 생성되는 코드다. 슬롯BG 스크립트의 ActivateRandomTowerSlot메서드를 호출하자.

잘안보이긴 하지만 Tower -> 해당 타워 이름으로 변경되는 것을 확인할 수 있다.


🐧 게임에 구현한다면?


🐧 내일 할 일

  • Draggble, Droppable 이벤트 처리

  • Tower Merge(Upgrade) 처리

0개의 댓글