20190929のC#に関する記事は8件です。

OutsystemsでC#を書いてみた

やったこと

  • 簡単な2項の足し算をするコードをC#で書いて、それをOutSystemsのロジック内で呼び出して使ってみた
  • Structure型のRecordをC#に渡してみることにも挑戦してみた

想定読者

初歩的なOutsystemsの操作は何となくわかるが、C#でExtensionを書いたことはない・どうやって書くか知りたい人

開発環境

  • Outsystems11(ServiceStudio, IntegrationStudio)
  • Vidual Studio

筆者のスキル

C#の詳しいことは全然わからん outsystemsも初心者

手順

C#のコードを書く(Extension)

  • 任意のApplication内に、Extentsionを作成する

image.png

  • Create Moduleボタンを押すと、勝手にIntegrationStudioが開いて、EnvironmentのPasswordを聞かれるのですなおに入力する
  • 以下のような画面が開くので必要ならDescriptionを書く

image.png

  • 左のツリーのActionsフォルダを右クリックして、Add Actionをクリック
  • 以下の図のように、Actionの引数や戻り値を設定できる画面になるので、それぞれ入力する
  • 今回は、2つの項目の引数を受け取って計算結果を返却するアクションを作成するので、以下のようなParametersを設定した

image.png

  • 左上のFileメニューを開いて、Edit Sourcecode .NETを選択する

image.png

  • 勝手にVisual Studioが開く
  • 右側のソリューションエクスプローラーでCaluculation.csを開くと先ほど入力した引数や戻り値などは勝手に補完してくれていることがわかる

image.png

// TODO: Write implementation for action
というコメントの下に今回やりたかった足し算のコードを入力する
ssResult = ssFirstTerm + ssSecondTerm;

  • コードが入力し終わったら、ファイルを保存してVisual Studioを閉じる
  • IntegrationStudioに戻って、1Click Publishボタンを押す(下図)

無題.png

image.png

  • Publishが終わってからServiceStudioに戻ると、ちゃんとExtensionが作成されてIntegrationStudioで作ったモジュールが反映されていることが確認できる

image.png

ServiceStudio側でExtensionを呼び出す

  • 下図のような画面を作って、FirstTermとSecondTermというLocalVariableを用意して、Inputの値が入るように設定する
  • Additionボタンを押すとC#で書いたアクションを呼び出すためのScreenAction(Addition)を呼び出すように設定する
    image.png

  • C#で書いたアクションのDependencyを貼る

image.png

  • 呼び出して使ってみる

image.png

  • Publishして計算させてみるとこんな感じで計算結果が正しく表示された

image.png

