20190323のUnityに関する記事は9件です。

【Unity(C#)】シンプルな入れ替えパズルの入れ替えロジック

入れ替えパズルとは

こんな感じのやつです。
15-puzzle.png
wiki

物理的に移動可能なマス目が一つだけ用意されていて、ブロックを移動させることで
イラストを完成させたり、数字を並べ替えたり、道をつないだりするゲームです。

unity1week

文字通り一週間でMade With Unityなゲームを完成させようというデスマーチです。
愉快な企画です。

今回、私も初参加しました。
毎回テーマが設定されて、そのテーマに沿った(こじつけでも可)ゲームを作成します。
今回のテーマは「つながる」でした。

入れ替えパズルの上をUnityちゃんに歩いてもらってゴールを目指すというものを作りました。

【IQ150】

入れ替えロジック

完成当初の入れ替えロジックは複雑に考えすぎてとんでもない遠回りな実装方法を取り入れてました。
今回作ったゲームもぐちゃぐちゃな実装のまま放置しているのでプレイする度に何かしらバグります。

もっとシンプルにできないかと思って考えてみました。

最終的に思いついた(というか上司からヒントもらった)のは
透明なブロックを動かして衝突した色付きブロックと入れ替えるという方法です。
MoveTransparent.png
上のとてもわかり易い図の通りです。

透明なブロックの移動範囲(今回は移動できる回数にしました)を3×3で定めて、
そのエリア内を移動させます。

以下、コードです。

    int x_MoveCount = 1;
    int z_MoveCount = 1;
    Vector3 thisObjPosition;
    Vector3 saveThisObjPosition;

    void Update()
    {

        if (Input.anyKey == false)
        {
            return;
        }

        thisObjPosition = this.gameObject.transform.position;

        if (Input.GetKeyDown(KeyCode.LeftArrow) && x_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x -= 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount -= 1;
        }

        if (Input.GetKeyDown(KeyCode.RightArrow) && x_MoveCount < 1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x += 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount += 1;
        }

        if (Input.GetKeyDown(KeyCode.UpArrow) && z_MoveCount < 1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.z += 1;
            this.gameObject.transform.position = thisObjPosition;
            z_MoveCount += 1;
        }

        if (Input.GetKeyDown(KeyCode.DownArrow) && z_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.z -= 1;
            this.gameObject.transform.position = thisObjPosition;
            z_MoveCount -= 1;
        }
    }

    void OnTriggerStay(Collider other)
    {
        //衝突してほしいゲームオブジェクトでなければ抜ける
        if (other.gameObject.tag != "Area")
        {
            return;
        }
        other.transform.position = saveThisObjPosition;
    }

透明なブロックの移動前の位置を保存

左の矢印キーを押した場合を例に見ていきます。

 if (Input.GetKeyDown(KeyCode.LeftArrow) && x_MoveCount > -1)
        {
            saveThisObjPosition = this.gameObject.transform.position;
            thisObjPosition.x -= 1;
            this.gameObject.transform.position = thisObjPosition;
            x_MoveCount -= 1;
        }

saveThisObjPosition = this.gameObject.transform.position;
で移動前の位置を保存してからポジションを変更しています。

 void OnTriggerEnter(Collider other)
    {
        //衝突してほしいゲームオブジェクトでなければ抜ける
        if (other.gameObject.tag != "Area")
        {
            return;
        }
        other.transform.position = saveThisObjPosition;
    }

衝突したら、その移動前のポジションを
衝突したゲームオブジェクトに代入しています。
other.transform.position = saveThisObjPosition;

GifCapture-201903232133398707.gif

できました。
ただこれだと同時入力(同一フレーム内で2ボタンの入力を受け取る)が可能?なのか
めちゃくちゃ早く動かすとバグります。
どうやらUnityにはキーボード入力の受付を一時的に止めるみたいな機能はないっぽいです。
なのでInputManagerみたいなのを自分で作って同時入力できないようにする必要があります。

もう少し調べて見つかったらまた記事まとめます。
追記


 if (Input.anyKey == false)
 {
    isCheckInput = false;
    return;
 }

 if (isCheckInput == false)
 {
    if (Input.GetKeyDown(KeyCode.RightArrow))
    {     
    isCheckInput = true;
    }
 }

こんな感じでいけました!
教えてくださった方ありがとうございます!

同時押し禁止についてはこちら

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

【Unity × ARKit】塗り絵ARを開発したので、技術的な話をまとめてみる

前々から作りたかった塗り絵AR、ついにUnityで開発できました!初の自作Unityアプリになります。2ヶ月ほどかかりましたが、何とか完成できて感無量…!!
fishar.gif

塗り絵ARはすでに色々な企業でやられていますが、技術的な話は少なく、参考になるソースもあまりなくて、険しい道のりでした。。

ここでは自身の備忘録、また塗り絵ARに興味がある方に向けて、仕組み、実装のプロセスを記したいと思います!

塗り絵ARの仕組み

スクリーンショット 2019-03-23 9.47.46.png

塗り絵ARの概略をシンプルにまとめてみました。

  • openCVでカメラからスキャン
  • スキャンしたUVマップをモデルにラッピング
  • ARkitでモデルを召喚!

勘所やポイント、詰まりやすいところ

モデリングについて

モデルに貼りつけるテクスチャ(ここでは塗り絵の下地)の型をそれっぽくする(今回は魚)よう設定するために、自分でモデリングする必要があります。

ソフトはフリーのblenderを使用しました!

Blenderのお話をすると長くなるので、ここでは塗り絵ARに特化して、勘所だけ紹介させて頂きます。ポイントはUV展開です。Blender限定ですが、操作はこちらになります!
スクリーンショット_2019-03-23_10_31_40-2.png
スクリーンショット_2019-03-23_10_35_05.png

・参考リンク:【Blender】テンキーでの視点変更【カメラ,フロント,ライト,トップ等】

ここまできたら、好きなように動かしたり回転させたら、ひとまず完了です!!!
fishUV.gif

・参考リンク:【Blender】キーボードでオブジェクトを移動・回転・拡大・縮小する方法(1/2)

openCVについて

e4aae69a-dad1-4c97-b5cd-1f2d56bbed90.jpg

次にopenCV(画像処理)を使って、塗られた絵をスキャンする必要があります。
(正確にスキャンするならプリンター一択ですが、手軽さに欠けるので、今回はカメラからスキャンします)

Asset StoreにOpenCV for Unityというプラグインがありますが、有料です(号泣

わりとお高いので、お試しするには少し不安だったので、つい前に無料になったOpenCV plus Unityを使いました(2019/3/23現在)

情報はそこまで落ちていないですが、Demoを解読しながら使えば、使えなくはなかったです。

ただしプラグインをインポートする時、Unsafeコードをチェックする必要があります。こちらの記事を参考にすればOKですので、ご興味ある方はぜひお試しください!

File>BuildSettings>PlayerSettingsを開いてOtherSettingsの"Allow 'unsafe' Code"の部分にチェックを入れます。

引用:OpenCV plus Unityを使ってみる(セットアップ、画像処理100本ノック1~10編)

ARKitのカメラ情報について

そもそもOpenCVでスキャンするには、webカメラで写した画像を渡す必要があります。ここで、もしARKitを使うとなると、Unityが用意しているクラス”WebCamTexture”が使えません。

そのかわり、ARKit Pluginで用いたカメラ画像を使うことになります。

流れとして、ARKit Pluginが最終的に映し出すマテリアル”YUVMaterial”のテクスチャを取得すればOK。UnityARVideo.csにGetWebCamTexture関数を追加。

流れはこんな感じです!

  • マテリアルの内容をRenderTextureに書出
  • Texture2Dにコピー

全ソースは下記の通り。ぜひ参考にしてみてください

UnityARVideo.cs
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Rendering;

namespace UnityEngine.XR.iOS
{

    public class UnityARVideo : MonoBehaviour
    {
        public Material m_ClearMaterial;
        private CommandBuffer m_VideoCommandBuffer;
        private Texture2D _videoTextureY;
        private Texture2D _videoTextureCbCr;
        private Matrix4x4 _displayTransform;

        private bool bCommandBufferInitialized;

        public void Start()
        {
            UnityARSessionNativeInterface.ARFrameUpdatedEvent += UpdateFrame;
            bCommandBufferInitialized = false;
        }

        void UpdateFrame(UnityARCamera cam)
        {
            _displayTransform = new Matrix4x4();
            _displayTransform.SetColumn(0, cam.displayTransform.column0);
            _displayTransform.SetColumn(1, cam.displayTransform.column1);
            _displayTransform.SetColumn(2, cam.displayTransform.column2);
            _displayTransform.SetColumn(3, cam.displayTransform.column3);       
        }

        void InitializeCommandBuffer()
        {
            m_VideoCommandBuffer = new CommandBuffer(); 
            m_VideoCommandBuffer.Blit(null, BuiltinRenderTextureType.CurrentActive, m_ClearMaterial);
            GetComponent<Camera>().AddCommandBuffer(CameraEvent.BeforeForwardOpaque, m_VideoCommandBuffer);
            bCommandBufferInitialized = true;

        }

        void OnDestroy()
        {
            if (m_VideoCommandBuffer != null) {
                GetComponent<Camera>().RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, m_VideoCommandBuffer);
            }
            UnityARSessionNativeInterface.ARFrameUpdatedEvent -= UpdateFrame;
            bCommandBufferInitialized = false;
        }

#if !UNITY_EDITOR && UNITY_IOS

        public void OnPreRender()
        {
            ARTextureHandles handles = UnityARSessionNativeInterface.GetARSessionNativeInterface().GetARVideoTextureHandles();
            if (handles.IsNull())
            {
                return;
            }

            if (!bCommandBufferInitialized) {
                InitializeCommandBuffer ();
            }

            Resolution currentResolution = Screen.currentResolution;

            // Texture Y
            if (_videoTextureY == null) {
              _videoTextureY = Texture2D.CreateExternalTexture(currentResolution.width, currentResolution.height,
                  TextureFormat.R8, false, false, (System.IntPtr)handles.TextureY);
              _videoTextureY.filterMode = FilterMode.Bilinear;
              _videoTextureY.wrapMode = TextureWrapMode.Repeat;
              m_ClearMaterial.SetTexture("_textureY", _videoTextureY);
            }

            // Texture CbCr
            if (_videoTextureCbCr == null) {
              _videoTextureCbCr = Texture2D.CreateExternalTexture(currentResolution.width, currentResolution.height,
                  TextureFormat.RG16, false, false, (System.IntPtr)handles.TextureCbCr);
              _videoTextureCbCr.filterMode = FilterMode.Bilinear;
              _videoTextureCbCr.wrapMode = TextureWrapMode.Repeat;
              m_ClearMaterial.SetTexture("_textureCbCr", _videoTextureCbCr);
            }

            _videoTextureY.UpdateExternalTexture(handles.TextureY);
            _videoTextureCbCr.UpdateExternalTexture(handles.TextureCbCr);

            m_ClearMaterial.SetMatrix("_DisplayTransform", _displayTransform);
        }

#else

        public void SetYTexure(Texture2D YTex)
        {
            _videoTextureY = YTex;
        }

        public void SetUVTexure(Texture2D UVTex)
        {
            _videoTextureCbCr = UVTex;
        }

        public void OnPreRender()
        {

            if (!bCommandBufferInitialized) {
                InitializeCommandBuffer ();
            }

            m_ClearMaterial.SetTexture("_textureY", _videoTextureY);
            m_ClearMaterial.SetTexture("_textureCbCr", _videoTextureCbCr);

            m_ClearMaterial.SetMatrix("_DisplayTransform", _displayTransform);
        }

#endif
//追加した関数
        public Texture2D GetWebCamTexture()
        {
            RenderTexture _arTexture = new RenderTexture(Screen.width, Screen.height, 0);
            Texture2D _arTexture2D = new Texture2D(_arTexture.width, _arTexture.height, TextureFormat.ARGB32, false);

            if (_videoTextureY != null && _videoTextureCbCr != null)
            {
                Graphics.Blit(null, _arTexture, m_ClearMaterial);

                RenderTexture back = RenderTexture.active;
                RenderTexture.active = _arTexture;
                _arTexture2D.ReadPixels(new Rect(0, 0, _arTexture.width, _arTexture.height), 0, 0);
                _arTexture2D.Apply();
                RenderTexture.active = back;
            }

            return _arTexture2D;
        }

    }
}

・参考リンク
- UnityのARKit Pluginのカメラ映像を利用してなにかする
- Unity ARKit Plugin のカメラ映像を使って何かするには

Shaderについて

ここでは、魅せ方のお話です。細かい内容なので、ご興味ある方のみご覧いただければと思います。

ARでより現実と調和させるには、影が必要不可欠です。一方で、塗り絵ARはライティングせず、ハッキリと表示させたい…という我儘な考えがありました。

この場合、UnlitでShaderを作成し、UsePassを通せば完成できます。

UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"

公式ページでサンプルもありますので、是非チェックしてみてください!
・参考URL:公式:頂点シェーダーとフラグメントシェーダーの例

次に新たな問題があります。海面に影を映し出さなくてはなりません。この時、シェーダーを少し改造する必要があり、影を実装する方法は以下の手順を踏むことになります。

  1. Tagsの中に"LightMode"="ForwardBase"(※1)を入れる

  2. CGPROGRAMの下に#pragmaを追加。(#pragma multi_compile_fwdbase)

  3. CGPROGRAM内でファイルをincludeする。(#include "UnityCG.cginc"、#include "AutoLight.cginc")
    vertex shaderからfragment shaderに渡す構造体の定義の中にLIGHTING_COORDS(idx1, idx2)を入れる(※2)

  4. vertex shader内でTRANSFER_VERTEX_TO_FRAGMENT(o);(マクロ)を記述する。
    (o)は出力される構造体名

  5. fragment shader内でLIGHT_ATTENUATION(i)マクロを使って影の度合いを得る
    (i)は入力される構造体名

  6. fragment shader内でLIGHT_ATTENUATION(i)マクロを使って影の度合いを得る
    (i)は入力される構造体名

引用:[Unity] AssetStoreのファーシェーダをupdateしたので分かったことを書いてみる

こちら参考に作ったShader(サンプル)がこちらです!
スクリーンショット 2019-03-23 12.08.18.png

ToonSeaShader.shader
Shader "Unlit/ToonSeaShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque"}
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                LIGHTING_COORDS(0,1)
            };

            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                TRANSFER_VERTEX_TO_FRAGMENT(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = _Color;
                float attenuation = LIGHT_ATTENUATION(i);
                return col* attenuation;
            }
            ENDCG
        }
    }
}

