2D 콘텐츠 제작

Geometry Dash [#1] - 게임시작화면 만들고 배경 스크롤링

송현호 2023. 9. 11. 17:17

인게임 시스템을 만들기에 앞서 가장 먼저 지오메트리의 시작화면을 만들기로 결정했다. 

정지된 화면을 보고 임시로 화면을 구성해봤는데 나름 괜찮게 나와서 이정도면 거의 다 한 거 아닌가 싶었지만 생각보다 실제 움직이는 시작화면을 보고 나서 넣어야 할 요소들이 많다는 걸 깨달았다.

 

가장 눈에 띄는 것 3개는 일정 시간마다 출현해서 움직이는 캐릭터와 움직이는 배경 그리고 배경색 변화인데 캐릭터를 움직이는 건 나중에 스테이지를 설계하면서 같이 하는게 나을 것 같고 배경을 움직이는 건 시작화면에도 적용해야하고 스테이지에도 적용해야하기 때문에 가장 먼저 게임의 기본이 되는 배경 스크롤링과 색변화를 구현하기로 했다.

 

지오메트리 게임 시작화면의 배경 스크롤링의 특이점은 바닥 쪽 부분이 스크롤링 되는 속도가 뒷 부분 배경이 스크롤링 되는 속도보다 더 빨라서 원근감이 구현된다는 점이다

 

이 점을 유의하고 만들기로 하겠다.

 

임시로 구성해본 UI

1. 배경 스크롤링

일단 나는 배경 스크롤링에 대한 지식이 없기 때문에 검색을 해보기 전 가장 먼저 백그라운드를 움직여 보기로 하였다.

 

배경이 잘 움직이는 모습, 당연하지만 원래 위치로 돌아가진 않는다.

 

이번엔 백그라운드와 그라운드의 속도를 다르게 줘봤다.

인스펙터에서 background의 속도는 100 ground의 속도는 500으로 설정하였다.

 

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

public class BackgroundScroll : MonoBehaviour
{
    public float speedBg;
    public float speedG;
    public Transform background;
    public Transform ground;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        background.position += new Vector3(-speedBg, 0, 0) * Time.deltaTime;
        ground.position += new Vector3(-speedG, 0, 0) * Time.deltaTime;
    }
}

생각한대로 잘 작동하는 모습

 

여기까지 하고나서 든 생각은 배경 스크롤링을 2가지 방법으로 구현할 수 있을 것 같았다

첫번째는 카메라의 위치를 옮기는 것이고 두번째는 배경오브젝트의 위치를 변경하는 것이다.

 

내 경우는 백그라운드와 그라운드의 속도가 다르기 때문에,

카메라의 위치를 변경하는 방식으로 하면 자연스러운 위치를 맞추기 까다로울 것 같아서

일단 배경오브젝트의 위치를 변경하는 방식으로 코드를 짜보기로 했다.

 

초기화도 생각보다 늦고 y축 값을 설정 안해줘서 ground 플랫폼이 따로 노는 모습

 

고민해본결과 캔버스의 좌표를 바탕으로 백그라운드의 위치가 설정되어 있기 때문에 이러한 현상이 발생하는 것 같다고 생각했다. 그래서 캔버스의 좌표를 반영해서 코드를 다시 작성하였다.

 

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

public class BackgroundScroll : MonoBehaviour
{
    public float speedBg;
    public float speedG;
    public Transform background;
    public Transform ground;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //background x축의 -1156 이하일때 x축 값을 1920으로 설정
        if(background.position.x < -1150)
        {
            background.position = new Vector3(1920, 540, 0);
        }
        background.position += new Vector3(-speedBg, 0, 0) * Time.deltaTime;
        //ground x축이 -1146 이하일때 x축 값을 1146으로 설정
        if (ground.position.x < -1146)
        {
            ground.position = new Vector3(1146, 540, 0);
        }
        ground.position += new Vector3(-speedG, 0, 0) * Time.deltaTime;
    }
}

 

하지만 이렇게 작성하면 기기마다 해상도가 다르기 때문에

실제로 동작할때는 자연스럽게 보이지 않을 수 있다.

 

그래서 bg를 좀 더 늘려주고 3번째 백그라운드의 중간을 지나갈 때 2번째 백그라운드의 중간으로 배경을 이동하는 식으로 코드를 짜면 결국 카메라의 시야가 배경의 끝을 벗어나는 일이 없기 때문에 그렇게 코드를 수정해보았다.

 

background 안의 bg 하나의 길이는 2048x2048이므로 x가 -1024일때 x값을 1024로 바꾸도록 해봤다.

 

 

잘 작동하는 모습

 

마지막으로 ground의 앵커를 하단으로 고정해서 어떤 기기든지 화면을 벗어나지 않게 조정해주었다

 

 

시뮬레이터를 사용해서 실행해봐도 잘 작동한다.

 

2. 배경색 변경

 

 

지오메트리 대시의 배경화면은 다음 처럼 주기적으로 색상이 변경되는데 

 

다음 사진의 색상을 반시계 방향으로 돌면서 변경된다.

그래서 if문으로 코드를 짜봤는데 에러가 발생했다

 

