콘텐츠로 이동

UnitGroup

UnitGroup은 epScript에서 여러 유닛을 효율적으로 그룹화하고 관리할 수 있는 자료구조입니다.
유닛의 EPD 를 순회할 때 순서를 고려할 필요가 없고 소·중규모 그룹 관리에 적합합니다.

대규모일 땐?

1000기 이상 많은 유닛을 대상으로 하는 대규모 그룹일 때는 EUDLoopUnit과 같은 전체유닛루프가 더 나을 수 있습니다. 또한 대규모 유닛을 대상으로 하면서 성능을 극한으로 최적화해야 하는 상황이라면, cptrick을 활용한 루프를 직접 제작하는게 가장 좋습니다.

  • 1000기 이상 대규모 유닛 관리 시 → EUDLoopUnit 권장
  • 성능 극한 최적화가 필요하다면 → cptrick 기반 직접 루프 제작 참고

1. UnitGroup이란 무엇인가?

스타크래프트에서 특정 유닛을 일괄 제어하려면, 각 유닛의 EPD 주소를 직접 관리해야 합니다.
UnitGroup은 이를 대신해주는 EPD 기반 컨테이너이며, cploop와 같은 순회 기능을 제공합니다. 이를 통해 특정 조건을 만족하는 유닛만 그룹에 모아 순회하거나 일괄 처리할 수 있습니다.


2. 주요 특징

  • 유닛 추가 / 제거 / 순회 / 초기화
  • 그룹 단위 상태 관리
  • dying 블록을 통한 사망 처리
  • 정해진 최대 용량 내에서만 관리 (생성 시 지정)
  • 코드 가독성과 유지보수성이 향상됨

3. 기본 메서드

add(unit_epd)

유닛의 EPD를 그룹에 추가합니다.

// 최대 1000개의 유닛을 저장할 수 있는 그룹 생성
// 전역변수로 선언하는 걸 추천합니다.
const marines = UnitGroup(1000);

function createGroupUnit() {
    // SCdata 자료형에서 설명하겠지만, 다음에 생성될 유닛의 포인터를 통해 CUnit 객체를 생성합니다.
    const cunit = CUnit().from_next();
    CreateUnit(1, "Terran Marine", "Anywhere", P1);

    marines.add(cunit);  // 개별 유닛 추가
}

cploop

그룹에 포함된 모든 유닛을 순회하면서 처리합니다. CurrentPlayer를 해당 유닛의 EPD로 설정하면서 순회합니다.

foreach(unit : marines.cploop) {
    // unit.dying으로 죽은 유닛 감지 가능
    foreach(dead : unit.dying) {
        // 죽은 유닛 처리
        // dying 블록이 끝나면 자동으로 remove() 호출
    }
    // 살아있는 유닛 처리
}

dying

유닛이 죽었는지 확인하고 처리할 수 있는 블록을 제공합니다. 죽은 유닛은 블록이 끝날 때 자동으로 그룹에서 제거됩니다.


remove()

현재 순회 중인 유닛을 그룹에서 제거합니다. 주의: dying 블록 안에서는 직접 호출하지 마세요. 자동으로 호출됩니다.

foreach(unit : marines.cploop) {
    if (condition) {
        unit.remove();  // 조건에 따라 수동으로 제거
    }
}

length

그룹에 포함된 유닛의 수를 반환합니다. (읽기 전용)

const count = marines.length;  // 현재 그룹에 포함된 유닛 수

move_cp(offset)set_cp(offset)

이 두 메서드는 CurrentPlayer를 조작하여 유닛의 메모리에 접근하는 방법을 제공합니다. 트리거 코드 생성 시점에서의 차이점이 중요합니다.

move_cp set_cp
이전 위치를 기준으로 계산된 트리거를 생성 항상 유닛의 시작 위치를 기준으로 계산
if문이나 반복문에서 현재 위치 추적이 어려울 수 있음 조건문과 관계없이 동일한 위치로 이동
제어문이 복잡할수록 의도하지 않은 위치로 이동할 위험 존재 독립적인 메모리 접근에 유용
연속된 메모리 접근에는 효율적이나, 제어문 내에서는 주의 필요 지정된 위치로 이동하기 때문에 직관적이나 매번 계산 필요

offset은 무엇인가?

Offset은 각 구조체의 시작점으로부터 떨어진 거리를 의미합니다. 그리고 여기서 이야기하는 Offset은 CUnit 구조체와 관련되어 있고 이에 대한 정보는 EUD Lab - CUnit 탭에서 확인할 수 있습니다.

foreach(unit : marines.cploop) {
    if(condition) {
        // move_cp: 현재 위치에서 상대적으로 이동
        unit.move_cp(0x08/4);  // HP 위치로
        const hp1 = bread_cp(0, 0);

        unit.move_cp(0x4C/4);  // 소유자 위치로 (이전 위치 기준)
        const owner = bread_cp(0, 0);
    } else {
        // set_cp: 절대적인 위치로 이동
        unit.set_cp(0x08/4);  // 직접 HP 위치로
        const hp2 = bread_cp(0, 0);

        unit.set_cp(0x4C/4);  // 직접 소유자 위치로
        const owner = bread_cp(0, 0);
    }
}

두 메서드는 조건문이나 반복문 안에서 트리거가 생성되는 방식에 차이가 있습니다. move_cp는 현재 위치를 추적하면서 트리거를 생성하지만, 복잡한 제어문에서는 위치 추적이 실패할 수 있습니다. 반면 set_cp는 매번 절대 위치로 이동하는 트리거를 생성하므로 제어문과 관계없이 안전합니다. 효율적인 순환과 cp트릭 활용을 위해 이부분을 주의해야합니다.

