반복문 (Loop Statements)
왜 반복문인가?
스타크래프트 1의 클래식 트리거 시스템(ScmDraft 2 등)으로 맵을 만들 때, 가장 고통스러운 순간은 "같은 작업을 여러 번 반복해야 할 때"입니다. 코드(텍스트)로 작성하는 반복노가다는 그나마 편합니다. ScmDraft 2에서 해야한다면, 마우스 클릭 지옥에 빠졌겠네요.
반복문은 특정 코드 블록을 여러 번 실행할 때 사용합니다. 유닛을 생성하거나, 모든 플레이어의 자원을 체크하는 등 유즈맵 로직의 핵심적인 역할을 수행합니다.
반복문은 '트리거 노가다'를 끝내는 열쇠입니다.
Epscript의 반복문을 사용하면 단 몇 줄의 코드로 맵 전체의 유닛에게 체력이 일정시간마다 줄어드는 효과를 만들어 낼 수 있습니다. 좀비헌터 유즈맵과 같이 수많은 총알이 날라가면서 적에게 피격하는 트리거도 이 반복문을 활용합니다.
1. for 문
for 문은 정해진 횟수만큼 반복할 때 주로 사용합니다. Epscript에서는 파이썬의 range와 유사한 방식을 사용합니다.
for (초기화; 조건식; 증감식) {
// 반복할 코드 (본문)
}
for (var i=0; i<10; i++) {
// i는 0으로 시작하여, (본문)의 코드가 실행되고
// i는 1씩 증가하며 10보다 커질 때까지 반복합니다.
}
- 초기화 (Initialization): 반복에서 사용할 변수(보통 i)의 시작 값을 정합니다. (딱 한 번만 실행)
- 조건식 (Condition): 이 조건이 참(true)인 동안에만 코드를 반복합니다.
- 코드 실행: 중괄호 { } 안의 내용을 실행합니다.
- 증감식 (Iteration): 코드가 한 번 끝나면 변수 값을 변화시킵니다. (이후 다시 2번 조건 확인으로 이동)
배열과 반복문의 조합
배열(Array)과 반복문(Loop)은 프로그래밍에서 '바늘과 실' 같은 관계입니다. 배열이 '데이터를 담는 서랍장'이라면, 반복문은 그 서랍장을 '순서대로 열어보는 행위'입니다. 배열과 반복문이 만나면 데이터의 개수가 1개든 1,000개든 코드의 양은 동일해집니다.
- 배열: 연관된 데이터를 하나의 변수 이름 아래 번호(인덱스)를 매겨 저장합니다.
- 반복문: 인덱스 번호를 0부터
배열의 길이-1까지 자동으로 바꿔가며 접근합니다.
디펜스게임에서 스테이지마다 등장해야하는 유닛번호를 배열에 저장해둔다면, 스테이지번호를 활용해서 배열에 접근하여 현재 스테이지의 유닛이나 다음스테이지, 혹은 모든 스테이지에 대한 정보를 빠르게 처리할 수 있습니다.
const STAGE_COUNT = 10;
const stageUnits = [$U("Terran SCV"), $U("Zerg Drone"), $U("Protoss Probe"), ...];
var stageNumber = 0;
function nextStage() {
// 스테이지 번호가 배열의 최대크기보다 작을 때에만 작동
if (stageNumber < STAGE_COUNT) {
const stageUnit = stageUnits[stageNumber];
stageNumber++;
eprintf("{}스테이지 시작!", stageNumber);
CreateUnit(10, stageUnit, "SpawnPoint", P8);
// 컴퓨터(P8) 소유의 stageUnit타입 유닛을 SpawnPoint에 10마리 생성.
}
}
range 함수 활용
Epscript에서도 for(i : py_range(10))와 같은 파이썬 스타일 문법을 지원하지만, 컴파일 최적화를 위해 위와 같은 JS-style 문법이 가장 널리 쓰입니다.
python 언어 기반의 Eudplib 라이브러리에서 파생된 언어라서 python에서 등장하는 용어들도 자주 나옵니다. 보통 python 문법은 스타크래프트 내에서 트리거로 만들어지지 않는 케이스가 많습니다. TEP의 for문을 생각하시면 편합니다. 반복문으로 만들었지만, 해당 반복횟수만큼 트리거로 만들어지는 느낌이죠.
2. while 문
while 문은 조건식이 참(True)인 동안 계속해서 코드를 반복합니다. 반복 횟수가 불명확할 때 유용합니다.
실용 예시: 빈 배열 슬롯 찾기
// 10개의 슬롯을 가진 인벤토리 배열이 있다고 가정합니다.
const INVENTORY_SIZE = 10;
const inventory = EUDArray(INVENTORY_SIZE);
function findEmptySlot() {
var i = 0;
// 슬롯이 0이 아니면(아이템이 있으면) 다음 슬롯으로 이동
// i가 10 미만일 때까지만 검사하여 배열 범위를 벗어나지 않게 합니다.
while (inventory[i] != 0 && i < INVENTORY_SIZE) {
i++;
}
if (i < INVENTORY_SIZE) {
// 빈 슬롯을 찾은 경우의 로직
eprintf("빈 슬롯 번호: {}", i);
return i;
} else {
// 모든 슬롯이 가득 찬 경우
eprintf("인벤토리가 가득 찼습니다.");
return -1;
}
}
무한 루프 주의
Epscript의 while 문 내부에 탈출 조건이 없거나 게임의 한 프레임(Trigger cycle) 내에서 너무 많은 반복이 일어나면 게임이 멈출 수 있습니다.
3. foreach 문
foreach는 주로 유닛루프이나 플레이어루프 등 Eudplib에서 미리 정의해둔 루프문을 활용할 때 사용합니다.
활용 예시: 모든 유닛 순회 (EUDLoopUnit)
맵에 존재하는 특정 조건의 유닛들을 모두 체크할 때 사용합니다.
function damageAllUnits() {
// 현재 맵에 있는 모든 유닛을 순회합니다.
foreach(cunit : EUDLoopCUnit()) {
// 유닛의 ID가 '저글링(37)'일 때, 체력이 1미만이면 사망하고
// 1보다 크면, 1/256씩 체력을 뺍니다.
if (cunit.unitType == 37) {
if (cunit.hp < 256) {
cunit.die();
}
else {
cunit.hp -= 1;
}
}
// Tip. 스타크래프트에서 체력은 소수점 단위 표현때문에 256을 체력1로 처리합니다.
// HP가 128이면, 0.5인 셈이죠.
}
}
활용 예시: 특정 플레이어 그룹 순회
// 플레이어 중 유저(사람)만 순회
foreach(cp : EUDLoopPlayer("Human")) {
setcurpl(cp);
SetResources(cp, Add, 100, Ore);
// 모든 유저(사람)에게 100미네랄을 추가합니다.
}
4. 제어문 비교 요약
| 구문 | 용도 | 특징 |
|---|---|---|
for | 정해진 횟수 반복 | 인덱스(i)가 필요한 작업에 적합 |
while | 조건 만족 시 반복 | "이 조건이 만족되는 동안에는 계속해"라는 상태 중심의 로직 |
foreach | 집합 데이터 순회 | EUD 핵심, 유닛이나 플레이어 일괄 처리 |
알아두면 좋은 점
Epscript의 반복문은 내부적으로 스타크래프트의 Preserve Trigger와는 다르게 작동합니다. 반복문은 하나의 트리거 실행 주기 내에서 모든 회차가 실행됩니다.
반복문에 도달하게 되면, 해당 반복문이 다 끝난 뒤에 다음에 위치한 코드가 실행됩니다.
| 구분 | Preserve Trigger | Epscript 반복문 (for, while) |
|---|---|---|
| 실행 속도 | 느림 (1사이클당 1회) | 매우 빠름 (1사이클 내 전체 완료) |
| 시간 흐름 | 실행 사이에 게임 시간이 흐름 | 실행 중에 게임 시간이 멈춰 있음 |
| 주요 용도 | 지속적인 감시, 쿨타임 계산 | 배열 탐색, 유닛 일괄 제어, 복잡한 연산 |