for(int i =0; i < background.Length; i ++) {
            if (background[i].color.r >= 0 && background[i].color.r < 1 && background[i].color.g == 0 && background[i].color.b == 1)
            {
                background[i].color += new Color32(1, 0, 0, 0);
            }
            if (background[i].color.r == 1 && background[i].color.g == 0 && background[i].color.b <= 1 && background[i].color.b > 0)
            {
                background[i].color -= new Color32(0, 0, 1, 0);
            }
            if (background[i].color.r == 1 && background[i].color.g >= 0 && background[i].color.g < 1 && background[i].color.b == 0)
            {
                background[i].color += new Color32(0, 1, 0, 0);
            }
            if (background[i].color.r <= 1 && background[i].color.r > 0 && background[i].color.g == 1 && background[i].color.b == 0)
            {
                background[i].color -= new Color32(1, 0, 0, 0);
            }
            if (background[i].color.r == 0 && background[i].color.g == 1 && background[i].color.b >= 0 && background[i].color.b < 1)
            {
                background[i].color += new Color32(0, 0, 1, 0);
            }
            if (background[i].color.r == 0 && background[i].color.g <= 1 && background[i].color.g > 0 && background[i].color.b == 1)
            {
                background[i].color -= new Color32(0, 1, 0, 0);
            }
        }

255 0 0 일때 3번째 if문이 실행되야하는데 2번째 if문이 실행되고 rgb값이 (255,0,0)에서 갑자기 (255,0,255)로 바뀌는 버그가 발생했다 신기한건 (255,0,255)면 분홍색인데 화면에 보이는 색상은 빨간색이다;;

아무래도 코드보다는 시스템 내부 rgb값의 문제 같았기 때문에 코드를 고치기보다는 다른 방법을 선택하기로 했다 인터넷에 해결방법을 찾아본 결과 컬러시프트 구현에 Mathf의 PingPong을 사용하는 것을 알았다.

Mathf.Pingpong의 매개변수는  (float t, float length); 로 이루어져 있는데 t값을 0부터 length까지 반복하는 것이고 t가 length를 넘어가면 다시 감소하는 함수이다

이걸 이용해서 t에 Time.time을 넣어 시간을 6개로 나눠서 컬러시프트를 구현해보았다.

 

 정상적으로 잘 작동한다! 속도만 좀 조절해주면 실제 지오메트리 게임의 배경과 유사한 배경을 만들 수 있다.

 

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

public class BgColorCtrl : MonoBehaviour
{
    [SerializeField]
    private Image[] background;
    [SerializeField]
    private Image[] ground;

    private void Start()
    {
        for(int i = 0; i < background.Length; i++)
        {
            background[i].color = new Color32(0, 0, 255, 255);
        }
        for (int i = 0; i < ground.Length; i++)
        {
            ground[i].color = new Color32(0, 0, 255, 255);
        }
    }

    void Update()
    {
        BackGroundColorShift();
        GroundColorShift();
    }

    private void BackGroundColorShift()
    {
        for (int i = 0; i < background.Length; i++)
        {
            if (Time.time % 30 >= 0 && Time.time % 30 < 5)
            {
                background[i].color = new Color(Mathf.PingPong(Time.time / 5, 1), 0, 1, 1);
            }
            else if (Time.time % 30 >= 5 && Time.time % 30 < 10)
            {
                background[i].color = new Color(1, 0, Mathf.PingPong(Time.time / 5, 1), 1);
            }
            else if (Time.time % 30 >= 10 && Time.time % 30 < 15)
            {
                background[i].color = new Color(1, Mathf.PingPong(Time.time / 5, 1), 0, 1);
            }
            else if (Time.time % 30 >= 15 && Time.time % 30 < 20)
            {
                background[i].color = new Color(Mathf.PingPong(Time.time / 5, 1), 1, 0, 1);
            }
            else if (Time.time % 30 >= 20 && Time.time % 30 < 25)
            {
                background[i].color = new Color(0, 1, Mathf.PingPong(Time.time / 5, 1), 1);
            }
            else if (Time.time % 30 >= 25 && Time.time % 30 < 30)
            {
                background[i].color = new Color(0, Mathf.PingPong(Time.time / 5, 1), 1, 1);
            }
        }
    }

    private void GroundColorShift()
    {
        for (int i = 0; i < ground.Length; i++)
        {
            if (Time.time % 30 >= 0 && Time.time % 30 < 5)
            {
                ground[i].color = new Color(Mathf.PingPong(Time.time / 5, 1), 0, 1, 1);
            }
            else if (Time.time % 30 >= 5 && Time.time % 30 < 10)
            {
                ground[i].color = new Color(1, 0, Mathf.PingPong(Time.time / 5, 1), 1);
            }
            else if (Time.time % 30 >= 10 && Time.time % 30 < 15)
            {
                ground[i].color = new Color(1, Mathf.PingPong(Time.time / 5, 1), 0, 1);
            }
            else if (Time.time % 30 >= 15 && Time.time % 30 < 20)
            {
                ground[i].color = new Color(Mathf.PingPong(Time.time / 5, 1), 1, 0, 1);
            }
            else if (Time.time % 30 >= 20 && Time.time % 30 < 25)
            {
                ground[i].color = new Color(0, 1, Mathf.PingPong(Time.time / 5, 1), 1);
            }
            else if (Time.time % 30 >= 25 && Time.time % 30 < 30)
            {
                ground[i].color = new Color(0, Mathf.PingPong(Time.time / 5, 1), 1, 1);
            }
        }
    }
}