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

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

戦闘【UnityでRTSを作る 5】

前回の記事では陣営を実装しました。
今回は陣営が分かれたNPCに戦闘を行わせたかったので戦闘を実装します。

攻撃目標を見つける

Linqを利用して攻撃目標を見つける関数を用意しました。
最も近い敵を見つけられます。

関数を呼ぶたびにFindObjectsOfTypeは避けたかったので、
シーンのCubeKunはリストに入れます。

CubeKun.cs

public static List<CubeKun> list = new List<CubeKun>();

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

protected new void OnDisable () {
	base.OnDisable();
	list.Remove(this);
}

public CubeKun GetTarget () {
	return
		list.Where(c => c != this && Team != c.Team).
		OrderBy(c => Vector3.Distance(GetFeetPosition(),c.GetFeetPosition())).
		FirstOrDefault();
}


そしてCubeKunにTargetという抽象プロパティを追加します。

CubeKun.cs

public abstract CubeKun Target { get; set; }


Playerには自動プロパティで実装しました。
Updateで攻撃目標が存在しない場合に攻撃目標を探します。
Update関数をオーバーライドしていますが、これは後述のエイミングの実装によるものです。
ちなみに、プレイヤーの移動を実装した記事から移動方法が変化しています。

Player.cs

public override CubeKun Target { get; set; }

private RaycastHit hit;

protected override void Update () {
	base.Update();

	if (!Target) Target = GetTarget();

	canMove = Input.GetMouseButton(0);
	canSearch = canMove;
	if (canMove && Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),out hit)) {
		Pointer.position = hit.point;
		MoveToPointerPosition();
	}
}


NPCにはTargetのSetterにtarget(経路探索の移動目標)を共に更新する処理を実装しています。
プレイヤーはポインターの方へ移動しないといけませんが、
NPCは攻撃目標に向かえばいいからです。

NPC.cs

public CubeKun Target {
	get { return _Target; }
	set {
		_Target = value;
		target = value ? value.Tr : null;
	}
}
private CubeKun _Target = null;

protected override void Update () {
	base.Update();
	if (target) {
		MoveTo(target.position);
	} else {
		Target = GetTarget();
	}
}

エイミング

この記事では腕を回転させることで狙いを定めさせます。
そのため、最初に腕の参照をしなければいけません。

腕の参照

これが標準的なキューブ君の階層です。
f:id:MackySoft:20170418213951j:plain
腕の名前が「Arm_L」と「Arm_R」です。
左腕には「MachineGun」という名の武器を持たせています。

CubeKunに腕のTransformの参照を持たせる配列を用意します。

CubeKun.cs

public Transform[] Arms { get; private set; }


さらにAwake関数に腕を見つける処理を追加します。

Arms = new Transform[] {
	Tr.FindChild("Arm_L"),
	Tr.FindChild("Arm_R")
};

腕を回転させる

Targetの方向に腕を回転させるためにUpdate関数を用意します。

CubKun.cs

public float armSpeed = 10;

protected virtual new void Update () {
	if (Target) {
		for (int i = 0;Arms.Length > i;i++) {
			var dir = Target.Tr.position - Arms[i].position;
			dir.y = 0;
			var newDir = Vector3.RotateTowards(
				Arms[i].forward,
				dir,
				armSpeed * Time.deltaTime,
				0
			);
			Arms[i].rotation = Quaternion.LookRotation(newDir);
		}
	} else {
		for (int i = 0;Arms.Length > i;i++) {
			Arms[i].localRotation = Quaternion.Euler(Vector3.zero);
		}
	}
}

armSpeedが腕の回転速度を決めます。
攻撃目標が存在する場合は、狙いを定め、存在しない場合はデフォルトの値に戻します。

完成イメージ

余談ですが弾がやたらと見えづらかったのでTrailRendererを追加しました。


敵味方の区別【UnityでRTSを作る 4】

今回は戦闘には欠かせない敵味方の区別を行います。

レイヤー

プレイヤー、味方、敵、味方の弾、敵の弾のレイヤーを用意しました。
f:id:MackySoft:20170417004127j:plain

当たり判定

