키우기 or 타이쿤

게임 기획을 세세히 하다 보니 고민이 생겼다.

원래는 프랜차이즈를 경영한다는 콘셉트를 앞세워 타이쿤에 중점을 둘 생각이었다.

정말 좋아했던 타이쿤 게임...

그러나 계속 키워나가는 데에만 집중하는 키우기 게임과는 달리 망할 때도, 힘겨울 때도 있는 타이쿤 게임의 특성상 이걸 재미있게 기획할 수 있을 것인가? 에 대하여 많은 고민이 되었다.

만들 수야 있겠지만 최종적으로 플레이 스토어에 배포하는 것이 목표였기 때문에 퀄리티에 신경 쓸 수밖에 없었고, 기획에 너무 많은 시간을 할애하고 싶진 않았다.

 

 

경영 대신 성장, 확장

작은, 한 점포를 운영하다 점점 더 많은 가게를 운영하게 된다는 콘셉트는 유지할 것이나, 한 점포만 운영하는 기간은 매우 짧을 것인데 너무 세세하게 만들 필요는 없을 것 같다. 나중에 정말 타이쿤 게임을 만들게 되면 모를까!

대신 더 많은 성장 단계와, 성장 요소들을 추가하는 것으로 방향을 잡았다.

 

이를테면 점포의 성장 단계를 더 늘리는 것이라든가, 처음부터 전체 지도를 오픈하여 점포를 세우는 것이 아닌, 점포 수에 따라 세울 수 있는 지역을 확장하는 것 등 말이다.

 

 

 

다시 잡은 콘셉트

 

이후 게임을 더 업데이트했을 때 전국, 전 세계로 성장하면 재밌겠단 생각이 들었다.

 

 

 

수집 요소

이제 점포 하나를 직접 경영한다는 느낌은 없을 것이다.

대신 게임의 매력적인 요소가 무엇일지 생각하다 보니 요즘 키우기 게임에 빠지지 않고 들어가 있는 수집 요소가 생각났다.

원래도 구인 애플리케이션을 통해 직원을 뽑는 기능을 넣으려 했었으니 다양한 직원을 수집하는 걸로 결정했다.

 

다만 걱정이 되는 점은, 수집 요소가 들어가는 이상 그림 그리는 시간에 많은 투자를 해야 할 것이라는 점이다.

 

 

오늘의 회고

실질적으로 개발한 부분이 하나도 없다.

기획은 아직도 완벽하지가 않고 계속 바뀌기만 한다...

고민하고 노트에 끄적인 시간은 긴데 포스팅은 짧으니 답답하다.

아무래도 이번 주는 계속 기획만 하다 끝날 것 같은 예감이 든다. 쉽지가 않다.

 

다른 일이 있어 일주일만에 포스팅하였다.

 

프로젝트 시작 이유?

취업 한파로 구직은 마음처럼 되지 않지만, 아무것도 안 하고 누가 뽑아주길 기다리기만 할 수는 없다.

SQLD 시험도 저번 주로 끝났겠다 작은 개인 프로젝트를 시작해 보기로 결정했다.

 

목표는 플레이 스토어 출시!

그동안 몇 번 사이드 프로젝트를 만들어 봤으나 출시까지 이어졌던 적은 한 번도 없다.

그러나 많은 게임 회사에서 실제 배포 경험을 중요하게 보기도 하고, 정 안되면 이 게임으로 먹고살아봐야지 하는 마음도 있어서 이번엔 조잡하더라고 출시까지 가볼 생각이다.

 

 

게임 컨셉

게임의 컨셉은 전부터 생각하던 것이 있었기에 어렵지 않게 정했다.

그러나 기획자가 아닌 일개 프로그래머인 나로서는 게임을 하나하나 기획한다는 것은 어려운 일이다.

대체적인 컨셉만 있을 뿐... 메커니즘을 하나하나 정의하다 보니 기획을 같이 맡았던 졸작의 추억이 새록새록 났다.

(그래서 이미 있는 게임을 만드는 것이구나...)

 

어떤 게임을 만들 것인가?

<- 아이러브 커피, 고양이 스낵바 ->

 

타이쿤 게임, 그것도 일단 카페를 운영하는 게임을 만드려고 한다.

타이쿤 게임 자체는 정말 흔하지만, 나는 포커스를 조금 달리 하여 한 카페를 키우는 것이 아니라 최고의 프랜차이즈 카페를 키우는 게임을 컨셉으로 잡았다. (스X벅스, 이X야)

카페 하나로 시작해서 지점을 늘려 인기 있는 프랜차이즈의 회장님이 되는 것이 게임의 목표이다!

 

 

기획?

 

대강 구성 요소를 정리한다고 한 것이나 더럽다.

나름의 기획서를 써 놓아야 앞으로 작업이 편할 것 같지만 개발 작업 하기도 전에 나가떨어질 것 같아서 일단 손님이 오고 물건을 판다는 아주 핵심적인 기능을 프로토타이핑 해보며 무엇이 필요할지 살펴보려고 한다.

 

 

노션 활용하기

 

나는 대학 시절부터 노션을 사용해 왔기 때문에 이번 프로젝트에서도 적극적으로 활용할 생각이다.

일단 오늘 작업할 일을 체크리스트로 만들었고 정확히 어떤 식으로 구현할지 세부 사항을 적어두었다.

 

 

무작정 구현해 봄

 

이게 뭔가 싶겠지만 일단 돌아가는 걸 확인했다.

 

저 의문의 숫자들은 주문을 기다리는 사람 수, 제조를 기다리는 사람 수, 번 돈이다.

 

중요 코드 살펴보기

Employee

using System;
using System.Collections;
using UnityEngine;

// 알바생 능력치
public class EmployeeStatus
{
    public int Counter { get; set; }
    public int Make { get; set; }
    public int Clean { get; set; }
}

// 알바생의 정보들
public class EmployeeModel
{
    public string Name { get; set; }
    public EmployeeStatus EmployeeStatus { get; set; } = new EmployeeStatus();
    // 월화수목금토일 오픈팀 오후팀 마감팀
    public (bool open, bool afternoon, bool close)[] IsWorkTime { get; set; } = new (bool open, bool afternoon, bool close)[7];
}

public class Employee : MonoBehaviour
{
    // 정보들은 나중에 세이브 파일에서 가져오기 용이하도록 분리해 두었다.
    private EmployeeModel model = new EmployeeModel();

    // 일하고 있나요?
    public bool IsWorking { get => isWorking; }
    private bool isWorking;

    private void Awake()
    {
        
    }

    private void Start()
    {
        Initialize();
    }

    public void Initialize()
    {
        // test
        model.EmployeeStatus.Counter = 5;
        model.EmployeeStatus.Make = 5;
        model.EmployeeStatus.Clean = 5;

        for (int i = 0; i < model.IsWorkTime.Length; i++)
        {
            model.IsWorkTime[i].open = true;
            model.IsWorkTime[i].afternoon = true;
            model.IsWorkTime[i].close = true;
        }
    }

    public void StartWorkingCounter(Action onEndWorking)
    {
        Debug.Log("오더 시작");
        StartCoroutine(Working(onEndWorking, 10 / model.EmployeeStatus.Counter));
    }

    public void StartWorkingMake(int makingTime, Action onEndWorking)
    {
        Debug.Log("제조 시작");
        StartCoroutine(Working(onEndWorking, makingTime * 10 / model.EmployeeStatus.Make));
    }

    public void StartWorkingClean(Action onEndWorking)
    {
        StartCoroutine(Working(onEndWorking, 10 / model.EmployeeStatus.Clean));
    }

    // 일 시키는 코루틴.
    // 속도는 능력치에 비례한다.
    // 처리가 끝나면 콜백함수 실행
    private IEnumerator Working(Action onEndWorking, float duringTime)
    {
        isWorking = true;
        yield return new WaitForSeconds(duringTime);
        isWorking = false;

        onEndWorking.Invoke();
    }
}

 

 

Store

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

// 배치된 알바생들을 가지고 있는 클래스
// 카운터, 제조, 청소 세 가지 일거리가 있다.
public class placedEmployees
{
    public List<Employee> Counter { get; set; } = new List<Employee>();
    public List<Employee> Make { get; set; } = new List<Employee>();
    public List<Employee> Clean { get; set; } = new List<Employee>();
}

public class Store : MonoBehaviour
{
    // 인기도. 테스트를 위해 50으로 고정했다. 즉 반반의 확률로 손님이 온다.
    private int popularity = 50;

