読者です 読者をやめる 読者になる 読者になる

ヒキニートがゲームを作るブログ

Unityでゲームを作る過程を投稿します

AIの改良【UnityでRTSを作る 10】

前回の記事でも言ったように、AIの改良にかなり手間取ってます。

ですがそろそろ更新しないと変更箇所がわからなくなりそうなので、
現在の進捗をここに記します。

ユニットと拠点を共通規格にする

ユニットと拠点に共通点が多いので、共通のクラスを継承させてコードの短縮を図ります。

CKWBehaviour.cs

using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Health))]
public abstract class CKWBehaviour : MonoBehaviour {

	public static List<CKWBehaviour> list = new List<CKWBehaviour>();
	
	[Range(0,100)]
	public int priority = 50;
	public float distanceStrength = 0.5f;

	public Transform Tr { get; private set; }
	public Rigidbody Rigid { get; private set; }
	public Health Health { get; private set; }

	public virtual Team Team { get; set; }
	
	protected virtual void Awake () {
		Tr = transform;

		Rigid = GetComponent<Rigidbody>();
		Rigid.constraints = RigidbodyConstraints.FreezeAll;

		Health = GetComponent<Health>();
	}

	protected virtual void OnEnable () {
		list.Add(this);
	}

	protected virtual void OnDisable () {
		list.Remove(this);
	}
	
	public float CalculatePriority (CKWBehaviour behaviour) {
		return priority + Vector3.Distance(Tr.position,behaviour.Tr.position) * distanceStrength;
	}
	
	public Team GetTeamRelative (CKWBehaviour behaviour) {
		if (
			Team != Team.Independent && behaviour.Team == Team.Independent ||
			Team == Team.Independent && behaviour.Team != Team.Independent
		)
			return Team.Independent;
		else if (Team == behaviour.Team)
			return Team.Ally;
		else
			return Team.Enemy;
	}

}

このクラスには共通のコンポーネントの初期化やTeamプロパティの実装の他に、
目標決めの為の変数や関数を実装しています。

名前 説明
priority 優先度を計算する際の基礎。
distanceStrength 優先度を計算に距離が及ぼす影響の強さ。
CalculatePriority 指定したCKWBehaviourの優先度を計算する。
GetTeamRelative 指定したCKWBehaviourとの相対的な関係を返す。
(例:同じ陣営ならAlly)

CKWBehaviourというネーミングですが、

CKWは、このゲームの名前の「CubeKunWars」の略で、
Behaviourは、登場するキャラなどの基本になるクラスに付けるようにしています。

移動処理の移行

CubeKunにCKWBehaviourを継承させ、今までのAIPathは継承できなくなったので、
移動処理はCubeKunMoverというクラス作り、そちらに移行させます。

CubeKunMover.cs

using UnityEngine;

public class CubeKunMover : AIPath {
	
	public float sleepVelocity = 0.4f;
	public CubeKun parent;

	public void Move () {
		if (!canMove || !target) return;
		
		var dir = CalculateVelocity(GetFeetPosition());
		RotateTowards(targetDirection);
		
		dir.y = 0;
		if (dir.sqrMagnitude > sleepVelocity * sleepVelocity) {

		} else {
			dir = Vector3.zero;
		}
		tr.Translate(dir * parent.Timeline.deltaTime,Space.World);
	}
	
	public override Vector3 GetFeetPosition () {
		return tr.position;
	}

}


CubeKunにCubeKunMoverを参照させます。

CubeKun.cs

public CubeKunMover Mover { get; private set; }

protected override void Awake () {
	//前略
	Mover = GetComponent<CubeKunMover>();
	Mover.parent = this;
}

今までCubeKun・Player・NPCで参照していたAIPathのフィールドなどの先頭には

Mover.

を付け足します。

目標を決める(Targeting)

目標を探す

CubeKun.cs

public CKWBehaviour GetAttackTarget () {
	return
		CKWBehaviour.list.Where(b => b && b != this && GetTeamRelative(b) == Team.Enemy).
		OrderBy(b => CalculatePriority(b)).
		FirstOrDefault();
}

public BasePoint GetSuppressionTarget () {
	return
		BasePoint.list.Where(b => b && GetTeamRelative(b) != Team.Ally).
		OrderBy(b => CalculatePriority(b)).
		FirstOrDefault();
}
名前 説明
GetAttackTarget 敵対関係にある優先度が一番高いCKWBehaviourを返します。
GetSuppressionTarget 自身の陣営と違う陣営の優先度が一番高い拠点を返します。

コルーチンで定期更新

今までは一度決めた目標のオブジェクトがDestroyされるまで目標が変わりませんでしたが、コルーチンで定期的に目標を更新するようにします。

CubeKun.cs

public float retargetingRate = 0.1f;

private Coroutine targetingCoroutine = null;

protected virtual void Start () {
	TargetingStart();
}

public void TargetingStart () {
	if (targetingCoroutine == null)
		targetingCoroutine = StartCoroutine(TargetingCoroutine());
}

public void TargetingStop () {
	if (targetingCoroutine != null)
		StopCoroutine(targetingCoroutine);
}

private IEnumerator TargetingCoroutine () {
	while (true) {
		Target = GetAttackTarget();
		if (!Target)
			Target = GetSuppressionTarget();
		if (Target) {
			switch (GetTeamRelative(Target)) {
				case Team.Independent:
					ShootStopAll();
					break;
				case Team.Ally:
					ShootStopAll();
					break;
				case Team.Enemy:
					ShootStartAll();
					break;
			}
		} else {
			ShootStopAll();
		}
		yield return Timeline.WaitForSeconds(retargetingRate);
	}
}

これにより、いままでPlayerやNPCのUpdate関数の中で行われていた目標更新関係の処理を削除しました。

イメージ