今回、手順1にある"LightMode"="ForwardBase"は必要なかった(ライティングに関わらず、あえて単調にしたかった)ので、入れていません。

これが大まかな影付きのでShader作成の処理の流れになるので、フラグメントシェーダーの内部処理を変えれば、影がつけれるようになります!

ちなみに単純にただ影だけ出したい場合は、ARKit Pluginに用意されているMobileARShadowシェーダを使えば解決できます!
・参考リンク:Unityで始めるARKit入門 影の表示編

最後に

塗り絵ARを作ってみて、Unityだけではなくモデリングやシェーダー、OpenCVなど総合的にスキルアップできました!(嬉しい)

折角ここまで頑張ったので、もっとブラッシュアップしたり種類を増やして、ストアに出そうかな…なんて思っています。

これから塗り絵ARを作ろう!と思っている方の参考にもなれば幸いです。
ここまでご覧いただき、ありがとうございましたm(_ _)m

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

【Unity】実機と同じシーケンスで再生開始するボタンをプレイボタンの横に追加【エディタ拡張】

動作

スクリーンショット 2019-03-23 16.12.11.png
3月-23-2019 16-12-41.gif

InitializeシーンがすぐTitleシーンをロードしているのでわかりづらいが、[0]をクリックしたら現在アクティブなGameシーンからではなくInitializeシーンから再生が開始されている。

