勉強不足で至らんブログ

勉強不足ですが、何か発信できればと思っています。

ScriptableObjectの入れ替えで振る舞いを変える

セルフQiita転載です。

qiita.com

はじめに

 ゲームを作っていて、必殺技の作成等を簡単に行えるようにできないだろうか?ということを考えたのが今回の発端です。そこで、ScriptableObjectにパラメーターとメソッドを定義して入れ替えるだけで挙動を変えれると簡単なのでは?と考えついて試してみたので備忘録として書きます。

最終的に、以下のようになります。 Sample1.gif

ScriptableObjectの入れ替えで振る舞いが変わります。

gifで動かしているプロジェクトは公開しているので、説明読むより触ってみたい!という方はご活用ください。 https://github.com/MizoTake/ChangeBehaviourWithScriptableObject

一連の流れ

 最初に、今回の処理がどういう流れで行われているのか書きます。

まず、gifにあったCubeはSpaceキーを押すとInspectorから代入されたScriptableObjectの振る舞いを実行します。 中身は以下のようになっています。 Cubeに貼られているスクリプトから

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

public class ControllerInput : MonoBehaviour
{
    [SerializeField]
    private Rigidbody _rigid;
    [SerializeField]
    private Behaviour _behaviour;

    public Rigidbody Rigid { get { return _rigid; } }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _behaviour.Move(this);
        }
    }
}

SpaceKeyが押されるとBehaviourというクラスのMoveメソッドを実行します。 次に、そのBehaviourというクラスです。

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

public abstract class Behaviour : ScriptableObject
{
    public abstract void Move(ControllerInput input);
}

public static class BehaviourExtension
{
    public static void Init(this Behaviour behaviour)
    {
        Debug.Log("Initialize");
    }
}

これは、abstractを宣言しているので基底クラスです。継承先で振る舞いを書いて欲しいメソッドを定義しています。今回は、Moveメソッドになります。 そして、ScriptableObjectクラスを継承しているので継承先のクラスをScriptableObjectにできるようになります。 また、abstractクラスには、そのまま拡張メソッドを追加できるので共通で何かを行いたい場合等に使うと便利です。(今回はTips程度に書いています。)

次にgifの最初にJumpしていたクラスです。

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

public class Jump : Behaviour
{

    public int Power;

    public override void Move(ControllerInput input)
    {
        this.Init();
        input.Rigid.AddForce(Vector3.up * Power, ForceMode.Impulse);
    }
}

これはBehaviourを継承してMoveの振る舞いを定義しています。そのためgifでは、JumpクラスがInputControllerのInspectorに代入され、Moveが呼ばれるためジャンプをしていました。

ここで便利になるのはScriptableObjectを作成した後、Powerの値を変えることでジャンプの高さも変えることが容易になるという点です。

また、this.Init()で呼び出すことによりabstractの拡張メソッドであるInitが呼ばれます。

ここまでが基本的な一連の流れになります。

次からは+αで便利だと思った部分を書きます。

sample2.gif

これは最初のgifでバラバラに実行されていた、ジャンプの振る舞いと色を変える振る舞いを複合して実行しているものになります。

まず、色を変えるクラスです。

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

public class ChangeMaterial : Behaviour
{

    public Material Target;

    public override void Move(ControllerInput input)
    {
        this.Init();
        input.gameObject.GetComponent<MeshRenderer>().material = Target;
    }
}

ScriptableObjectにした後、InspectorでMaterialを代入しています。そして、Jump同様にInputControllerのInspctorに代入することで動作します。

次に複合させたクラスです。

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

public class Composite : Behaviour
{

    public Behaviour[] Behaviours;

    public override void Move(ControllerInput input)
    {
        this.Init();
        foreach (var behaviour in Behaviours)
        {
            behaviour.Move(input);
        }
    }
}

中身を見ると何てことはないと思います。配列をInspectorから入れて、for文で一気に実行するというものです。

ただ、Behaviourクラスがあるだけで振る舞いの複合やランダム実行などが簡単に定義でき、Compositeクラス自体もBehaviour継承クラスなので、InputControllerのInspectorに代入するだけで切り替えれるというのは便利だと思っています。

以上で今回の記事はすべてになります。 (…ScriptableObjectにメソッドが書けるのが個人的に驚きだった)

