1분기

- SDK, 빌드 등 설치 후 바로 사용할 수 있게 끔 파이프라인 구축 후 에셋패키지로 변경

- 나머지 분기 때 SDK와 Third-Party로 인한 병목현상 없게 끔 하기

- Low-BM 1개 출시

 

2분기

- Low-BM 3개 출시

- Middle-BM을 위한 파이프라인 구축

 

3분기

- Middle-BM 2개 출시

- High-BM을 위한 파이프라인 구축

 

4분기

- High-BM 1개 출시

 

 

 

Low-BM (AD > IAP)

BM에 광고가 대다수인 경우

IAP는 거의 없음

ex. 하이퍼 캐주얼, 수박게임과 같이 메인 시스템 한 개로 이루어진 게임

 

Middle-BM (AD = IAP)

BM 비율이 광고와 인앱결제가 비슷하거나 광고가 더 많은 경우

ex. 캐주얼 게임, 캣점프(스킨 등), 무한의 계단(동일), 머지게임

 

High-BM (AD < IAP)

BM 비율 중 광고가 거의 없고 IAP가 대부분인 경우

ex. 방치형 RPG, 슈팅게임

 

장르로 개발할 게임을 나누는 것 보단 BM으로 나누는 것이 편해 BM으로 나누었다.

BM이 간단하면 간단할 수록 컨텐츠도 줄어들고, 구현해야할 부분이 적어지고 신경 쓸 부분이 적어진다.

Low부터 시작하여 출시 파이프라인을 단단히 구축하고 개발 외 다른 부분에 병목현상이 없게 끔 1~2분기를 사용하고,

3~4 분기에는 BM을 탄탄하게 짠 게임을 개발하려 한다. 홧팅

로그라이크나 탑다운 슈팅 게임에서 사용할 수 있는 스크립트 입니다.

벽에 가려지지 않은 몬스터들 중에서 가장 가까운 몬스터를 반환하는 스크립트입니다.

 


[전체 코드]

    public Transform Target()
    {
        Transform result = null;
        GameObject[] activating_monsters = GameObject.FindGameObjectsWithTag("Monster");
        List<GameObject> ray_hitted_monsters = new List<GameObject>();

        foreach (GameObject monster in activating_monsters)
        {
            RaycastHit2D ray_hit = Physics2D.Raycast(transform.position, monster.transform.position - transform.position, 1000, (-1) - (1 << LayerMask.NameToLayer("Player")));

            if (ray_hit.collider.tag == "Monster")
            {
                ray_hitted_monsters.Add(ray_hit.collider.gameObject);
            }
        }

        if (ray_hitted_monsters.Count > 0)
        {
            result = ray_hitted_monsters[0].transform;

            float distance_with_monster = Vector2.Distance(transform.position, result.transform.position);

            foreach (GameObject monster in ray_hitted_monsters)
            {
                float distance = Vector2.Distance(transform.position, monster.transform.position);

                if (distance < distance_with_monster)
                {
                    result = monster.transform;
                    distance_with_monster = distance;
                }
            }
        }

        return result;
    }

 


        Transform result = null;
        GameObject[] activating_monsters = GameObject.FindGameObjectsWithTag("Monster");
        List<GameObject> ray_hitted_monsters = new List<GameObject>();

 

result = 반환해야하는 값

activating_monsters = 현재 씬에 Monster 태그가 붙어있는 모든 몬스터들

  • 로그라이크에 사용하려면 맵 이동 시에 activating_monsters를 설정해주면 됩니다.
  • 현재는 테스트 중이므로 임의로 사용한 코드입니다.

ray_hitted_monsters = 벽에 가려져있지 않은 몬스터들

 


        foreach (GameObject monster in activating_monsters)
        {
            RaycastHit2D ray_hit = Physics2D.Raycast(transform.position, monster.transform.position - transform.position, 1000, (-1) - (1 << LayerMask.NameToLayer("Player")));

            if (ray_hit.collider.tag == "Monster")
            {
                ray_hitted_monsters.Add(ray_hit.collider.gameObject);
            }
        }

 

모든 몬스터들 중 RaycastHit2D를 활용하여 Monster 태그가 붙어있으면 ray_hitted_monsters에 저장합니다.

무기가 플레이어 자식 오브젝트로, 플레이어 중심으로 실행되기 때문에 Player 레이어는 제외하고 실행합니다.

 


        if (ray_hitted_monsters.Count > 0)
        {
            result = ray_hitted_monsters[0].transform;

            float distance_with_monster = Vector2.Distance(transform.position, result.transform.position);

            foreach (GameObject monster in ray_hitted_monsters)
            {
                float distance = Vector2.Distance(transform.position, monster.transform.position);

                if (distance < distance_with_monster)
                {
                    result = monster.transform;
                    distance_with_monster = distance;
                }
            }
        }

 

