기존에 TrackModifier 클래스에는 frequency변수가 있는데 이 변수가 현재 장애물이나, 기타 트랙에 나오는 모든 오브젝트들의 간격을 책임지고 있다.
나는 여기서 장애물이 이 일정한 간격을 유지한 채로 연속으로 나오게 하는 것을 구현하고 싶었다.
처음에는 코드에서 건드리지 말고 유니티에서 TrackObject를 추가해서 frequency로 차이를 둬서 연속으로 보이는 느낌이 나게 해 볼까 하다가 은근히 노가다일 것 같단 생각을 했다.
예전에는 귀찮아도 코드로 짜는 게 더 싫어서 노가다를 해봤겠지만 요즘엔 안되면 말지란 생각으로 그냥 코드를 건드려본다. 그런 걸 보면 예전보다는 코드랑 사이가 더 좋아졌는지도?
일단 내가 처음 생각했던 건 그냥 frequency 변수를 하나 더 추가하자였다.
연속으로 나오게 하고 싶은 장애물의 개수를 지정해 줄 변수로 groupSize로 정해주고, 그룹 내 장애물 간격을 정해줄 변수로 innerFrequency를 만들어 줬다.
생각보다 쉽게 빠르게 작업이 완료되었다. 아래 코드가 내가 만들어 둔 전체 함수 코드이다.
나중에 또 다른 곳에서 재사용하게 될 수도 있으니 groupSize, innerFrequency 변수를 매개변수로 빼서 관리해줄까도 싶었지만 필요하면 그때 바꿔주기로 하고, 지금은 매개변수로 너무 많은 애들이 들어가 있어서 추가해 주면 가독성이 너무 떨어질 것 같아 그냥 저대로 두었다.
m.label에 따라 트랙에 연속으로 그룹지게 나오게 하고 싶은 장애물이 있을 때 아래 AssignTrackGroupPosition함수를 불러와 사용해 주면 된다.
바로 다음날 작업한 걸 정리하려던 나는 내일 할까 미뤘다가 파일정리를 하면서 Shift + Delete로 프로젝트를 삭제시키는 끔찍한 실수를 저질렀다. 잠시 잠깐 멘붕에 빠져있다가 PuranUtilitiesSetup이라는 복원프로그램을 사용해 봤다.(이 프로그램은 공짜로 풀린 복원 프로그램인 것 같은데 안타깝게도 cs파일은 찾지 못하는 것 같았다. 텍스트 파일이나 사진파일 같은 건 바로 찾을 수 있는 것 같았음.)
나의 경우는 파일을 찾지도 못했을 뿐더러 중간에 갑자기 노트북이 업데이트되는 해프닝이 있었다. 업데이트가 되길래 약간은 불안했다만 "그저 기우겠지."란 생각으로 더 이상 생각하지 않기로 했다. 그렇게 노트북이 힘겨워하기에 그만두고 다시 구현하는 쪽을 선택해서 지금 다시 올린다. 그래도 한번 만들어 봤다고 시간이 엄청 투자되지는 않아서 다행이었다.
using UnityEngine;
using System.Collections.Generic;
public class ObstacleManager : MonoBehaviour
{
// 도로의 전체 너비와 카메라 높이 설정
[Header("Road Settings")]
public int roadWidth = 1;
public float cameraHeight = 1000;
public float speed = 0.8f;
[Header("Obstacle Settings")]
public GameObject[] obstaclePrefabs;
[SerializeField] private float obstacleSpeed = 0.8f;
[SerializeField] private float spawnY = 3.16f;
[SerializeField] private float endYPosition = -6f;
[SerializeField] private int poolSize = 5;
[SerializeField] private int maxObstacles = 3; // 동시에 존재할수 있는 최대 장애물 수
[SerializeField] private float minObstacleSpacing = 0.5f; // 장애물 X간 최소 간격
[SerializeField] private float heightCheck = 1f; // 장애물 Y간 최소 간격
[SerializeField] private int consecutiveNum = 3; //연속 장애물 허용 범위
[SerializeField] private float spawnRate = 0.02f;
private Queue<GameObject>[] obstaclePools;
private List<GameObject> activeObstacles = new List<GameObject>();
private int consecutiveSpawnCount = 0; // 연속 생성 횟수
private bool? lastSpawnSide = null; // 마지막 생성 방향 (null: 초기상태)
private float spawnTimer;
private readonly Vector3 scaleVector =Vector3.one;
private void Awake()
{
activeObstacles = new List<GameObject>(maxObstacles);
InitializeObstaclePools();
}
private void InitializeObstaclePools()
{
obstaclePools = new Queue<GameObject>[obstaclePrefabs.Length];
for (int i = 0; i < obstaclePrefabs.Length; i++)
{
obstaclePools[i] = new Queue<GameObject>();
for (int j = 0; j < poolSize; j++)
{
CreateObstacles(i);
}
}
}
private void CreateObstacles(int typeIndex)
{
GameObject obj = Instantiate(obstaclePrefabs[typeIndex], transform);
obj.SetActive(false);
obj.AddComponent<ObstacleData>();
obstaclePools[typeIndex].Enqueue(obj);
}
private void Update()
{
spawnTimer += Time.deltaTime;
if (spawnTimer >= spawnRate)
{
spawnTimer = 0f;
if (Random.value < spawnRate)
{
SpawnObstacle();
}
}
UpdateObstacles();
}
private void UpdateObstacles()
{
Vector3 position;
float progress, scale, xOffset, newX, newY;
for (int i = activeObstacles.Count - 1; i >= 0; i--)
{
GameObject obstacle = activeObstacles[i];
ObstacleData data = obstacle.GetComponent<ObstacleData>();
position = obstacle.transform.position;
// 장애물의 진행도 계산 (0~1 사이 값)
progress = (spawnY - position.y) / (spawnY - (transform.position.y + endYPosition));
// 진행도에 따른 크기 보간
scale = Mathf.Lerp(0.1f, 3f, progress);
// 바깥쪽으로 이동하는 힘을 더 강하게 수정
xOffset = data.isLeftSide ? -progress * 4f : progress * 4f;
newX = data.initialX + (xOffset * roadWidth);
// 스케일이 커질수록 더 빠르게 이동 //scale 0.1_기본속도 1, scale 1.5_기본속도 4.5, scale 최대_기본속도 8.25
newY = position.y - (obstacleSpeed * (1f + (scale - 0.1f) * 2.5f) * Time.deltaTime);
position.x = newX;
position.y = newY;
obstacle.transform.position = position;
obstacle.transform.localScale = scaleVector * scale;
if (newY < transform.position.y + endYPosition)
{
ReturnToPool(obstacle);
activeObstacles.RemoveAt(i);
}
}
}
public class ObstacleData : MonoBehaviour // 장애물의 초기 x위치와 방향을 저장하는 컴포넌트
{
public float initialX;
public bool isLeftSide;
}
private bool IsValidSpawnPosition(Vector3 newPosition)
{
foreach (GameObject obstacle in activeObstacles)
{
// y축 간격이 heightCheck의 간격보다 작은지 체크
if (Mathf.Abs(obstacle.transform.position.y - newPosition.y) < heightCheck)
{
// x축 간격이 최소 간격보다 작으면 생성 불가
if (Mathf.Abs(obstacle.transform.position.x - newPosition.x) < minObstacleSpacing)
{
return false;
}
}
}
return true;
}
private void SpawnObstacle()
{
if (activeObstacles.Count >= maxObstacles)
{
return;
}
//50% 확률로 장애물 왼쪽, 오른쪽으로 스폰
bool spawnOnLeft = Random.value > 0.5f;
HandleConsecutiveSpawns(ref spawnOnLeft);
int typeIndex = Random.Range(0, obstaclePrefabs.Length);
GameObject obstacle = GetObstacleFromPool(typeIndex);
if (obstacle == null)
{
return;
}
// 초기 생성 위치를 도로 중앙 근처로 수정 //0.1 수치 증가시키면 장애물이 도로의 바깥쪽 생성,감소 >>중앙에 가깝게 생성.
float xPos = spawnOnLeft ? -roadWidth * 0.1f : roadWidth * 0.1f;
Vector3 newPosition = new Vector3(xPos, spawnY, 0);
// 새로운 위치가 적절한 간격을 유지하는지 확인
if (!IsValidSpawnPosition(newPosition))
{
ReturnToPool(obstacle);
return;
}
SetupObstacle(obstacle, xPos, spawnOnLeft);
}
private void HandleConsecutiveSpawns(ref bool spawnOnLeft) //연속으로 같은 방향에 장애물이 생성되는 것을 제어
{
// 이전과 같은 방향으로 생성하려는 경우
if (lastSpawnSide.HasValue && spawnOnLeft == lastSpawnSide.Value)
{
consecutiveSpawnCount++;
// consecutiveNum번 연속 생성되면 반대편에 생성
if (consecutiveSpawnCount >= consecutiveNum)
{
spawnOnLeft = !lastSpawnSide.Value;
consecutiveSpawnCount = 0;
}
}
else
{
consecutiveSpawnCount = 0;
}
lastSpawnSide =spawnOnLeft;
}
private void SetupObstacle(GameObject obstacle, float xPos, bool isLeft) // 생성된 장애물의 초기 설정을 담당
{
obstacle.transform.position = new Vector3(xPos, spawnY, 0);
obstacle.transform.localScale = scaleVector * 0.1f;
var data = obstacle.GetComponent<ObstacleData>();
data.initialX = xPos;
data.isLeftSide = isLeft;
obstacle.SetActive(true);
activeObstacles.Add(obstacle);
}
private void ReturnToPool(GameObject obstacle)
{
obstacle.SetActive(false);
for (int i = 0; i < obstaclePrefabs.Length; i++)
{
if (obstacle.name.Contains(obstaclePrefabs[i].name))
{
obstaclePools[i].Enqueue(obstacle);
break;
}
}
}
private GameObject GetObstacleFromPool(int typeIndex)
{
return obstaclePools[typeIndex].Count > 0 ? obstaclePools[typeIndex].Dequeue() : null;
}
}
장애물은 왼쪽과 오른쪽에 생성되는 장애물에 따라 도로 벽 쪽으로 붙어서 떨어지게 작업했다.
만들다 보니까 겹쳐서 생성될 때도 있길래 최소 x, y값의 간격을 정해서 생성되게 작업했다. 그렇게 만들었더니 간격은 마음에 드는데 뜸하게 만들어지는 것 같기도 하고 이건 수치 조절하면 될 것 같은데 그 적정한 선을 찾기가 쉽지가 않구만.
장애물이 연속해서 나오는 것도 별로길래 몇 개 같은 쪽으로 나오면 다시 만들어질 때는 다른 쪽으로 만들어지게 만들어 줬다.
주석처리를 많이 해두어서 코드를 보는 데는 문제가 없을 듯하다.
하지만 솔직하게 이 코드는 pseudo 3d game라 할 수 없다. 그래서 시간이 될 때 다시 도전해 보는 걸로 마무리 지었다.
일단 내가 만든 방법은 2d 배경 1장에 도로의 가운데 라인과, 장애물이 플레이어의 반대 방향(위에서 아래 방향)으로 이동하게 해서 플레이어는 가만히 있지만 이동하는 느낌을 만들어 봤다. 장애물 만드는 방법과 도로 라인 만드는 방법을 다르게 해 봤다. 사실 두 가지 다 각자 테스트 해보다가 각자 만들어졌지만 원리는 비슷할 것이다.
생각보다 많은 다양한 게임들의 스프라이트들을 다운로드할 수 있다. 만약 배경이 있다면 aseprite 프로그램을 사용해서 제거해 준다. 물론 코드로 뒷배경을 삭제시켜 주는 방법도 있다. 하지만 나는 이 프로그램을 돈 주고 샀기 때문에 뽕을 뽑아야 한다는 마음으로 작은 작업이라도 사용하는 편이다.
ai를 사용해서 나만의 배경을 만들어 볼까도 했지만 2시간 동안 하늘의 사이즈를 줄여주지 않는 ai에게 굴복하고 그만뒀다.(회원가입도 했는데... 하늘을 줄여주는 게 그렇게 싫었던 걸까. 그렇게 소량으로만 줄여주고 싶었던 것일까. 한글도 영어도 전부 먹히지 않았던 너란 ai. 배경 오브젝트들이 점점 한자들이 나오길래 중국인가 싶었지만 난 중국어는 못하는 걸.)
언덕도, 산도, 나무도, 하늘도 더이상 나의 계획대로 변경되지 않았던 나름 예뻤던 내가 요청한 배경. 도로 안에 가운데 라인도 빼달라고 빼달라고 그렇게 말했는데 완강했던 너의 모습. 잊지 못할 거야.
2. 유니티에서 몇 가지 작업을 해준다.
일단 우선적으로 2d project를 만들어 준다. 그리고 Obstacle Manager(장애물 관리 매니저) 오브젝트와, RoadLineManager(도로 라인 관리 매니저)를 만들어 준다. 프리팹들은 장애물 프리팹과, 라인 프리팹만 있으면 된다. UI Canvas를 만들어 주고 필요하다면 플레이어가 이동할 수 있는 버튼을 추가로 만들어 주면 된다. 나의 경우 버튼을 눌러 좌, 우로 이동할 수 있게 작업했다. 속도를 늘리는 버튼과 속도를 줄이는 버튼 또한 만들어 두었다.
3. 이제 스크립트를 짜보자.
우선 도로 라인이 내려오게 만들어 주는 RoadLineManager를 만들어 보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class RoadLineManager : MonoBehaviour
{
[Header("Line Spawn Settings")]
public GameObject roadLinePrefab;
public int poolSize = 10;
public float spawnInterval = 1f;
[Header("Line Position Settings")]
public float startYPosition = 5.7f;
public float startXPosition = 0.15f;
public float endYPosition = -1f;
public float fixedLineSpacing = 1.8f;
[Header("Line Scale Settings")]
public float startScaleX = 0.05f;
public float startScaleY = 0.02f;
public float endScaleX = 0.4f;
public float endScaleY = 0.5f;
[Header("Movement Settings")]
public float initialMoveSpeed = 5f;
public float currentMoveSpeed;
public float maxMoveSpeed = 15f;
public float accelerationRate = 2f;
[Header("Line Settings")]
public int maxVisibleLines = 6;
private List<GameObject> linePool;
private float[] linePositions;
//private float[] spacings = new float[] { 1.0f, 1.2f, 1.5f, 1.8f, 2.2f, 2.7f };
private float[] spacings = new float[] { 0.8f, 1.2f, 1.8f, 2.5f, 3.3f, 4.2f };
private void Start()
{
linePool = new List<GameObject>();
linePositions = new float[maxVisibleLines];
InitializePool();
currentMoveSpeed = initialMoveSpeed;
UpdateLinePositions();
SpawnInitialLines();
}
private void InitializePool()
{
for (int i = 0; i < poolSize; i++)
{
GameObject line = Instantiate(roadLinePrefab);
line.SetActive(false);
linePool.Add(line);
}
}
private void UpdateLinePositions()
{
linePositions[0] = startYPosition;
for (int i = 1; i < maxVisibleLines; i++)
{
float spacing = fixedLineSpacing * spacings[i-1];
linePositions[i] = linePositions[i-1] - spacing;
}
}
private void SpawnInitialLines()
{
for (int i = 0; i < maxVisibleLines; i++)
{
SpawnLineAtPosition(linePositions[i]);
}
}
private void SpawnLineAtPosition(float yPosition)
{
GameObject line = GetInactiveLine();
if (line != null)
{
float t = (startYPosition - yPosition) / (startYPosition - endYPosition);
t = Mathf.Clamp01(t);
float scaleX = Mathf.Lerp(startScaleX, endScaleX, t);
float scaleY = Mathf.Lerp(startScaleY, endScaleY, t);
line.transform.position = new Vector3(startXPosition, yPosition, 0);
line.transform.localScale = new Vector3(scaleX, scaleY, 1);
line.SetActive(true);
}
}
private void Update()
{
UpdateLines();
}
private void UpdateLines()
{
int visibleCount = 0;
float highestY = float.MinValue;
foreach (GameObject line in linePool)
{
if (line.activeInHierarchy)
{
Vector3 pos = line.transform.position;
float t = (startYPosition - pos.y) / (startYPosition - endYPosition);
t = Mathf.Clamp01(t);
float scaleX = Mathf.Lerp(startScaleX, endScaleX, t);
float scaleY = Mathf.Lerp(startScaleY, endScaleY, t);
// 스케일이 클수록 더 빠르게 이동하도록 수정
float speedMultiplier = scaleY / startScaleY;
pos.y -= (currentMoveSpeed * speedMultiplier) * Time.deltaTime;
line.transform.localScale = new Vector3(scaleX, scaleY, 1);
line.transform.position = pos;
if (pos.y < endYPosition)
{
line.SetActive(false);
}
else
{
highestY = Mathf.Max(highestY, pos.y);
visibleCount++;
}
}
}
if (visibleCount < maxVisibleLines)
{
if (highestY == float.MinValue)
{
SpawnLineAtPosition(startYPosition);
}
else
{
int currentIndex = visibleCount - 1;
float spacing = fixedLineSpacing * spacings[currentIndex];
float newYPos = highestY + spacing;
if (newYPos <= startYPosition)
{
SpawnLineAtPosition(newYPos);
}
}
}
}
private GameObject GetInactiveLine()
{
return linePool.Find(line => !line.activeInHierarchy);
}
public void IncreaseSpeed()
{
currentMoveSpeed = Mathf.Min(currentMoveSpeed + accelerationRate, maxMoveSpeed);
}
public void DecreaseSpeed()
{
currentMoveSpeed = Mathf.Max(currentMoveSpeed - accelerationRate, initialMoveSpeed);
}
public void ResetSpeed()
{
currentMoveSpeed = initialMoveSpeed;
}
}
이 코드를 짤 때 몇 가지 골머리 써야 됐었던 부분이 있었다.
그중 가장 큰 게 라인이 프리팹으로 활성화가 될 때 아래로 내려오는 느낌으로 원근감 있게 내려오려면 처음 적용해 줬던 간격을 계속 유지하면 안 되는 문제가 있었다. 이 부분을 수정하기 위해 나에겐 여러 번의 시도가 필요했다. 그래서 UpdateLines() 함수는 정말 쉴 새 없이 수정됐었다. 그리고 마침내 해결된 방법이 바로 위에 적어둔 스케일의 사이즈에 따라 빠르게 이동시키는 것. 이 부분이 사실 그렇게 어려운 부분은 아니었다. 처음부터 수치를 디버깅해서 확인해 봤으면 문제가 되지 않았을 것이다. 변경시켜 주던 수치값이 제대로 늘어나지 않는 오류를 모르고 도대체 왜 내가 원했던 데로 나오지 않는 거지 하다가 시간을 두배로 썼다. 잊지 말자 디버깅.
버튼에 적용시켰던 speed up 버튼과 speed down 버튼에 사용할 함수도 여기 있는 IncreaseSpeed() 함수와 DecreaseSpeed() 함수를 연결해 주면 된다. 필요시 나머지 함수들도 연결시켜줘도 기능은 할 것이다.
나의 경우는 변수들을 많이 만들어 최대한 유니티에서 배경을 바꿨을 때도 사용할 수 있게 작업했다. 지금 보이는 것과 같이 저 배경은 free이기 때문에 가져온 스프라이트지 그렇게 내 마음에 들지 않기 때문일지도 모른다. 도로의 시작과 끝을 잡아줄 위치 변수들과, 라인의 스케일을 관리해 줄 변수들, 라인과 라인 사이의 간격을 정할 변수로 되어있다. 그리고 화면에 나올 라인의 개수 변수가 있는데 이건 간격과 도로의 총길이에 따라 나오지 않을 수도 있다. 프리팹은 오브젝트 풀링을 사용해서 최적화해 두었다.
장애물 매니저는 내일 다시 정리해 두겠다. 여기서는 역시나 오브젝트풀로 장애물을 관리하고, 도로의 좌, 우에 따라붙어서 원근감 있게 스케일이 커지도록 작업했다.
++ 나도 모르게 필요없는 파일인줄 알고 프로젝트 삭제시켜서 장애물 매니저는 그렇게 날라갔다...다시 작업해서 올리도록 하겠다.
며칠 전 공부했던 곳에서 봤던 단어 pseudo! 그때도 낯설었던 단어 슈도라는 단어를 이렇게 다시 만날 줄이야.
그때 공부했을 땐 슈도 코드에 대해서였지만 지금은 2d로 3d게임 환경을 만드는 것이다. 오래전 우리가 게임했을 때 대체로 많이 보였던 기법인데 대표적인 게임이 "Out Run" 게임이 있다. 이외에도 레이싱 게임에서는 종종 많이 발견됐었던 것 같다. 그때는 그게 3D인지 2D인지 알게 뭐였는가. 그저 게임하는 게 즐거웠을 뿐이었지.
그렇게 즐거웠던 게임이 이제 단순히 "참 즐거웠던 게임이었지."란 과거형의 기억이 아닌 순간이 왔다. 새로 작업해 보기로 한 프로젝트 팀에서 딱 "Out Run"게임처럼 구현해 주길 바라는 말을 듣게 되었다.
사실 처음엔 뭐 얼마나 어렵겠어라는 생각이 전부였다. 못할 건 없지란 생각으로 일주일 동안 시도해 볼 시간을 얻어 Test를 해보기 전까지 정말 딱 그 생각이었다. 생각보다 구글에 "How do i make OutRun"라고 치는 순간 기가 막힌 정보들도 찾을 수 있어서 쉽게 접근할 수 있겠다 생각했었다.
위에 글을 읽어보고 하루 후에 유니티를 켜봤다가 그새를 잊은 건지 허튼짓을 했었던 순간이 있었다. 바로 2d 스프라이트들을 아래쪽에 쭉 깔아 두고 플레이어한테 다가오게 하면 안 되나 했다가 그렇게 되면 3d가 되는 거란 걸 인식하고 세상 똥멍청이인가 싶었었다.
누군가는 또 이 방법을 사용해서 작업을 했던 흔적을 발견했다. 완전히 똥멍청이 생각은 아니였던 걸로!
뭐 그래도 뭐든 시도는 좋은 거니까 라는 마인드로다시 시도해 봤다. 그러다세그먼트들 작업을 할 때 생각했다. 왜 2D환경에서 3d를 구현하고 싶은 걸까. 옛날이라면어쩔 수없었던 선택일지 몰라도요즘 같은현대 사회에 왜 3d로 만들면 한방에 해결이 되는 이 상황을 난 지금 왜 이렇게 집중하고 있을까였다.ㅎㅎ 이렇게 오늘도 난 성장했다.
그럼에도 불구하고 뭔가 슬슬 화면에 나오기 시작했을 때쯤 쾌감은 짜릿했다.
하지만 내 코드에서는 몇 가지 문제점들이 보였다. 속도가 붙을수록 세그먼트들의 간격이 생기는 상황. 뭔가 잘못 만든 게 분명했다. 작업하면서 스케일값도 중요했다. 일주일 동안 이 작업만 할 수 있는 스케줄은 아니었기에 다른 쉽게 가는 방법이 있을까 하고 생각하다가 도로에 있는 차선만 이동하는 것도 나쁘지 않겠다는 생각을 했다. 결과적으로는 나쁘지 않은 결괏값을 얻을 수 있었다. 위의 방식대로 하던 중 문제가 있었던 부분은 연속으로 나오는 부분에서 간격이 벌어지는 문제였다면 라인은 연속해서 나오지 않기에 그대로 작업을 해보았다.
작업할 때 가장 중점적으로 생각한 부분이 멀리 있는 공간에서 화면 가까이로 오는 부분에서 느껴져야 할 원근감과 스케일 부분, 그리고 왼쪽 도로와 오른쪽 도로에서 오는 장애물의 경우 차선을 따라 각각의 끝에 자연스럽게 유지돼서 붙어 내려와야 하는 부분. 그리고 애증의 간격 부분.
간격이 생각보다 어려웠던 게 차량 이동을 생각할 때 도로 위의 라인이 멀리서 가까이로 올수록 간격이 넓어져야 하는데 간격을 정해두고 스케일만 키웠던 게 문제였다. 그래서 접근한 부분이 스케일 값이 커질수록 position y의 값을 더 작아지게 해서 간격을 늘려볼까도 생각을 했는데 이렇게 구현해 보니 무슨 문제인지 전혀 원하는 방향대로 나오지 않길래 디버그를 해봤더니 알았다. 내가 원한 만큼의 수치가 계산에서 이루어지고 있지 않았다. 수치를 조절하는 부분을 다시 수정하고 봤더니 이젠 프리팹으로 만들어 둔 오브젝트가 시작 부분에서 종료되는 부분까지 나오는 거리가 문제가 돼서 활성화가 안 되는 버그를 보고 눈을 감았다. 이것저것 머릿속에서 한참 생각해 보다가(프리팹 순서를 정해서 활성화해 볼까, 리스크에 위치값을 저장시켜서 그 부분에 가면 위치를 두 배씩 늘려볼까, 등등 지금 생각해 보면 별의별 말도 안 되는 생각들이 있었다.) 속도 값을 늘려볼까 싶었는데 이게 생각보다 괜찮은 결괏값을 만들어 줬다. 그렇게 장애물과, 라인의 움직임을 구현시켜 두었더니 한결 마음이 편해졌다. 결과적으로 프리뷰느낌으로 보기에 나쁘지 않을 정도의 상태를 만들어 두고 생각해 봤는데 이 방법이 최선의 선택은 아닌 것 같다. 저 위의 내용을 토대로 다시 한번 작업해 봐야겠다.
사실 이제 더 이상 이런 부분의 코드 작업을 안 할 줄 알았는데 막상 해보니까 너무 재미있었다. 아무래도 이쪽 길로 가지 않는다 해도 취미로라도 계속해서 관심을 유지해야겠다.
using GoogleMobileAds;
using GoogleMobileAds.Api;
using System;
using UnityEngine;
// https://developers.google.com/admob/unity/interstitial?hl=ko 참고
public class GoogleMobileAdsScript : MonoBehaviour
{
public GoogleMobileAdsScript instance;
private InterstitialAd _interstitialAd;
// These ad units are configured to always serve test ads.
//자신의 광고단위ID 넣어주기
#if UNITY_ANDROID
//private string _adUnitId = "";
private string _adUnitId = "";
#elif UNITY_IPHONE
// private string _adUnitId = "";
#else
private string _adUnitId = "unused";
#endif
public void Awake()
{
// Initialize the Google Mobile Ads SDK.
MobileAds.Initialize((InitializationStatus initStatus) =>
{
// This callback is called once the MobileAds SDK is initialized.
});
}
public void Start()
{
LoadInterstitialAd();
}
public void LoadInterstitialAd()
{
// 이전에 로드된 광고 체크, 있으면 제거하고 해제
if(_interstitialAd != null)
{
_interstitialAd.Destroy();
_interstitialAd = null;
}
// 새로 광고를 로드하기위한 요청 생성
var adRequest = new AdRequest();
// 광고단위 ID _adUnitId와 adRequest 객체를 전달 받아 광고를 로드.
InterstitialAd.Load(_adUnitId, adRequest,
(InterstitialAd ad, LoadAdError error) =>
{
if (error != null || ad == null)
{
Debug.LogError("interstitial ad failed to load an ad" + "with error : " + error);
return;
}
Debug.Log("Interstitial ad loaded with response : " + ad.GetResponseInfo());
_interstitialAd = ad;
// 성공한 경우 로드된 광고에 대한 이벤트 핸들러를 등록.
RegisterEventHandlers(_interstitialAd);
});
}
// Shows the interstitial ad.
public void ShowInterstitialAd() //광고 표시
{
if (_interstitialAd != null && _interstitialAd.CanShowAd())
{
Debug.Log("Showing interstitial ad.");
_interstitialAd.Show();
}
else
{
LoadInterstitialAd(); //광고 재로드
Debug.LogError("Interstitial ad is not ready yet.");
}
}
// 전면광고에 발생하는 이벤트에 대한 핸들러 등록
private void RegisterEventHandlers(InterstitialAd interstitialAd)
{
// 전면광고 지급 관련 이벤트
interstitialAd.OnAdPaid += (AdValue adValue) =>
{
Debug.Log(String.Format("Interstitial ad paid {0} {1}.",
adValue.Value,
adValue.CurrencyCode));
};
//
interstitialAd.OnAdImpressionRecorded += () =>
{
Debug.Log("Interstitial ad recorded an impression.");
};
// 전면광고가 클릭 되었을 때 이벤트
interstitialAd.OnAdClicked += () =>
{
Debug.Log("Interstitial ad was clicked.");
};
// 전면광고가 열렸을 때 호출
interstitialAd.OnAdFullScreenContentOpened += () =>
{
Debug.Log("Interstitial ad full screen content opened.");
};
// 전면광고가 닫혔을 때 호출
interstitialAd.OnAdFullScreenContentClosed += () =>
{
Debug.Log("close Scene");
};
// 전면광고가 못 열였을 때 호출
interstitialAd.OnAdFullScreenContentFailed += (AdError error) =>
{
Debug.LogError("Interstitial ad failed to open full screen content " +
"with error : " + error);
};
}
private void RegisterReloadHandler(InterstitialAd interstitialAd)
{
// Raised when the ad closed full screen content.
interstitialAd.OnAdFullScreenContentClosed += () =>
{
Debug.Log("Interstitial Ad full screen content closed.");
// Reload the ad so that we can show another as soon as possible.
LoadInterstitialAd();
};
// Raised when the ad failed to open full screen content.
interstitialAd.OnAdFullScreenContentFailed += (AdError error) =>
{
Debug.LogError("Interstitial ad failed to open full screen content " +
"with error : " + error);
// Reload the ad so that we can show another as soon as possible.
LoadInterstitialAd();
};
}
}
이제 다 끝났다. 유니티에서 광고를 사용하고 싶은 부분에서 ShowInterstitialAd 함수를 불러오면 된다.
그럼 얘가 나올 것이다.
이렇게 다 하고 광고 나오는 건 확인했는데 유니티 Gradle 문제가 생겨서 빌드가 안된다. 하... 속이 상한다. 뭐가 또 문제인 걸까!