ゲーム開発サークル「丸ダイス」の開発ブログです! サークル公式サイトはこちら

【新作】ピタゴラスの永久機関【C92】

コミケット92で新作出します!

サークル「丸ダイス」としてコミケット92に出展します!スペースは「1日目東し33b」です!

というわけでおしながきです! …1コしかありませんけどね!

ピタゴラス永久機関(新作/完成)1,000円

www.youtube.com

「しかけを使ってナゾを解く "永久機関"のピタゴラパズル」

コソコソと開発を進めていたギミックパズルピタゴラス永久機関の完成版となります!

様々なしかけを使って"永久機関"を作っていくパズルゲーム。

塔を登った先には何が待つのか? 40以上のステージで遊びごたえはたっぷりです!

どうしても解けない時はヒントやスキップがあるので、 パズルが苦手なひとでも安心ですよ!多分!

※動作環境

  • Windows 7/8/10 (C92版は64bit OSのみ。それ以降の頒布では32bit OSに対応する予定です)
  • 解像度 800x600 以上
  • インストールには CD/DVDドライブが必要です

【Unity】NCMB3.0.0 インポート時にiOSビルドのリンカエラー

前回(【Unity】NCMB インポート時のAndroidビルドエラーを読み解く - 丸ダイスの卓上開発日誌Android版でのビルドエラーを解決したら、今度はiOSのCloud Buildでビルドエラーが。

※結局、2.2.0か3.0.0かはエラーと無関係だったので、新しい方を使っています。

原因

ログを見てみると、Unityのビルドは通って、xcodeのビルドの一番最後にこんな出力。

       [xcode] Undefined symbols for architecture armv7:
       [xcode]   "_OBJC_CLASS_$_UNUserNotificationCenter", referenced from:
       [xcode]       objc-class-ref in NCMBAppControllerPushAdditions.o
       [xcode] ld: symbol(s) not found for architecture armv7
       [xcode] clang: error: linker command failed with exit code 1 (use -v to see invocation)

obj-cは書いたことないですが、「NCMBAppControllerPushAdditions.oの中で参照されてる_UNUserNotificationCenterってシンボルがUndefinedやぞコラ!」っつってるんだから、リンカエラーでしょう多分。

まさにNCMBの.oで参照されてるようなので、今起きたエラーと見てよさそうですね。

解決方法

原因は読み解けたので、_UNUserNotificationCenterが含まれてるライブラリがどっかにあるじゃろうと踏んでググったらあっさりヒット。

stackoverflow.com

UserNotifications.framework を追加すればいけるぜって言ってますね。欲しいUndefined symbolsが含まれてそうな名前です。

Macを起動するまでもなくiOS のポストプロセッサにframeworkを追記してcommit。そのままCloud Build復活!

緑のチェックマークが並ぶと気持ちいいですね!

..
    [PostProcessBuildAttribute(0)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string pathToBuiltProject)
    {
..
        PBXProject proj = new PBXProject();
..
        // Add framework
        proj.AddFrameworkToProject(target, "UserNotifications.framework", false);
..
    }
}

※ポストプロセッサはこのあたりを参考に前から使ってるものです。UnityでXcodeの設定を自動化する方法まとめ - スマゲ

【Unity】NCMB インポート時のAndroidビルドエラーを読み解く

「NCMBのインポートの仕方」という記事ではないです(公式にやり方載ってますし…)。「よく分かんないエラーが出た時に、ググって出たAnswerをそのまま試すより、ログをしっかり読み込んだ方が早く解決出来たよ!」という記事です。

NCMBのインポート

リリースページ(Releases · NIFTYCloud-mbaas/ncmb_unity · GitHub)から、 v2.2.0 を選んでzipを解凍。

中身の.unitypackageをダブルクリック。表示されたインポートダイアログで「Import」をクリック。うんうん。普通ですね。

ここで、「Assets/Plugins/Android」以下に.jar, .aar ファイルがたくさん放り込まれているのが分かります。 「Unity Plugins フォルダ」なんかでググると、どうやらプラットフォームごとのネイティブプラグインを入れるフォルダのようです。つまり、NCMBではコア部分の実装がネイティブプラグインで提供されているわけですね。