만약 모든 몬스터 중 벽에 가려지지 않은 몬스터가 있다면 실행되는 코드입니다.

ray_hitted_monsters 중 가장 가까운 거리의 몬스터를 찾는 스크립트 입니다.

'Unity' 카테고리의 다른 글

[Unity] 조이스틱으로 플레이어 이동  (2) 2023.11.10

- 플레이어 이동 코드는 예시로 작성하였습니다. 플레이어 이동 코드는 stick_direction 변수를 사용하여 만드시면 됩니다.

 


[전체 코드]

using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;

public class Joy_Stick : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
    public Transform stick;
    public Transform player;

    private Vector3 stick_offset_position;
    private Vector3 stick_direction;
    private float joy_stick_radius;

    void Start()
    {
        Set_Radius();
    }

    private IEnumerator Move_Player()
    {
        while (true)
        {
            player.Translate(stick_direction * Time.deltaTime * 10.0f);

            yield return null;
        }
    }

    private void Set_Radius()
    {
        joy_stick_radius = GetComponent<RectTransform>().sizeDelta.y * 0.5f;

        float canvas_radius = transform.parent.GetComponent<RectTransform>().localScale.x;
        joy_stick_radius *= canvas_radius;
    }

    public void OnDrag(PointerEventData eventData)
    {
        Vector3 touch_position = Camera.main.ScreenToWorldPoint(eventData.position);
        touch_position.z = 0;

        stick_direction = (touch_position - stick_offset_position).normalized;

        float touch_distance = Vector3.Distance(touch_position, stick_offset_position);

        if (touch_distance < joy_stick_radius)
        {
            stick.position = stick_offset_position + stick_direction * touch_distance;
        }
        else
        {
            stick.position = stick_offset_position + stick_direction * joy_stick_radius;
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        stick_offset_position = stick.transform.position;

        StartCoroutine(Move_Player());
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        StopAllCoroutines();

        stick.position = stick_offset_position;
        stick_direction = Vector3.zero;
    }
}

    public void OnPointerDown(PointerEventData eventData)
    {
        stick_offset_position = stick.transform.position;

        StartCoroutine(Move_Player());
    }​

- 조이스틱을 터치 하였을 때 스틱의 초기 위치를 설정.

- 플레이어 이동 코루틴 실행

 


    public void OnPointerUp(PointerEventData eventData)
    {
        StopAllCoroutines();

        stick.position = stick_offset_position;
        stick_direction = Vector3.zero;
    }

- 조이스틱을 누른 후 손가락을 화면에서 때었을 때 실행.

- 모든 코루틴을 정지

- 스틱을 초기 위치에 설정.

- 조이스틱 방향 초기화

 


    public void OnDrag(PointerEventData eventData)
    {
        Vector3 touch_position = Camera.main.ScreenToWorldPoint(eventData.position);
        touch_position.z = 0;

        stick_direction = (touch_position - stick_offset_position).normalized;

        float touch_distance = Vector3.Distance(touch_position, stick_offset_position);

        if (touch_distance < joy_stick_radius)
        {
            stick.position = stick_offset_position + stick_direction * touch_distance;
        }
        else
        {
            stick.position = stick_offset_position + stick_direction * joy_stick_radius;
        }
    }

        Vector3 touch_position = Camera.main.ScreenToWorldPoint(eventData.position);
        touch_position.z = 0;

- 터치 한 곳의 위치값을 받아온 후 z값을 0으로 변경.


        stick_direction = (touch_position - stick_offset_position).normalized;

- 현재 터치 중인 위치와 스틱의 초기 위치를 사용하여 현재 터치 위치의 방향을 받아옴.


        float touch_distance = Vector3.Distance(touch_position, stick_offset_position);

        if (touch_distance < joy_stick_radius)
        {
            stick.position = stick_offset_position + stick_direction * touch_distance;
        }
        else
        {
            stick.position = stick_offset_position + stick_direction * joy_stick_radius;
        }

- 터치 중인 위치와 스틱의 초기 위치에 비례한 거리와 조이스틱의 반지름 크기를 비교하여 더 작다면 스틱을 현재 터치 중인 곳으로, 더 크다면 위치를 조이스틱의 반지름 크기에 비례하여 이동.

 

 


    private void Set_Radius()
    {
        joy_stick_radius = GetComponent<RectTransform>().sizeDelta.y * 0.5f;

        float canvas_radius = transform.parent.GetComponent<RectTransform>().localScale.x;
        joy_stick_radius *= canvas_radius;
    }

 