上記gifだと最初からHierarchyにInitializeシーンが置いてあるけど、なくても大丈夫。

モチベーション

普段は Zenject でDIしているので各シーンを直接再生しながら開発しているけど、たまに実機と同じシーケンスで再生したいことがあった。

それまでは

  • Hierarchyに"Scenes In Build"で0にしてあるInitializeシーンを配置&Unload状態に
  • 必要なときにInitializeシーンだけアクティブにして再生

をやってたけど何度もやってて切り替えが面倒になったので、押しやすい場所にやりたいことを一発でやれるボタンを置こうと思った。

導入方法

再生ボタンの両サイドにボタンを配置できるエディタ拡張「unity-toolbar-extender」を入れて、以下のコードを適当なEditor/以下に配置する。

PlayFromScene0Button.cs
#if UNITY_EDITOR
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace UnityToolbarExtender
{
    internal static class ToolbarStyles
    {
        public static readonly GUIStyle CommandButtonStyle;

        static ToolbarStyles()
        {
            CommandButtonStyle = new GUIStyle("Command")
            {
                fontSize = 16,
                alignment = TextAnchor.MiddleCenter,
                imagePosition = ImagePosition.ImageAbove,
            };
        }
    }

    [InitializeOnLoad]
    public class PlayFromScene0Button
    {
        private const string Scene0HierarchyIndexKey = "Scene0HierarchyIndex";
        private const string ShouldResetPlayModeStartSceneKey = "ShouldResetPlayModeStartScene";
        private const string ButtonLabel = "0";
        private const string ButtonTooltip = "Play from first scene of \"Scenes In Build\"";

        static PlayFromScene0Button()
        {
            ToolbarExtender.LeftToolbarGUI.Add(OnToolbarGUI);
            EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
        }

        private static string GetConfigValue(string name)
        {
            return EditorUserSettings.GetConfigValue(name);
        }

        private static void SetConfigValue(string name, string value)
        {
            EditorUserSettings.SetConfigValue(name, value);
        }

        private static void ResetConfigValue(string name)
        {
            EditorUserSettings.SetConfigValue(name, null);
        }

        private static void OnToolbarGUI()
        {
            if (Validate() == false) return;

            GUILayout.FlexibleSpace();

            // ボタン表示
            if (GUILayout.Button(new GUIContent(ButtonLabel, ButtonTooltip), ToolbarStyles.CommandButtonStyle))
            {
                PlayFromScene0();
            }
        }

        [MenuItem("Edit/Play from scene 0 %@", true)]
        private static bool Validate()
        {
            // "Scenes In Build"にシーンが登録されていなければinvalid
            if (EditorBuildSettings.scenes.Length == 0) return false;

            // プレイモード時はinvalid
            if (EditorApplication.isPlayingOrWillChangePlaymode) return false;

            return true;
        }

        [MenuItem("Edit/Play from scene 0 %@", false, 149)]
        private static void PlayFromScene0()
        {
            // シーンを保存するかどうか尋ねる
            if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
            {
                var scene0Path = EditorBuildSettings.scenes[0].path;
                var scene0Asset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scene0Path);
                var sceneSetups = EditorSceneManager.GetSceneManagerSetup();
                var scene0Setup = sceneSetups.FirstOrDefault(s => s.path == scene0Path);

                // シーン0がHierarchyに存在&ロードされていない場合は閉じる(その状態でHierarchyに存在しているとうまく動かない)
                if (scene0Setup != null && scene0Setup.isLoaded == false)
                {
                    var scene = SceneManager.GetSceneByPath(scene0Path);
                    EditorSceneManager.CloseScene(scene, true);
                    var scene0HierarchyIndex = sceneSetups.ToList().IndexOf(scene0Setup).ToString();

                    // シーン0をあとでHierarchyに復元するためにインデックスを保存
                    SetConfigValue(Scene0HierarchyIndexKey, scene0HierarchyIndex);
                }

                SetConfigValue(ShouldResetPlayModeStartSceneKey, bool.TrueString);
                EditorSceneManager.playModeStartScene = scene0Asset;
                EditorApplication.isPlaying = true;
            }
        }

        private static void OnPlayModeStateChanged(PlayModeStateChange state)
        {
            if (state == PlayModeStateChange.EnteredPlayMode && GetConfigValue(ShouldResetPlayModeStartSceneKey) == bool.TrueString)
            {
                ResetConfigValue(ShouldResetPlayModeStartSceneKey);
                EditorSceneManager.playModeStartScene = null;
            }

            if (state == PlayModeStateChange.EnteredEditMode)
            {
                int scene0HierarchyIndex;

                // 必要であればシーン0をHierarchyに復元(特別な場合を除き自動で復元される)
                if (int.TryParse(GetConfigValue(Scene0HierarchyIndexKey), out scene0HierarchyIndex) && -1 < scene0HierarchyIndex)
                {
                    ResetConfigValue(Scene0HierarchyIndexKey);

                    var scene0Path = EditorBuildSettings.scenes[0].path;
                    var scene0 = EditorSceneManager.OpenScene(scene0Path, OpenSceneMode.AdditiveWithoutLoading);
                    var dstScenePath = EditorSceneManager.GetSceneManagerSetup()[scene0HierarchyIndex].path;
                    var dstScene = SceneManager.GetSceneByPath(dstScenePath);

                    EditorSceneManager.MoveSceneBefore(scene0, dstScene);
                }
            }
        }
    }
}
#endif