Unity Editor 上では何事もなくコンパイルが通ります。

ビルド失敗!

ネイティブプラグインは、実際にそのプラットフォームでビルドするまで動作を検証出来ません。Android版をビルドしてみると、見事にビルドエラー。

Unity上での表示は「Unable to convert classes into dex format. See the Console for details.」Consoleを見ろって言ってますね。どれどれ。

Consoleを見てみる

CommandInvokationFailure: Unable to convert classes into dex format.
...

この後数千行続きます。長っ!とひるむところですが、ここで”ググりません”。少なくともUnityは「ビルドが失敗した理由はコレだよ!」と言っているのですから、付き合ってあげようじゃありませんか。

エラー出力の構造を推測してみる

先ほどのConsoleの出力の最初数行

CommandInvokationFailure: Unable to convert classes into dex format.
C:/Program Files/Java/jdk1.8.0_111\bin\java.exe -Xmx2048M -Dcom.android.sdkmanager.toolsdir="C:/Program Files (x86)/Android/android-sdk\tools" -Dfile.encoding=UTF8 -jar "C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer/Tools\sdktools.jar" -

stderr[
Uncaught translation error: java.lang.IllegalArgumentException: already added: Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat;
..

1,2行目を見ると、文章になっている1行目はエラーの概要、2行目はオプション付きの長々としたコマンドのようです。

その後に

stderr[
Uncaught translation error: java.lang.IllegalArgumentException: already added: Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat;
Uncaught translation error: java.lang.IllegalArgumentException: already added: Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoIcsImpl;
..

と続くところを見ると、「2行目を実行した時に、1行目のエラーが出て、エラーの詳細はstderr の中身の通りだよ」ということのようです。

あれ?じゃあstderrの後は?と見てみると

]
stdout[
processing archive C:\home\(Unityプロジェクトのパス)\Temp\StagingArea\android-libraries\AmazonAppStore\libs\.\classes.jar...
processing com/unity/purchasing/amazon/AmazonPurchasing.class...
processing com/unity/purchasing/amazon/AmazonPurchasing$1.class...
..

とあります。あとは最後まで全部stdoutの中身のようです。processing の後はクラス名のようですから、なるほど、2行目のコマンドでビルドした時の標準出力がstdoutの中に出てるわけですね。 つまりエラー出力の構文は

エラー概要
実行したjavaのビルドコマンド
stderr[ エラー詳細 ]
stdout[ javaのビルドログ ]

ということのようです。

java.lang.IllegalArgumentException: already added を解決しよう

今回のエラーは 「java.lang.IllegalArgumentException: already added」でした。中のクラスはもちろん書いた覚えはないですし、クラス名をProjectビューで検索してもそんな.jarは表示されません。 .jarや.aarの中には複数のクラスがまとまって提供されています。

でも、上のようにエラー出力の構文が分かると、(Unityではなく)javaのビルドした時のビルドログにクラスのビルド順序が記載されています。「already added」 なので、同じクラスを2コ追加しちゃってるのかな?とアタリをつけてログ全体で「android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat」を検索してみると…

processing android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.class

stdout[]の中にこの行が2つありました。フルネームが全く同じクラス名が2つ…なるほど、これではビルドできそうにありませんね…。 どうやら、今回のインポートで同じクラス定義をしている.jar, .aarが2つになってしまったようなので、それを取り除けば良さそうです。

ここで、さきほどの検索結果をもう一度見てみると、

processing archive C:\home\(Unityプロジェクトのパス)\Temp\StagingArea\android-libraries\support-v4-24.0.0\libs\.\classes.jar...
processing android/support/v4/BuildConfig.class...
processing android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.class...
..

ヒットしたクラス定義のすぐ上に、クラス定義ではなく.jarのファイルパスのようなものがあります。stdout[]全体を改めて眺めて見ると

processing archive (ファイルパス.jar)
processing (クラスフルネーム)
processing (クラスフルネーム)
..
processing archive (ファイルパス.jar)
processing (クラスフルネーム)
processing (クラスフルネーム)
..

が何度も繰り返されているようです。

なるほど!つまり(ファイルパス.jar)のアーカイブファイルに定義されているたくさんの(クラスフルネーム)を次々にビルドしているわけですね!

Tempの中に.jarを追加した覚えはないですが、.jarの固有名っぽい 「support-v4-24.0.0」には覚えがあります。UnityのProjectビューで検索してみると、見事ヒット! もう一つの方も、「processing (クラスフルネーム)」の上をたどって、含まれているarchiveは「android-support-v4.jar」だと分かりました。

NCMB.unitypackageをもう一度実行してみると、どうやらandroid-support-v4.jar は今回NCMBのインポート時に追加されたファイルのようです。

とりあえず新しい方のandroid-support-v4.jarを消してビルドしてみると、見事成功!※正確には、別の似たエラーが出たのでそちらも同じように解決しています。

めでたしめでたし。

ログ読みのススメ

こうやってログの中身をしっかり追ってみると、実はエラーの理由は人の目で追える形でしっかりと書かれていることが分かりました。

「よく分からんエラー」に遭遇した時は、エラー文を検索窓に突っ込んで、「こうしたら直りました」を実行するだけで直ることもあります。

でも、問題が起きた時に答えだけを求めるやり方より、一から仕組みを理解するようにした方が、似たケースが起きた時にすぐに対処出来て自分のプロジェクトをより自在に扱えるようになるのかなぁと、今更ながらに思った次第でした。

実際、※の別エラーは本当にあっという間に解決出来ました。

ログを読みましょう!Qiitaには嘘を書けますが、ログは嘘を付きません!

Skyboxを一発でテクスチャセットとして出力するPhotoshopプラグイン

Skyboxを一発でテクスチャセットとして出力するPhotoshopプラグイン

作りました

f:id:marudice:20170328025222p:plain こんな感じの画像(4096x3072px) から f:id:marudice:20170328025336p:plain こんな感じにSkyboxで必要なテクスチャをsuffix付きで同じフォルダに出力します。

Photoshopプラグインを入れる方法は君自身の手で見つけ出してくれ!

// OutputSkyboxTexes.jsx
var size = 1024;

function saveOne(doc, suffix, x_idx, y_idx)
{
  var clone = doc.duplicate();
  clone.crop([x_idx*size, y_idx*size, (x_idx+1)*size, (y_idx+1)*size])
  docName = doc.fullName.fsName.toString();

  fileName = docName.substr (0, docName.length - 4) + "_" + suffix;
  pngSaveOpt = new PNGSaveOptions();
  pngSaveOpt.interlaced = false;

  saveFileObj = new File(fileName);
  //保存する
  try {
      clone.saveAs(saveFileObj, pngSaveOpt, true, Extension.LOWERCASE );
      clone.close(SaveOptions.DONOTSAVECHANGES);
  } catch(msg) {
      alert(msg + " 保存できませんでした");
  }
}

function main()
{
  var doc = activeDocument;
    try {
        //ファイル名を取得
        docName = doc.fullName.fsName.toString();
    } catch(msg) {
        alert(msg + " 一度どこかにセーブしてから実行して下さい");
        return;
    }


  saveOne(doc, "up", 2, 0);
  saveOne(doc, "left", 3, 1);
  saveOne(doc, "front", 2, 1);
  saveOne(doc, "back", 0, 1);
  saveOne(doc, "right", 1, 1);
  saveOne(doc, "down", 2, 2);
}

//本体実行
main();

ココが便利!

フィルターとか色調補正とか、Skyboxを調整してはUnityで確認して…の作業が格段に早くなります! いっそAssetフォルダ内にpsdごと放り込んでもいいカモ。

More

↑は、1枚のSkybox用Textureが1024x1024ですが、sizeをいじれば512x512とかも出来ます。 saveOne(doc, “up”, 2, 0); … の繰り返しのとこを変えれば、サイコロ展開図の位置関係が多少違っても対応出来ます。

おわり!

【新作リリース!】Calc Blocks

記事書くのが遅くなってしまいましたが、新作アプリ「Calc Blocks」をリリースしました! >∀<

iOS: goo.gl

Android: goo.gl

頭使う系パズルが好きな人にオススメです! 頭使うのがキライな人にはオススメ出来ません!(正直)

ゲーマーよ、頭を使おう!

f:id:marudice:20170101151505j:plain:w200

画面上のブロックをドラッグすると、それがそのまま計算されます!で…

f:id:marudice:20170101151525p:plain:w200  f:id:marudice:20170101151538p:plain:w200

ひたすら目標数を作っていきます! 左右のバーがなくなったらゲームオーバー!急げ急げ!

数字は目標数を作っていると大きくなります。まずは10を目指して頑張れ!

死ぬまでにどこまで到達出来るか!

レコード記録&ネットランキングがあるので、自分の計算力を試してみて下さい!

f:id:marudice:20170101151558p:plain:w200  f:id:marudice:20170101153158p:plain:w200

通勤通学のヒマつぶしなんかにいいかもしれないですね~。

あ、課金すると広告が消えて&1コ機能が追加されます。 別にしなくても遊べますけどネ。

【Unity】シンプルかつ軽量なオブジェクトプールの実装

UnityのInstantiateが遅い。皆さん言ってますね。実際、モバイルとかでエフェクトや弾をガンガン出していると目に見えて遅くなっていきます。 というわけで、「使い終わったオブジェクトは貯めておいて(あるいは最初にまとめて生成して)、必要になったら取り出す」という「オブジェクトプール」の方法が良く取られます。

実装の方法はいろいろあるようですが、軽く調べたところ今回実装した方法はスマートかつ無駄なオーバーヘッドも少ない感じがしたので、上げておこうかなという次第です。

Githubに公開しておきました(被使用・使用側の実装、サンプルシーン) github.com

実装

using UnityEngine;
using System.Collections.Generic;

public abstract class PoolObj<T> : MonoBehaviour {
    private static GameObject mOriginal;
    private static Stack<T> mObjPool = new Stack<T>();

    public static void SetOriginal(GameObject origin) {
        mOriginal = origin;
    }

    public static T Create()
    {
        T obj;
        if (mObjPool.Count > 0)
        {
            obj = Pop();
        }
        else
        {
            var go = Instantiate<GameObject>(mOriginal);
            obj = go.GetComponent<T>();
        }
        (obj as PoolObj<T>).Init();
        return obj;
    }

    private static T Pop()
    {
        var ret = mObjPool.Pop();
        return ret;
    }

    public static void Pool(T obj)
    {
        (obj as PoolObj<T>).Sleep();
        mObjPool.Push(obj);
    }
    
    public static void Clear()
    {
        mObjPool.Clear();
    }

    public abstract void Init();
    public abstract void Sleep();
}

これだけです。こいつをPoolObj.csなどとしてプロジェクトに放り込めばスクリプト単体で動作します。

使用時の最小コード - Hoge オブジェクトをプールする

  • プールされるオブジェクトは Init(); Sleep(); で初期化時、休眠時の処理を実装する(しなければコンパイルエラーになる)
//Hoge.cs プールされるオブジェクト
using UnityEngine;

public class Hoge : PoolObj<Hoge> {
    public override void Init()
    {
           //gameObject.SetActive(true);
           //初期化時の処理を実装
    }
    public override void Sleep()
    {
           //gameObject.SetActive(false);
           //休眠時の処理を実装
    }
}
//Hogeを使用する側 (呼び出し位置は全て任意. SetOriginal() が 最初のCreate(); より先なら良い)
    public GameObject originalPrefab; // Hoge.cs が付いている
...
    Hoge.SetOriginal(originalPrefab); //元となるプレハブを指定
...
    Hoge hoge = Hoge.Create(); //インスタンスを取得
...
    Hoge.Pool(hoge); //インスタンスを休眠

所感

どうも、プールする親玉もMonoBehaviourにしてListで管理するみたいな実装が多い印象を受けたのですが、今回の実装のようにすれば任意のコードから操作出来ますし、Pool(); は自分で呼んでも他の誰かから呼ばれてもOKです。 (そもそも、オブジェクトの管理者にUpdate(); などMonoBehaviourの機能は必要ありません)

初期化時・休眠時の処理についても、よくある実装では gameObject.SetActive(); を使うことが多い印象です。確かに一番典型的な例ではこれで構わないでしょうね。

が、巨大なオブジェクトでは SetActive(); はInstantiate(); ほどでないにせよ遅いことが分かっていますし、後のBulletでの使用例のように、変数初期が必要な場合もあります。

つまり、オブジェクトごとに最適な休眠状態は違うが、オブジェクト指向に則った実装では、そのオブジェクトの使用者はそれを意識する必要がない。 ということです。

こんな理由で上のような実装になりました。注意点としては

  • シーンをまたいだ際にClearしないと、nullがプールされた状態のままになる

といったところでしょうか。最後にサンプルソースを。(Githubと同じです)

使用例 - クリックされた位置に Bullet を発射するシンプルなシーン

サンプルコード

//Bullet.cs プールされるオブジェクト

using UnityEngine;

public class Bullet : PoolObj<Bullet> {

    int mCount = 0;
    private Vector3 mVelocity;
    // Update is called once per frame
    void Update () {
        if( mCount > 120)
        {
            Bullet.Pool(this);
        }
        transform.Translate(mVelocity);
        mCount++;
    }
    public void Shoot(Vector3 pos, Vector3 velocity) {
        mVelocity = velocity;
        transform.position = pos;
    }


    public override void Init()
    {
        mCount = 0;
        gameObject.SetActive(true);
    }

    public override void Sleep()
    {
        gameObject.SetActive(false);
    }
}
//Game.cs 使用者

using UnityEngine;

public class Game : MonoBehaviour {

    public GameObject mBulletPrefab;

    void Awake()
    {
        Bullet.SetOriginal(mBulletPrefab);
    }

    void Update()
    {
        // ボタンが押されたらその方向に弾を撃つ
        if (Input.GetMouseButtonDown(0))
        {
            Bullet bullet = Bullet.Create();

            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            float speed = 1;
            bullet.Shoot(ray.origin, ray.direction * speed);
        }
    }
}

【Windows 10】右クリックでエクスプローラーがクラッシュする時の対処方法

概要

シェル拡張 (Shell Extension)とは

7Zipなんかのソフトをインストールすると、ファイルやフォルダを右クリックしたときに「7Zipで解凍/圧縮」といったメニューが表示されることがあります。

また、Dropboxを使っていると、Dropbox内のファイル・フォルダが同期済み/同期中かをアイコンで表示してくれたり。

こういう機能を総称してシェル拡張(Shell Extension)と言います。アプリごとによく使う機能をWindowsの Explorerと統合して使いやすく出来るんですね。

解決方法

エクスプローラー上の右クリックで出るメニュー(Context Menu)が表示されずにエクスプローラーが落ちるということは、明らかにここが怪しいです。

ということで、行き当たったこちらのページに従って、

  • ShellEx Viewなるソフトをダウンロード・実行。 

  • Options->Hide All Microsoft Extensions にチェック。

  • Context Menu に該当するシェル拡張(Type が Context Menu となっているもの。右クリックした時の追加項目です。)のうち、必要なもの(私の場合, Tortoise SVN) 以外を全てDisableに。

以上で無事解決しました。Windowsエクスプローラーが不安定/クラッシュする際は、同ソフトでシェル拡張を無効化するのを試してみては。

余談

git のクライアントはTortoise Git はやめて、Source Tree を使うことにしました。

以前、Windows10 でtortoise svnのアイコンが正しく表示されない問題

を解決する際にレジストリをいじっていたので、レジストリのアイコンオーバーレイ(Tortoise SVN とかでファイルフォルダに✓/!マーク が付くアレです) に関する項目を見に行ってみました。
するとTortoise Git では同じ内容のレジストリキーが違う命名規則で名付けられていて(上記問題に対応するためと思われます)、↑のクラッシュ問題も含めて混在させるのがなんだか怪しそうな気がしたからです。

ちなみに、Win+R -> regedit で見られるアイコンオーバーレイのレジストリキーはここ↓です。レジストリの編集は危険なので くれぐれも安易に編集しないように

\HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionExplorerShellIconOverlayIdentifiers