ビールが飲みたい。

ゲーム開発の備忘録などを書きます。

Excelでマスターデータを作り、JsonでScriptableObjectに読み込ませる備忘録

はじめに

この記事は、Excelでマスターデータを作り、jsonとして出力し、UnityのJsonUtilityを利用してScriptableObjectに読み込ませるまでの一連の流れを列挙した備忘録です。

暇ができたから記事を書いたものの、ExcelをUnityで直接読み込んだりした方が楽なので、特に理由が無ければこちらの記事等を参考にされた方が良いかと思います。

robamemo.hatenablog.com

ExcelからJSONを出力したかったり、JSONを読み込ませたかったり、サーバーからJSONAPIで受け取る予定だけどまだサーバー準備中だから......みたいなちょっと変わった事情がある方が参考になるかなぁという感じの記事です。

そういうやり方もできるンダナーくらいの流し読み推奨。

※10/4 12:00 追記

ScriptableObjectの何が良いのかってスライドをUnity様が丁度同じ日にアップしていました。

www.slideshare.net

Excel等からScriptableObjectを生成する方法も少し記述されていて、こちらの手法が使えればよりスマートに事が進むと思います。

バージョン情報

  • Excel 2016
  • Unity 2017.1.0f3

ExcelからJSONで出力する

Excelを開き、item.xlsmなど適当な名前で保存します。
.xlsx形式だと保存後に書いたマクロが消えてしまうのでご注意下さい。

まずはマスターデータを作ります。 f:id:eggame:20171003165153p:plain 今回はこんな感じ。

次にテーブルをjsonで出力出来るよう、vbaを書きます。
f:id:eggame:20171003171618g:plain

  • メニューの[開発] > [Visual Basic]をクリック
  • [挿入] > [標準モジュール] を選択肢し、Module1を作成
  • マクロを記述(今回動かしたコードは下記に記載しました。)
  • utf-8で保存したいため、とあるライブラリを使用します。
     [ツール] > [参照設定] から
    Microsoft ActiveX Data Objects
    の適当なバージョンにチェック
  • 実行ボタンよりマクロを実行

targetFilePath変数に出力時のファイル名が設定されています。 コードは下記の通り。

Option Explicit

Sub CreateJson()

  Dim stringData As String
  Dim targetFilePath As String
  
  Dim sheetName As String
  Dim key As String
  Dim value As String
  
  Dim row As Integer
  Dim col As Integer
    
'****
' Init
'****
  targetFilePath = ThisWorkbook.Path & "\item.json"
  
  'ファイルを一旦削除
  If Dir(targetFilePath) <> "" Then
    Kill targetFilePath
  End If