OutsystemsのStructureをC#に渡したい場合はどうするの?

  • IntegrationStudioでStructureを定義するとその型でレコードを受け取れるようになる
  • こんな感じで定義できて
    image.png

  • VisualStudioで見るとStructures.csに勝手にコードを書いてくれてる すごーい!
    image.png

  • EditStructure.csで、Nameの値がfooかどうか見てあげる(C#わかんない)
    書いたのはこれだけ

EditStructure.cs
        public void MssGetStructureAndValidateValue(RCStructureRecord ssStructure, out bool ssResult) {
            ssResult = false;

            String ValueForCompare = "foo";
            if (ssStructure.ssSTStructure.ssName.Equals(ValueForCompare))
            {
                ssResult = true;
            }

        } // MssGetStructureAndValidateValue
  • ServiceStudioで組み込む
    image.png

  • Nameにfooが入力されたらOK それ以外ならNGが表示させることができたよ!
    image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

class NestedList<T> : List<NestedList<T>> {}

タイトルなにこれ?

今まで見たことがなかったコードなのですが、実は書いてもエラーが発生しないコードなんです。
(コレクションクラスが継承可能だという事実も同時に知りました……。)

ということで、リストの型ネストによって何ができるのかを見ていきましょう。

ネストさせるとどうなる?

辞書の型ネストの方が構造が分かり易かったので、まずそちらから。

class NestedDictionary<T> : Dictionary<T, NestedDictionary<T>> {}

と書くと、これは、Tを用いてNestedDictionary<T>を取り出す、というのを無限に繰り返せるデータ構造になります。
実際にこれを使ってみると……

var dic =
  new NestedDictionary<string>()
  {
    {
      "hoge", new NestedDictionary<string>()
      {
        {
          "asdf" , new NestedDictionary<string>()
        },
        {
          "qwer", new NestedDictionary<string>()
          {
            {
                "abcd", new NestedDictionary<string>()
            }
          }
        }
      }
    },
    {
      "fuga", new NestedDictionary<string>()
      {
        {
          "test", new NestedDictionary<string>()
        },
        {
          "qiita", new NestedDictionary<string>()
          {
            {
              "haha", new NestedDictionary<string>()
            }
          }
        }
      }
    }
  };

foreach(var s in dic["fuga"]["qiita"].Keys)
  Console.WriteLine(s);
//dic変数の中の"fuga"をKeyとしたValueを取り出す
//さらにそこから"qiita"をKeyとしたValueを取り出す
//結果として"haha"が出力される

というコードが書けます(ネスト深っ)

これを踏まえた上で、Listの型ネストを見てみましょう。
ただし、タイトル通りだと性質が分かりづらいので、少しコードを書き加えます。

public class NestedList<T> : List<NestedList<T>>
{
    public T Value { get; set; }
    public NestedList(T value) => Value = value;
}

何してるかいまいちピンとこないかもしれません。
実際に使ってみましょう。

var list =
  new NestedList<string>("top")
  {
      new NestedList<string>("umum....")
      {
          new NestedList<string>("aaa"),
          new NestedList<string>("bbb")
      },
      new NestedList<string>("fmfm...")
      {
          new NestedList<string>("areare?"),
          new NestedList<string>("oooooo")
          {
              new NestedList<string>("naninani?")
          },
          new NestedList<string>("qiita")
      }
  };
Console.WriteLine(list[1][2].Value);
//listの1番目("fmfm..."を含む)のオブジェクトの中の2番目("qiita"を含む)のオブジェクトを取り出して中身を表示する
//"qiita"が出力される

Valueプロパティを追加したことによってツリー構造っぽく見えるようになりました。
逆に言えば、コードを少し書き加えたのは、ツリー構造の性質を持っていることを分かりやすくしたかったからでした。

上記以外にも色々試したところ、リストの型ネストは、"ツリー構造"または"無限次元リスト"のような感じがしました。
ツリー構造は、リストの一般化だった……?

実際に使える?

ツリー構造を取れるので、それが必要なプログラムには使えるかと思います。
実際に、ツリー構造に関係が深いコードをいくつか書いてみました。

//名前は違うけど、NestedListと同じです。
public class NTree<T> : List<NTree<T>>
{
    public T Value { get; set; }
    public NTree(T value) => Value = value;
    //深さ優先探索(行きがけ順)
    private IEnumerable<NTree<T>> DFSNodes()
    {
        var stack = new Stack<NTree<T>>();
        stack.Push(this);
        while(stack.Any())
        {
            var tree = stack.Pop();
            yield return tree;
            tree.Reverse();
            foreach(var part in tree)
                stack.Push(part);
            tree.Reverse();
        }
    }
    //深さ優先探索(行きがけ順)の値を求める
    public IEnumerable<T> DFSValues() => DFSNodes().Select(t => t.Value);
    //幅優先探索(レベル順)
    private IEnumerable<NTree<T>> BFSNodes()
    {
        var queue = new Queue<NTree<T>>();
        queue.Enqueue(this);
        while(queue.Any())
        {
            var tree = queue.Dequeue();
            yield return tree;
            foreach(var part in tree)
                queue.Enqueue(part);
        }
    }
    //幅優先探索(レベル順)の値を求める
    public IEnumerable<T> BFSValues() => BFSNodes().Select(t => t.Value);
    //葉の数を調べる
    public int CountLeaves() => DFSNodes().Count(part => part.IsEmpty());
    //葉の値だけを取り出す
    public IEnumerable<T> Leaves() => DFSNodes().Where(part => part.IsEmpty()).Select(part => part.Value);
}

幅優先探索、たった7行で作ることができました……!
(実は今まで一回もまともに実装できた試しが無かった)
深さ優先探索もたったの9行。驚きです。
葉の数や値も簡単に出せました。

おわりに

コレクションクラスを継承する、という発想にはなかなか至りにくいとは思います。
しかしながら、使い方次第で大きな可能性を秘めていることが今回でわかりました。
頭の片隅に置いておき、状況によって使ってみるのも手かもしれません。

ライセンス

CC0で置いておきます。必要になれば使ってください。
CC0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】OculusIntegrationを使ってVR空間でSliderを操作

目的

VR空間内でスライダーのUIを操作して、ゲームの難易度を設定する
というのが今回の目的です。

この記事ではVR空間内でスライダーのUIを操作の部分を記述します。

デモ

実際に作成したものがこちらです。
見た目は置いといて、機能としては完成です。

Slider.gif

OculusIntegrationの設定

①OVRInputModuleの設定

Create EmptyでGameObjectを生成して、OVRInputModuleをAdd Componentします。

設定項目としては3か所だけです。
今回は人差し指のトリガーで操作する設定にしてます。
Cursorの箇所は後述します。
InputSettings.PNG

②Cursorの設定

続いて、Cursorの設定です。
Create EmptyでGameObjectを生成して、LaserPointerをAdd Componentします。
同じオブジェクトにLineRendererもAdd Componentします。

あと、LineRendererが太すぎるのでWidthを0.03くらいにしました。
お好みでmaterialを設定してレーザーの色を変えたりできます。
LineRenderWidth.PNG

今回のデモではカーソルは表示していませんが、
必要な場合はCursorVisualお好きなカーソル(ゲームオブジェクト)をアタッチすればOKです。
CursorVisual.PNG

③OVRRaycasterを追加

CanvasにOVRRaycasterをAdd Componentします。
これで、そのCanvasの子のUIに対して操作が可能になります。
もともとあるGraphyicRaycasterは消してしまって構いません。

設定完了!

これでもうUIを操作できます。
OculusIntegration神ですね。

コード

スライダーの値を取得するメソッド、GetLevelを用意します。

Sliderにアタッチ
using UnityEngine;
using UnityEngine.UI;

public class LevelGetFromSlider : MonoBehaviour
{
    Slider levelSlider;

    void Start()
    {
        levelSlider = this.gameObject.GetComponent<Slider>();
    }

    public int GetLevel()
    {
        return (int)levelSlider.value;
    }
}

Textにアタッチ
using UnityEngine.UI;
using UnityEngine;

public class LevelText : MonoBehaviour
{
    [SerializeField]
    LevelGetFromSlider _levelGetFromSlider;

    Text levelText;

    void Start()
    {
        levelText = this.gameObject.GetComponent<Text>();
    }

    public void LevelTextChange()
    {
        levelText.text = _levelGetFromSlider.GetLevel().ToString();
    }

}

後はテキスト側でOn Value Changedに登録するメソッド内で先程のGetLevelを使います。
On Value Changedというのは、Slider Componentに用意されているイベントハンドラーです。
画像のように登録して使います。

OnValueChanged.PNG

これでSliderの値が変更された際に、テキストも変わります。

Editor上でSliderのValueを直接編集しても反映されないので注意です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでProjectSettingsと共にunitypackageをExportする

雑記

レイヤー設定と共にパッケージを作成したい機会があったので。
問題ないかは不明。

http://myoujing.wpblog.jp/2014/03/274/
こちらを参考にしましたが、余計なdll等も含まれてしまったため
以下のようにしてみました。

#if UNITY_EDITOR

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class ExportSetting : MonoBehaviour
{
    [MenuItem("Assets/ExportWithSettings")]
    static void Export()
    {
        string[] assetPaths = {
            "Assets/含めたいフォルダー",
            "ProjectSettings/AudioManager.asset",
            "ProjectSettings/ClusterInputManager.asset",
            "ProjectSettings/DynamicsManager.asset",
            "ProjectSettings/EditorBuildSettings.asset",
            "ProjectSettings/GraphicsSettings.asset",
            "ProjectSettings/InputManager.asset",
            "ProjectSettings/NavMeshAreas.asset",
            "ProjectSettings/NetworkManager.asset",
            "ProjectSettings/Physics2DSettings.asset",
            "ProjectSettings/ProjectSettings.asset",
            "ProjectSettings/QualitySettings.asset",
            "ProjectSettings/TagManager.asset",
            "ProjectSettings/TimeManager.asset",
            "ProjectSettings/UnityConnectSettings.asset"
        };

        string exportPath = "test.unitypackage";

        AssetDatabase.ExportPackage(assetPaths, exportPath, ExportPackageOptions.Interactive | ExportPackageOptions.Recurse);

    }
}

#endif

ProjectSettingsは再帰で読まれなかった。

環境

Unity 2017 4.2.8f1

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

windows form で 隠れるパネルを作成する

やりたいこと

端っこに隠れるパネルを作成する
image.png

環境

  • Microsoft Visual Studio Community 2019
  • Microsoft .NET Framework Ver.4.8

使用するコントロール

使用するのは 3つだけ
* Panel panel1 これが隠れるパネル
* Timer timer1 隠れるアニメーション用タイマー
* Label label1 クリックイベントで閉じたり開いたりさせる

作成

コントロールの配置

form にpanel1を配置します。
[プロパティ]
* BackColor = "ActiveCaption"
* Dock = "Left"
image.png
panel1上にラベルで閉じたり開いたりするための つまみ を作成します。
[プロパティ]
* AutoSize = "False"
* BackColor = "ControlDarkDark"
* Dock = "Right"
* Font = メイリオ, 12pt, style=Bold
* ForeColor = "White"
* text = "<"
* TextAlign = "MiddleCenter"
image.png
timerを設置します。
[プロパティ]
Interval = 10 この数値が小さいほど動きが早くなります。

コーディング

使用する 変数、定数 の準備

Form1.cs
    public partial class Form1 : Form
    {
        const int PITCH = 10; // 閉じる(開く)移動幅 小さくするとなめらかになります。
        bool _is_hide = false; // 表示状況 true:非表示状態 false:表示状態
        int _hidePanelWidth; // 現在の隠れるパネルの幅

Form の Loadイベントで隠れるパネルの現状の幅を取得

Form1.cs-Form1_Load()
    private void Form1_Load(object sender, EventArgs e)
    {
        _hidePanelWidth = this.panel1.Width; // 隠れパネルの幅を取得
    }

ラベルの Clickイベント を記述します。
クリックされるごとにタイマーをスタートします。

Form1.cs-Label1_Click()
        private void Label1_Click(object sender, EventArgs e)
        {
            timer1.Start(); // タイマースタート
        }

タイマーの Tickイベント を記述します。
タイマーがスタートされると、 Interval で設定されている時間(ms)ごとにTickイベントが実行されます。

Form1.cs-Timer1_Tick()
        private void Timer1_Tick(object sender, EventArgs e)
        {
            // 表示状況により分岐
            if (_is_hide)
            { // 閉じているとき
                this.panel1.Width = this.panel1.Width + PITCH; // 幅をピッチ分増やす
         
         // 増加後の幅が当初の幅を超えたか 
                if (this.panel1.Width >= _hidePanelWidth)
                {
                    timer1.Stop(); // タイマーをストップ
                    this.panel1.Width = _hidePanelWidth; // 当初の幅に再設定
                    label1.Text = "<"; // つまみの方向を反転
                    _is_hide = false; // 表示状況を反転
                }
            }
            else
            { // 開いているとき
                this.panel1.Width = this.panel1.Width - PITCH; // 幅をピッチ分減らす
         
         // 減少後の幅がつまみの幅を下回った場合 
                if (this.panel1.Width <= label1.Width)
                {
                    timer1.Stop(); // タイマーをストップ
                    this.panel1.Width = label1.Width; // つまみ分の幅に再設定
                    label1.Text = ">"; // つまみの方向を反転
                    _is_hide = true; // 表示状況を反転
                }
            }
        }

これで、こんな感じになります。
image.png

少しつまらないので、ボタンでも置いてみます...
あっ!
image.png

ボタンの端が残ってしまっています。
隠しパネル内に配置したコントロールは Anchor を "Top, Right" にすることで解決できます。
image.png
ちゃんとボタンも隠れました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NETCore3.0がリリースされたのでVisualStudio2019からBlazorを作成してAzureにアップしてみる

はじめに

.NET Core 3.0がリリースされたので、プレビュー版ではないVisual Studio 2019からBlazorAzureにアップできるか試してみた。

環境

  1. Visual Studio 2019 Community Version 16.3.0 010.png

ちなみに、Azure側はまだ.NET Core 3.0をサポートしていない模様。
008.png

Blazorで試すテンプレートについて

今回はBlazorサーバーアプリBlazor WebAssembly Appを試す。
001.png

Azure側の環境

BlazorサーバーアプリBlazor WebAssembly Appともにリソースグループは新規作成で作り、ホスティングプランは日本に変更して作成する。
002.png
この辺りは任意で。
003.png

Blazorサーバーアプリ編

新規作成でBlazorサーバーアプリを選択し、何もせずそのまま発行まで進むと、警告が出て、無理矢理デプロイするとエラーになってしまった。

004.png

回避策

今まで同様、配置モードを自己完結にすれば動作するようだ。
005.png

実際に動いた画面
006.png

Blazor WebAssembly App編

特にエラーなくアップできたが、こちらはターゲットフレームワークがnetstandard2.0になっている(前から)

99.png

これでプレビュー版が削除できる!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】RPGのレベルシステムの書き方

レベルシステム、RPG作っていれば必ず書くと言っていい処理ですが、自分流の書き方を残しておきます。

累計経験値と累計経験値に応じたレベルのテーブルがあれば、そこから現在Lvと次のLvまでの残り経験値を算出できます。
しかし、Lvを取得する度に都度計算していては流石に重たいので、累計経験値が加算される度に現Lvと残り経験値を更新するようにします。

累計経験値に応じたレベルのテーブルを関数の引数にとって外部から参照してますが、これはレベルテーブルは固有な値としてstatic readonlyで修飾して使うことが多いためです。

あるいは、UnityならScriptableObjectから参照する場合もあるでしょう。

いずれにせよ外部またはサブクラス上のstaticな値を汎用的に参照させたいので関数の引数に含めました。

ExpLevelClass.cs
using System.Linq;
using UnityEngine;

[System.Serializable]
public class ExpLevelClass
{
    [SerializeField] int _exp;
    [SerializeField] int _remainExp;
    [SerializeField] int _minLevel;
    [SerializeField] int _level = 1;

    public int Exp => _exp;
    public int RemainExp => _remainExp;
    public int MinLevel => _minLevel;
    public int Level => _level;

    // Expを加算してLvを初期化する
    public void AddExp(int exp, int[] expArray)
    {
        //カンストを考慮して加算
        _exp = Mathf.Clamp(_exp + exp, 0, expArray[expArray.Length - 1]);
        // 値の更新
        UpdateLevel(expArray);
        UpdateRemainExp(expArray);
    }

    void UpdateLevel(int[] expArray)
    {
        // 現Exp以下の値の中で最大の値のインデックスを取得
        var maxIdx = expArray.Where(x => x <= _exp).Select((val, idx) => new {V = val, I = idx})
            .Aggregate((max, working) => (max.V > working.V) ? max : working).I;
        _level = maxIdx + 1;
    }

    void UpdateRemainExp(int[] expArray)
    {
        // 現Expより大きい値の中で最小の値のインデックスを取得
        var minIdx = expArray.Where(x => x > _exp).Select((val, idx) => new {V = val, I = idx})
            .Aggregate((min, working) => (min.V < working.V) ? min : working).I;
        _remainExp = expArray[minIdx] - _exp;
    }
}
Player.cs
[System.Serializable]
public class Player
{
    public string Name = "主人公";
    public ExpLevelClass ExpLevel = new ExpLevelClass();

    static readonly int[] TOTAL_EXP_ARRAY = {0,100,300,600,1000};

    // 経験値獲得処理
    public void AddExp(int exp)
    {
        ExpLevel.AddExp(exp, TOTAL_EXP_ARRAY);
    }
}

おまけ

関数でLevelとRemainExpの計算結果を返す

ExpLevelClass.cs
using System.Linq;
using UnityEngine;

[System.Serializable]
public class ExpLevelClass
{
    [SerializeField] int _exp;
    [SerializeField] int _remainExp;
    [SerializeField] int _minLevel;
    [SerializeField] int _level = 1;

    public int Exp => _exp;
    public int RemainExp => _remainExp;
    public int MinLevel => _minLevel;
    public int Level => _level;

    // Expを加算してLvを初期化する
    public (int afterLevel, int remainExp) AddExp(int exp, int[] expArray)
    {
        //カンストを考慮して加算
        _exp = Mathf.Clamp(_exp + exp, 0, expArray[expArray.Length - 1]);
        // 値の更新
        UpdateLevel(expArray);
        UpdateRemainExp(expArray);

        return (Level, RemainExp);
    }

    void UpdateLevel(int[] expArray)
    {
        // 現Exp以下の値の中で最大の値のインデックスを取得
        var maxIdx = expArray.Where(x => x <= _exp).Select((val, idx) => new {V = val, I = idx})
            .Aggregate((max, working) => (max.V > working.V) ? max : working).I;
        _level = maxIdx + 1;
    }

    void UpdateRemainExp(int[] expArray)
    {
        // 現Expより大きい値の中で最小の値のインデックスを取得
        var minIdx = expArray.Where(x => x > _exp).Select((val, idx) => new {V = val, I = idx})
            .Aggregate((min, working) => (min.V < working.V) ? min : working).I;
        _remainExp = expArray[minIdx] - _exp;
    }
}
Player.cs
[System.Serializable]
public class Player
{
    public string Name = "主人公";
    public ExpLevelClass ExpLevel = new ExpLevelClass();

    static readonly int[] TOTAL_EXP_ARRAY = {0,100,300,600,1000};

    // 経験値獲得処理
    public (int afterLevel, int remainExp) AddExp(int exp)
    {
        return ExpLevel.AddExp(exp, TOTAL_EXP_ARRAY);
    }
}

参考

Linqで快適な生活を
[LINQ][C#] Aggregateの使い方(畳み込み, fold)
C# 7 の Tuple は戻り値が複数の時に便利

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

レベルに応じてゲームの難易度を変える実装

UNITYでゲーム作るときに難易度調整するためのデータ作りについて

ScriptableObjectを使います。
このような数値調整をUNITY上でやりたい場合のやり方です。
スクリーンショット 2019-09-29 0.19.43.png

上記の画像にあるデータは、いくつのモノを壊したら、次のレベルに進めるか、そのレベルの難易度はどのぐらいかといったデータを表しています。

どんなゲームを作った際に使ったものか

まず、Garbagersというゲームを作ったのですが、その際の難易度などに関するデータ作りに関して作ったものです。
https://mosq.xyz/Garbagers/

まず基本となるデータ

スリーマッチパズルゲームで、多くのゴミを壊すと次のレベルに進み少しづつ難しくなるゲームでした。テトリスのように。
なので、いくつ壊したら次のレベルに進めるか(int)、ゴミが出現するインターバル時間(float)を設定できるようにしました。(GVLevelInfo)
さらに、ゴミがつっかえて溜まってくるとゴミが速く出るようにしました。
横、縦への速度、回転速度、インターバルを指定できるようにしました。(GVPressureInfo)

LevelClasses.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class GVLevelInfo
{
    public int NextBlockCount;
    public float SpawnInterval = 0f;
}

[Serializable]
public class GVPressureInfo
{
    public float HorizontalSpeed;
    public float VerticalSpeed;
    public float AngularSpeed;
    public float Interval;
}

[Serializable]アトリビュートをつけておく必要があります。
これらをレベルごとに用意するためリストにします。
それとScriptableObjectにして、UNITY上に置けるデータにします。
(全部publicで書いちゃってますが、UNITYでは[SerializeField]アトリビュートをつけても、シリアライズされます)

GVLevelInfoのリスト化したScriptableObjectを作る

class宣言以降、三行だけが重要であとはおまけの関数です。

GVLevelInfoList.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class GVLevelInfoList : ScriptableObject
{
    public List<GVLevelInfo> List = new List<GVLevelInfo>();

    public int GetLevel(int breakCount)
    {
        int level = 0;
        foreach(var v in List)
        {
            if (v.NextBlockCount <= breakCount)
            {
                ++level;
                continue;
            }
            break;
        }
        return level;
    }

    public float ToNextRatio(int level,int breakCount)
    {
        int prevCount = 0;      
        if (0 <= level-1)
        {
            prevCount = List[level-1].NextBlockCount;
        }
        if (level < List.Count)
        {
            var v = List[level];
            return (breakCount - prevCount) / (float)(v.NextBlockCount-prevCount);
        }
        return 1f;
    }
}

GVPressureInfoのリストも同様に作ります。

GVPressureInfoList.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GVPressureInfoList : ScriptableObject
{
    public List<GVPressureInfo> List = new List<GVPressureInfo>();

    public GVPressureInfo GetPressure(int pressure)
    {
        return List[Mathf.Clamp(pressure,0,List.Count-1)];
    }
}

ということで、データ構造としては用意できました。
あとは、このデータをUNITYプロジェクトの中に追加する方法です。

Editorフォルダを作ります

UNITYにおいて、Editorフォルダは特別な名前です。
ここに置かれたスクリプトファイルは、UNITYエディタ上でのみ利用され、ビルドして出力したアプリなどには含まれません。
エディタ上で、データを作ったりする際に使います。

LevelInfoMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public static class LevelInfoMenu
{
    [MenuItem ("Create/CreateGVLevelInfoList")]
    static void CreateGVLevelInfoList ()
    {
        var asset = ScriptableObject.CreateInstance<GVLevelInfoList> ();

        AssetDatabase.CreateAsset (asset, "Assets/LevelInfoList.asset");
        AssetDatabase.Refresh ();
    }

    [MenuItem ("Create/CreateGVPressureInfoList")]
    static void CreatePressureInfoList ()
    {
        var asset = ScriptableObject.CreateInstance<GVPressureInfoList> ();

        AssetDatabase.CreateAsset (asset, "Assets/PressureInfoList.asset");
        AssetDatabase.Refresh ();
    }
}

このスクリプトを書くと、UNITYのトップメニューにCreateが追加されています。
実行すると、指定されたパスの場所にデータが追加されます。
インスペクター上で、データを編集することが可能です。

保存場所を選べるようにしたい場合は、下記のような関数を使います。
https://docs.unity3d.com/ja/current/ScriptReference/EditorUtility.SaveFilePanel.html
→プロジェクト内に保存する場合は、こっちでした。
https://docs.unity3d.com/ScriptReference/EditorUtility.SaveFilePanelInProject.html

スクリーンショット 2019-09-29 0.19.43.png

スクリーンショット 2019-09-29 0.31.12.png

これをゲームで使っていきます。

実際に使う

普通に、MonoBehaviourにインスペクタから設定することができます。

GarbagersSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GarbagersSystem : MonoBehaviour {
    [SerializeField]
    LevelInfoUI _levelInfo;
    [SerializeField]
    GVPressureInfoList _pressureInfo;
...

インスペクタに先ほどのメニューから作ったデータをドラッグ&ドロップすればデータが入るので、使いましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む