스타봇

스타크래프트 봇 동아리, 내전용 봇 개발 (#7)

오잎 클로버 2021. 7. 15. 12:00
728x90

저그 유저의 필수 유닛 중 하나이지만 본인이 당하면 역겨운 유닛, '러커 (럴커)'.

 

※본 저자는 러커보다는 럴커가 더 편하여 럴커라고 적겠습니다. 글을 읽는 데에 참고해주세요.

 

저그의 희망 중 하나인 '럴커'는 지상으로만 공격이 가능하고, 또 공격을 하기위해서는 반드시 '버러우(잠복)'을 해야만 한다.

럴커의 공격하는 거리는 6이지만, 실제 타격 범위는 7로 코드를 짜는 데 7로 작성하였다.

(물론 추후 6으로 수정가능)

이번 코드는 꽤 복잡하나, 중간중간 설명을 하겠다.

 

private void lurkerControl(Unit unit) {
	if (!unit.exists() || unit == null || pos == null)
		return;
	boolean inOrderRange = unit.getDistance(pos) <= 3 * 32;
	Unit target = getTarget(unit, 8);
		
	if (target != null) {
		boolean isThreat =
			target.getType() == UnitType.Terran_Missile_Turret ||
			commandUtil.CanAttackGround(target) &&
		!target.getType().isWorker();
		int dist = unit.getDistance(target);
		int lurkerRange = UnitType.Zerg_Lurker.groundWeapon().maxRange();
			
		if (Config.DrawUnitTargetInfo) {
			MyBotModule.Broodwar.drawLineMap(unit.getPosition(), target.getPosition(), Color.Blue);
			if (unit.getTarget() != null && unit.getTargetPosition().isValid())
				MyBotModule.Broodwar.drawLineMap(unit.getPosition(), pos, Color.Orange);
			MyBotModule.Broodwar.drawCircleMap(unit.getPosition(), 12, Color.Red);
			// System.out.println("거리" + dist);
		}
		if (dist <= 65 || isThreat || dist <= lurkerRange) {
			if (unit.canBurrow())
				commandUtil.burrow(unit);
			else if (unit.isBurrowed()) {
				if (dist <= lurkerRange)
					commandUtil.attackUnit(unit, target);
			}
		}
		else if (!isThreat && dist > lurkerRange ||
			isThreat && dist > Math.max(lurkerRange, 32 + commandUtil.GetAttackRange(unit, target))) {
			if (unit.canUnburrow() && !inOrderRange && okToUnburrow(unit))
				commandUtil.unburrow(unit);
			else if (unit.isBurrowed()) {
				if (dist <= lurkerRange)
					commandUtil.attackUnit(unit, target);
			}
			else 
				commandUtil.move(unit, target.getPosition());
		}
		else {
			if (unit.isBurrowed()) {
				if (dist <= lurkerRange)
					commandUtil.attackUnit(unit, target);
			}
			else
				commandUtil.move(unit, target.getPosition());
		}
	}
	else {
		if (Config.DrawUnitTargetInfo)
			MyBotModule.Broodwar.drawLineMap(unit.getPosition(), pos, Color.White);
			
		// 타겟 선정하지 못함
		if (inOrderRange) {
			if (unit.canBurrow())
				commandUtil.burrow(unit);
		}
		else {
			if (unit.canUnburrow()) {
				if (okToUnburrow(unit))
					commandUtil.unburrow(unit);
			}
			else
				commandUtil.move(unit, pos);
		}
	}
}

위 코드가 이름답게 럴커를 컨트롤하는 코드이다.

pos는 럴커가 버러우 전에는 움직이기만 가능하기에 executeCombat 메소드에 작동시키는 데 문제가 존재하기에

executeCombat에 있는 chokePoint 및 공격 위치를 저장하도록하는 변수이다.

 

저기 Config을 사용한 이유는 UI가 많으면 렉이 심하게 걸릴 수 있기에 if문으로 한 것이다.

대부분 코드에 있는 숫자들이 값이 높은 이유는 스타크래프트는 PositionTilePosition으로 나뉜다.

1 Position이 32 TilePosition이라 생각하면 편하다.

(한 타일당 32 픽셀이다)

타겟이 자신의 시야 (럴커의 시야는 8이다.)안에 있다면 여러 조건에 따라 버러우, 버러우 해제, 공격, 이동을 한다.

꽤 복잡하지만 조금만 생각해보면 플레이를 할 때 많은 이들이 무의식적으로 하는 것이다.

굳이 적자면 이러하다.

 

더보기

첫 번째 조건
1. 유닛(럴커가) 지정된 타겟과의 거리가 65픽셀보다 작거나 같다 또는 
타겟이 터렛 또는 타겟이 지상공격 가능 그리고 타겟이 일꾼이 아니라면가 참이다. 또는
타겟과 유닛(럴커)와의 거리가 럴커 최대 사거리 안에 있다면
1) 럴커가 버러우할 수 있다면
-> 럴커를 버러우한다.
2) 럴커가 버러우 상태라면
-> 또다시 유닛(럴커)와의 거리가 럴커 최대 사거리 안에 있다면
---> 럴커가 해당 타겟을 공격한다.
2. 타겟이 터렛 또는 타겟이 지상공격 가능 그리고 타겟이 일꾼이 아니라면가 거짓이다. 그리고
타겟이 유닛(럴커)와 거리가 럴커의 최대 사거리보다 크다. 또는
타겟이 터렛 또는 타겟이 지상공격 가능 그리고 타겟이 일꾼이 아니라면가 참이다. 그리고
럴커의 최대사거리 + 32픽셀이 타겟과의 거리가 더 멀다면
1)
-> 럴커가 버러우 해제할 있다. 그리고 유닛과 pos의 거리가 96픽셀보다 같거나 작다가 거짓이다. 그리고
okToUnburrow가 가능하다면 
---> 럴커를 버러우 해제합니다.
2)
-> 럴커가 버러우 상태라면
---> 타겟과 럴커와의 거리가 럴커의 최대거리가 작거나 같다면
타겟을 공격한다.
3)
-> 나머지라면
타겟으로 이동한다.
3. 나머지라면
-> 럴커가 버러우 상태라면
---> 타겟과 럴커와의 거리가 럴커의 최대거리가 작거나 같다면
타겟을 공격한다.
-> 나머지라면
타겟으로 이동한다.
두 번째 조건
나머지라면
1. 타겟을 선정하지 못했다면
-> 럴커가 버러우할 수 있다면
럴커를 버러우한다.
2. 나머지라면
1)
-> 럴커가 버러우 해제할 수 있다면
---> okToUnburrow가 가능하다면 
럴커를 버러우 해제한다.
2)
-> 나머지라면
럴커를 pos로 움직인다.

 

