Unity研究所<Unityによるゲーム開発、映像開発>

Unity初心者40代後半中年中間管理職おやじが綴るゲームで小遣い稼ぎできるようになるまでのブログ

Unity 小ネタ 汎用オブジェクトの使いまわし

f:id:Harukichi:20210923181339g:plain


こんなアプリを作っていて、ふと思った

 

本当のぱちんこなら、玉は発射するたびに作るわけではなく、かつ、いらなくなったら捨てるわけでもなく、内部またはお店の設備の中で循環して使いまわしているはず

 

リソースは有限

使えるなら使いまわした方が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)/~~~

 

今回は以上

 

 

 

 

 

 

 

 

 

 

Unity 小ネタ 別のオブジェクトへ処理が終了したことを通知する UnityEventの活用

あるオブジェクトが動作を終了したタイミングで、他のオブジェクトが動作を開始するような処理を実装する場合に、どのような方法を思いつくだろうか?

 

アニメーションであれば、アニメーションのタイムライン上にイベントを設定して、別な処理を割り付けて、ということは可能

 

では、上記ではなく、スクリプト内のある処理の実行が終了したことを、別のオブジェクトに通知したいような場合はどうするか?

 

 オブジェクトA :処理を実行し、終了したらオブジェクトBに通知

 オブジェクトB :ある処理を実行

 

〇実装案

オブジェクトAは処理が終了したらフラグを立てる

オブジェクトBは、Update関数内からオブジェクトAの終了フラグを毎フレームチェックする

 

上記は、Update関数で毎フレーム監視するというのは効率が良くないし、フラグが別の処理で更新されたりしたらややこしいことになりそう

 

できれば、処理が終了した時にイベントを発生させて、オブジェクトBの処理を呼び出したいところ・・・

 

 

 

ということで、ググっていたら、良い方法が見つかったので書き残しておくことにする

 

〇UnityEventを活用して、カスタムのイベントを発生させる

 テスト的に作ってみたのがこれ!!

f:id:Harukichi:20211003221052g:plain

①スタートボタンを押すと、スクリプト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の処理を割り付け

f:id:Harukichi:20211003221714p:plain

 

本当に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;

    }

 

 

今回は以上

 

 

Unity 小ネタ コルーチンの活用例 コルーチンの途中停止

以前から作っているパチンコアプリについては、なかなか各部の制御で手こずっており、進捗がすこぶる悪い

 

理由は、パチンコアプリならではの図柄の制御やアタッカーといわれる大当りした時に

開放する扉やらの制御がかなり面倒

シーケンス制御的な要素が多く、動作のバリエーションや調整も必要になるので、最終的にはデータの調整のみで、ゲーム仕様の調整をしたいし・・・

 

てなかんじで、一つ何とか乗り越えた部分の大当りした時のアタッカーの扉の制御において、やや複雑なコルーチンの制御にて解決させたので、書き残しておくことにする

 

実際の動作サンプル

 右下の赤いシャッターが開閉する動作を見てほしい

f:id:Harukichi:20210923181339g:plain

 

〇シャッターの動作シーケンス

①開放前に一定時間待つ

②時間が来たら一定時間開放

➂②の開放時間中に玉が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にて止めて、そのあとから制御が再び始まってくれればよいと考えていたが、そうはいかず元のコルーチンも中途半端なところで止まってしまったため、致し方なく上記のような方法を採用した

 

 他のサイトでもいろいろ探したが、結果上記と同じような感じだった

 

今回は以上

Unity 小ネタ ScriptableObjectで数字の表示を制御してみた!!

前々から「ScriptableObject」なるものが気になっていたが、使い方も用途もわからず放置して数か月たったある日・・・

 

ふとYou Tubeにて、いつも勉強させてもらっている

Unityゲームスタジオ スタジオしまづさんの下記の動画を拝聴

 

youtu.be

 

この中ではモンスターの特性を設定するためのデータを「ScriptableObject」を活用して管理されている

 

なるほど、データの基本構造が同じデータで、パラメーターだけが異なるようなデータを量産するのに向いているのか!!

 

かつ、SerializeFieldとしてパラメーターを定義すると、Unityのエディター上で編集できる

 

今までだと、Script内で定義しているので、データの編集も管理も不便だなぁと感じていた

 

データだけ別ファイルにするというのもあるけど、読み込みも微妙に面倒だったり・・・

 

 

前置きが長くなったが、とりあえず使ってみようと思い、下記のような数字表示の制御に使ってみたので、やり方を紹介しておく

なお、見た目7セグメントLED的な表示だが、10枚のスプライトを切り替えて順に表示しているだけ

今回はこの辺の解説は省いているので注意を

f:id:Harukichi:20210911230741g:plain

数字の表示の制御にあたっては下記のような数値を制御するだけだが、途中で数字を更新する速度や、色を変えたりしたいので、構造は単純だが微妙なパラメーターの調整が結構面倒だったりするもの

 

〇制御に使用するパラメーター

 ・同じ状態を維持する時間

 ・数字の更新スピード(スプライトの入れ替えスピード)

 ・色

 

上記を一つの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上の表示は下記の通りf:id:Harukichi:20210911231936p:plain

 