'****
' 処理
'****
  Dim stm As ADODB.Stream
  Set stm = New ADODB.Stream
  
  stm.Charset = "UTF-8"
  stm.LineSeparator = adLF
  stm.Open

  
  sheetName = "Sheet1"
  'ループを開始する行番号を入れて下さい。
  row = 3
  
  stringData = stringData + "{"
  stringData = stringData + """" + "item" + """" + ":["
    
  Do
    stringData = stringData + "{"
    
    col = 1
    Do
      
      key = """" + CStr(Worksheets(sheetName).Cells(2, col).value) + """"
      value = """" + CStr(Worksheets(sheetName).Cells(row, col).value) + """"
      stringData = stringData + key + ":" + value
      
      col = col + 1
      
      If IsEmpty(Worksheets(sheetName).Cells(row, col).value) = False Then
        stringData = stringData + ","
      End If
      
    Loop Until IsEmpty(Worksheets(sheetName).Cells(row, col).value) = True

    row = row + 1
    
    If IsEmpty(Worksheets(sheetName).Cells(row, 1).value) = True Then
      stringData = stringData + "}"
    Else
      stringData = stringData + "},"
    End If
    
  Loop Until IsEmpty(Worksheets(sheetName).Cells(row, 1).value) = True
  
  stringData = stringData + "]}"
  
  stm.WriteText stringData, adWriteLine
  stm.SaveToFile targetFilePath, adSaveCreateOverWrite
  stm.Close
  
  MsgBox ("出力完了")
  
  End Sub

詳しくは解説しませんが、stringData変数にテーブルから読み取ったデータをどんどん繋げていき、最後にtargetFilePathで設定したファイルへまとめて書き込みます。
この設定の場合、jsonファイルはxlsmファイルと同じディレクトリに出力されます。

以下のようなjsonファイルが作成されます。(出力後に整形しました。)

item.json

{
  "item": [
    {
      "idx": "item1",
      "name": "木の棒",
      "discription": "故郷の木の枝を拾いました。",
      "hp": "0",
      "attack": "1",
      "diffence": "1",
      "speed": "1",
      "assetbundle": "item_wood"
    },
    {
      "idx": "item2",
      "name": "竹やり",
      "discription": "一般的な武器です。",
      "hp": "0",
      "attack": "2",
      "diffence": "2",
      "speed": "2",
      "assetbundle": "item_bamboo_spear"
    },
    {
      "idx": "item3",
      "name": "錆びたナイフ",
      "discription": "この竜の紋章は何でしょう。",
      "hp": "0",
      "attack": "3",
      "diffence": "1",
      "speed": "3",
      "assetbundle": "item_rusted_knife"
    },
    {
      "idx": "item4",
      "name": "なべのふた",
      "discription": "投げたい。",
      "hp": "5",
      "attack": "0",
      "diffence": "5",
      "speed": "0",
      "assetbundle": "item_pot_lid"
    }
  ]
}

最後に、シートにボタンを用意し、押すと出力されるようにしておくと少し楽になります。 f:id:eggame:20171003171816g:plain

  • [開発] > [挿入] > [ボタン]をクリック
  • クリック長押ししながら適当なサイズへマウスを移動
  • マウスを離すと機能を登録するウィンドウが出現するので、処理を書いたプロシージャ名を選択

うまく設定出来れば、クリックするとjsonが出力されるようになります。

JSONを読み込むための準備

ここからUnityでの作業に移ります。

読み込むjsonの形と一致するよう、マスターデータクラスを作ります。
フィールド名とjsonのkeyが一致していないと無視されてしまうので、ご注意下さい。
JsonUtilityでシリアライズするため、各クラスへ[Serializable]をつけます。

ItemMasterData.cs

using System;
using System.Collections.Generic;

[Serializable]
public class ItemMasterData{

    public List<ItemData> item = new List<ItemData>();

    [Serializable]
    public class ItemData
    {
        public string idx;
        public string name;
        public string discription;
        public int hp;
        public int attack;
        public int defense;
        public int speed;
        public string assetbundle;
    }
}

マスターデータを入れるScriptableObjectを作ります。

ItemSO.cs

using UnityEngine;

[CreateAssetMenu]
public class ItemSO : ScriptableObject {
    public ItemMasterData itemMasterData;
}

ScriptableObjectの作り方は色々あると思いますが(マスターデータ用クラスと統合しちゃったり)、今回はわけて作りました。

jsonファイルをStringで読み込むスクリプトを用意します。

JsonHelper.cs

using UnityEngine;
using System;
using System.IO;
using System.Text;

public class JsonHelper
{
    /// <summary>
    /// JSONファイルをStringで読み込みます。
    /// </summary>
    /// <param name="filePath">streamingAssetsフォルダからのパス</param>
    /// <param name="fileName">ファイル名</param>
    /// <returns>jsonのstringデータ</returns>
    public static String GetJsonFile(String filePath, String fileName)
    {
        string fileText = "";

        // Jsonファイルを読み込む
        FileInfo fi = new FileInfo(Application.streamingAssetsPath + filePath + fileName);
        try
        {
            // 一行毎読み込み
            using (StreamReader sr = new StreamReader(fi.OpenRead(), Encoding.UTF8))
            {
                fileText = sr.ReadToEnd();
            }
        }
        catch (Exception e)
        {
            // 改行コード
            fileText += e + "\n";
        }

        return fileText;
    }
}

ボタンをクリックするとScriptbleObjectへjsonデータが読み込めるスクリプトを作ります。

LoadMasterDataFromJson.cs

using UnityEngine;

public class LoadMasterDataFromJson : MonoBehaviour {

    public ItemSO itemSO;

    private void Awake()
    {
        if (!itemSO)
        {
            itemSO = Resources.Load<ItemSO>("MasterData/ItemMasterData");
        }
    }

    private void LoadFromJson()
    {
        itemSO.itemMasterData = new ItemMasterData();
        itemSO.itemMasterData = JsonUtility.FromJson<ItemMasterData>(JsonHelper.GetJsonFile("/","item.json"));
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(20, 20, 100, 50), "読み込み"))
        {
            LoadFromJson();
        }
    }
}