Set_Radius()

- 조이스틱의 크기와 캔버스의 크기에 비례해서 반지름을 계산.

- 인 게임 중에서는 처음에 한 번만 불러오기 때문에 Awake나 Start에서 사용.

 

 

 

 

 

 

 

 

'Unity' 카테고리의 다른 글

[Unity] 유니티 가장 가까운 몬스터 찾기  (0) 2023.11.10
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MultipleObjectPooling : MonoBehaviour
{
    public GameObject[] poolPrefabs;
    public int poolingCount;

    private Dictionary<object, List<GameObject>> pooledObjects = new Dictionary<object, List<GameObject>>();

    public void CreateMultiplePoolObjects()
    {
        for (int i = 0; i < poolPrefabs.Length; i++)
        {
            for (int j = 0; j < poolingCount; j++)
            {
                if (!pooledObjects.ContainsKey(poolPrefabs[i].name))
                {
                    List<GameObject> newList = new List<GameObject>();
                    pooledObjects.Add(poolPrefabs[i].name, newList);
                }

                GameObject newDoll = Instantiate(poolPrefabs[i], transform);
                newDoll.SetActive(false);
                pooledObjects[poolPrefabs[i].name].Add(newDoll);
            }
        }
    }

    public GameObject GetPooledObject(string _name)
    {
        if (pooledObjects.ContainsKey(_name))
        {
            for (int i = 0; i < pooledObjects[_name].Count; i++)
            {
                if (!pooledObjects[_name][i].activeSelf)
                {
                    return pooledObjects[_name][i];
                }
            }

            int beforeCreateCount = pooledObjects[_name].Count;

            CreateMultiplePoolObjects();

            return pooledObjects[_name][beforeCreateCount];
        }
        else
        {
            return null;
        }
    }
}

싱글톤 디자인 패턴은 유니티상에서 다른 스크립트에 접근을 해야할 때 번거로움을 줄여주어

편하게 해주는 용도로 사용하는 디자인 패턴입니다.

 

단 싱글톤을 적용할 스크립트는 씬 내에서 하나밖에 존재하지 않다는 가정하에 사용해야 합니다.

 

예로 게임 내에서 돈을 관리하는 스크립트는 MoneyManager 스크립트 하나 뿐일 것이므로

MoneyManager에 싱글톤 디자인 패턴을 사용하여 스크립트에 접근하기 쉽고 간편하게 하는 것 입니다.

 

빠르게 코드 먼저 훑고 가겠습니다.

 

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

public class SingleTon<T> : MonoBehaviour
{
    private static T _instance;

    public static T instance
    {
        get
        {
            return _instance;
        }
    }
    protected virtual void Awake()
    {
        _instance = this.GetComponent<T>();
    }
}

 

싱글톤 스크립트는 이렇게 짭니다.

 

T.instance 로 접근하게 됩니다.

 

에제 보겠습니다

 

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

public class CometManager : SingleTon<CometManager>
{
    public Transform waypoint;
    [HideInInspector] public Transform[] waypoints;

    public ObjectPooling comets;

    private void Start()
    {
        waypoints = waypoint.GetComponentsInChildren<Transform>();
        comets = GetComponent<ObjectPooling>();
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            StartCoroutine(SpawnComet(10, 10));
        }
    }

    public IEnumerator SpawnComet(int cometCount, float _maxHealth)
    {
        yield return null;

        for (int i = 0; i < cometCount; i++)
        {
            Comet newComet = comets.Pooling().transform.GetComponent<Comet>();
            newComet.maxHealth = _maxHealth;
            newComet.gameObject.SetActive(true);

            yield return new WaitForSeconds(1);
        }
    }
}

 

 

 

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

public class Comet : MonoBehaviour
{
    public float maxHealth;
    private float currentHealth;

    private int currentIndex;

    private void OnEnable()
    {
        currentIndex = 0;
        currentHealth = maxHealth;
        transform.position = CometManager.instance.waypoint.position;
        StartCoroutine(MoveToWayPoint());
    }

    private void OnDisable()
    {
        StopAllCoroutines();
    }

    private IEnumerator MoveToWayPoint()
    {
        while (true)
        {
            yield return null;

            transform.position = Vector3.MoveTowards(transform.position, CometManager.instance.waypoints[currentIndex].position, 2.0f * Time.deltaTime);

            if(transform.position == CometManager.instance.waypoints[currentIndex].position)
            {
                if(currentIndex >= 3)
                {
                    currentIndex = 0;
                }
                else
                {
                    currentIndex++;
                }
            }
        }
    }

