槍足軽を作る

今回は槍足軽を創ります。
槍は戦国時代の主力兵器で、主に「持槍」と「長柄」の2種類がありました。
持槍は武士が用いた槍で、長さは約4mのものが主流だったようです。
一方長柄は足軽が用いた槍で、5〜6mもありました。足軽はこの長い槍を使って集団戦を行ったのですね。

ひとこと
f:id:tatsuyann:20190116211017g:plain

キャラクター作成

では槍足軽のキャラクターを作ります。無料のユニティちゃんを使うことにしました。
槍と槍のアニメーションも無料アセットを見つけました。
f:id:tatsuyann:20190109203601p:plain:w200f:id:tatsuyann:20190109204203p:plain:w200f:id:tatsuyann:20190109204247p:plain:w200

槍はヨーローッパ風の槍でしたが、無料なのでまあいいでしょう。これを長さだけビヨーンと伸ばして使います。
槍は5.5mぐらいとして、室町時代の男性の平均身長157cmで計算すると、だいたい自分の身長の3.5倍の槍を使っていたことになります。
ということで、槍をユニティちゃんの身長の3.5倍ぐらいまで伸ばしました。
f:id:tatsuyann:20190109210948p:plain
そして、ユニティちゃんに装備させると...
f:id:tatsuyann:20190109211449p:plain
おお、カッコイイ!! 立派な槍足軽だ!

足軽の戦い方

実際に、槍足軽はどのように戦ったのでしょうか。
彼らの部隊は数十から百数十名で構成され、陣太鼓などの号令に合わせて進退しました。

槍隊が敵の槍隊と戦闘する場合、互いの槍が触れるところまで近づき、一斉に叩き合います。
まず相手の穂を下に下げさせ、さらに叩き続けて相手が動けないようにします。
そして小手を叩き、相手がひるんだ隙に頭を叩くのです。
ちなみに、5mもの槍だとただ振り下ろすだけで、穂が当たれば指などは簡単に骨折するそうです。
壮絶な叩き合いですね...

槍隊はこのようにして叩き合い、敵の陣形を崩した方が勝ちとなります。
どちらかの陣形が崩れると、戦闘は次のフェーズに移行します。

プログラム

ではこの槍足軽を、敵が攻撃範囲に入ると槍を叩きつけるようにプログラムしましょう。
まず攻撃の間合いを設定します。敵がこの内側に入ると槍を叩きつけるのです。
間合いはSphereColliderを使います。ユニティちゃんのInspectorから、[Add Component] → [Physics] → [Sphere Collider] を選択します。
Colliderの大きさは、一足一刀で面を打てるぐらいに設定します。
f:id:tatsuyann:20190111223951p:plain
これぐらいでしょうか。

次に、このコライダーの中に敵が侵入したことを認識できるようにしましょう。
敵味方を識別するため、タグを設定します。自軍を「BlueArmy」、敵軍を「RedArmy」とでもしておきましょう。
PlayerのInspectorから[Tag] → [Add Tag]でBlueArmyを作成し、TagにBlueArmyを設定します。
f:id:tatsuyann:20190115220309p:plain
Yari Ashigaruも同様にしてTagにRedArmyを設定します。

そしてコライダーでの敵の感知ですが、合戦では複数の敵と相対するのでコライダー内の敵をリストで管理するようにします。
ただし、このままだと360度の敵を感知できてしまうので、視野を設定します。そして視野の中にいる敵はもう一つのリストで管理することにします。
つまりコライダーの中に入った敵をcolListで管理し、さらに視野に入った敵はtargetListで管理するということです。

float visualAngle = 70f;  // 視野角
public List<GameObject> colList = new List<GameObject>();     // コライダー内の敵を管理するリスト
public List<GameObject> targetList = new List<GameObject>();    // 視野内の敵を管理するリスト

次に敵がコライダーに出入りした時の処理です。

// コライダーに敵が入った時の処理
private void OnTriggerEnter(Collider other) {
    if (other.gameObject.CompareTag("BlueArmy") && !colList.Contains(other.gameObject)) {
	colList.Add(other.gameObject);
    }
}

// コライダーから敵が出た時の処理
private void OnTriggerExit(Collider other) {
    if (other.gameObject.CompareTag("BlueArmy") && colList.Contains(other.gameObject)) {
	colList.Remove(other.gameObject);
	targetList.Remove(other.gameObject);
    }
}

そしてコライダーの中の敵が視野に入っているか確認し、視野に入っている敵のリストを返すメソッドを作ります。

// 視野の中に入っているかチェック
bool IsSight(GameObject targetObj) {
    Vector3 direction = targetObj.transform.position - transform.position;
    RaycastHit hit;
    if(Vector3.Angle(transform.forward, direction) < visualAngle) {
	return true;
    }
    return false;
}

// ターゲットリストの更新
List<GameObject> refleshTargetList(List<GameObject> colList) {
    List<GameObject> tList = new List<GameObject>();
    for(int i=0; i < colList.Count; i++) {
	GameObject obj = colList[i];
	if(IsSight(colList[i])) {
	    tList.Add(colList[i]);
	}
    }
    return tList;
}

そして、コライダーの中に複数の敵がいた場合どの敵を攻撃するかを決めるメソッドを作ります。
ここでは単純に、最も距離が近い敵を攻撃対象にすることにします。

//攻撃範囲内にいる敵の中から、攻撃する敵を選ぶ
GameObject SelectTarget(Vector3 myposi, List<GameObject> targets) {
    float tmpDis = 0;
    float nearDis = 0;
    int nearIndex = 0;
    nearDis = Vector3.Distance(myposi, targets[0].transform.position);
    for(int i=1; i < targets.Count; i++) {
	tmpDis = Vector3.Distance(myposi, targets[i].transform.position);
	if(tmpDis < nearDis) {
	    nearDis = tmpDis;
	    nearIndex = i;
	}
    }
    return targets[nearIndex];
}

これで攻撃すべき敵を認識できるようになりました!
それでは、Updateメソッドを変数して実際に攻撃するようにします。

// コライダー内に敵がいる時
if(colList.Count >= 1) {
    // 視野に入っている敵のリストを更新
    targetList = refleshTargetList(colList);
    // 視野内に敵がいる時
    if(targetList.Count >= 1) {
	GameObject target = SelectTarget(transform.position, targetList);
	// 敵の方を向く
	Quaternion targetRotation = Quaternion.LookRotation(target.transform.position - transform.position);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 10);
	// 攻撃
	animator.SetBool("is_attack", true);
    }
}

完成しました!