レイヤーの当たり判定をいじります。
f:id:MackySoft:20170417004456j:plain
プレイヤー及び味方の弾は敵だけに当たり、
敵の弾はプレイヤー及び味方だけに当たります。
また、弾がぶつかり合うことはありません

レイヤーを変更するための拡張メソッド

スクリプトからレイヤーの一括変更を行うのが結構めんどくさかったので、
そのための拡張メソッドを用意しました。

GameObjectExtension.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class GameObjectExtension {

	public static void SetLayer (this GameObject gameObject,int layer, bool needSetChildrens = true) {
		if(!gameObject) return;

		gameObject.layer = layer;
		
		if(!needSetChildrens) return;
		
		foreach(Transform childTransform in gameObject.transform)
		  SetLayer(childTransform.gameObject,layer,needSetChildrens);
	}

	public static void SetLayer (this GameObject gameObject,string layerName,bool needSetChildrens = true) {
		SetLayer(gameObject,LayerMask.NameToLayer(layerName),needSetChildrens);
	}
}

GameObjectの子要素のレイヤーも一括で変更できるようになります。

陣営を分ける

ここから実際に陣営を分けていきます。
そのために陣営を表す列挙型を用意しました。

public enum Team {
	Ally,
	Enemy
}

ユニットの陣営分け

PlayerとNPCの基底クラスのCubeKunクラスに陣営を表す抽象プロパティを用意します。

CubeKun.cs

public abstract Team Team { get; set; }

つまりCubeKunを継承しているPlayerとNPCには別々の処理を記述します。

Player.cs

public override Team Team {
	get { return Team.Ally; }
	set { throw new NotImplementedException(); }
}

Playerは必ず味方陣営に所属します。

NPC.cs

public override Team Team {
	get { return _Team; }
	set {
		gameObject.SetLayer(value.ToString());
		_Team = value;
	}
}
[SerializeField]
private Team _Team = Team.Ally;

private void OnValidate () {
	Team = _Team;
}

Setterに自動でレイヤーを変更する処理を用意しました。

武器の陣営分け

前回の武器のソースコードから変更点があります。
弾の発射は前回ではWeaponクラスのShoot関数で行っていましたが、
今回からはBulletクラスのShot関数を呼ぶことになります。
そこに弾のレイヤーを変更する処理を挿みます。

Bullet.cs

public CubeKun Parent { get; private set; }

public void Shot (Transform point,CubeKun parent,int power,float speed,float time) {
	var ins = Instantiate(this,point.position,point.rotation);
	Parent = parent;
	gameObject.SetLayer("Bullet_" + Parent.Team.ToString());
	ins.power = power;
	ins.speed = speed;
	Destroy(ins.gameObject,time);
}

これによりWeaponのShoot関数が簡略化されました。

Weapon.cs

public CubeKun Parent { get; private set; }

private void Start () {
	Parent = GetComponentInParent<CubeKun>();
	ShootStart();
}

public void Shoot () {
	for (int i = 0;firingPoints.Length > i;i++) {
		bullet.Shot(firingPoints[i],Parent,power,speed,time);
	}
}

次回は陣営を分けたNPC同士で戦わせます。

NPCの移動と武器【UnityでRTSを作る 3】

今日はNPCの移動及び武器を実装しました。

モデル

まず最初に、NPCにプレイヤーと同じモデルを使うとややこしいので、
区別するためにNPCのモデルを作りました。
f:id:MackySoft:20170415164121j:plain

NPCの移動処理

これは前回の記事で紹介したCubeKunクラスを継承させてMoveTo(移動のための汎用関数)にターゲットの位置を渡してあげるだけなので簡単でした。

NPC.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MackySoft.CubeKunWars {
	public class NPC : CubeKun {

		private new void Update () {
			if (target) MoveTo(target.position);
		}
    
	}
}

武器

弾の処理

武器から発射する弾のコードです。
弾のステータスと単純な移動処理だけです。