フォルダを作っていきます。

今回はScriptableObjectをResources.Loadで読み込むことを想定し、
Resources/MasterDataの中に格納します。

jsonファイルをStreamingAssetsフォルダから読み込むため、StreamingAssetsフォルダを作成し、item.jsonを格納します。

これで下準備完了です。

JSONをScriptableObjectへ読み込む

データを入れるScriptableObjectを作ります。
MasterDataフォルダを右クリックし、[Create] > [Item SO]を選択すると、MasterDataフォルダ内に新規オブジェクトが生成されます。
名前をItemMasterDataにしておきます。

ファイル名は、LoadMasterDataFromJson.csでの読み込み処理に影響が出ますので、別の名前にする際はLoadMasterDataFromJson.csを適宜編集して下さい。

f:id:eggame:20171003174704g:plain

LoadMasterDataFromJson.csを適当なGameObjectに追加し、シーンを再生するとGame画面にGUIボタンが出現します。
GUIボタンをクリックするとScriptableObjectにjsonファイルからデータが読み込まれます。

f:id:eggame:20171003175524g:plain

使用後、LoadMasterDataFromJson.csを使わないときはInstectorのチェックを外したり、削除したりすれば動かなくなります。

おわりに

VBAなどハードコーディングしちゃってる部分もあり、ちょっとコードがわかりづらいかもしれません。すいません。
何かあればコメント欄にお願いします。

Unity 1週間ゲームジャム「フロー」でクオータービューに挑戦しました

※8/30 16:00 Isometric 2.5D Toolsetの話にUltimate Isometric Toolkitのことを追記しました。

はじめに

祝!初エントリー!

Unity1週間ゲームジャム お題「フロー」に参加し、無事投稿したのでその時の話を簡単にまとめようと思います。

僕の参加は今回で4回目。

Unity1週間ゲームジャムとは

Unityroomで行われる、naichiさん(@naichilab)主催の1週間でお題に沿ったゲームを制作するゲームジャムです。

f:id:eggame:20170829185948p:plain

Unity 1週間ゲームジャム | ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

スタンスは「ゲームを作るきっかけになれば」というもので、基本的にUnityを使ったどんな物でもあり。期限過ぎちゃっても完成しなくても全然オッケー!という非常に条件がゆるく参加しやすいイベントです。

作成したゲームについて

【Waterworks】

Waterworks | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

f:id:eggame:20170829183003g:plain

水源から水路を繋げて、ゴールへ水力を導くゲームです。

水力を伝えると効果が発動するギミックを持ったタイルも実装し、単調なパズルにならないように調整できる可能性を見せました。
(実際はステージ作成の練度が足らず、単調なステージばかりになってしまいましたが…)

使用したAsset

BGMは昔買って死蔵していた素材から漁ってきた落ち着いたJazzっぽいもの、
TextureはKENNEYさんのPublic Domain素材
UIはPhotoshopとFont Awesomeで作りました。

ネタ出しの話

僕がこのゲームジャムに参加する時は何かの技術学習や実験のついでになりますが、今回はお題が出る前からIsometric 2.5D Toolsetを試してみる事を決めていました。

どんなお題が来てもクオータービュー!

お題「フロー」が発表され、補足説明で「流れ」という単語が見えた時点で「水を流して導くパズル」というコンセプトが思い浮かび、その後日本語や英語で辞書を引いたりしましたが特に心変わりはなくそのまま作業に入りました。

ついついコンセプトに合わせて機能を実装する事が頭に浮かんできてそれに集中してしまうため、 実際それは面白いゲームなのかどうかを検討することがおざなりになってしまうのが悪い癖です。

Isometric 2.5D Toolsetの話

今回初使用です。参加の主目的。

オブジェクトの移動や当たり判定、物理演算などをクオータービューの形式で管理してくれるAssetです。 IsoWorldコンポーネントをつけたGameObjectを親にし、子にIsoObjectコンポーネントをつけたタイルを置くことで、 XYZ座標の動きをIsometric状態にしてくれます。

f:id:eggame:20170829181302p:plain
本ゲームのステージのhierarchy。

f:id:eggame:20170829181246g:plain
切りの良い場所に吸い付くので気持ちいい。