    // 주문 큐, 제조 큐. 밀린 주문, 제조를 나타낸다. 현재 진행중인 주문, 제조는 포함하지 않는다!
    private Queue<(int makingtime, int price)> orderQueue = new Queue<(int makingtime, int price)>();
    private Queue<(int makingtime, int price)> makingQueue = new Queue<(int makingtime, int price)>();
    // 점포에 소속된 모든 알바생 리스트이나 지금은 강제로 시작하자마자 배치하였기 때문에 사용하지 않았다.
    private List<Employee> employeesList = new List<Employee>();
    // 배치된 알바생들을 가지고 있음
    private placedEmployees placedEmployeeList = new placedEmployees();
    // 번 돈
    private int Money = 0;

    // 초기화 함수. 테스트 용으로 매개변수로 알바생들을 받고 있으나 없앨 예정.
    public void Initialize(Employee employee1, Employee employee2, Employee employee3)
    {
        placedEmployeeList.Counter.Add(employee1);
        placedEmployeeList.Make.Add(employee2);
        placedEmployeeList.Clean.Add(employee3);

        // Queue를 구독하는 코루틴 실행
        StartCoroutine(SubscribeOrderQueue());
        StartCoroutine(SubscribeMakingQueue());
    }

    // 현재 1초에 한 번 실행되고 있다.
    public void Order()
    {
        // 인기도에 따라 확률적으로 손님이 온다.
        if (popularity >= Random.Range(0, 100))
        {
            // test 나중에는 상품 목록 만들어 랜덤으로, 주문에 걸리는 시간도 랜덤으로 주면 좋을 듯
            orderQueue.Enqueue((3, 1000));
            // 주문 큐에 변동이 생겼기 때문에 UI 반영
            GameManager.instance.StoreUIPresenter.SetOrderCountUI(orderQueue.Count);
        }
    }

    // 판매 완료되면 호출. 번 돈을 반영 함
    private void Sale(int price)
    {
        Money += price;
        GameManager.instance.StoreUIPresenter.SetMoneyUI(Money);
    }

    // 주문 큐를 구독.
    // 대기가 하나라도 있고, 일 하지 않는 주문 담당 알바생이 있을 경우 일을 시킨다.
    private IEnumerator SubscribeOrderQueue()
    {
        while (true) 
        {
            if (orderQueue.Count > 0)
            {
                for(int i = 0; i < placedEmployeeList.Counter.Count; i++)
                {
                    if (!placedEmployeeList.Counter[i].IsWorking)
                    {
                        (int makingTime, int price) = orderQueue.Dequeue();
                        GameManager.instance.StoreUIPresenter.SetOrderCountUI(orderQueue.Count);
                        placedEmployeeList.Counter[i].StartWorkingCounter(() => 
                        {
                            makingQueue.Enqueue((makingTime, price));
                            GameManager.instance.StoreUIPresenter.SetMakingCountUI(makingQueue.Count);
                        });
                    }
                }
            }
            yield return null;
        }
    }

    // 제조 큐를 구독.
    // 대기가 하나라도 있고, 일 하지 않는 제조 담당 알바생이 있을 경우 일을 시킨다.
    private IEnumerator SubscribeMakingQueue()
    {
        while (true)
        {
            if (makingQueue.Count > 0)
            {
                for (int i = 0; i < placedEmployeeList.Make.Count; i++)
                {
                    if (!placedEmployeeList.Make[i].IsWorking)
                    {
                        (int makingTime, int price) = makingQueue.Dequeue();
                        GameManager.instance.StoreUIPresenter.SetMakingCountUI(makingQueue.Count);
                        placedEmployeeList.Make[i].StartWorkingMake(makingTime, () =>
                        {
                            Sale(price);
                        });
                    }
                }
            }
            yield return null;
        }
    }
}

 

이 외에 시간을 체크하고 다른 인스턴스들을 생성하는 GameManager, UI 표시를 담당하는 StoreUI 클래스가 있지만 생략.

스크립트에 대한 설명은 주석을 확인하면 된다.

 

 

오늘의 회고

함수 이름도 비문이고 로직도 깔끔하지 않다.

체인점을 많이 차리는 게 목표인 게임인데 점포, 알바생이 많아지면 수천 개의 객체를 생성해서 코루틴을 돌릴 것인가?

제일 핵심적인 부분을 구현해 본 만큼 무엇을 개선해야 할지, 전체적인 그림을 어떻게 그려야 할지 눈에 쏙쏙 들어왔다.

 

이번 목표는 그냥 구현하고 끝이 아니다.

클린 코드, 최적화를 목표로 하고 있기 때문에 한 줄 한 줄 신중하게 써 내려가도록 할 것이다.

그걸 위해서 구현은 접어 두고 잠시 구조를 탄탄히 하는 시간부터 가져봐야겠다.

+ Recent posts