コメントにちょっと書いてるけど"Scenes In Build"で0のシーンがUnload状態でHierarchyに置いてある場合だけ挙動が変だったので、その場合は一旦シーンをHierarchyから消して後で復元している。

仕様

  • 動作確認済み
    • macOS High Sierra(10.13.6)
    • Unity 2018.2.20f1
    • シーン0が
      • Hierarchyにない
      • Hierarchyにある
        • ロード状態
        • アンロード状態
    • 他のシーンを(複数)開いているとき
  • ショートカットキー:⌘@
  • "Scenes In Build"にシーンが登録されていなければボタン非表示
  • 再生中もボタン非表示
  • 再生前に必要であればシーンを保存するか確認
    • "Don't Save"なら保存せずに再生
    • "Cancel"なら再生しない
  • 再生後に再生前のHierarchy復元

コガネブログさんによると

自分が「unity-toolbar-extender」を試してみたところ、
MacBook Pro の Unity では正常に動作しましたが、
Windows 7 や Windows 10 の Unity ではボタンが表示されない現象に遭遇しました

もしかしたら「unity-toolbar-extender」は Windows では使用できないかもしれません

とのことなのでWindowsなどでは動かないかも。

所感

Unityエディターのデフォルト機能としてほしいくらいには便利。

参考

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

