Unity C# 自作スクリプト:オブジェクトの表示をプレイヤーとの距離で切り替える
先日、FANBOXとPatreonで試作のステージモデルを公開しました。
https://www.patreon.com/posts/54967136
https://btamodev.fanbox.cc/posts/2616780
このステージモデルのアセットはそのままUnityで使えますが、これにあわせるとさらに実用性が上がる(かもしれない)、Unityゲーム向け自作スクリプトをひとつ記してみます。
OutRangeCuller.cs
using UnityEngine;
using UnityEngine.Events;
namespace FUNSET
{
/// <summary>
/// 簡易のオブジェクトカリングシステム
/// どこか空オブジェクトにアタッチして、該当するNPCやオブジェクトを登録する
/// プレイヤーのカメラから遠く離れてかつ視覚の外に消えるか判定する
/// 単純に、カメラから離れてるオブジェクト(子オブジェクトを抱えた)を非表示にする
/// </summary>
public class OutRangeCuller : MonoBehaviour
{
[SerializeField, Header("カリング対象をセット"), Tooltip("カリング対象")]
GameObject[] _CullActivateSet;
[SerializeField, Header("プレイヤーかカメラの位置"), Tooltip("プレイヤーかカメラの位置")]
GameObject PlayerCamera;
[SerializeField, Header("アウトレンジ判定距離"), Tooltip("アウトレンジ判定距離")]
float Distance=30;
[Header("カリング判定頻度(秒)"), Tooltip("カリング判定頻度")]
[SerializeField]
float Checkinterval = 0.3f;
float CheckTime = 0;
private void Start()
{
CheckTime = Checkinterval;
}
public void Update()
{
CheckTime -= Time.deltaTime;
if (CheckTime > 0) { return; }
CheckTime = Checkinterval ;
for (int i = 0; i < _CullActivateSet.Length; i++)
{
CheckDistance(i);
}
}
/// <summary>
/// カメラからの距離を判定
/// </summary>
/// <param name="a">_cullActivateSetの要素数</param>
public void CheckDistance(int a)
{
//距離でカリング判定する。カメラの向きから補正する
Vector3 cameramukihosei;
cameramukihosei = PlayerCamera.transform.forward * Distance * 0.4f;
float _d = ((PlayerCamera.transform.position + cameramukihosei) - _CullActivateSet[a].transform.position).sqrMagnitude;
//Debug.Log("Distance" + _d + "Distance" + Mathf.Pow(Distance, 2));
//距離が一定以上離れたら非表示に
if ((_d > Mathf.Pow(Distance, 2))) { _CullActivateSet[a].SetActive(false); }
else
{
_CullActivateSet[a].SetActive(true);
}
}
}
空オブジェクトにこのスクリプトをアタッチして、シーン上のゲームオブジェクト複数とプレイヤーもしくはカメラのオブジェクトを登録、判定距離と判定頻度を設定すると、
プレイヤーの位置から一定距離離れたゲームオブジェクトが非表示になります。近づけば表示が戻ります。
描画負荷が高そうな大量のゲームオブジェクト、例えば草や木などを、距離が離れたら自動的に非表示にすることができます。
仕組みは超単純です。それがなぜ使えるのかというと、LODやオクルーションカリングは結構重いからです。モバイル用3Dゲームではほぼ使えないレベルです。
一つ一つのオブジェクトごとに丁寧に、毎フレーム距離や視界を判定して、表示切り替え判定を処理させる…とやるのは複雑で無駄が多いです。
セットアップと調整もかなりの手間です。
一定範囲の草や小物は一つのゲームオブジェクト下にまとめて、1つのスクリプトに登録して、大雑把に表示の切り替えを距離で判定・制御することで、大きく処理と製作のコストが稼げます。
加えて、毎フレーム計算するのでなく、一定時間ごとに実行するようにします。1フレーム毎に実行と30フレームに一度では、処理負荷が30倍違う理屈です。
このステージモデルの場合は、GardenDetailオブジェクトに大量の庭の草木を入れてあります。GardenDetailオブジェクトをOutRangeCullerに登録して表示非表示を切り替え対応にすれば、その子に入れた草木モデルごとごっそり切り替わってくれます。
上記の配布アセットの場合は狭く密集した日本の住宅モデルで、それを大量に配置する使い方を想定してるので、庭に生えてる草やプロップは見栄え以外ゲーム性には関わらず、画面に映らないときには確実に非表示になってほしいやつです。プレイヤーが家2-3軒ぶん離れたところで切り替わるように距離を設定しとけば、切り替わる瞬間はまず見えないから、このスクリプトが有効に働くことでしょう。まとめてあるオブジェクト数が多いほど効果が大きくなります。
使えない状況
プレイヤーから50メートルも離れたら、塀に囲まれた庭の芝生なんてまず見えないでしょう。でも背の高い木は見える可能性はあります。マップに高低差があったら上からなら見切れる可能性はあります。プレイヤーが双眼鏡を持ってたり、一瞬で数百メートルを移動・ワープするようなゲームシステムでもまずいでしょう。
庭の草木モデルに密接にかかわる処理をしてたら、当然非表示になったら処理が失敗するわけなので、このスクリプトは使えません。
一度に表示させる量が多すぎたらメモリリークとガベージコレクションが云々…という懸念もあります。
そこまで対応させたいとなったら、ゲームシステムに特化した作りに改造するー判定距離を細かく変えたり判定処理を増設したりすることになります。
参考;
過去の自分の関連記事(HP)
もうちょっと追求する、モバイルのパフォーマンスの謎
https://bta25fun.wixsite.com/modev
私のアセットストア
https://assetstore.unity.com/publishers/26569
FANBOX
https://btamodev.fanbox.cc/
Patreon
https://www.patreon.com/funsetmodev