注意点は、IsoRigidbodyコンポーネントやIsoBoxColliderコンポーネントなど、Unity標準のRigidbodyやColliderを使用しないため、クリック時の当たり判定などはIPointerDownHandlerやUniRxのObservableDragTrigger.OnDragAsObservable()が効かなくなり、かわりにIsoPhysics.RaycastNonAlloc()などの専用に用意されているAPIを利用して実装することになります。

また、IsoWorld内の非アクティブなGameObjectは描画順などの計算の対象にならないため、非アクティブだったGameObjectをアクティブにした際、描画順序がおかしいままになる場合があります。

f:id:eggame:20170829182201g:plain

このためイベントによりタイルが表示されるようなギミックを作った場合にSetActive(true)後にどれかのタイルの座標を少し動かしてやるなどのハックが必要になります。

今思うと、意図的に再計算させるメソッドが調べたらあるかもしれませんし、GameObjectを非表示にしないでSpriteRendererだけ非アクティブにするなり鍵マークをつけるなりで常に計算してもらえるような状況を作るべきだったかもしれません。

本ゲームでは物理演算などを使用せずキャラクターの操作も無く、単にタイルを並べるだけで成立するためこのアセットの旨味はあまりなかったかもしれません。

ただ、これから物理演算を利用した追加ギミックなどの拡張をする気持ちになれば有効活用できそうです。(スイッチを押して大玉を転がして穴に入れると鍵が開くゼ◯ダっぽいギミックとか)

※追記

今回は利用しませんでしたが、外部ツールのTiledで簡単にステージ作成作業が出来ます。 活用出来ると良いかもしれません。

※追記2 8/30 16:00

Ultimate Isometric Toolkit

Isometric 2.5D Toolsetと同等の機能を持つ、もう少し評価の高いUltimate Isometric ToolkitというAssetがあります。

記事を書いた後にこちらも購入して試してみましたが、サンプルを見た時点でのIsometric 2.5D Toolsetとの違いは下記の通りです。

【Isometric 2.5D Toolsetと違う所】

  • 高低差を考慮したパスファインディングがある
  • 単一オブジェクトを用いた地形自動生成機能
  • デフォで用意してあるキャラクターコントローラーの動きが少し滑らかでジャンプが出来る
  • 錯視ステージが作れる(との説明がStoreにあるものの、サンプルシーンは無い)
  • チュートリアルがチョット多い
  • 3Dオブジェクトのソート(描画順序)には対応していない
  • Sceneのスナップが機能しない(ストアの説明には出来ると書いてある) ※Unity2017.1
  • Isometric 2.5D ToolsetはPlayMaker対応
  • Isometric 2.5D Toolsetは複数の異なった角度で計算される空間を同一シーンに持てる

ちなみにどちらもArea Effector 2D等のEffector 2Dには対応していない様子です。

3Dモデルとの親和性は薄いですが、パスファインディングが標準で用意されているのは強力ですね。

なお、Ultimate Isometric Toolkitには現在無料のLite版が存在します。
こちらで出来る事はStoreを引用すると、

  • 太陽系外惑星/エイリアン&自然のテーマのためのスプライトシート
  • アイソメトリックソーティング
  • アイソメトリック衝突検出&物理
  • カスタムエディターツール(アイソメトリックスナッピング、カスタムハンドル)
  • 1個の連続移動のためのアイソメトリックキャラクターコントローラー
  • 2個のサンプルシーン

となっており、サンプルを見たところキャラの横移動やタイルの配置をクオータービューで行うことまでは出来るという感じでした。

アイソメトリック衝突検出&物理と書いてありますがAssetにはIsoRigidbodyやIsoBoxCollider等は同梱されておらず、コレだけでは物理演算と衝突検出は出来そうにありません。

ひとまずクオータービューゲームを作ろうとしたらどんなことを考えたら良いのかを確認するために、まずコレを触ってみると良いのではないでしょうか。

水力の伝搬の話

managerにDictionary<Vector3,waterParameter>のカタチで水力の発生している座標と各方向の水力を保存しておき、Worldのタイルに変化があった際に各々のタイルがそこを参照して自分自身の水力を算出して、変化があればmanagerに再登録する仕組みを取っています。

で、自身の持っている水力を元にShaderで2枚のTextureをLerpして水力の見た目を変化させています。