実際に作成したScriptableObject 

f:id:Harukichi:20210911234535p:plain

 

今回作成したScriptableObjectの配列を作り、Editor上で割り付け

f:id:Harukichi:20210911233748p:plain


以上のような感じで、時間経過ごとに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;

    }

}

Unity 小ネタ RigidBody Fixed TimeStep 玉が転がるスピード、減衰力の調整方法

RigidBody 玉が転がるスピード、減衰力の調整方法ということで、

玉を転がすゲームを作りながら得たノウハウを紹介!!

 

といっても、大した内容ではないので、あしからず(-_-メ)

 

まあ、一応紹介しておくと、こんなアプリを作っている

youtu.be

play.google.com

 

ついでに・・・

play.google.com

 

前置きが長くなったが、UnityEditorのプロジェクトのデフォルトで、玉を転がすと、Physics Materialの摩擦を0にしようが下記のような感じで、玉の流れの勢いの減衰力が自然界のもののようには全然いかず、かなりもっさりで、使えない

さらに途中で無意味に止まってしまったりもする(-_-メ)

f:id:Harukichi:20210613202427g:plain

 

ここで、Physics MaterialのMassをいじってみたりするが、物理学的に、重さには関係なく重力は空気抵抗がなければ万物落下速度は同じである(あってるよね??)

f:id:Harukichi:20210613202731g:plain

 

なので、何ら変化なし

 

じゃあ、今度は本当にGravity(重力)をいじってみる

f:id:Harukichi:20210613203200g:plain

勢いが強すぎて、コリジョンをすり抜けてしまう (T_T)/~~~

 

なので、すり抜けないようにRigidbodyのCollistion DetectionをContinuousに

f:id:Harukichi:20210613203528p:plain

 

すり抜けなくなったが、玉の減衰が強いのは変わらず

f:id:Harukichi:20210613203357g:plain

 

 

ということで、前置きが長くなったが、Project SettingsのTimeのFixed Timestepの値をデフォルト0.02を0.005に変更する

f:id:Harukichi:20210613203930p:plain

 

 

すると、下記のように格段に玉の勢いが増し、減衰力も減らすことができた

f:id:Harukichi:20210613204344g:plain

 

ただ、なんかよくわからないタイミングで、何が気に入らないのか止まってしまうことがある

 

そこで、PhysicsのSleep Tjresholdをデフォルトの0.005から0.001に減らしてみる

f:id:Harukichi:20210613204757p:plain

 

祝!! 無事なんとか、気持ちいい感じに玉が流れるようになった!! 

f:id:Harukichi:20210613204953g:plain

 

 まとめ

 要は物理演算の計算の間隔を短くすると、計算の精度が上がるということらしい

 ただし、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);

 

以上、ここまで

 

 

 

 

BallParadise プライバシーポリシー

【BallParadise】プライバシーポリシー

 

Y’Mfc(以下「当方」という)は、当方の提供する【BallParadise】の利用者(以下「ユーザー」という)に関する個人情報を含んだ情報(以下「ユーザー情報」という)の取扱いについて、以下のとおりプライバシーポリシー(以下「本ポリシー」という)を定めます。

 

1.情報を取得するアプリ提供者

Y’Mfc

 

2.取得するユーザー情報と目的

本アプリケーションで取得するユーザー情報と目的は以下のとおりです。

取得するユーザー情報

利用目的

 

端末に関する情報

なし

 なし  
 

位置情報

なし

・ユーザーに最適化された広告の提供

・位置情報に基づく広告の提供

・パフォーマンス分析

・アプリケーションの改善

・ユーザーへのサポート

 

 
 

広告に関する情報

・表示場所

・クリックの有無

・閲覧回数

・広告の製品またはサービスの利用状況

 

 

3.取得方法

当方は、情報収集モジュールを内蔵した本アプリケーションにより、ユーザー情報を自動取得します。

 

4.通知・公表または同意取得の方法・利用者関与の方法

(1)通知・公表

当方は、本ポリシーに関する通知・公表は本アプリケーションまたは当方のホームページに掲載する方法で行います。

 

(2)同意取得の方法

同意の取得は、本アプリケーションの初回起動時に取得する方法で行います。

 

(3)利用者関与の方法

ユーザー情報の取得は、本アプリケーションをアンインストールすることで中止することができます。

 

5.お問い合わせ

当方のプライバシーポリシーに関する、ご意見、ご質問、苦情の申出その他ユーザー情報の取扱いに関するお問い合わせは、以下の窓口にご連絡ください。

 

ys.mfc.sp@gmail.com

 

6.改定

当方は、当方の裁量に基づいて、本ポリシーを変更します。但し、取得するユーザー情報、利用目的、第三者提供に変更が発生した場合、本アプリケーションまたは当社のホームページで通知するとともに、本ポリシー変更後、本アプリケーションの初回起動時に改めてユーザーから同意を取得します。

 

7.Y’s MFCプライバシーポリシー

本ポリシーに定めのない事項については、Y’s MFCプライバシーポリシーが適用されます。

 

 

8.制定日・改定日

制定:2021年05月16日