이쯤되면 궁금해지는 메소드가 2개가 있다.

getTargetokToUnburrow이다.

 

먼저 okToUnburrow를 설명하자면

private boolean okToUnburrow(Unit unit) {
	return MyBotModule.Broodwar.getFrameCount() % 25 == 0 &&
		unit.getHitPoints() > 31 &&
		!unit.isIrradiated();
}

체력이 31보다 크고 이레디에이트를 맞지않고있고 동시에 25프레임이 맞다면 

true를 반환하지만 이 중 하나라도 만족하지않는다면 false를 반환한다.

 

private Unit getTarget(Unit unit, int r) {
		
	List<Unit> targets = unit.getUnitsInRadius(r*32);
	List<Unit> oldTargets = new ArrayList<Unit>();
		
	if (targets.isEmpty())
		return null;
	
	int lurkerRange = UnitType.Zerg_Lurker.groundWeapon().maxRange();
	
	List<Unit> targetsInRange = new ArrayList<Unit>();
	for (Unit target : targets) {
		if (target.getPlayer() == MyBotModule.Broodwar.enemy()) {
			if (unit.getDistance(target) <= lurkerRange)
				targetsInRange.add(target);
			oldTargets.add(target);
		}
		
	}
				
	if (unit.isBurrowed() && !targetsInRange.isEmpty())
		return getFarthestTarget(unit, targetsInRange);
		
	List<Unit> newTargets = targetsInRange.isEmpty() ? oldTargets : targetsInRange;
	return getNearestTarget(unit, newTargets);
}

두 리스트를 만들고 targetsInRange리스트는 사거리 내에 들어가 있는 적군을 담고,

oldTargets는 시야에 있는 모든 적군을 담는다.

 

럴커가 버러우 상태이고 targetsInRange가 비어있지않다면

가장 멀리 있는 타겟을 찾아 반환해주고

targetsInRange가 비어있다면 oldTargetsnew리스트에 담고 아니라면

new리스트에 targetsInRange를 담는다.

그리곤 가장 가장 가까운 타겟을 찾아 반환한다.

 

가장 가까이있는 타겟을 찾는 메소드는

private Unit getNearestTarget(Unit unit, List<Unit> targets) {
	int highPriority = 0;
	int closestDist = Integer.MAX_VALUE;
	Unit bestTarget = null;
		
	for (Unit target : targets) {
		int distance = unit.getDistance(target);
		int priority = getAttackPriority(target);
			
		if (priority > highPriority || priority == highPriority && distance < closestDist) {
			closestDist = distance;
			highPriority = priority;
			bestTarget = target;
		}
		
	}
	return bestTarget;
}

..으로 하는 데 그러면 또다시 getAttackPriority메소드가 궁금해지는 데, 해당 메소드는 꽤 길지만 단순하다.

또, 제일 먼 타겟을 찾는 메소드는 가장 가까운 타겟을 찾는 메소드를 잘 수정하여 하길 바란다.

private int getAttackPriority(Unit target) {
	UnitType targetType = target.getType();
		
	if (targetType == UnitType.Terran_Ghost &&
			target.getOrder() == Order.NukePaint ||
			target.getOrder() == Order.NukeTrack)
		return 15;
		
	if (targetType == UnitType.Terran_Vulture_Spider_Mine && !target.isBurrowed())
		return 10;
		
	if (targetType == UnitType.Protoss_High_Templar ||
			targetType == UnitType.Protoss_Reaver ||
			targetType == UnitType.Zerg_Defiler)
		return 10;
		
	if (commandUtil.CanAttackGround(target) && !target.getType().isWorker())
		return 9;
		
	if (targetType == UnitType.Terran_Missile_Turret)
		return 9;
		
	if (targetType.isWorker()) {
		if (target.isConstructing() || target.isRepairing())
			return 9;
		return 8;
	}
		
	if (targetType == UnitType.Terran_Medic)
		return 8;
	
	if (targetType == UnitType.Terran_Comsat_Station)
		return 7;
		
	if (targetType == UnitType.Protoss_Observatory ||
			targetType == UnitType.Protoss_Robotics_Facility)
		return 7;
		
	return 5;
}

시행 영상을 마지막으로 오늘 글을 마무리하겠다.

더보기

코드 짜는 거 보다 블로그 글 쓰는 게 더 힘든 것 같네요;;