こちらのShaderはShader Forgeで作りました。

大量のタイルに変化が発生するような移動を行った際、計算によるフリーズが少し発生するため、もっと効率のいい方法を思いつきたいです。

Pro Camera2Dの話

今回初使用です。

2Dでのカメラの移動、ズーム、イベントによる映すターゲットの切り替え、ついでにカメラを利用したtransitionなどがとても簡単に実装できるようになります。

本ゲームではDragでカメラターゲットを動かした際の追従アニメーション、描画範囲の制限、追加でクリア時のズームの実装に利用しました。 ほんとに簡単に気持ちいい挙動を実装できたので、今後積極的に利用してくかもしれません。

Skyboxを利用したディゾルブで画面遷移を作った話

今回はTextureは速攻で決まっていたものの背景どうしたら良いか迷い、 少し考えるのに時間を使いました。

全体的にどうするか考えている所。

その結果以前のゲームジャムで評判が良さそうだったSkyboxをグラデーションにする演出をやってみました。

で、グラデーション背景にシンプルなタイルが敷かれた様子を見て、「なんかこれちょっとオシャンティだな。」と感じた結果、 各所にオシャンティ要素を散りばめてみようと考えたわけです。

アイコンや枠線などは極力シンプルにする予定にし、 最初あまり見かけないからUI背景をすりガラスっぽくしてオシャレにしようかと目論みましたが大雑把すぎでダメ。

ここでいつも僕のゲーム画面遷移がうるさいよな…と感じたことを思い出して同一背景でキレイにシーン遷移出来ないかと考えました。

いろいろ考えた結果、同じSkyboxさえずっと表示されていれば地続きでゲームが進んでるように見えるなと思い、 フェードなどで各オブジェクトを背景に溶け込ませ浮き上がらせな感じで続いてる感を出せないかと考えました。

で、ネックになるのはタイル。そしてステージを開始すると生成されるパーティクル。

ParticleはDontDestroyOnLoadに登録して再生を制御しても良かったですが、大量のタイルはSpriteRendererで表示しているため、さらっとCanvasGroupのalphaをいじるようなことは出来そうにありません。

Worldをいじりづらいなら、Skyboxで覆ってしまえばいいじゃない!

と思った結果、サブカメラを用意してRenderTextureにSkyboxを保存し、そのRenderTextureにShaderでエフェクトをかけることで溶けるような演出を加えることに成功しました。

Shaderはこちらの動画を参考にShader ForgeでノイズTextureを元にOpacity ClipをいじるShaderを用意しました。

アニメーションをDOTweenで

あとは時間差のUIアニメーションは全てDOTweenで制御。

ステージ選択エリアのアウトラインがぐるっとひかれるのは、BGの親にMaskを用意し、MaskのImage.typeをfillにして円形に描画されるようにし、fillAmountをいじって表示しています。

まとめ

良かった点

  • クオータービューの構築について大分理解が進んだ。
  • Pro Camera2Dは今後も1軍候補。
  • 各画面遷移が流れるように繋げられたため、気持ち良かった。

反省点

  • 物理演算やキャラクターの移動などがないため、Isometric 2.5D Toolsetを使う意味が薄かった。(アプリ化できそうであれば有効活用したいです。)
  • デバッグが荒く、投稿後にギミックが最初から開放されていたりタイルの表示がおかしかったりした。
  • タイルをマウスオーバーしても変化がないため、タイルの高低差によっては意図したタイルがタップできなかったり。
  • 操作の手間や視認性が悪く、爽快感が薄い印象。
  • SEがまともに鳴らない理由が未だわかりません。
  • タイルの説明などがなく、遊び方の説明が不親切。

感想

初めてまともにブログを書いたため、きちんと要点が書けていない&考察が甘いかもしれませんが許して!

ついでに途中で編集に疲れて色々リンクはるの省いたのも許し!て!

主目的であるクオータービューの扱いに大分慣れたので個人的には大満足です。

ただ、ここまで記事を読んでいただいた通り企画のこと一切書いていないのはそれだけあんまり考えずに実装しているためで、企画力貧弱なのなんとかしたいですね…。

(見られている割にハートも少ない…。愛をください…。)

記事書くの大変なので今後も書くかどうかはわかりませんが、ゲーム制作は今後も頑張っていきたいと思います。