また、ScriptableObjectの作成に関しては、個人的に以下の記事がオススメです。 【UnityEditor】ScriptableObjectのアセットを右クリックから作れるようにするエディター拡張

右クリックから作れるようになるので簡単作成できるようになって便利でした!

LICENSEファイルの中身を一つにまとめてくれるEditor拡張

自分のQiita転載です

qiita.com

GitHub等からAssetsを持ってきて、アプリをリリースする時などにLICENSEファイルをまとめるのがめんどくさいなぁと思ったりしませんか?僕は数回やってめんどくさいと思いました。

なのでEditor拡張でAssets配下の"LICENSE"ファイルを検索して"Assets/USE_ASSETS_LICENSE"にまとめるようにしてみました。

これで、ライセンス表記をする際に"Assets/USE_ASSETS_LICENSE"からtextを読んでもいいですし、開いてコピペでもいいと思います。良しなに使っていただければと思います。

public static class CreateUseLicenseFile
    {

        private const string USE_LICENSE_FILE = "use_assets_license_file";
        private const string fileName = "USE_ASSETS_LICENSE";

        [MenuItem("Assets/Create/Used LICENSE File")]
        static void CreateUsedLICENSEFile()
        {
            System.Text.StringBuilder builder = new System.Text.StringBuilder();

            string[] files;
            string assetPath = Application.dataPath + "/" + fileName;

            AssetDatabase.MoveAssetToTrash(assetPath);

            files = AssetDatabase.FindAssets("LICENSE");
            foreach (var guid in files)
            {
                if (Path.GetFileName((AssetDatabase.GUIDToAssetPath(guid))) == "CreateUseLicenseFile.cs" ||
                    Path.GetFileName((AssetDatabase.GUIDToAssetPath(guid))) == fileName) continue;
                var path = AssetDatabase.GUIDToAssetPath(guid).Substring("Assets".Length);
                StreamReader reader = new StreamReader(Application.dataPath + path);
                builder.Append(reader.ReadToEnd()).AppendLine();
                reader.Close();
            }

            string text = builder.ToString();

            if (AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)) != null && EditorPrefs.GetInt(USE_LICENSE_FILE, 0) == text.GetHashCode())
                return;

            System.IO.File.WriteAllText(assetPath, text);
            EditorPrefs.SetInt(USE_LICENSE_FILE, text.GetHashCode());
            AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
        }
    }

こういう拡張があったら二番煎じですみません。。。

最近、作り始めたフレームワークの中に入れています。もし、ご興味のある方は是非。 https://github.com/MizoTake/MomijiFramework/blob/master/Editor/CreateUseLicenseFile.cs

シンプルなアクションゲーム Reviver を公開しました!

 約4ヶ月ほどの開発を得て、ひとまず公開できるレベルになったので公開しました!

現在、Androidのみ対応です。片手でタップするだけでも遊べます。

 結構シンプルにまとめたつもりです。操作も簡単ですが、人によっての遊び方に幅が少しでも出るようにしたつもりです。是非、インストールしてください!

 

play.google.com

 

 ちなみに、これはゲームエンジン Unityで制作してます。

 今回、デザイナーと一緒にやらず1人でどこまでできるのか試すために作ったのですがキャラクターはCubeのままで公開まで行きました。開発途中でちょうどテラシュールブログの方で

tsubakit1.hateblo.jp

が公開されてタイミングがすごくよかったです。これを見て参考に実装しました。人間のようなキャラクターがなくてもCubeをアニメーションさせるだけで全然違いますね。

 

また、今回使用したAssetsは

UniRx

DoTween

Material UI

上記3つです。

Material UI はAssets Storeからではなく古いバージョンであればGitHubから使用できたので使用しました。

https://github.com/InvexGames/MaterialUI

 

 気になって買ったAssetsもあったんですが開発途中だったのもあり使用しませんでした。
 ・Arbor2

GUIで状態管理ができるようになるAssetsです。次回から使っていこうと思っています。 

 

また、アイコンに関してはGoogleが提供している

material.io

を使用することで1人で作っても、アプリっぽい見た目になったと思います。

 

音は

dova-s.jp

を利用させていただきました!結構、色々な雰囲気の音があってBGMもSEもここで見つけました。

 

 そして、ランキング機能があるのですが、Google Spread Sheetを使用したGoogle App Scriptで実装しています。なので、ランキングはアプリのプレイヤー全員が相手ということになります! 

 無料で使えて簡単にランキング等なら作れるのでおすすめです。

 

 今回、社会人になって初めて作ったのですが思ったよりも時間がとれなくて焦ってしまいました。学生のうちにサクサク作って、公開していればよかったとしみじみ思います……でも、作れる時に作ればいいので、これを皮ぎりに作っていこうと思います!

UnrealEngine4をGPD Pocketで動かしてみた

大八耐というイベントにGPD PocketでUnity動かして参加していました。

 

途中で、ふと疑問…UnrealEngine動かしたらどうなるんだ?

 

動かしました。

www.youtube.com

 

ついでにFPSを表示しています。平均20くらいですかね。

全く動かないわけではないです…軽いものであれば電車でUnrealEnigneも…?

大八耐2017 in 東京参加してました!

ちょくちょく記事に書いている八耐…の大きいやつ大八耐が10/7, 8であっていたのでゲーム作っていました。

 

Sphicと言います。

www.youtube.com

 

球を数珠のようにつなげて蛇にするのですが、あまりうまくいっておりません。

さらには対戦させたかったのですが間に合いませんでした。

チームで分担していたものの、うまく分担できてなかったような気もしてみます。

 

自分はゲームのメインロジックは触ってないので何ともです。精進せねば…

 

大八耐の他の方の作品は、面白いものから完成度の高いもの…料理…様々なものが作られていました!

福岡の記事ですが、雰囲気としてはこんな感じ…

大八耐2017 中間発表会が終わりました!|新着情報|大八耐

 

モノ作りの幅がないハッカソン…様々な作り手の方がいるので面白いです。。。

 

余談ですが、GPD Pocketで参加しておりました。

f:id:MakeTake:20171009012326j:plain

怖かったです。

なんとか復旧はしましたけど…

f:id:MakeTake:20171009012432j:plain

第2回 八時間耐久作品制作会(仮) in 東京 !!

6月に引き続き第2回の東京八耐を開催しました!!

 

www.facebook.com

 

今回も前回同様、20名程の参加者の方々が来てくださいました!

 

そして、今回は…発表時に写真を取り忘れることが多く…手元にあまりないですが、紹介していこうと思います!申し訳ないです!

 

・Lazy Kの処理経過を表示するプログラムの作成

・Swiftで大喜利のお題を出すアプリの制作

・アプリのアイコンを変化させるYES NO枕アプリ

・気温が一定以上になると怪談話を話し出すIoTデバイス

・Processingで花火を打ち上げる壁紙作成

・リアルな背景等を利用したコンセプトアート

・EDM風ゲーム音の作成

・Unityでノイズをパーティクルやキャラクターに適応させるシェーダー

Blenderでエフェクトの作成

f:id:MakeTake:20170828011018j:plain

・Sprite Lampを用いた2D絵の作成

・pixivの画像を制作者好みに扱うソフトウェアの作成

f:id:MakeTake:20170828011453j:plain

・あくびが伝達する……ソフトウェア?

f:id:MakeTake:20170828011553j:plain

・暗号化をテーマにしたアナログゲームのルール作成

 

以上が今回の八耐で発表されていたものです!

制作が途中までしかできなかったものや、前回の八耐から改良されていっているもの…様々ですが色々な作品が見れておもしろかったです!

 

www.facebook.com

 

次回の八耐は、10月 7, 8日 大八耐 という2日間あるイベントになります!

場所は、前回、今回と変わらずドリコム様の会場を借りる予定です。いつもの八耐とは違い少し大きなイベントになりますのでご興味がある方は是非お願いします!

UnityのlayerをenumにしてくれるEditor拡張を作った

たまにしてる、自分のQiita記事転載です(ブログ更新と言いたい)

GameObjectのTag管理を楽にしたくて調べてたら、これをみつけました。

UnityEditorInternal.InternalEditorUtilityを使う - けいごのなんとか

Tagを扱いやすくするクラスの自動生成…なんと甘美な響き…!! 結構前の記事ではありますがUnity5.6.3とかでも動作してくれるので、すごく助かります。

…ここでTagだけでなくLayerも扱いやすくできれば…どうせならEnumに…と思いまして、上記の記事のクラス自動生成を見よう見まねでLayerからEnumスクリプトの自動生成を作ってみました。

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