【備忘録】Unityでクイズゲームを作成するためにCSV取り込みを行ってみた

完全なる初心者の備忘録です。
またほぼこの方のページのコピペです。
http://ikedih.blog69.fc2.com/blog-entry-241.html

【事の経緯】
1.だいぶ前にAndroidStudioでクイズゲームを作成した。
  その際参考にしたサイト→https://terakoya.site/course/android-quiz/
  完成したゲーム→https://play.google.com/store/apps/details?id=shohei.yamamoto.quizcollege

2.今度はUnityでクイズゲームを作成したい。問題文はCSV管理したい。
3.UnityにCSV取り込むってどうやってやるのか、試行錯誤。
4.いろいろ当たった結果、初心者の自分は以下のやり方が一番うまくいったのでメモ。無事コンソールにCSVの行数、項数、データの中身が表示された。←いまここ
5.さて、いつになればクイズゲームは完成するのか…。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;    // 追記

public class CSVRerder: MonoBehaviour {
    public TextAsset csvFile;        // GUIでcsvファイルを割当
    List<string[]> csvDatas = new List<string[]>(); //ここは参考ブログとは違う

    // Use this for initialization
    void Start() {

        // 格納
        string[] lines = csvFile.text.Replace("\r\n", "\n").Split("\n"[0]);
        foreach (var line in lines) {
            if (line == "") { continue; }
            csvDatas.Add(line.Split(','));    // string[]を追加している
        }

        // 書き出し
        Debug.Log(csvDatas.Count);         // 行数
        Debug.Log(csvDatas[0].Length);       // 項目数
        Debug.Log(csvDatas[1][2]);        // 2行目3列目
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity超簡単!チュートリアルの作り方

やりたいことのイメージ

スクリーンショット 2019-03-23 16.13.16.png

実装一覧
・テキスト送り
・テキスト送り後クリックアイコンを表示
・クリックアイコンの点滅
・ボタンを押せばクリックアイコンが移動

階層

スクリーンショット 2019-03-23 16.15.17.png

テキスト送り

・Panelを作成
・PanelにTextを作成

TextController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TextController : MonoBehaviour {

    string[] sentences; // 文章を格納する
    [SerializeField] Text uiText;   // uiTextへの参照

    [SerializeField][Range(0.001f, 0.3f)]
    float intervalForCharDisplay = 0.05f;   // 1文字の表示にかける時間

    private int currentSentenceNum = 0; //現在表示している文章番号
    private string currentSentence = string.Empty;  // 現在の文字列
    private float timeUntilDisplay = 0;     // 表示にかかる時間
    private float timeBeganDisplay = 1;         // 文字列の表示を開始した時間
    private int lastUpdateCharCount = -1;       // 表示中の文字数

    public bool finished;
    void Start () {
        sentences = new string[]{
            "みなさんこんにちは",
            "スタジオしまづの嶋津です",
        };
        SetNextSentence ();
    }


    public void TextUpdate(bool _IsPush) {
        // 文章の表示完了 / 未完了
        if (IsDisplayComplete ()) {
            //最後の文章ではない & ボタンが押された
            if (currentSentenceNum < sentences.Length && _IsPush) {
                SetNextSentence ();
            }
            else if(currentSentenceNum >= sentences.Length){
                finished = true;
                currentSentenceNum = 0;
            }
        } else {
            if (_IsPush) {
                timeUntilDisplay = 0; //※1
            }
        }

        //表示される文字数を計算
        int displayCharCount = (int)(Mathf.Clamp01((Time.time - timeBeganDisplay) / timeUntilDisplay) * currentSentence.Length);
        //表示される文字数が表示している文字数と違う
        if (displayCharCount != lastUpdateCharCount) {
            uiText.text = currentSentence.Substring (0, displayCharCount);
            //表示している文字数の更新
            lastUpdateCharCount = displayCharCount;
        }
    }

    // 次の文章をセットする
    void SetNextSentence(){
        currentSentence = sentences [currentSentenceNum];
        timeUntilDisplay = currentSentence.Length * intervalForCharDisplay;
        timeBeganDisplay = Time.time;
        currentSentenceNum++;
        lastUpdateCharCount = 0;
    }

    bool IsDisplayComplete(){
        return Time.time > timeBeganDisplay + timeUntilDisplay; //※2
    }
}

以下のようにUiTextに表示したいTextを設定
スクリーンショット 2019-03-23 16.19.52.png
・Textにボタンを設定

クリックアイコン

・ClickImageを作成

ClickController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ClickController : MonoBehaviour {
    Image clickImage;
    int phase;
    List<Vector3> position = new List<Vector3>(){
        new Vector3(-330,147,0),
        new Vector3(0,147,0),
        new Vector3(330,147,0),
    };

    // Use this for initialization
    void Start () {
        phase = -1;
        clickImage = GetComponent<Image>();
        clickImage.enabled = false;
    }
    void Update(){
        clickImage.color = new Color(1,1,1, Mathf.PingPong(Time.time,1));
    }
    void ViewClickImage(int _phase){
        if(_phase>=position.Count){
            _phase = -1;
            clickImage.enabled = false;
            return;
        }
        this.transform.localPosition = position[_phase];
        clickImage.enabled = true;      
    }

    public void ViewClickIcon(){
        phase = 0;
        ViewClickImage(phase);
    }
    public void PushButton(int _number){
        if(_number != phase){
            return;
        }
        phase++;
        ViewClickImage(phase);
    }
}

・ボタンを3つ作成
・それぞれのボタンのOnClickに設定

スクリーンショット 2019-03-23 13.14.18.png

GameMainSystem

・テキスト送りの開始を管理
・クリックアイコンの表示タイミングを管理

GameMainSystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class GameMainSystem : MonoBehaviour {
    [SerializeField]
    TextController textController;
    bool IsTextPush = false;
    [SerializeField]
    ClickController clickController;
    void Update(){
        if(textController.finished){
            textController.finished = false;
            clickController.ViewClickIcon();
            return;
        }
        textController.TextUpdate(IsTextPush);
        IsTextPush = false;
    }
    public void PushText(){
        IsTextPush = true;
    }
}

・それぞれ設定
スクリーンショット 2019-03-23 13.15.32.png
・Textに設定したボタンでPushTextが発動するように設定

おまけ

チュートリアルの作り方は色々あると思います。どれが正解かはわかりませんが、自分はコマンドクラフトというゲームでこれらのテクニックを使いました。その結果前作にあった「チュートリアルがひどい」というコメントは0件になりました。
何かの役に立てればと思います^^

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

UI.Button をスクリプトで複製した時に、onClick イベントも複製される時・されない時

スクリプトで複製したボタン
このボタンのコールバックを削除したい。リセットしたい。全消ししたい。
ときに、イベントも複製されるんだっけ...されないんだっけ...と思って。

  • 環境 2018.3.8f1

1. スクリプトで、AddListener() した場合。(非永続的)

onClick イベントは複製されない。引き継がれない

2. インスペクタ上から登録した場合。(永続的)

onClick イベントも複製される。引き継がれる


削除する。(削除はできないから呼ばれないようにする)

int persistentEventCount = btn.onClick.GetPersistentEventCount();
for (int i = 0; i < persistentEventCount; i++)
    btn.onClick.SetPersistentListenerState(i, UnityEventCallState.Off);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UI.Image にペイントする。線を手書きする。

描けた。
スクリーンショット

参考
Find x,y cordinates of click on UI.Image new gui system - Unity Answers
Unityでお絵描きしてみる
背景が透明なTexture2Dを作る
Enumerable.Repeat(TResult, Int32) Method (System.Linq) | Microsoft Docs
Sprite.Create - Unity スクリプトリファレンス


なんかきっとどこかで必要になったら使おう。

環境

  • バージョン 2018.3.8f1

Canvasは、 Screen Space – Overlay
Imageのピボットは、(0.5、0.5)
ImageのGameObjectにアタッチする。

using UnityEngine;
using System;
using System.Linq;
using UnityEngine.UI;
using UnityEngine.EventSystems;

/// <summary>
/// お絵描き
/// </summary>
public class Painter : MonoBehaviour
{
    Texture2D texture;
    Vector3 beforeMousePos;

    Color bgColor = Color.white;
    Color lineColor = Color.black;

    void Start()
    {
        var img = GetComponent<Image>();
        var rt = GetComponent<RectTransform>();
        var width = (int)rt.rect.width;
        var height = (int)rt.rect.height;
        texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
        img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);

        //背景が透明なTexture2Dを作る
        //http://d.hatena.ne.jp/shinriyo/20140520/p2
        Color32[] texColors = Enumerable.Repeat<Color32>(bgColor, width * height).ToArray();
        texture.SetPixels32(texColors);
        texture.Apply();
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            beforeMousePos = GetPosition();
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 v = GetPosition();
            LineTo(beforeMousePos, v, lineColor);
            beforeMousePos = v;
            texture.Apply();
        }
    }