// 주의: move_cp 사용 시 제어문에서의 위험 예시
foreach(unit : marines.cploop) {
    if(condition1) {
        unit.move_cp(0x08/4);  // HP 위치로
        if(condition2) {
            // 여기서의 위치가 예상과 다를 수 있음
            unit.move_cp(0x4C/4);  // 의도한 위치로 이동하지 않을 수 있음
        }
        unit.move_cp(0x98/4);
        //move_cp 는 컴파일 시간에 CurrentPlayer에 몇을 더해야할지 계산 후 cp 변경하는 트리거를 생성해주기 move_cp 라인이 보일 때마다 현재 currentPlayer 값을 캐싱합니다.
        //unit.move_cp(0x98/4); 라인을 실행할 때 현재 위치를 0x4C/4 로 알고 addcurpl 을 해주기 때문에 condition2 가 false 일때는 필요한 것보다 작은 값을 더하게 됩니다.
    }
}

// 안전: set_cp 사용: set_cp 는 기억된 현재 위치를 항상 하나로 고정하고 연산하기 때문에 move_cp 가 가진 문제를 피해갈 수 있지만 cp 트릭은 최적화에 초점을 맞추고 있기 때문에 안정성이 걱정되어 set_cp 만 쓰고 싶다면 차라리 const cunit = CUnit(unit.epd); 를 써서 CUnit 사용을 고려해보세요.
foreach(unit : marines.cploop) {
    const cunit = CUnit(unit.epd)
    if(cunit.hp > 256) {
        const orderId= cunit.orderID;
        if(condition2) {
        }
    }
}

4. 사용 예시

체력이 낮은 유닛을 감지하고 치료하는 예시입니다. 처음 입문하시는 분들에겐 cptrick보단 UnitGroup을 활용하면서 내부는 scdata 같은 자료형을 적극적으로 사용하기를 권장합니다.

unitgroup_cptrick.eps
const s = StringBuffer(1024);
const lowHpUnits = UnitGroup(1000);

function afterTriggerExec() {
    // 새로 생성된 유닛들 중에서 체력이 낮은 유닛 찾기
    foreach(cunit : EUDLoopNewCUnit()) {
        if (cunit.hp <= 50*256) {
            lowHpUnits.add(cunit);
        }
    }

    // 그룹 내 유닛들 처리 (cp트릭 활용버전)
    foreach(unit : lowHpUnits.cploop) {
        foreach(dead : unit.dying) {
            // 죽은 유닛 처리
            dead.move_cp(0x4C/4);
            const owner = maskread_cp(0, 0xF);
            setcurpl(owner);
            s.printf("유닛이 치료받기 전에 죽었습니다.");
        }
        // 살아있는 유닛 치료
        // 먼저, 해당 유닛타입의 최대체력 가져오기
        unit.move_cp(0x64/4);
        const unitType = maskread_cp(0, 0xFFFF);
        const scUnit = TrgUnit(unitType);
        const maxHp = scUnit.maxHp;

        // cp트릭 내부에서는 함수 이름이 _cp로 끝나는 함수를 써야 합니다.
        // 만약, 위의 scUnit.maxHp처럼 cp트릭이 아닌 경우에는 set_cp를 써서 안전하게 이동합니다.

        // 최대체력보다 현재체력이 낮은 경우 회복시켜주기
        unit.set_cp(0x08/4);
        if (Deaths(CurrentPlayer, AtMost, maxHp-1, 0)) {
            SetDeaths(CurrentPlayer, Add, 256, 0); // 체력은 256단위이므로, 256을 써야 1 회복됨.
        }
    }
}
unitgroup_noncptrick.eps
const s = StringBuffer(1024);
const lowHpUnits = UnitGroup(1000);

function afterTriggerExec() {
    // 새로 생성된 유닛들 중에서 체력이 낮은 유닛 찾기
    foreach(cunit : EUDLoopNewCUnit()) {
        if (cunit.hp <= 50*256) {
            lowHpUnits.add(cunit);
        }
    }
    // cp트릭 대신 SCdata 자료형 활용
    foreach(unit : lowHpUnits.cploop) {
        foreach(dead : unit.dying) {
            // 죽은 유닛 처리
            const cunit = CUnit(dead.epd);
            setcurpl(cunit.owner);
            s.printf("유닛이 치료받기 전에 죽었습니다.");
        }
        // 살아있는 유닛 치료
        // 먼저, 해당 유닛타입의 최대체력 가져오기
        const cunit = CUnit(unit.epd);
        const scUnit = TrgUnit(cunit.unitType);
        const maxHp = scUnit.maxHp;

        // 최대체력보다 현재체력이 낮은 경우 회복시켜주기
        if (cunit.hp < maxHp) {
            cunit.hp += 256;
        }
    }
}

5. 주의할 점

  • UnitGroup은 생성 시 지정한 최대 용량 이상의 유닛을 저장할 수 없습니다.
  • dying 블록 안에서는 remove()를 직접 호출하지 마세요.
  • 모든 유닛이 포함된 그룹은 그룹화 할 필요 없는 그룹입니다. EUDLoopUnit을 고려하세요.
  • 그룹 순회 시에는 cploop를 사용하세요.

6. 정리

  • UnitGroup유닛 EPD 관리의 복잡성을 줄이는 컨테이너입니다.
  • 순회 시 cploop를 통해 CurrentPlayer 조작을 자동화합니다.
  • 죽은 유닛 처리를 위한 dying 블록을 제공합니다.
  • 메모리 효율을 위해 정해진 크기로 관리됩니다.
  • 처음 입문하는 분들에게는 UnitGroup 기본적인 사용법(cploop, dying)과 SCdata 자료형을 섞어서 사용하기를 권장합니다.