Bullet.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MackySoft.CubeKunWars {

	[RequireComponent(typeof(BoxCollider))]
	public class Bullet : MonoBehaviour {
		
		public Transform Tr {
			get { return _Tr ? _Tr : (_Tr = transform); }
		}
		private Transform _Tr;

		public int power = 20;
		public float speed = 10;

		private void Update () {
			Tr.Translate(Tr.forward * speed * Time.deltaTime,Space.World);
		}
		
	}
}

武器による弾の発射

武器には弾の発射と、その処理を一定間隔でループさせるためのコルーチンを実装します。

Weapon.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MackySoft.CubeKunWars {
	public class Weapon : MonoBehaviour {
		
		public Transform[] firingPoints;

		public float interval = 0.5f;

		[Header("Bullet Settings")]
		public Bullet bullet;
		public int power = 20;
		public float speed = 5;
		public float time = 5;
    
		private Coroutine shootCoroutine = null;

		private void Start () {
			ShootStart();
		}

		public void ShootStart () {
			if (shootCoroutine == null)
				shootCoroutine = StartCoroutine(ShootCoroutine());
		}

		public void ShootStop () {
			if (shootCoroutine == null) return;
			shootCoroutine = null;
			StopCoroutine(shootCoroutine);
		}

		public void Shoot () {
			for (int i = 0;firingPoints.Length > i;i++) {
				var ins = Instantiate(bullet);
				ins.Tr.position = firingPoints[i].position;
				ins.Tr.rotation = firingPoints[i].rotation;
				ins.power = power;
				ins.speed = speed;
				Destroy(ins.gameObject,time);
			}
		}

		private IEnumerator ShootCoroutine () {
			while (true) {
				Shoot();
				yield return new WaitForSeconds(interval);
			}
		}

	}
}

体力

武器があっても減る体力が無いと意味がないので、体力を実装するHealthクラスを用意します。

Health.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace MackySoft.CubeKunWars {
	
	[Serializable]
	public class OnValueChangedEvent : UnityEvent<int> { }

	public class Health : MonoBehaviour {
		
		[SerializeField]
		private int _Value = 100;

		[Header("Events")]
		public OnValueChangedEvent onValueChanged;
		public UnityEvent onDie;

		public int Value {
			get { return _Value; }
			set {
				_Value = value > 0 ? value : 0;
				onValueChanged.Invoke(Value);
				if (IsDead) onDie.Invoke();
			}
		}

		public bool IsDead {
			get { return Value == 0; }
		}

		private void OnValidate () {
			Value = _Value;
		}

	}
}

それに従ってCubeKunクラスに「弾に当たった時にダメージを受ける」処理を追加しました。

CubeKun.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Pathfinding;

namespace MackySoft.CubeKunWars {

	[RequireComponent(typeof(Health))]
	[RequireComponent(typeof(Rigidbody))]
	public abstract class CubeKun : AIPath {

		public float sleepVelocity = 0.4F;
		
		public Transform Tr { get; private set; }
		public Rigidbody Rigid { get; private set; }
		public Health Health { get; private set; }
		
		protected override void Awake () {
			base.Awake();
			Tr = transform;

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

			Health = GetComponent<Health>();
			Health.onDie.AddListener(() => Destroy(gameObject));
		}
		
		protected virtual void OnCollisionEnter (Collision collision) {
			var bullet = collision.gameObject.GetComponent<Bullet>();
			if (bullet) {
				Health.Value -= bullet.power;
				Destroy(bullet.gameObject);
			}
		}

		public void MoveTo (Vector3 position) {
			if (canMove) {
				var dir = CalculateVelocity(GetFeetPosition());
				RotateTowards(targetDirection);

				dir.y = 0;
				if (dir.sqrMagnitude > sleepVelocity * sleepVelocity) {

				} else {
					dir = Vector3.zero;
				}
				Tr.Translate(dir * Time.deltaTime,Space.World);
			}
		}

		public override Vector3 GetFeetPosition () {
			return Tr.position;
		}

	}
}

プレイヤーの移動【UnityでRTSを作る 2】

前回の記事ではざっくりと開発計画をしましたが、
ついに今回ゲーム制作が始まります。

今回はプレイヤーの移動です。

モデル

まずはプレイヤーのモデルの紹介です。