    /// <summary>
    /// UIのクリック座標のx、y座標を求める - 新しいguiシステムの画像 - Unity Answers
    /// https://answers.unity.com/questions/892333/find-xy-cordinates-of-click-on-uiimage-new-gui-sys.html
    /// </summary>
    public Vector3 GetPosition()
    {
        var dat = new PointerEventData(EventSystem.current);
        dat.position = Input.mousePosition;

        var rect1 = GetComponent<RectTransform>();
        var pos1 = dat.position;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rect1, pos1,
            null, out Vector2 localCursor))
            return localCursor;

        int xpos = (int)(localCursor.x);
        int ypos = (int)(localCursor.y);

        if (xpos < 0) xpos = xpos + (int)rect1.rect.width / 2;
        else xpos += (int)rect1.rect.width / 2;

        if (ypos > 0) ypos = ypos + (int)rect1.rect.height / 2;
        else ypos += (int)rect1.rect.height / 2;

        Debug.Log("Correct Cursor Pos: " + xpos + " " + ypos);
        return new Vector3(xpos, ypos, 0);
    }

    /// <summary>
    /// Unityでお絵描きしてみる
    /// http://tech.gmo-media.jp/post/56101930112/draw-a-picture-with-unity
    /// </summary>
    public void LineTo(Vector3 start, Vector3 end, Color color)
    {
        float x = start.x, y = start.y;
        // color of pixels
        Color[] wcolor = { color };

        if (Mathf.Abs(start.x - end.x) > Mathf.Abs(start.y - end.y))
        {
            float dy = Math.Abs(end.x - start.x) < float.Epsilon ? 0 : (end.y - start.y) / (end.x - start.x);
            float dx = start.x < end.x ? 1 : -1;
            //draw line loop
            while (x > 0 && x < texture.width && y > 0 && y < texture.height)
            {
                try
                {
                    texture.SetPixels((int)x, (int)y, 1, 1, wcolor);
                    x += dx;
                    y += dx * dy;
                    if (start.x < end.x && x > end.x ||
                        start.x > end.x && x < end.x)
                    {
                        break;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    break;
                }
            }
        }
        else if (Mathf.Abs(start.x - end.x) < Mathf.Abs(start.y - end.y))
        {
            float dx = Math.Abs(start.y - end.y) < float.Epsilon ? 0 : (end.x - start.x) / (end.y - start.y);
            float dy = start.y < end.y ? 1 : -1;
            while (x > 0 && x < texture.width && y > 0 && y < texture.height)
            {
                try
                {
                    texture.SetPixels((int)x, (int)y, 1, 1, wcolor);
                    x += dx * dy;
                    y += dy;
                    if (start.y < end.y && y > end.y ||
                        start.y > end.y && y < end.y)
                    {
                        break;
                    }
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    break;
                }
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]AssetDatabaseにあるアセットの型名を全て取得する

実装

AssetFinder.cs
//Asset型の型情報をすべて取得
public Type[] GetAllAssetClassTypeInfo()
{
    return AssetDatabase.FindAssets("t:object").Select(guid => new
    {
        var assetPath = AssetDatabase.GUIDToAssetPath(guid);
        return AssetDatabase.GetMainAssetTypeAtPath(assetPath);
    }).Distinct();
}

解説

UnityのアセットはUnityEngine.Object型を継承しているはずなので、FindAssetsで型検索でobjectを指定してguidを取得。
取得したguidを元にアセットクラスのType型に変換できるGetMainAssetTypeAtPathで型情報を取得。
このままだと重複するのでDistinctで型情報が重複しないようにする。

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

[Unity] エラーが出たらEditorWindowで作った画面を開くようにする

ErrorReceivedWindow.cs
public class ErrorReceivedWindow : EditorWindow
{
   //1回だけ呼び出したいので画面を開いたときに呼び出す
   [InitializeOnLoadMethod]
   private static void _onInit()
   {
        //ログが更新されたら呼び出すようにEイベントを登録
        Application.logMessageReceived += ApplicationOnLogMessageReceived;
   }

   private static void ApplicationOnLogMessageReceived(string condition, string stacktrace, LogType type)
   {
       //エラーが出たら画面を開く
       if (type == LogType.Exception)
       {
           GetWindow<ErrorReceivedWindow>();
       }
   }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む