Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(82일차) - [팀플] 바둑알 효과 추가 및 씨네머신 활용

by 독기품은토끼 2025. 9. 16.

1. 바둑알 효과

팀플 4일차!

오늘은 오목판을 던질때 얼굴 변화, 애니메이션 동작이 있긴 하지만

뭔가 좀 부족한 느낌이 들어서 바둑알이 사방으로 튀는 효과까지 넣어주려고 한다.

public void SpawnStones() // 바둑알 튀는 연출
{
    if (omokBoard == null || stone == null || stone.Length == 0) return;

    var origin = omokBoard.transform.position;
    var forward = omokBoard.transform.forward;

    for (int i = 0; i < stoneCount; i++)
    {
        var prefab = stone[Random.Range(0, stone.Length)]; // 검은알, 흰알 중에 하나 랜덤으로 나타나게
        var go = Instantiate(prefab, origin, Random.rotation);

        // 3D 물리
        var rb = go.GetComponent<Rigidbody>();
        if (!rb) rb = go.AddComponent<Rigidbody>(); // 프리팹에 넣어두긴 했는데 혹시 몰라서 넣어둠

        // 퍼짐 방향 계산
        var randYaw = Random.Range(-spreadAngle, spreadAngle);
        var dir = Quaternion.AngleAxis(randYaw, Vector3.up) * forward;
        dir = (dir + Vector3.up * upwardBias).normalized;

        var force = Random.Range(forceMin, forceMax);
        rb.AddForce(dir * force, ForceMode.Impulse);

        Destroy(go, lifeTime);
    }
}

 

spreadAngle 값만큼 좌우로 퍼지도록 랜덤 각을 뽑은 후

Vector3.up (Y축)을 기준으로 randYaw만큼 회전시킨 방향을 만들어서 forward값을 곱해 그 방향으로 퍼지도록 해주었다.

forward값을 넣어주기 싫었는데

forward값을 빼면 dir값 자체가 Quaternion 타입의 값으로 갖고있어서 

dir = (dir + Vector3.up * upwardBias).normalized; 해당 연산할 때 Quaternion  + Vector 값으로는 연산이 불가능하니까 넣어주었다. (그렇다고 해당 연산을 빼버리면 바둑알이 바로 바닥에 고꾸라져서 좀 멋이 없다!)

 

2. 씨네머신 연출

우리 팀의 아이디어는 오목 게임에서 복싱으로 넘어가는 구조이다 보니

시각적으로 판이 뒤집어졌다! 라는 것을 표현해주고 싶었다.

그래서 예전에 수업시간에 배운 씨네머신 카메라를 활용해줄 것이다!

 

 

음.. 너무 심심한디ㅠ

그냥 씨네머신 카메라 두개 생성한 다음 재생시키니까 정말 멋없는 연출이 나왔다.

내가 원하는 건 카메라가 360도 회전하면서 바깥쪽으로 나아가길 바랬는데

지금은 그냥 뒤로 이동하기만 한다.

 

 

그래서 카메라에도 애니메이션을 적용시켜 주었다.

씨네머신 연출에 맞게 애니메이션이 동작해야했기 때문에 적절한 타이밍에 로테이션 값을 수정해주었다.

 

 

그리고 카메라가 무언가를 바라보도록 해주기 위해 Tracking Target에 빈 오브젝트를 넣어주었다. (위치만 설정해서)

이걸 안해주면 이상한 기준으로 360도 회전해버려서 이걸로 좀 보완해주었다.

 

 

타임라인을 활용해서 throw 애니메이션도 여기서 실행되도록 해주었고

팀원분이 만들어주신 벽 떨어지는 애니메이션도 적절한 위치에 넣어주었다.

 

그리고 오목경기장에서 복싱 경기장으로 바뀌도록 Active도 설정해주었다!

 

signal이라는 게 있다는걸 까먹고.. 이것저것 하느라 좀 너무 시간을 오래 잡아먹었다

 

3. 기존에 작성한 로직 문제 해결

1. DontDestroyOnLoad

Throw 스크립트를 처음에는 Room씬에 Throw Manager라는 빈 오브젝트의 컴포넌트로 추가했었는데

이렇게 하면 Room씬에 이미 캐릭터가 배치된 상태라면 정상적으로 던지는 애니메이션은 작동하지만

표정 변화는 원하는대로 작동되지 않는다. -> 기존 표정에 화난 표정이 덮어진 상태로 나왔다

그러니까 얼굴 두개가 같이 있었다는 거..

 

그래서 FindFirstObjectByType 으로 캐릭터 참조해서 표정 변화 해주었는데?

이러면 멀티에서는 작동이 안됨

왜냐면 씬에 캐릭터가 2개 이상 배치될 거기 때문에 가장 첫번째 캐릭터만 참조하기 때문임!!

 

그래서 throw 스크립트 자체를 캐릭터 컴포넌트로 넣는 방법을 택했음..

 

그런데 또 이렇게하면 생기는 문제는

Intro씬에서 Room씬으로 전환될 때 throw가 가져야하는 변수들 (결투 신청 버튼, 오목판 프리팹, 게시판 프리팹 등)이 인스펙터에 연결이 안됨

 

    void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    void OnSceneLoaded(Scene s, LoadSceneMode m)
    {
        StartCoroutine(ConnectionFrame()); // 로드 직후엔 못 찾는 문제 해결용
    }
    
    // 씬 전환 시 문제없도록 인스펙터 자동 연결
    void ConnectionThrowRefs()
    {
        if (!guideTarget)
        {
            var t = FindByTagAllScenes("Guide");
            if (t) guideTarget = t;
        }

        if (!omokBoard)
        {
            var t = FindByTagAllScenes("OmokBoard");
            if (t) omokBoard = t.gameObject;
        }
    }

    // 새로 로드된 씬에서 오브젝트 탐색 (캐릭터가 DontDestoryOnLoad로 가져와져서)
    Transform FindByTagAllScenes(string tag)
    {
        for (int s = 0; s < SceneManager.sceneCount; s++)
        {
            var sc = SceneManager.GetSceneAt(s);
            if (!sc.isLoaded) continue;

            foreach (var root in sc.GetRootGameObjects())
            {
                var trs = root.GetComponentsInChildren<Transform>(true);
                for (int i = 0; i < trs.Length; i++)
                    if (trs[i].CompareTag(tag)) return trs[i];
            }
        }
        return null;
    }

 

그래서 이런식으로 이벤트를 구독해서 씬이 로드될 때마다 참조를 갱신하게 해주었다.

 

2. 씬 로드 직후 탐색 실패 문제

근데 이 마저도 문제인게 바로 탐색을 시도하면 오브젝트가 로드되기 전에 쓔웅 탐색을 해버려서

또 null이 떠버리는 문제가 있었다.

    IEnumerator ConnectionFrame()
    {
        yield return null;
        ConnectionThrowRefs(); // 재탐색
        if (!omokAnim && omokBoard)
            omokAnim = omokBoard.GetComponent<Animator>();
    }

 

그래서 이 문제는 코루틴으로 해결해주었다.