우리 Godot 게임은 비싼 PC에서만 충돌했습니다 (이유는 여기)
Source: Dev.to
실제 문제들
우리는 네 가지 별개의 이슈가 동시에 작용해 상황을 악화시키고 있다는 것을 찾아냈습니다.
1. 게임 플레이 중 리소스 로딩
아래 코드는 겉보기엔 무해해 보입니다:
func start_boss_fight():
var boss_scene = load("res://scenes/InspectionBossFight.tscn")
var boss = boss_scene.instantiate()
load() 호출은 디스크에서 읽는 동안 100‑500 ms 동안 모든 것을 멈추게 합니다. 여기에 셰이더 컴파일을 더하면 Windows TDR(Timeout Detection and Recovery)에 걸리게 됩니다. Windows는 GPU 드라이버가 ~2 초 안에 응답하지 않으면 프로세스를 강제로 종료합니다. 크래시 리포트는 없고, 그냥 죽은 상태가 됩니다.
해결책: 시작 시점에 미리 로드(preload)합니다.
const InspectionBossFightScene = preload("res://scenes/InspectionBossFight.tscn")
func start_boss_fight():
var boss = InspectionBossFightScene.instantiate() # 즉시 생성
셰이더 머티리얼도 마찬가지로, 시작 시에 템플릿을 만들고 런타임에 복제하도록 합니다.
2. 영원히 기다리는 await
func _on_achievement_unlocked():
await EventBus.game_saved
show_notification()
저장이 실패해 해당 시그널이 절대 발생하지 않으면 코루틴이 영원히 대기하게 되고 게임이 멈춥니다.
해결책: Fire‑and‑forget 방식 사용.
func _on_achievement_unlocked():
EventBus.request_save.emit()
show_notification() # 확인을 기다리지 않음
3. 쌓여가는 트윈(Tween)
UI 애니메이션마다 새로운 트윈을 만들었고, 이전 트윈을 전혀 정리하지 않았습니다. 몇 시간 플레이 후에는 수천 개의 죽은 트윈 레퍼런스가 메모리에 남게 됩니다.
해결책: 트윈을 추적하고 새로 만들기 전에 기존 것을 종료합니다.
var _active_tweens: Dictionary = {}
func fade_ambient_light():
if _active_tweens.has("ambient_fade") and _active_tweens["ambient_fade"].is_valid():
_active_tweens["ambient_fade"].kill()
var tween = create_tween()
_active_tweens["ambient_fade"] = tween
tween.tween_property(light, "energy", 0.5, 1.0)
4. 안전 제한이 없는 루프
우리의 전투기 정리 루프는 한 프레임에 수천 개의 잘못된 엔트리를 처리하려 했습니다:
func cleanup_fighters():
for i in range(fighters.size() - 1, -1, -1):
if not is_instance_valid(fighters[i]):
fighters.remove_at(i)
해결책: 반복 횟수에 제한을 둡니다.
const MAX_ITERATIONS_PER_FRAME = 100
func cleanup_fighters():
var iterations = 0
for i in range(fighters.size() - 1, -1, -1):
iterations += 1
if iterations > MAX_ITERATIONS_PER_FRAME:
break
if not is_instance_valid(fighters[i]):
fighters.remove_at(i)
더 큰 작업(예: 100명 이상의 유닛에게 동시에 버프 적용)에서는 프레임마다 작업을 나눠 처리합니다:
func apply_cascade_inspiration(units: Array):
var index = 0
while index = units.size():
break
units[index].apply_inspiration()
index += 1
await get_tree().process_frame
TL;DR
- 리소스는 시작 시점에 미리 로드(preload)하세요. Windows에서 런타임
load()는 GPU 드라이버 타임아웃을 유발할 수 있습니다. - 절대 발생하지 않을 수도 있는 시그널을
await하지 마세요. 상황에 맞게 fire‑and‑forget 패턴을 사용하세요. - 트윈을 추적하세요. 새 트윈을 만들기 전에 기존 트윈을 종료합니다.
- 루프에 제한을 두세요. 특히 동적 배열을 처리하는 경우에 중요합니다.
- 큰 작업은 프레임 단위로 나눠 수행하세요. 메인 스레드가 응답성을 유지할 수 있습니다.
핵심 포인트: 고성능 PC일수록 이 버그들을 더 빨리 발견합니다. 프레임당 루프가 더 많이 실행되기 때문이죠. 최고의 하드웨어가 최악의 문제를 찾아내는 경우가 종종 있습니다.
우리는 Lost Rabbit Digital이며, GitHub에서 활동하고 있습니다. Starbrew Station은 Godot 4.5로 제작되었습니다.