-
무한모드 제작#1Galaxy Ball/4. 싱글플레이 - 무한모드 2025. 1. 1. 18:31
현재 허가가 떨어지기까지 무한대기중....
언제 이 기다림이 끝나게 될지 알수가 없으니 그동안 무한모드를 제작해보도록 하겠다
무한모드에 대해 간단하게 소개하자면 스테이지가 65개로 한정되어있는 싱글플레이 모드와 달리
끝없이 계속해서 게임을 즐길수 있는 모드이며 적유닛과 고정구체의 배치는 랜덤으로 이뤄지되,
스테이지가 올라감에 따라 적당한 밸런스로 점점 난이도가 올라가도록 할것이다
그리고 너무 스테이지가 올라가 난이도에 문제가 있을시 적 유닛의 체력을 전체적으로 올려주는 식으로
밸런스를 맞춰보도록 하겠다
그래서 어떻게 밸런스를 맞출거냐? 원리는 이런식이다
예시로 들고온 적 유닛 5개. 이 유닛들에게 순수 난이도로만 난이도 점수를 매겨줄것이다
Enemy1 : 1.0
Enemy2 : 1.1
Enemy3 : 1.3
Enemy4 : 1.5
Enemy5 : 1.7
.....
이런 식으로. 그리고 스테이지 1의 난이도 레벨이 1.0이라면 이 수치에 맞아 떨어지는 적 유닛을 랜덤 생성 하는것이다
그러니 스테이지1은 무조건 Enemy1이 등장하는식
만약 스테이지 10의 난이도레벨이 3이라면 Enemy4 두개가 등장하는것이다
이런식으로 수치가 늘어날수록 더 다양한 조합이 가능하게 되고 점점 더 어려워지게 되는것이다
자 그럼 기본틀부터 한번 만들어주자
우선 씬을 EndlessInGame이라는 이름으로 새로 만들어주었다.
물론 싱글플레이와 거의 비슷하기에 틀은 그대로 가져오고 하단부분에 스테이지 넘버링을 붙여주었다
using UnityEngine; public class StageGameManager : MonoBehaviour { public float ELlevel; private float ELlevelIDCache; private void Awake() { if (instance == null) { ELlevelIDCache = PlayerPrefs.GetFloat("ELlevel", 1); ELlevel = ELlevelIDCache; } else { Destroy(gameObject); // 중복 방지 } } ...... public void SaveELlevel() { if (ELlevelIDCache != ELlevel) // 값이 변경된 경우에만 저장 { PlayerPrefs.SetFloat("ELlevel", ELlevel); PlayerPrefs.Save(); ELlevelIDCache = ELlevel; } } }
그리고 기존에 쓰던 StageGameManager를 가져와 무한모드에 쓰일 ELlevel를 float타입으로 추가한뒤
레벨을 올리고 나서 ELlevel을 저장해줄 SaveELlevel 메서드도 만들어주었다
그리고 SPGameManager처럼 ELGameManager에서 클리어 조건을 만족시키면
ELlevel를 조금씩 올려준뒤 저장하는 방식을 사용할 것이다
자 그리고 이제 적 유닛에게 각각 고유의 난이도에 따라 스탯을 붙여줄 예정이다
그리고 Endless모드용 적 유닛을 따로 분류하여 주었다. 차이점이 있다면
무한모드에 부적절한 적 유닛은 빼주고 모든 유닛의 태그를 "Enemy"로 태그해주었다
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ELEnemyStat : MonoBehaviour { public float EnemyStat; }
그리고 정말 간단한 스탯 하나만 받을 수 있는 스크립트를 만들어 프리팹에 전부 붙여주었다
적 유닛마다 고유의 값을 하나씩 넣어줘야하는데 이것만큼 효율적인 방법은 없는것같다
난이도는 가장 기본 유닛을 1로 기준해서 각각 적절하게 스탯을 매겨주었다
추후에 밸런스 조절로 얼마든지 수정될 수 있다
자 그리고 이 무한모드에서 가장 중요한 스크립트 작성이 남아있다
그것은 바로 StageGameManager에서 ELlevel값을 받아온뒤
각각 적 유닛 프리팹에서 고유한 EnemyStat값까지 받아와 ELlevel에 최대한 일치하는 선에서
적 유닛중 일부를 랜덤 조합하여 생성하는 스크립트를 만들어줘야 한다
using UnityEngine; using System.Collections.Generic; // List를 사용하기 위해 추가 public class ELStageSpawn : MonoBehaviour { public GameObject[] EnemyPrefabs; // 생성할 적 프리팹 배열 public static StageGameManager stageGameManager; private void Start() { stageGameManager = FindAnyObjectByType<StageGameManager>(); if (stageGameManager != null) { SpawnEnemies(stageGameManager.ELlevel); } } public void SpawnEnemies(float targetLevel) { // ELlevel보다 작은 EnemyStat을 가진 프리팹 필터링 List<GameObject> validPrefabs = new List<GameObject>(); Debug.Log("---- EnemyPrefabs 정보 출력 ----"); foreach (var prefab in EnemyPrefabs) { ELEnemyStat stat = prefab.GetComponent<ELEnemyStat>(); if (stat != null) { // 각 프리팹의 EnemyStat 값을 출력 Debug.Log($"Prefab Name: {prefab.name}, EnemyStat: {stat.EnemyStat}"); if (stat.EnemyStat <= targetLevel) { validPrefabs.Add(prefab); } } else { Debug.LogWarning($"Prefab Name: {prefab.name}에는 ELEnemyStat 컴포넌트가 없습니다!"); } } foreach (var validPrefab in validPrefabs) { ELEnemyStat stat = validPrefab.GetComponent<ELEnemyStat>(); Debug.Log($"Valid Prefab Name: {validPrefab.name}, EnemyStat: {stat.EnemyStat}"); } // 유효한 프리팹이 없는 경우 if (validPrefabs.Count == 0) { Debug.LogWarning("ELlevel보다 작은 EnemyStat을 가진 프리팹이 없습니다."); return; } float currentTotal = 0f; int safetyCounter = 15; // 안전 장치 int spawnCount = 0; // 목표와 오차 범위 내인지 확인하며 생성 while (Mathf.Abs(currentTotal - targetLevel) >= 0.1f && safetyCounter > 0) { safetyCounter--; // 유효한 프리팹 중 하나를 랜덤으로 선택 GameObject selectedEnemy = validPrefabs[Random.Range(0, validPrefabs.Count)]; ELEnemyStat stat = selectedEnemy.GetComponent<ELEnemyStat>(); Debug.Log($"Loop Iteration: CurrentTotal = {currentTotal}, TargetLevel = {targetLevel}, SelectedPrefabStat = {stat.EnemyStat}"); if (currentTotal + stat.EnemyStat > targetLevel) { // 초과 허용 범위 내인지 확인 (최대 1.0f 초과) if ((currentTotal + stat.EnemyStat) - targetLevel <= 1.0f) // 초과 허용 범위 { currentTotal += stat.EnemyStat; Debug.Log($"Selected prefab {selectedEnemy.name} to slightly exceed target. Final Total: {currentTotal}"); break; // 목표값에 도달하여 루프 종료 } continue; // 목표값을 넘지 않도록 다음 프리팹으로 넘어감 } // 적 생성 Vector2 spawnPosition = new Vector2( Random.Range(-2.4f, 2.4f), Random.Range(-2.7f, 4.5f) ); Instantiate(selectedEnemy, spawnPosition, Quaternion.identity); // 현재 합계 업데이트 currentTotal += stat.EnemyStat; spawnCount++; Debug.Log($"Spawned: {spawnCount}, CurrentTotal: {currentTotal}, Target: {targetLevel}"); } if (safetyCounter == 0) { Debug.LogWarning($"SpawnEnemies: 안전 장치 발동! 목표 달성 실패. CurrentTotal: {currentTotal}, Target: {targetLevel}"); } Debug.Log($"Total Spawned: {spawnCount}, Final Total: {currentTotal}"); } }
그리고 어제부터 별의별 시행착오를 겪으며 만든 ELStageSpawn 스크립트...
테스트 하다가 유니티가 멈춘것만 한두번이 아니다
물론 ELlevel을 완벽하게 맞출 필요까진 없고 오차범위를 +- 0.1 정도까지는 주었다
그렇게 Inspector창에서 쓰기로한 32개의 적 유닛을 넣어주면 된다
각각 ELlevel을 1, 5, 10으로 맞추었을때다. 잘 스폰되는것을 확인해볼수 있다!
'Galaxy Ball > 4. 싱글플레이 - 무한모드' 카테고리의 다른 글
자잘한 수정 + 게임 이쁘게 다듬기 (0) 2025.01.05 무한모드 제작#2 (1) 2025.01.03