Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

김산나

[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 2월 23일 회고록 - State Machine(FSM), 구글 플레이 스토어 출시 및 애드몹 본문

Unity Engine

[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 2월 23일 회고록 - State Machine(FSM), 구글 플레이 스토어 출시 및 애드몹

김산나 2026. 2. 23. 19:33

2026_02_23 강의 요약본

 

오랜만에 돌아온 강의 시간.

 

 

1. State Machine

게임의 상태를 관리하는 프레임워크이다.

Action + Enum조합으로 작동시킨다.

Enum은 알겠는데, Action은 무엇인가?

 

메서드의 깔때기?라고 보면 된다.

메서드를 담는 변수?이다.

 

<사용 예>

void SayHello(string name) => Console.WriteLine($"안녕, {name}!");
Action<string> messageAction = SayHello;
messageAction("Gemini"); // 출력: 안녕, Gemini!

 

메서드 생성, 그 메서드를 Action에 담고, Action에 변수를 넣으면 매개변수로 그대로 전달되어 내부의 메서드가 실행된다.

 

장점이라면 Action의 매서드를 탈부착하기 쉽다는 점이다.

특히 State machine에서 그 장점이 잘 보인다.

 

다시 State Machine으로 돌아오자.

 

<코드 전문>

더보기
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEditor.Experimental.GraphView;

// enum + switch방식으로 구현
// 자료구조 - Dictionary(상태 -> 색상), Stack(상태 히스토리) 사용

public class EnemyFSM : MonoBehaviour
{
    // 상태 정의
    public enum State{Idle, Chase, Attack, Flee};

    [Header("현재 상태")]
    public State currentState = State.Idle;

    [Header("플레이어 참조")]
    public Transform player;

    [Header("AI 설정")]
    public float chaseRange = 5f;
    public float attackRange = 1.5f;
    public float fleeDistance = 7f;
    public float moveSpeed = 3f;
    public float fleeSpeed = 4f;

    [Header("HP")]
    public float maxHp = 100f;
    public float hp = 100f;
    public float lowHpThreshold = 30f;
    
    Dictionary<State, Color> stateColor = new Dictionary<State, Color>()
    {
        {State.Idle, Color.white},
        {State.Chase, Color.yellow},
        {State.Attack, Color.red},
        {State.Flee, new Color(0.3f, 0.5f, 1f)}
    };
    
    Stack<State> stateHistory = new Stack<State>();

    // 상태 전환 콜백
    public Action<State, State> onStateChanged;
    SpriteRenderer sr;

    void Start()
    {
        sr = GetComponent<SpriteRenderer>();
        hp = maxHp;

        EnterState(currentState);
    }

    void EnterState(State state)
    {
        // Dictionary에서 색상 가져오기
        if(sr != null && stateColor.TryGetValue(state, out Color color))
        sr.color = color;
    }

    public void ChangeState(State newState)
    {
        if(currentState == newState)
        return;

        State oldState = currentState;
        ExitState(currentState);

        // Stack에 이전 상태 기록
        stateHistory.Push(currentState);
        currentState = newState;
        EnterState(newState);

        onStateChanged?.Invoke(oldState, newState);
        Debug.Log($"FSM {oldState}->{newState}");
    }

    // 상태 종료
    void ExitState(State state)
    {
        
    }

    void MoveToward(Vector3 target, float speed)
    {
        Vector2 dir = ((Vector2)target - (Vector2)transform.position).normalized;
        transform.position += (Vector3)(dir * speed * Time.deltaTime);
    }


    // --- HP 관련 ---
    public void TakeDamage(float amount)
    {
        hp = Mathf.Max(0, hp - amount);
    }

    public void Heal(float amount)
    {
        hp = Mathf.Min(maxHp, hp + amount);
        // HP 회복 후 도망 중이면 Idle로 복귀
        if (currentState == State.Flee && hp > lowHpThreshold)
            ChangeState(State.Idle);
    }
    // --- 외부 접근용 ---
    public float DistToPlayer()
    {
        if (player == null) return float.MaxValue;
        return Vector2.Distance(transform.position, player.position);
    }

    public Stack<State> GetStateHistory() => stateHistory;
    public Dictionary<State, Color> GetStateColors() => stateColor;

    void Update()
    {
        if(player == null) return;

        float dist = Vector2.Distance(transform.position, player.position);

        switch(currentState)
        {
            case State.Idle:
                // 가만히 서 있기 - 플레이어가 가까우면 추적 시작
                if(dist < chaseRange)
                    ChangeState(State.Chase);
                break;
            
            case State.Chase:
                // 플레이어를 향해 이동
                MoveToward(player.position, moveSpeed);
                if(dist < attackRange)
                    ChangeState(State.Attack);
                else if(dist > chaseRange)
                    ChangeState(State.Idle);
                break;
            
            case State.Attack:
                // 공격 상태 - HP가 낮으면 도망
                if(hp <= lowHpThreshold)
                    ChangeState(State.Flee);
                else if(dist > attackRange)
                    ChangeState(State.Chase);
                break;

            case State.Flee:
                // 플레이어의 반대 방향으로 도망
                Vector2 fleeDir = (Vector2)transform.position - ((Vector2)player.position).normalized;
                transform.position += (Vector3)(fleeDir * fleeSpeed * Time.deltaTime);

                if(dist > fleeDistance)
                {
                    hp = maxHp;
                    ChangeState(State.Idle);
                }
                break;
        }

    }

}

상태는 Idle, Chase, Attack, Flee 네 가지이다.

 

Idle: 플레이어가 감지범위 바깥에 있음

Chase: 플레이어가 감지범위 안으로 들어옴

Attack: 플레이어가 공격범위 안으로 들어옴

Flee: 자신의 HP가 30 미만으로 떨아질 때

 

각 상태를 enum으로 선언하고, 플레이어, 적 정보에 대한 필드를 작성한다.

 

그리고 Dictionary를 사용해 각 상태에 맞는 색상을 지정한다.

이 색상은 적의 상태를 시각적으로 확인하기 위한 용도이다. (sprite renderer의 색상으로 지정)

 

게임 시작 시 Idle상태가 됨.

계속 Idle상태로 업데이트 되다가 player가 chaseRange에 들어오면 state가 Chase로 변경된다.

    void Update()
    {
        if(player == null) return;

        float dist = Vector2.Distance(transform.position, player.position);

        switch(currentState)
        {
            case State.Idle:
                // 가만히 서 있기 - 플레이어가 가까우면 추적 시작
                if(dist < chaseRange)
                    ChangeState(State.Chase);
                break;

 

Switch문을 통해 관리한다.

현재 State에서 분기되는 조건을 해당 case에 넣어둔다.

 

그리고 state가 넘어갈 때, 내용을 기록하면 undo나 기보용으로 사용할 수 있다.

 

 

2. 구글 플레이스토어 출시

 

진짜 인디 개발자가 되는 단계.

저번 개인 프로젝트로 만든 게임으로 실습을 진행했다.

 

1단계! 애드몹 가입하기!

https://admob.google.com/

 

Google AdMob: 모바일 앱 수익 창출

인앱 광고를 사용하여 모바일 앱에서 더 많은 수익을 창출하고, 사용이 간편한 도구를 통해 유용한 분석 정보를 얻고 앱을 성장시켜 보세요.

admob.google.com

 

가장 무난합니다.

유니티도 " 적극 " 지원함...

 

애드몹을 가입한 뒤 다음 패키지를 Import합니다.

 

https://github.com/googleads/googleads-mobile-unity

 

GitHub - googleads/googleads-mobile-unity: Official Unity Plugin for the Google Mobile Ads SDK

Official Unity Plugin for the Google Mobile Ads SDK - googleads/googleads-mobile-unity

github.com

 

 

Import한 뒤 Asset > Google Mobile Ads > Setting으로 간다.

 

이 창이 뜹니다.

여기에 자신의 애드몹 ID를 넣어야 합니다.

애드몹 ID는 앱을 추가해야 발급받습니다.

 

앱 추가 선택

 

 

플랫폼 선택 후 아직 스토어에 등록하지 않았기 때문에 아니오 선택을 한다.

 

이름 결정 후 

 

 

광고 단위 선택

 

종류가 엄청 많은데, 종류마다 설명에 친절하게 사용법이 나와있기 때문에 걱정하지 않아도 된다.

배너, 보상형 두 개를 선택해 보았다.

 

https://developers.google.com/admob/unity/quick-start?hl=ko&_gl=1*vdvi9y*_up*MQ..*_ga*MTMxOTU3ODYyNi4xNzcxODQyMDcz*_ga_SM8HXJ53K2*czE3NzE4NDIwNzIkbzEkZzAkdDE3NzE4NDIwNzIkajYwJGwwJGgw

 

Google 모바일 광고 Unity 플러그인 설정  |  Google for Developers

Google 모바일 광고 Unity 플러그인을 설정하고 사용을 시작합니다.

developers.google.com

 

안내문의 Unity파트에서 각 광고의 종류마다 구현법이 나와있다.

내용을 참조하여 코드를 작성한다.

 

각 광고 단위마다 ID가 발급된다. 그걸 적절하게 넣으면 되는데, 테스트 과정엔 가이드에 있는 테스트 ID를 사용해야 한다.

안 그러면 정책 위반으로 정지먹을 수 있다고 한다...

 

 

테스트 광고판은 이런 식으로 나온다.

 

앱 출시는 구글 개발자 계정에 빌드 파일을 올려야 한다.

빌드는 apk가 아닌 abb라는 다른 형식이다.

 

 

이걸 체크하고 빌드하면 된다.

 

추가로 세팅해야 하는 내용

 

1) 해상도

본인의 개발 의도에 맞는 해상도를 선택한다.

그리고 가로 게임인지, 세로 게임인지, 둘 다 호환되는 게임인지에 따라 하단의 네 가지 체크표시를 다르게 해야 한다.

위 두 개는 세로, 아래 두 개는 가로이다.

 

 

핸드폰의 어느 면을 아래로 쓸 건지 선택하면 된다. 가로는 보통 둘 다 사용하는 게 좋기 때문에 둘 다 체크하는 편이 좋음 (안되는 앱 다 불편하드라)

 

 

유니티 스플레쉬 (로딩창) 제거

 

최소는 가장 낮은 버전으로, 타겟은 최신 API레벨로 해야 통과될 확률이 높다고 한다.

 

 

Keystore을 등록하고, 비밀번호를 입력한다.

 

이제 빌드를 한 뒤 

https://developer.android.com/distribute/console?hl=ko

 

Google Play Console  |  Android Developers

Google Play Console로 앱과 게임을 게시하고 Google Play에서 비즈니스를 성장시키세요. 앱 품질 개선, 잠재고객 참여 유도, 수익 창출 등에 도움이 되는 기능을 활용할 수 있습니다.

developer.android.com

안드로이드 플레이스토어 기준, 구글 개발자 콘솔 페이지에 업로드하면 된다.

25달러가 있어야 출시 가능하다 (1회성 비용)

그 돈이 없어서 구경했는데, 올리는 게 꽤 번거로웠다.

개인정보처리방침, 게임 소개, 스크린샷, 아이콘 등 미리 준비해야 할 것들이 있다.

익숙해지려면 좀 걸릴듯.

 

 

 

===========================================================

 

State Machine을 사용할 수 있게 되었다 !

플레이스토어에 게임을 올리고 광고를 적용할 수 있게 되었다 !