◆c# stringの変数の中身が空かを調べる
String. Is Null OrEmpty(String value)
valueが空だとtrueが帰ってくる
value==""も同じな気はするが精度は怪しい
◆VSでコーディング補完
ifと入力の後にTABを二回押すと、(){}を自動で入力してくれる
こんなアプリを作っていて、ふと思った
本当のぱちんこなら、玉は発射するたびに作るわけではなく、かつ、いらなくなったら捨てるわけでもなく、内部またはお店の設備の中で循環して使いまわしているはず
リソースは有限
使えるなら使いまわした方がECOというもの
アプリのメモリ、リソースも有限
特にスマホアプリならなおさら・・・
ということで、作っているアプリでも、毎回玉のPrefabから生成して、いらなくなったら捨ててというのをやめて、最初から一定数作っておいて、盤面上に出てないやつを次に盤面上に発射するようにしてみる
//スタート関数内で下記を呼び出して、一定数生成したオブジェクトを配列に入れておく
void SetupTamaArray()
{
TamaArray = new GameObject[TAMARRRAYSIZE];
for(int i = 0; i < TAMARRRAYSIZE; i++)
{
TamaArray[i]=Instantiate(TamaPrefab, transform.position, transform.rotation);
//オブジェクトは非アクティブにしておく
TamaArray[i].SetActive(false);
}
//玉の生成、盤面への発射
void TamaSpawn()
{
//最初は下記のように毎回玉を生成していた
// Rigidbody rb = Instantiate(TamaPrefab, transform.position, transform.rotation).GetComponent<Rigidbody>();
float currentPower = power;
currentPower*= Random.Range(1.0f, 1.0005f);
Rigidbody rb = new Rigidbody();
for (int i = 0; i < TAMARRRAYSIZE; i++) {//配列の中から使っていないものを検索
if (!TamaArray[i].activeSelf) //オブジェクト非アクティブならこれを使う
{
//玉を発射するための準備 位置、角度、移動量を初期化
TamaArray[i].transform.position = transform.position;
TamaArray[i].transform.rotation = Quaternion.identity;
rb = TamaArray[i].GetComponent<Rigidbody>();
rb.velocity = new Vector3(0, 0, 0); //前回のものが残っていると不都合なので
//初期化したらアクティブに
TamaArray[i].SetActive(true);
//盤面上に発射
rb.AddForce(Vector3.up * currentPower, ForceMode.Impulse);
//非アクティブなものが見つかったので、検索は終了
break;
}
}
}
//玉が盤面外に排出されたらコライダーで検知して、非アクティブ化する
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Tama"))
{
// Destroy(other.gameObject); 最初は毎回削除していた
other.gameObject.SetActive(false); //非アクティブ化
}
}
今回の説明は以上
では、実際どのくらい無駄が減ったとか、パフォーマンスが上がったのかを知りたいところであるが・・・
見た目は・・・
変わらんなぁ
まあまあのCPU、グラボを積んだPC上では見た目でわかるはずはないわなぁ
数値で確認したいところだが、やり方がわからない
数値でのパフォーマンスの違い確認は、次回の宿題としておこう
〇余談
普通の人は興味ないと思うが、あらかじめ玉のオブジェクトは15個作ってみたが、実際に同時に使われるのは6個もなかなかいかない感じだった
あと、玉のオブジェクトを非アクティブ化しても打ち出されて落下した際の慣性というか、重力というか・・は残っているので、これを再度発射する際にはクリアしておかないとまともに玉が飛んでくれなくなる
これに気づかず、解消するために結構時間がかかった(T_T)/~~~
今回は以上
あるオブジェクトが動作を終了したタイミングで、他のオブジェクトが動作を開始するような処理を実装する場合に、どのような方法を思いつくだろうか?
アニメーションであれば、アニメーションのタイムライン上にイベントを設定して、別な処理を割り付けて、ということは可能
では、上記ではなく、スクリプト内のある処理の実行が終了したことを、別のオブジェクトに通知したいような場合はどうするか?
例
オブジェクトA :処理を実行し、終了したらオブジェクトBに通知
オブジェクトB :ある処理を実行
〇実装案
オブジェクトAは処理が終了したらフラグを立てる
オブジェクトBは、Update関数内からオブジェクトAの終了フラグを毎フレームチェックする
上記は、Update関数で毎フレーム監視するというのは効率が良くないし、フラグが別の処理で更新されたりしたらややこしいことになりそう
できれば、処理が終了した時にイベントを発生させて、オブジェクトBの処理を呼び出したいところ・・・
ということで、ググっていたら、良い方法が見つかったので書き残しておくことにする
〇UnityEventを活用して、カスタムのイベントを発生させる
テスト的に作ってみたのがこれ!!
①スタートボタンを押すと、スクリプトAが三秒間のカウントダウンを開始
②終了したらイベント発動
➂スクリプトBのテキストの更新処理を呼び出す
オブジェクトA///////////////////////////////////////////////////////////////////
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events; //イベントを使用する場合はこいつが必要
public class AManager : MonoBehaviour
{
[SerializeField] Text Msgtext;
float counter = 0;
[SerializeField] UnityEvent customEvent; //インスペクターで割り付けられるようにする
public void CountDownStart()
{
StartCoroutine(Countdown(3f));
}
IEnumerator Countdown(float duration)
{
counter = duration;
while (counter > 0) {
counter -= 0.05f;
Msgtext.text = "スクリプトA 実行中" + counter.ToString("f3");
yield return new WaitForSeconds(0.05f);
}
Msgtext.text = "スクリプトA 終了";
yield return new WaitForSeconds(1f);
Msgtext.text = "スクリプトA イベント発動";
customEvent.Invoke(); //処理が終了したのでイベントを発動
}
}
オブジェクトB///////////////////////////////////////////////////////////////////
using UnityEngine;
using UnityEngine.UI;
public class BManager : MonoBehaviour
{
[SerializeField] Text textB;
public void UpdateText()
{
textB.text = "スクリプトB : イベントを受信";
}
}
インスペクタ上でイベントが発生した時に実行するオブジェクトBの処理を割り付け
本当にUpdate関数で監視するより処理負荷が低いかは定かではないが、スクリプトがわかりやすく、処理の流れも一方向のため、間違えは起きにくそうな印象
10/23追記
上記のままだと、イベントを発生させるまでもなく、オブジェクトBの関数を呼び出すだけでよいのでは、という疑問がわくところ
このようなイベントが役に立つのは、オブジェクトBの関数を呼び出すオブジェクトが複数あり、呼び出し元毎に処理を変えるような場合だ
public void UpdateText(int index)
{
switch(index){
case 0:
textB.text = "スクリプトB : オブジェクト0からイベントを受信";
break;
case 1:
textB.text = "スクリプトB : オブジェクト1からイベントを受信";
break;
}
今回は以上
以前から作っているパチンコアプリについては、なかなか各部の制御で手こずっており、進捗がすこぶる悪い
理由は、パチンコアプリならではの図柄の制御やアタッカーといわれる大当りした時に
開放する扉やらの制御がかなり面倒
シーケンス制御的な要素が多く、動作のバリエーションや調整も必要になるので、最終的にはデータの調整のみで、ゲーム仕様の調整をしたいし・・・
てなかんじで、一つ何とか乗り越えた部分の大当りした時のアタッカーの扉の制御において、やや複雑なコルーチンの制御にて解決させたので、書き残しておくことにする
実際の動作サンプル
右下の赤いシャッターが開閉する動作を見てほしい
〇シャッターの動作シーケンス
①開放前に一定時間待つ
②時間が来たら一定時間開放
➂②の開放時間中に玉が10個入ったら、開放時間が残っていても閉じる
➃次の開放までの待ち時間
〇具体的な実装方法
IEnumerator CountDownRoutine;
IEnumerator DoOnOffSequence(OnOffData[] onOffDatas)
{
//セットするデータの構造は、開放前時間、開放時間、次の開放までの時間
//となっていて、このセットが配列になっている
for (int i = 0; i < onOffDatas.Length; i++ ) {
//開放前時間 一定時間待つ
yield return new WaitForSeconds(onOffDatas[i].preopen);
//シャッター開放 専用の別のコルーチンを呼び出す
CountDownRoutine= TimerCountDown(onOffDatas[i].open);
//下記のようにすると上記の処理の終了を待ってくれる
yield return StartCoroutine(CountDownRoutine);
//開放時間が過ぎるか途中で終了となったら閉じる
Close();
yield return new WaitForSeconds(onOffDatas[i].close);
}
}
//シャッター開放中の別のコルーチン
IEnumerator TimerCountDown(float duration)
{
//実行時間のセット
CountDownTimer = duration;
//ループ開始
while (true) {
//時間を減算
CountDownTimer -= 0.1f;
//一定時間待つ
yield return new WaitForSecondsRealtime(0.1f);
//タイマーが0になったら終了
if (CountDownTimer <= 0f)
{
yield break;
}
}
}
//玉が一定数入ったことにより開放を途中終了する場合の処理
//別のスクリプトから呼び出される
public void ActionStop()
{
//カウントダウン用のタイマーを0にして、コルーチンを外部から終了させる
CountDownTimer = 0f;
}
〇考察
一見コルーチンの中で時間をカウントダウンするというのはナンセンスな気もするし、ならコルーチンを使わずに実装もできそうな気もする
最初の目論見としては、一定数玉が入ったらStopCorutioneにて止めて、そのあとから制御が再び始まってくれればよいと考えていたが、そうはいかず元のコルーチンも中途半端なところで止まってしまったため、致し方なく上記のような方法を採用した
他のサイトでもいろいろ探したが、結果上記と同じような感じだった
今回は以上
前々から「ScriptableObject」なるものが気になっていたが、使い方も用途もわからず放置して数か月たったある日・・・
ふとYou Tubeにて、いつも勉強させてもらっている
Unityゲームスタジオ スタジオしまづさんの下記の動画を拝聴
この中ではモンスターの特性を設定するためのデータを「ScriptableObject」を活用して管理されている
なるほど、データの基本構造が同じデータで、パラメーターだけが異なるようなデータを量産するのに向いているのか!!
かつ、SerializeFieldとしてパラメーターを定義すると、Unityのエディター上で編集できる
今までだと、Script内で定義しているので、データの編集も管理も不便だなぁと感じていた
データだけ別ファイルにするというのもあるけど、読み込みも微妙に面倒だったり・・・
前置きが長くなったが、とりあえず使ってみようと思い、下記のような数字表示の制御に使ってみたので、やり方を紹介しておく
なお、見た目7セグメントLED的な表示だが、10枚のスプライトを切り替えて順に表示しているだけ
今回はこの辺の解説は省いているので注意を
数字の表示の制御にあたっては下記のような数値を制御するだけだが、途中で数字を更新する速度や、色を変えたりしたいので、構造は単純だが微妙なパラメーターの調整が結構面倒だったりするもの
〇制御に使用するパラメーター
・同じ状態を維持する時間
・数字の更新スピード(スプライトの入れ替えスピード)
・色
上記を一つのScriptableObjectとして定義し、さらに配列化して、時間が経過したら次
ScriptableObjectに移動してという形で動作の変化をつけていくようにしてみる
データ配列イメージ
0 動作開始フェーズのデータ
1 高速更新フェーズのデータ
2 停止フェーズのデータ
で、実際にコーディングしてみる
〇ScriptableObjectの定義
using UnityEngine;
[CreateAssetMenu] ⇒これにより下記のようにHerrarchyのプラスボタンを押して表示されるメニューに表示され、簡単にファイルが作成されるようになる
public class SequenceDataObj : ScriptableObject ⇒専用の定義
{
[SerializeField] public int Mode = 0;//0:更新,1:書き換え
[SerializeField] public float Duration = 0;//当該データを使用する時間
[SerializeField] public float UpdateSpeed = 0;//数字の更新スピード
[SerializeField] public Color color; //数字の色
[SerializeField] public int TeishiNumOffset = 0;//最後に止める数字までの送りコマ数
}
※データのアクセスは制限するほどのものでもないので、すべてpublicで定義
上記のスクリプトの作成でのUnity Editor上の表示は下記の通り
実際に作成したScriptableObject
今回作成したScriptableObjectの配列を作り、Editor上で割り付け
以上のような感じで、時間経過ごとにScriptableObjectのデータを入れ替えつつ作成したのが、上の方の動画のような動作
説明は以上
感想としては、こうした表示制御はデータの作成、調整、並び替えなどにいちばん時間がかかるので、この作業の効率が良くなった点は大きいかも!!
長くなったので、説明はしないが、今回作成したスクリプトは下記の通り
参考になれば幸いです
下記をImageオブジェクトにアタッチして制御しています
---------------------------------------------
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class SegController : MonoBehaviour
{
SequenceDataObj CurrentSeqData;
[SerializeField] Sprite Segsprites;
[SerializeField] SequenceDataObj tempdata;
[SerializeField] SequenceDataObj sequenceDatas;
bool IsPlaying = false;
Coroutine coroutine = null;
int counter = 0;
Image image;
int CurrentPicNum=0;
int SeqDataPos = 0;
int SeqDatasize = 0;
int TeishiPicNum = 0;
int TempPicNum = 0;
void Start()
{
image = GetComponent<Image>();
SegPicChange(7,new Color(255,255,255));
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
HendouStart(sequenceDatas,7);
}
}
public void HendouStart(SequenceDataObj[] sequenceDatas,int Teishinum)
{
//動作データを取得
SeqDataPos = 0;
SeqDatasize = sequenceDatas.Length;
SetupSeqdata(sequenceDatas[SeqDataPos]);
TeishiPicNum = Teishinum;
//動作コルーチンスタート
IsPlaying = true;
coroutine = StartCoroutine(HendorLoop());
}
IEnumerator HendorLoop()
{
for(; ; ) {
if (counter > 0)
{
counter--;
UpdatePic();
}
else
{
SeqDataPos++;
if (SeqDataPos == SeqDatasize)
{
IsPlaying = false;
StopCoroutine(coroutine);
}
else {
SetupSeqdata(sequenceDatas[SeqDataPos]);
UpdatePic();
}
}
yield return new WaitForSeconds(CurrentSeqData.UpdateSpeed);
}
}
void SetupSeqdata(SequenceDataObj nextdata)
{
CurrentSeqData = nextdata;
if (CurrentSeqData.Mode == 1)
{
PicNumAdjust(CurrentSeqData.TeishiNumOffset);
}
counter = (int)(CurrentSeqData.Duration / CurrentSeqData.UpdateSpeed);
}
//イメージの差しかえ
void SegPicChange(int number,Color color)
{
image.sprite = Segsprites[number];
image.color = color;
}
//図柄番号の更新
void UpdatePic()
{
CurrentPicNum++;
if (CurrentPicNum >= 10)
{
CurrentPicNum = 0;
}
SegPicChange(CurrentPicNum,CurrentSeqData.color);
}
//最終的に停止させたい数字にするための途中書き換えのための調整
void PicNumAdjust(int offset)
{
CurrentPicNum = TeishiPicNum - offset;
if (CurrentPicNum < 0)
CurrentPicNum += Segsprites.Length;
}
}
RigidBody 玉が転がるスピード、減衰力の調整方法ということで、
玉を転がすゲームを作りながら得たノウハウを紹介!!
といっても、大した内容ではないので、あしからず(-_-メ)
まあ、一応紹介しておくと、こんなアプリを作っている
ついでに・・・
前置きが長くなったが、UnityEditorのプロジェクトのデフォルトで、玉を転がすと、Physics Materialの摩擦を0にしようが下記のような感じで、玉の流れの勢いの減衰力が自然界のもののようには全然いかず、かなりもっさりで、使えない
さらに途中で無意味に止まってしまったりもする(-_-メ)
ここで、Physics MaterialのMassをいじってみたりするが、物理学的に、重さには関係なく重力は空気抵抗がなければ万物落下速度は同じである(あってるよね??)
なので、何ら変化なし
じゃあ、今度は本当にGravity(重力)をいじってみる
勢いが強すぎて、コリジョンをすり抜けてしまう (T_T)/~~~
なので、すり抜けないようにRigidbodyのCollistion DetectionをContinuousに
すり抜けなくなったが、玉の減衰が強いのは変わらず
ということで、前置きが長くなったが、Project SettingsのTimeのFixed Timestepの値をデフォルト0.02を0.005に変更する
すると、下記のように格段に玉の勢いが増し、減衰力も減らすことができた
ただ、なんかよくわからないタイミングで、何が気に入らないのか止まってしまうことがある
そこで、PhysicsのSleep Tjresholdをデフォルトの0.005から0.001に減らしてみる
祝!! 無事なんとか、気持ちいい感じに玉が流れるようになった!!
まとめ
要は物理演算の計算の間隔を短くすると、計算の精度が上がるということらしい
ただし、CPUへの負荷が増えるため、用途に合わせて調整する必要ありとのこと
基本的な仕様について
そういえば、どうやって上記の動画のようなことを実現しているかを全く触れていなかったので、要点のみ書き残しておく
●構造物
・Blenderで作成したモデルデータ Blenderのファイル形式で読み込み
・コライダーは、Mesh Colliderを使用
・Physics Materialは、bouncinessのみ0.005
その他は0 CombineはMaximum
・Materialは、パイプのみRendering modeをTransparentにして、
albedoのA(アルファ)値を適当に下げて透かしている
●ボール
・3D Objectのsphereを使用
・Rigidbody、Sphere Colliderを使用
Mesh Collider同士の当たり判定はできないので注意が必要
・Physics Materialは、bouncinessのみ0.005 用途に合わせて要調整
その他は0 CombineはMaximum
・Prefab化して、スクリプトから制御して発射している
Instantiate(TamaPrefab, TamaSpawnPos.transform.position, TamaSpawnPos.transform.rotation);
以上、ここまで