[Unity]魚群のスクリプト。任意の位置にランダム生成される魚群のプログラム。
どうも、だらはです。
現在鋭意制作中のモルディブ旅行ゲームに、魚群プログラムを実装しました!
基本コピペで実装可能なので、良かったらシェアしていって下さい(*’▽’)
スクリプト概要
本稿にて紹介するスクリプトでできることは、以下のことです。
- 指定した領域に任意数の魚(魚群)を生成する。
- プレイヤーから離れたら非アクティブとなる。
また、スクリプトは『凹みTips』様の記事を参考にさせて頂きました。(マジ神!!)
◆参考とさせて頂いた記事
こちらで紹介されているスクリプトに対して、以下の変更を施しました。
◆変更内容
- 壁の位置を、XYZ座標入力化。
- 魚を、親オブジェクのトBox Collider内に分散生成。
- 魚群を、プレイヤーから離れると非アクティブ化。
導入手順
①魚オブジェクトにCollider、Rigidbody、スクリプトをアタッチ
魚オブジェクトに以下の要素をアタッチします。
- Collider(今回は魚オブジェクトが簡易なのでBox Colliderを採用)
- BoidFish.cs
- Rigidbody
②ScriptableObjectを使って、パラメタテーブルを作る。
ParamFish.csを右クリック -> Create -> BoidFish -> ParamFish。
ParamFish.csからパラメタテーブルを作成する。
③空のGameObjectを作成し、BoxCollider、スクリプトをアタッチ
Create Emptyにて空のGameObjectを作成し、以下の要素をアタッチします。
- Box Collider(IsTrigger ON。魚の移動領域のため)
- SimBoidFish.cs(魚オブジェクトのプレハブ/Paramを指定)
- EnableNearChild.cs
以上です。
◆BoidFish.cs
using UnityEngine;
using System.Collections.Generic;
public class BoidFish : MonoBehaviour
{
public SimBoidFish simboidFish { get; set; }
public ParamFish paramFish { get; set; }
public Vector3 pos { get; private set; }
public Vector3 velocity { get; private set; }
Vector3 accel = Vector3.zero;
List neighbors = new List();
void Start()
{
pos = transform.position;
velocity = transform.forward * paramFish.initSpeed;
}
void Update()
{
// 近隣の個体を探して neighbors リストを更新
UpdateNeighbors();
// 壁に当たりそうになったら向きを変える
UpdateWalls();
// 近隣の個体から離れる
UpdateSeparation();
// 近隣の個体と速度を合わせる
UpdateAlignment();
// 近隣の個体の中心に移動する
UpdateCohesion();
// 上記 4 つの結果更新された accel を velocity に反映して位置を動かす
UpdateMove();
}
void UpdateMove()
{
var dt = Time.deltaTime;
velocity += accel * dt;
var dir = velocity.normalized;
var speed = velocity.magnitude;
velocity = Mathf.Clamp(speed, paramFish.minSpeed, paramFish.maxSpeed) * dir;
pos += velocity * dt;
var rot = Quaternion.LookRotation(velocity);
transform.SetPositionAndRotation(pos, rot);
accel = Vector3.zero;
}
void UpdateWalls()
{
if (!simboidFish) return;
var Collider = GetComponentsInParent<BoxCollider>();
var scaleXP = Collider[1].size.x * 0.5f + transform.parent.position.x;
var scaleXM = Collider[1].size.x * 0.5f - transform.parent.position.x;
var scaleYP = Collider[1].size.y * 0.5f + transform.parent.position.y;
var scaleYM = Collider[1].size.y * 0.5f - transform.parent.position.y;
var scaleZP = Collider[1].size.z * 0.5f + transform.parent.position.z;
var scaleZM = Collider[1].size.z * 0.5f - transform.parent.position.z;
accel +=
CalcAccelAgainstWall(-scaleXM - pos.x, Vector3.right) +
CalcAccelAgainstWall(-scaleYM - pos.y, Vector3.up) +
CalcAccelAgainstWall(-scaleZM - pos.z, Vector3.forward) +
CalcAccelAgainstWall(+scaleXP - pos.x, Vector3.left) +
CalcAccelAgainstWall(+scaleYP - pos.y, Vector3.down) +
CalcAccelAgainstWall(+scaleZP - pos.z, Vector3.back);
}
Vector3 CalcAccelAgainstWall(float distance, Vector3 dir)
{
if (distance < paramFish.wallDistance)
{
return dir * (paramFish.wallWeight / Mathf.Abs(distance / paramFish.wallDistance));
}
return Vector3.zero;
}
void UpdateNeighbors()
{
neighbors.Clear();
if (!simboidFish) return;
var prodThresh = Mathf.Cos(paramFish.neighborFov * Mathf.Deg2Rad);
var distThresh = paramFish.neighborDistance;
foreach (var other in simboidFish.boidFishes)
{
if (other == this) continue;
var to = other.pos - pos;
var dist = to.magnitude;
if (dist < distThresh) { var dir = to.normalized; var fwd = velocity.normalized; var prod = Vector3.Dot(fwd, dir); if (prod > prodThresh)
{
neighbors.Add(other);
}
}
}
}
void UpdateSeparation()
{
if (neighbors.Count == 0) return;
Vector3 force = Vector3.zero;
foreach (var neighbor in neighbors)
{
force += (pos - neighbor.pos).normalized;
}
force /= neighbors.Count;
accel += force * paramFish.separationWeight;
}
void UpdateAlignment()
{
if (neighbors.Count == 0) return;
var averageVelocity = Vector3.zero;
foreach (var neighbor in neighbors)
{
averageVelocity += neighbor.velocity;
}
averageVelocity /= neighbors.Count;
accel += (averageVelocity - velocity) * paramFish.alignmentWeight;
}
void UpdateCohesion()
{
if (neighbors.Count == 0) return;
var averagePos = Vector3.zero;
foreach (var neighbor in neighbors)
{
averagePos += neighbor.pos;
}
averagePos /= neighbors.Count;
accel += (averagePos - pos) * paramFish.cohesionWeight;
}
}
◆ParamFish.cs
using UnityEngine;
[CreateAssetMenu(menuName = "BoidFish/ParamFish")]
public class ParamFish : ScriptableObject
{
public float initSpeed = 2f;
public float minSpeed = 2f;
public float maxSpeed = 5f;
public float neighborDistance = 1f;
public float neighborFov = 90f;
public float separationWeight = 5f;
public float wallDistance = 3f;
public float wallWeight = 1f;
public float alignmentWeight = 2f;
public float cohesionWeight = 3f;
}
◆SimBoidFish.cs
using UnityEngine;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections;
public class SimBoidFish : MonoBehaviour
{
[SerializeField]
int boidCount = 100;
[SerializeField]
GameObject boidPrefab;
[SerializeField]
ParamFish paramFish;
List boidFish_ = new List();
public ReadOnlyCollection boidFishes
{
get { return boidFish_.AsReadOnly(); }
}
void AddBoid()
{
var Collider = GetComponent<BoxCollider>();
var ColX = transform.position.x + Collider.size.x * (0.5f - Random.value)* 0.5f;
var ColY = transform.position.y + Collider.size.y * (0.5f - Random.value) * 0.5f;
var ColZ = transform.position.z + Collider.size.z * (0.5f - Random.value) * 0.5f;
var go = Instantiate(boidPrefab, new Vector3(ColX, ColY, ColZ), Random.rotation);
go.transform.SetParent(transform);
var boidFish = go.GetComponent<BoidFish>();
boidFish.simboidFish = this;
boidFish.paramFish = paramFish;
boidFish_.Add(boidFish);
for (int i = 0; i < boidFish_.Count; i++)
{
boidFish_[i].gameObject.SetActive(false);
}
}
void RemoveBoid()
{
if (boidFish_.Count == 0) return;
var lastIndex = boidFish_.Count - 1;
var boid = boidFish_[lastIndex];
Destroy(boid.gameObject);
boidFish_.RemoveAt(lastIndex);
}
void FixedUpdate()
{
while (boidFish_.Count < boidCount) { AddBoid(); } while (boidFish_.Count > boidCount)
{
RemoveBoid();
}
}
}
◆EnableNearChild.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnableNear : MonoBehaviour
{
private bool near;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (near)
{
foreach (Transform item in this.transform)
{
item.transform.gameObject.SetActive(true);
}
}
else
{
foreach (Transform item in this.transform)
{
item.transform.gameObject.SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider)
{
if (collider.gameObject.name == "Player") {
near = true;
}
}
void OnTriggerExit(Collider collider)
{
if (collider.gameObject.name == "Player")
{
near = false;
}
}
}
ディスカッション
コメント一覧
まだ、コメントがありません