    public void GetDamage(float _damage)
    {
        if(currentHealth - _damage > 0)
        {
            currentHealth -= _damage;
        }
        else
        {
            gameObject.SetActive(false);
        }
    }
}

 

 

 

씬 내에서 단 하나밖에 존재하지 않는 CometManager 스크립트를 생성 후 싱글톤을 사용하여

접근이 용이하게 만들었습니다.

 

Comet 스크립트 상에서 CometManager 스크립트에 접근할 때 CometManager.instance 이 코드 하나만으로

CometManager 스크립트에 접근하였습니다.

 

 

혹 이해가 되지 않거나 다른 궁금한 점이 있으시다면 댓글 남겨주시면 최대한 빠른 시일 내에 답변 드리겠습니다.

 

감사합니다.

오브젝트 풀링은 게임 오브젝트를 생성하고 삭제하는 스크립트 상에서 GC(Garbage Collector)를 생성하지 않기 위해

오브젝트 풀링 디자인 패턴을 주로 사용하게 됩니다.

 

우선 오브젝트 풀링을 검색하고 사용하려는 사람들은 프로그래밍 언어에 대한 기본적인 지식이 있다고 생각하여

작동 원리만 간단히 설명하겠습니다.

 

유니티 상에서의 오브젝트 풀링의 기본적인 작동원리는 게임 오브젝트를 생성할 때 GC(Garbage Colletor)를 생성하지 않기

위해 Destroy를 최소한으로 사용하여 오브젝트를 생성 및 파괴를 하는 원리입니다.

 

InstantiateDestroy 대신 gameObject.SetActive(boolean)를 사용하게 되는데 생성할 때는 

gameObject.SetActive(true)를, 파괴할 때는 gameObject.SetActive(false)를 사용하게 된다.

 

애니팡과 같은 퍼즐게임을 예로 들었을 때

만일 파란색 박스 안의 박스들을 파괴하게 되면 핑크색 박스 안의 박스들이 아래로 내려오게 되는데

이때 파괴한 파란색 박스안의 박스들을 gameObject.SetActive(false)로 파괴를 대신하여

재사용 가능한 상태로 만들어주어 GC(Garbage Collector)를 생성하지 않게 합니다.

 

또한 핑크색 박스안의 박스들은 gameObject.SetActive(true)를 사용하여 Instantiate를 사용하지 않아 

GC(Garbage Collector)가 생성되지 않습니다.

이런 식으로 계속해서 반복하여 GC(Garbage Collector)를 생성하지 않게 합니다.

 

유니티 상에서 사용 가능한 ObjectPooling 예제를 아래에 남겨놓겠습니다.

 

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

public class ObjectPooling : MonoBehaviour
{
    public List<GameObject> pooledObjects = new List<GameObject>();
    public GameObject poolPrefabs;

    public int poolingCount;

    private void Awake()
    {
        CreatePoolObjects();
    }

    public void CreatePoolObjects()
    {
        for (int i = 0; i < poolingCount; i++)
        {
            GameObject newPoolingObject = Instantiate(poolPrefabs, transform.position, transform.rotation, transform);
            newPoolingObject.SetActive(false);
            pooledObjects.Add(newPoolingObject);
        }
    }

    public GameObject GetPooledObject()
    {
        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (!pooledObjects[i].activeSelf)
            {
                return pooledObjects[i];
            }
        }

        int beforeCreateCount = pooledObjects.Count;

        CreatePoolObjects();

        return pooledObjects[beforeCreateCount];
    }

    public List<GameObject> GetAllDeactivePooledObjects()
    {
        List<GameObject> deactivePooledObjects = new List<GameObject>();

        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (!pooledObjects[i].activeSelf)
            {
                deactivePooledObjects.Add(pooledObjects[i]);
            }
        }

        return deactivePooledObjects;
    }

    public List<GameObject> GetAllActivePooledObjects()
    {
        List<GameObject> activePooledObjects = new List<GameObject>();

        for (int i = 0; i < pooledObjects.Count; i++)
        {
            if (pooledObjects[i].activeSelf)
            {
                activePooledObjects.Add(pooledObjects[i]);
            }
        }

        return activePooledObjects;
    }
}

 

게임이 처음 시작한 후 설정해놓은 poolingCount만큼 poolPrefabsInstantiate로 먼저 생성해주고

이후 GetPooledObject를 통해 현재 gameObject.activeSelffalseGameObject를 반환해주는 함수를 통해

오브젝트 풀링을 사용할 수 있습니다.

 

혹 이해가 되지 않거나 어려운 부분이 있으면 댓글로 남겨주세요.

최대한 빠른 시일내에 답변드리겠습니다.

 

감사합니다.

+ Recent posts