f:id:MackySoft:20170413205955j:plain
キューブ君(CubeKun)と呼びます。
ゲーム制作を始めた当初に作ったキャラクターです。

このキューブ君がプレイヤーとなります。

そしてこのプロジェクトの名前は「CubeKun Wars」となりました。
(変更の可能性もあります)

カメラ

まず最初にカメラを制御するためのコードを書きます。
トップダウン型のゲームなのでカメラが下に向いている前提のコードです。

CameraController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MackySoft.CubeKunWars {
	public class CameraController : MonoBehaviour {

		public Transform target;
		public float height = 10;

		private Transform tr;

		void Start () {
			tr = transform;
		}
		
		void Update () {
			if (!target) return;
			tr.position = target.position + Vector3.up * height;
		}
	}
}

移動処理

経路探索にはA* Pathfinding Projectを使用します。

CubeKun.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Pathfinding;

namespace MackySoft.CubeKunWars {

	public abstract class CubeKun : AIPath {

		public float sleepVelocity = 0.4F;
		
		public Transform Tr { get ; private set; }

		protected override void Awake () {
			base.Awake();
			Tr = transform;
		}

		public void MoveTo (Vector3 position) {
			if (canMove) {
				var dir = CalculateVelocity(GetFeetPosition());
				RotateTowards(targetDirection);

				dir.y = 0;
				if (dir.sqrMagnitude > sleepVelocity * sleepVelocity) {

				} else {
					dir = Vector3.zero;
				}
				Tr.Translate(dir * Time.deltaTime,Space.World);
			}
		}

		public override Vector3 GetFeetPosition () {
			return Tr.position;
		}

	}
}


Player.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MackySoft.CubeKunWars {
	public class Player : CubeKun {

		public Transform Pointer { get; private set; }

		protected override void Awake () {
			base.Awake();
			canMove = false;
			Pointer = new GameObject("pointer").transform;
			target = Pointer;

			Camera.main.orthographic = true;
		}

		private new void Update () {
			canMove = Input.GetMouseButton(0);
			canSearch = canMove;
			if (canMove) {
				Pointer.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
				MoveToPointerPosition();
			}
		}

		public void MoveToPointerPosition () {
			MoveTo(Pointer.position);
		}

	}
}

動画

クリックし続けるとキューブ君がカーソルのある方向へ向かいます。

Make RTS with Unity Part.1[Player Mover]

どんなゲームを作るか【UnityでRTSを作る 1】

これから作り始めるRTSっぽいゲームの話をします

開発日記を始めるわけ

「誰にも見せずやってても続かなかった」からです。
2年間ゲームを作ってて一番開発が続いたのが、
YouTubeに進歩を投稿していたプロジェクトです。(今はありませんがブログも同時進行でした)

そんなわけで今一度、開発日記を初めようと思います。

ゲームの内容

タイトル通り、RTSのような群衆同士をを戦わせるゲームを作ります。
ただし通常のRTSと違って、プレイヤーは神様ではなく、
NPCと同じようにユニットとして存在し、戦うことができます。

ルール

  • お互いにマップに配置された拠点を取り合いながら戦います
  • 拠点はユニットのスポーン、弾薬の補充など、戦略的に重要な意味を持ちます
  • 先に相手を全滅させたら勝ちです

開発手順

メインシーンから作る

タイトル画面などのUIが主体となるシーンは後回しです。
めんどくさいうえに大した達成感がないからです。

小さく作る

最初から立派なものを作らず、モデルは少なく、UIもてきとーに作ります。
まずは最低限のゲームサイクルを作ります。

使用するアセット

現在使用予定のアセットです。

A* Pathfinding Project

ユニットの移動に使用します。

Chronos

ポーズ、倍速処理に使用します。

Dynamic CullingGroup

製作中の自作アセットです。
ランダムマップになる予定なので、動的なカリングをするために使用します。

あいさつ

はじめまして。

これからUnityでゲームを作る過程を投稿してしていきます。

 

「へぇ~!ヒキニートでもゲーム作れるんだ。じゃあ俺でも作れるじゃん!」

的な勇気をゲーム開発者たちに与えていきます。

 

twitter.com

 

www.youtube.com

 

github.com