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

Unity初心者おやじが綴るゲームで小遣い稼ぎできるようになるまでのブログ

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日

 

Unity 小ネタ スマホでのオブジェクトの操作 スライド

f:id:Harukichi:20210430001329g:plain

 

新たなスマートフォンのゲームアプリの開発中!!

 

カーソルの操作の制御で、画面に左右のボタンを設けて・・・というのはよくあるが、スマホだもんね

ボタンでの操作というのはやっぱ野暮じゃない?

 

てな感じで、画面のスワイプというかスライドでカーソルを動かすことに挑戦したので、やり方を書き残しておく

 

大まかな手順

①操作したいオブジェクトにEventTriggerを追加

スクリプトでオブジェクトが押されたこと、そのままスライドされたことを検知して、移動距離に応じてオブジェクトも動かす

 

◆Event Triggerの追加

操作したいオブジェクトにEvent Triggerを追加し、PointerDown、PointerUpのイベントが発生した時に呼び出す関数を割り付ける

f:id:Harukichi:20210430002012p:plain

 

スクリプトでの制御

//オブジェクトが押された(タップされた)イベント発生時に呼び出される関数

public void OnPointerDown()
{

   //押された状態に設定
   TapFlag = true;

   //押された位置を取得

   Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f);

   //スマホ内の座標に変換して記憶しておく
   lastMousePos = Camera.main.ScreenToWorldPoint(pos);

}

//オブジェクトから指が離れた(タップをやめた)イベント発生時に呼び出される関数

public void OnPointerUp()
{

 //押されていない状態に設定
 TapFlag = false;

  //押された位置をクリア
 lastMousePos = Vector3.zero;

}

 

//Update関数にて、オブジェクトが押されている状態中を監視し、移動していたらオブジェクトの位置も更新

void Update()
{
  if (TapFlag)
  {

   //現在位置を取得

   Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f);
   NewMousePos = Camera.main.ScreenToWorldPoint(pos);

 

   //前回位置からの移動量を算出 X座標のみ
   float DestX = NewMousePos.x - lastMousePos.x;

   //移動量をそのまま反映すると動作がもっさりなため10倍
   float Xpos = transform.position.x + DestX*10f;
   

   //画面内からはみ出さないようにX値を調整
   transform.position = new Vector3(Mathf.Clamp(Xpos, -12, 12),    transform.position.y, transform.position.z);

   //位置情報を更新

   lastMousePos = NewMousePos;

  }

}

 

◎今回の罠 ハマったポイント

 Camera.main.ScreenToWorldPoint()が今回のハマったポイント

 最初はこの関数にInput.mousePositionを引数として設定していたが、動かない

 よくわからないがカメラの位置情報のみが取得され、タップしている位置を変えても値も変化しない

 

 ということで、ググりまくること3時間(-_-メ)

 

 ようやく同じ罠にはまった先人の記事を発見!! (感謝)

 

 この関数の引数はVector3型で、Input.mousePositionからも当然Vector3値がセットされるものと思っていたが、xとyしか設定されず、zのデータがないので、ScreenToWorldPoint()からはまともなデータの取得ができない

 

 カメラにより表示される画面の内容はカメラの位置からZ方向に「10」離れた位置のものらしい

 なので、タップした位置データの作成は、タップした位置情報にz情報を10として設定している

  Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f);

 

今回は以上

 

 

 

 

Unity 小ネタ アニメーションの切り替え 点灯、点滅

コインゲームの電飾表示的なものを作りたくて今日も悪戦苦闘

 

できたものは下記の通り

右側の5つあるボタンのようなものを、点灯⇒点滅⇒点灯という感じでアニメーションさせている

今日はこれをどうやって作ったかを書き残しておく

 

f:id:Harukichi:20210418225344g:plain

 

◆手順

①アニメーションの作成 Animation

②アニメーションの切り替えフローの作成 Animator

スクリプトでのアニメーションの切り替え

 

◆用意したもの

・UI Image

・消灯用のグレーのスプライト

・点灯用の色のついたスプライト

 

◆アニメーションの作成 Animation

UIのImageにAnimationを割り当てる

下記のメニューをクリックするとTimelineが表示されるので、真ん中のCreateボタンを押してアニメーションを作成

 

f:id:Harukichi:20210418230607p:plain

 

【消灯アニメーション】

Add PropertyからSpriteを選択すると、割り付けられているSpriteが自動で配置される

今回は表示が固定なので最初の位置のみにキーフレームを設定

f:id:Harukichi:20210418230003p:plain

 

【点灯アニメーション】

消灯と同じ要領で、グレーのSpriteが表示された状態で、プロジェクトのフォルダから色付きのSpriteをドラック&ドロップ

いらないグレーのSpriteのキーフレームは削除して、最初の位置にキーフレームを設定

f:id:Harukichi:20210418231209p:plain

 

【点滅アニメーション】

同じ要領で今度は色あり、色なし、色ありの順にキーフレームを配置

f:id:Harukichi:20210418231323p:plain

 

作成したアニメーションはプロジェクトウインドウの中に作成される

f:id:Harukichi:20210418232047p:plain

 

②アニメーションの切り替えフローの作成 Animator

アニメーションの切り替えフロー制御用のAnimatorを作成

 

f:id:Harukichi:20210418231454p:plain

 

作成すると、作成したオブジェクトのインスペクター上にAnimatorが追加され、Controllerの箇所に割り付けられているものが表示される

f:id:Harukichi:20210418232225p:plain

 

Animatorウインドウを開いて、①で作成したアニメーションをドラック&ドロップで配置

右クリックししてMake Transitionにて矢印をつないでいき、切り替えの流れを作成する

f:id:Harukichi:20210418232527p:plain

 

どのような条件で切り替えを行うかを設定する

・左上のParametersを押してプラスボタンを押して、切り替えの条件に使う変数を設定

・フローの矢印をクリックして、インスペクター上のConditionsに参照する変数と条件を設定する

 

下記の例の場合はこんな感じ

・変数 int型 名前はStatus

・切り替えの条件はStatusが2に等しくなった場合

f:id:Harukichi:20210418232905p:plain


スクリプトでのアニメーションの切り替え

やっていることは

・UI ImageのAnimatorを取得

・SetInteger関数を使用して、Status変数にint型の値を設定

 

f:id:Harukichi:20210418233442p:plain

 

今回の場合は、5個表示があり、上から順に点滅を開始し、常に一番下のみ点滅させたいので、消灯⇒点滅、その下のものを点滅させたらその上は点灯に切り替えているので、ややややこしくなっているが、やっていることは大したことはない

 

 

だいぶ適当な感じだが、覚書なので、あしからず

 

今日は以上