[InitializeOnLoad]
public class LayerEnumCreator
{
  private const string LAYER_ENUM_HASH_KEY = "Layer_Enum_Hash";

    [MenuItem("Assets/LayerEnumCreator")]
    public static void _layerNameCreator ()
    {
        if (EditorApplication.isPlaying || Application.isPlaying)
            return;

        BuildlayerName ();
    }
    
    static LayerEnumCreator ()
    {
        if (EditorApplication.isPlaying || Application.isPlaying)
            return;

        BuildlayerName ();
    }

    static void BuildlayerName ()
    {
        System.Text.StringBuilder builder = new System.Text.StringBuilder ();

        builder = WriteTagManagerClass (builder);


        string text = builder.ToString ().Replace (",}", "}");
        string assetPath = currentFolderPath + "../LayerEnum.cs";
        
        if (AssetDatabase.LoadAssetAtPath (assetPath.Replace ("/Editor/..", ""), typeof(UnityEngine.Object)) != null && EditorPrefs.GetInt (LAYER_ENUM_HASH_KEY, 0) == text.GetHashCode ())
            return;

        System.IO.File.WriteAllText (assetPath, text);
        EditorPrefs.SetInt (LAYER_ENUM_HASH_KEY, text.GetHashCode ());
        AssetDatabase.Refresh (ImportAssetOptions.ImportRecursive);
    }

    static System.Text.StringBuilder WriteTagManagerClass (System.Text.StringBuilder builder)
    {
        List<string> layerNames = InternalEditorUtility.layers.ToList();

        builder.AppendLine ("public class LayerEnum");
        builder.AppendLine ("{");

        {
            WriteLayerEnum (builder, layerNames);
        }

        builder.AppendLine ("}");
        return builder;
    }
    
    static void WriteLayerEnum (System.Text.StringBuilder builder, List<string> layerNames)
    {
        builder.Append ("\t").AppendLine ("/// <summary>");
        builder.Append ("\t").AppendFormat ("/// Access Layer Number Enum").AppendLine ();
        builder.Append ("\t").AppendLine ("/// </summary>");
        builder.Append("\t").Append ("public enum Layer {").AppendLine ();
        layerNames.ForEach ((layerName, i) => {
            var comma = (i == layerNames.Count() - 1)? "" : ",";
            if(i >= 3) {
                i += 1;
                if (i >= 6) {
                    i += 2;
                }
            }
            builder.Append("\t").Append("\t").AppendFormat (@"{0} = {1}", layerName.SymbolReplace(), i + comma).AppendLine();
        });
        builder.Append("\t").AppendLine ("};");
    }


    static string currentFolderPath {
        get {
            string currentFilePath = new System.Diagnostics.StackTrace (true).GetFrame (0).GetFileName ();
            return "Assets" + currentFilePath.Substring (0, currentFilePath.LastIndexOf ("/") + 1).Replace (Application.dataPath, string.Empty);
        }
    }
}

UnityのEditorの方でLayerの3, 6, 7は入力ができず空白なのでenumに含めず、スキップする処理にしてますが…あまり気に入ってないです… あと、実はこれだけでは動かないです。クラス拡張も行なっているので…

using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using System;

public static class ClassExtension
{
    public static string SymbolReplace(this string name)
    {
        string[] invalidChar = new string[] {" ", " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "-", "=", "^", "~", "¥", "|", "[", "{", "@", "`", "]", "}", ":", "*", ";", "+", "/", "?", ".", ">", ",", "<"};
        invalidChar.ToList ().ForEach (s => name = name.Replace (s, string.Empty));
        return name;
    }
    
    public static void ForEach<T> (this IEnumerable<T> collection, Action<T, int> action)
    {
        int i = 0;
        foreach (T t in collection) {
            action (t, i++);
        }
    }
}

この拡張を加えれば動くと思います。 ただ、注意点なのはLayerは8から入力できますが、8を入力せず9を入力すると番号がズレるため、その辺は ご自分でよしなにお願いします。 初めての自動生成スクリプト(見よう見まね)なのでツッコミ所があれば突っ込んでいただければと思います。

デフォルトの結果だとこうなります。

public class LayerEnum
{
    /// <summary>
    /// Access Layer Number Enum
    /// </summary>
    public enum Layer {
        Default = 0,
        TransparentFX = 1,
        IgnoreRaycast = 2,
        Water = 4,
        UI = 5
    };
}