ぐーるらいふ

底辺。

【Unity】Androidビルドを転送すると INSTALL_PARSE_FAILED_MANIFEST_MALFORMED が出る【NCMB】

ゲームが出来た!実機で動かそう!

と思い、ビルドを行い、adbで実機に転送しようとした所

xxxxx.apk: 1 file pushed. 3.6 MB/s (47771508 bytes in 12.536s)
          pkg: /data/local/tmp/xxxxxxx.apk
Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED]

というエラーが出てしまった。

「あー、アプリ名に.(ドット)が入ってるのがマズいのかな?もしくはパッケージ名辺りが悪いとか」

と当てずっぽうで修正に入ったら変にハマってしまったのでメモっておく。

原因と解決策

原因はNCMBのUnitySDKプラグインを入れていまして、その中にある
Plugins/Android/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="YOUR_PACKAGE_NAME" >
 <!-- Put your package name here. -->

  <uses-sdk android:minSdkVersion="14"/>

    <!-- [START gcm_permission] -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <!-- Put your package name here. -->
    <permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <!-- Put your package name here. -->
    <uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" />
    <!-- [END gcm_permission] -->
~~~~~

この

YOUR_PACKAGE_NAME

これだった…。
これをそのままにしてビルドして入れようとしてたので、エラーになっていたのでした。
これを修正したらあっさりインストール出来ました。

まずググろう

ほんとに…分からなかったらまずググろう。反省。
無駄な三十分を過ごしたー!あー!

【Unity】[TIPS] DoTweenで画像のalpha値をアニメーションさせたい

canvasgroupならFadeIn , Outがすぐ出来るみたいだけど、
uGUIのImageならどうやるのかをメモ。
ちょっと調べれば分かることですが…。

DOTween.ToAlphaを使う

var  fadeImage = GetComponent<Image>();
fadeImage.enabled = true;
var c = fadeImage.color;
c.a = 1.0f; // 初期値
fadeImage.color = c;

DOTween.ToAlpha(
	()=> fadeImage.color,
	color => fadeImage.color = color,
	0f, // 目標値
	1f // 所要時間
);

DoTween.ToAlphaを使うと、colorのalpha値に対して処理を行うことが出来る。
そのイベントを値を入れたいImageに渡してあげればOK。

沢山あったらどうなるんや

こんなテストコードで軽く動かしてみる。

public Image[] testImages;

public void fadeTest(){
        for (var i = 0; i < testImages.Length; i++)
        {
            // 初期化
            var img = testImages[i];
            var c = img.color;
            c.a = 1.0f;
            img.color = c;

            DOTween.ToAlpha(
                () => img.color,
                color => img.color = color,
                Random.Range(0.0f, 0.2f), // 目標値
                Random.Range(1f, 3f) // 所要時間
            ).SetLoops(-1 , LoopType.Yoyo);
        }
}

f:id:ghoul_life:20180201173209g:plain

問題なし。便利ですね!

【Unity】uGUIで円形のプログレスバーを簡単に作る

お疲れ様です。ぐーるです。
uGUIでの円形プログレスバーの作り方をメモっておきます。

手順

1.一番下になる背景を作る

Imageを作り、円形の画像を当てはめ、名前を「ProgressBar」としておく
f:id:ghoul_life:20180131181258p:plain

ここに実際に使用した200x200の白い円形のpng画像を貼ってあるのですが
見えませんね。見えません。見えないのでイメージ画像も一緒に貼ってあります。
f:id:ghoul_life:20180131171339p:plain
f:id:ghoul_life:20180131171345p:plain

2.中央のマスク部分を作る

1で作ったProgressBarをコピーして子要素にする。その後大きさをちょっと縮小(100x100)して、
名前を「Center」にしておく

f:id:ghoul_life:20180131181308p:plain

3.ゲージ部分を作る

1で作ったProgressBarをコピーして子要素にする。

f:id:ghoul_life:20180131181315p:plain

大きさはそのままにして、色を好きな色に変更、
Image TypeをFilledにします。
f:id:ghoul_life:20180131181340p:plain
f:id:ghoul_life:20180131181348p:plain

4.完成

Image Typeの下にあるImage Amountの値を変えれば
円形プログレスバーの出来上がりです。
f:id:ghoul_life:20180131181359g:plain

uGUIスゲー

あっという間にできてしまった。
画像ではShadow入れて見やすくしていたり、名前を付けてたりしますが、
その辺はもちろん自由です。空中にゲージだけ浮かせたいって人は1の背景も要らないですね。

【Unity】ソロだけど、Zenjectを使ってみたい。超初心者向け?

万年ソロプレイなぐーるです。よろしくお願いします。
また間が空いてしまいました。
なんていうか年末年始は問題がよく出たり、人が抜けたり人が増えたり
と運用をメインにしているとバタバタな期間だったりします。
2-3月は少し落ち着くといいなあ。(4月からはまたバタつく)

zenjectを使ってみたい

github.com


C#用のDI container。
オジサン的にはSpringとかseasorとかむかーし使ってたなぁなんて印象です。
個人的にこういうの結構好きでして、多人数開発になると「こうかはばつぐんだ!」っていう感じです。
とはいえ、個人で使う分には手に余るような部分が多く、手を出しづらいのですよね。

重い腰を上げる

まずは「zenject」で検索。

Unity3DのDIフレームワーク、Zenjectの紹介 | Aiming 開発者ブログ

ふむふむ、installerとcontextとbindかぁ。
え、bindするには必ずinstallerをアタッチしないとダメなのかな?
と思ったのだけど、公式のreadmeを見てみると、すごくシンプルでも動きそう?

やってみた手順

1. unityで適当なプロジェクトを作る
2. Asset Storeでzenjectを検索して、プロジェクトにインポートする
3. 上部メニューGameObject > Zenject > SceneContext を押下して、SceneContextを作成
4. uGUIで適当にテストするbuttonを作る

f:id:ghoul_life:20180122204841p:plain

4. 以下の適当スクリプトを作る(計3つ)
DIContainerに使いまわしたいオブジェクトをBindする。
そして使いたい所でResolveするだけ。

f:id:ghoul_life:20180122205822j:plain

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

// DIContainerにbindして色々な所で使いたいクラス
public class Node {
	Node(){
		Debug.Log("Node class New : " + this.GetHashCode());
	}

	public void print(string message)
	{
		Debug.Log("node print == " + message + " | hashCode == " + this.GetHashCode());
	}
}
Exec1.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Zenject;

public class Exec1 : MonoBehaviour {
	[Inject]
	DiContainer container;

	public void Bind(){
		// 以下の3つのbind typeがある。
		Debug.Log("bind");
		//container.Bind<Node>().AsSingle();
		//container.Bind<Node>().AsTransient();
		container.Bind<Node>().AsCached();
	}

	public void Resolve(){
		var node = container.Resolve<Node>();
		node.print("exec1");
	}

	public void UnBind(){
		Debug.Log("unbind");
		container.Unbind<Node>();
	}
}
Exec2.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Zenject;

// 使う所その2
public class Exec2 : MonoBehaviour {
	[Inject]
	DiContainer container;

	public void Resolve(){
		var node = container.Resolve<Node>();
		node.print("exec2");
	}
}

5. 上で作ったbuttonにイベントを割り当てる
6. 動かしてみる

結果

最小構成で簡単にbindして使う事が出来た。

containerにbindする時以下の3つを指定できる。
これを理解しておこう。

AsSingle()

f:id:ghoul_life:20180122210320p:plain

bind , unbindを繰り返してもnewは一度しか発生せず、resolveの結果は変わらなかった。
つまりunbindしてもzenject内にオブジェクトを保持していて、
再bind時にはそれが利用されているようだ。
ということはGCの対象にならず、アプリを終了するまで開放することが出来ないだろう。

AsTransient()

f:id:ghoul_life:20180122210500p:plain

resolveする度にnewをしている。
そのため、毎回別のオブジェクトとして扱われる。

AsCached()

f:id:ghoul_life:20180122210711p:plain

AsSingle()と挙動が似ている。unbind、再bindを行うとnewされるようだ。(newされる瞬間は最初のResolve時)
つまり、unbind時にはzenjectからも開放されているため、GCの対象になってくれる。
最も使いやすいのでは無いだろうか。迷ったらとりあえずコレで良さそう。

こんな挙動の違いを見ることが出来た。

終わりに

シンプルに使おうと思うとこんなに簡単なんですね。
個人開発でしかunityを使ったことが無いので、こういった開発補助ライブラリ
は余り使わなくてもいいのかもしれないけど、メモとして残しておく!

都内は大雪です。

【Unity】InkPainterをちょっと理解してみたかった

お疲れ様です。
年末商戦とか関係なく休日が潰れてしまうぐーるです。
ゼノブレイド2を買ったのですが、全然出来なくて困ってます。

InkPainter

突然ですが、InkPainterってご存知でしょうか
f:id:ghoul_life:20171212221512p:plain
https://www.assetstore.unity3d.com/jp/#!/content/86210
Unityでスプラトゥーンごっこが出来ちゃう!しかも無料!

製作者さんはこちら
esprog.hatenablog.com
twitter.com

「おおー、すげー」で終わらせたいのだけど、

内部的にどうなってるのかな?
最小限で必要な要素ってなんだろう?

とふと気になったのでちょっと調べてみました。

最小構成の全体の流れ

まずは流れを掴むためにnormalmapもheightmapも無視した状態の最小構成を調査しました。
最小限必要な処理の流れを抜き出してみると

  1. materialからmainTextureを取り出して、RenderTextureにコピー
  2. materialのrenderTextureとmainTextureを差し替える(renderTextureがセットされた状態になる)
  3. bufferを利用してrenderTextureにインクを描画する

というすごくシンプルな実装という事がわかった。

最小構成に整理したスクリプト

オリジナルのInkPainterを読むと色々と機能があるが、
最低限必要なものだけを抜き出して整理したスクリプトはこんな感じ。


最小構成のシェーダー

shaderもcgincで共通化されていた箇所を取り出してきて、
1ソース内に収めて、整理したらこんな感じ。


Unityプロジェクトに落とし込む

1.Unityでprojectを作る
f:id:ghoul_life:20171212190149p:plain

2.planeを生成してカメラを調整でもしておく
f:id:ghoul_life:20171212190153p:plain

3.Scriptを作る(ソースは上に)
f:id:ghoul_life:20171212190211p:plain

4.Shaderを作る(同じくソースは上に)
f:id:ghoul_life:20171212190218p:plain

5.リソースを用意する(brushとmainTexture)
f:id:ghoul_life:20171212190208p:plain
f:id:ghoul_life:20171212190248p:plain
f:id:ghoul_life:20171212190257p:plain
↑使用したリソース二つなのだけど、見えないかも…?

6.materialを作り、planeにアタッチ
f:id:ghoul_life:20171212190156p:plain

7.Scriptもplaneにアタッチ
f:id:ghoul_life:20171212190221p:plain

8.実行する
f:id:ghoul_life:20171212190446p:plain
マウスでドラッグした箇所にブラシで設定したテクスチャの
形に描画をすることが出来るようになっており、
ゲームを止めて起動し直すと全てが初期化されているのが
確認出来ました。(リソースを破壊してない)

感想

この実装ではマウスでやってるけど、ボールが転がった跡にしたり、
一定時間で消したりとか使用用途はありそう。

平たく言えば

「テクスチャの上にブラシテクスチャで描画出来るようにしただけ」

になってしまったが、
mainTextureをコピーしてRenderTextureを作り、
それを利用してカスタマイズした表現をするという方法を理解して貰えればと。

normalmapも合わせて色の変化やにじみとかもやれるようにすると
もっと表現の幅が広がりそうなのでもしかすると続きを書くかも。

とりあえず一旦ここまで。
ゼノブレイド2やるぞーやるぞー…。

追記

Unity 5.6.0以前で開くと、エラーが出ていた。
Shaderに

#include "UnityCG.cginc"

が必要みたいだったので、追記しておきました。

【Unity】第六回 unity1week「space」参加作品「スペースキューブ」の開発

ぐーるです。
ついに始まった、unity1week。
皆勤賞で今回も参加しました。

f:id:ghoul_life:20171121143440p:plain

今回の作品はこちら。
https://unityroom.com/games/spaceproj

unityちゃんが宇宙貨物をスペースシャトルに運ぶゲームです。
三次元的に登ったり降りたり動かしたり重ねたりといった工夫を凝らして
ステージをクリアするパズルゲームです。全10ステージ。

お題について

「space」宇宙モノか隙間モノか…と漠然と考える。

大きな分類で考えると

で二分されそう。

うーん、どっちかって言うとシューティングゲームかなぁ。
なんて思っていたのだけど。どうも食指が動かない。

まぁ、グラディウスとかのような名作をイメージしてしまうのは
必然というかそういうのしか頭に浮かばない。
じゃあ、カジュアルゲーム
これも前にやったしなぁ。と考え直す。
「!」
と来たのは隙間は隙間でも箱をどこに置くか?といったスキマを埋める系のゲーム。
じゃあこれにしてみようかな、と作り始めた。

まず初めに

ざっくりイメージを固める。

ステージがあって、荷物は1つまたは複数で、荷物を指定の位置に置くことが出来たらクリア

というシンプルなものをイメージ。
spaceは「スキマに置け!」みたいな語呂合わせで何とか乗り切ってしまえばいいや。
と軽く考えていたが、全体的な雰囲気を宇宙っぽくしようと考える。

blender

ちょっと怪しげなキューブにすれば、それっぽく見えるだろう。
でもモデルのUV画像を取得して編集するにはどうすればいいんだ?
という事で調査して手順化して下記にまとめておきました。

ghoul-life.hatenablog.com

モデルづくり

ゲーム内で使ったモデルはunich-chanを覗いて全て自作です。
今思えばunich-chanも止めて宇宙服の人とかにすれば良かったかなー。

キューブはこんな感じでBlenderで。

f:id:ghoul_life:20171121131441p:plain

やっとプログラムに入る

テンプレートやTweenアニメーションエンジンは一切使わないで全部自作。
unityの基本APIのみで実装していきます。

ステージ構成

大きく分けて3つに分類しました。

  • Floor
  • Gimmick
  • Player
Floor

足場になるオブジェクト。プレイヤーは動かすことが出来ないシンプルなもの。コードもシンプル。
起点と終点をセット出来て、配置して命令(InitMove)を出すと終点に移動してから固定される。

また、Stateを持っていて、基点となるGameManagerが状態を監視している。
StateがCreateFinishになると「あ、ステージ作成完了したんだな」と分かるよくあるヤーツです。

今回のゲームでは1つしか無かったが、親クラス(FloorObj)で管理していて、
色々なオブジェクトを使えるようにしている。

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

public class FloorManager : SingletonMonoBehavior<FloorManager> {

    protected STATE_STAGE _state;
    protected List<GameObject> gameObjList;
    public GameObject floorObjPrefab;
    private const float ADD_INIT_Y_VAL = 20.0f; // 初期位置の高さ。ここから降ってくる

    // Update is called once per frame
    void Update () {
        // ステージ作成中
        if (State == STATE_STAGE.CREATE)
        {
            bool isCreateFinish = true;
            foreach (var go in gameObjList)
            {
                var fo = go.GetComponent<FloorObj>();
                if (fo.IsInitMove) // まだ初期化動作が終わっていない
                {
                    isCreateFinish = false;
                    break;
                }
            }
            if (isCreateFinish)
            {
                State = STATE_STAGE.CREATE_FINISH;
            }
        }
    }

    public void Init()
    {
        Clean();
        if (gameObjList == null)
        {
            gameObjList = new List<GameObject>();
        }
    }
    /**
     * Create Stage Floor
     */
    public void Create()
    {
        State = STATE_STAGE.CREATE;

        var stageDatas = Define.GetFloorList();

        var xLen = stageDatas.Length;
        for(var xIdx = 0; xIdx < xLen; xIdx++)
        {
            var zLen = stageDatas[xIdx].Length;
            for(var zIdx = 0; zIdx < zLen; zIdx++)
            {
                var yLen = stageDatas[xIdx][zIdx];
                for (var yIdx = 0; yIdx < yLen; yIdx++)
                {
                    var targetPos = new Vector3(xIdx, yIdx, zIdx);
                    var p = new Vector3(xIdx, yIdx + ADD_INIT_Y_VAL, zIdx);
                    CreateFloorObj(targetPos, p);
                }
            }
        }
    }

    public void Clean()
    {
        State = STATE_STAGE.CLEAN;
        CleanGameObjList();
        State = STATE_STAGE.CLEAN_FINISH;
    }

    protected void CleanGameObjList()
    {
        if (gameObjList != null)
        {
            for (var i = 0; i < gameObjList.Count; i++)
            {
                var go = gameObjList[i];
                Destroy(go.gameObject);
            }
            gameObjList.Clear();
        }
    }

    //-------------------------------------------------------------------
    /**
     * floor prefab create
     */
    private void CreateFloorObj(Vector3 targetPos, Vector3 initPos)
    {
        var go = Instantiate(floorObjPrefab);
        var fo = go.GetComponent<FloorObj>();
        if (fo)
        {

            go.transform.SetParent(this.transform);
            go.transform.position = initPos;
            fo.targetPos = targetPos;
            fo.waitTime = Random.Range(0, 2.0f);
            fo.InitMove();

            gameObjList.Add(go);
        }
        else
        {
            Debug.LogError("ERR CreateFloorObj. please add FloorObj script!!");
            Destroy(go);
        }
    }

    public STATE_STAGE State
    {
        get { return _state; }
        set { _state = value; }
    }

    public bool isCreate()
    {
        return _state == STATE_STAGE.CREATE;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FloorObj : MonoBehaviour {

    public Vector3 targetPos; // ここに向かって動く
    public float waitTime; // 動き出すまでに待機時間を設定してランダム性を出す
    private bool isInitMove = true;
    private float time = 20.0f;
	// Use this for initialization
	void Start () {
	}
	
	// Update is called once per frame
	void Update () {
	}

    public void InitMove()
    {
        if (isInitMove)
        {
            StartCoroutine(_InitMove());
        }
        
    }

    private IEnumerator _InitMove()
    {
        yield return new WaitForSeconds(waitTime);

        while(Vector3.Distance(this.transform.position, targetPos) > 0.01f)
        {
            this.transform.position = Vector3.MoveTowards(this.transform.position, targetPos, time * Time.deltaTime);
            yield return new WaitForEndOfFrame();
        }
        this.transform.position = targetPos;
        isInitMove = false;

        yield return null;
    }

    public bool IsInitMove
    {
        get { return isInitMove; }
    }
}
Gimmick

ゴールも荷物も全てギミックとしました。
Carry , Beam といった子クラスがあり、クラス別に独自の動作が出来る。
Rigidbodyは使わず、Colliderのみ。
その中のCarryを抜粋。当たり判定はRaycastで行っています。
詳しくは後述。

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

public class Carry : GimmickObj {

    private bool isMove;
    private float moveTime = 1.0f;
    private float downMoveTime = 5.0f;
    private HIT_DIR hitDir;

    Ray leftRay;
    Ray rightRay;
    Ray forwardRay;
    Ray backRay;
    Ray downRay;

    // Use this for initialization
    void Start () {
        isMove = false;
        hitDir = HIT_DIR.NONE;
    }

    void FixedUpdate()
    {
        var p = this.transform.position;

        RaycastHit hit;
        leftRay = new Ray(p, Define.vectorLeft);
        rightRay = new Ray(p, Define.vectorRight);
        forwardRay = new Ray(p, Define.vectorForward);
        backRay = new Ray(p, Define.vectorBack);
        downRay = new Ray(p, Define.vectorDown);
        var distance = 1.0f;

        if (!isMove)
        {
            // 落下判定
            if (!Physics.Raycast(downRay, out hit, distance, LayerMask.GetMask("Floor", "Gimmick")))
            {
                if (!AudioManager.Instance.IsPlaySE()) AudioManager.Instance.PlaySE(AudioManager.SE.FALL_BOX);
                MoveDown();
            }

            // player接触判定
            if (Physics.Raycast(leftRay, out hit, distance, LayerMask.GetMask("Player")))
            {
                hitDir = HIT_DIR.LEFT;
            }
            else
            if (Physics.Raycast(rightRay, out hit, distance, LayerMask.GetMask("Player")))
            {
                hitDir = HIT_DIR.RIGHT;
            }
            else
            if (Physics.Raycast(forwardRay, out hit, distance, LayerMask.GetMask("Player")))
            {
                hitDir = HIT_DIR.FORWARD;
            }
            else
            if (Physics.Raycast(backRay, out hit, distance, LayerMask.GetMask("Player")))
            {
                hitDir = HIT_DIR.BACK;
            }
            else
            {
                hitDir = HIT_DIR.NONE;
            }
        }
    }

    public bool MoveLeft() { return Move(Define.vectorLeft , moveTime); }
    public bool MoveRight() { return Move(Define.vectorRight, moveTime); }
    public bool MoveForward() { return Move(Define.vectorForward, moveTime); }
    public bool MoveBack() { return Move(Define.vectorBack, moveTime); }
    public bool MoveUp() { return Move(Define.vectorUp, moveTime); }
    public bool MoveDown() { return Move(Define.vectorDown, downMoveTime); }

    // 移動
    private bool Move(Vector3 movePos , float tmpMoveTime)
    {
        if (isMove) { return false; }
        RaycastHit hit;
        var ray = new Ray(this.transform.position , movePos);
        var distance = 1.0f;
        // 衝突判定
        if (Physics.Raycast(ray, out hit, distance, LayerMask.GetMask("Floor" , "Gimmick")))
        {
            var fo = hit.collider.gameObject.GetComponent<FloorObj>();
            var gio = hit.collider.gameObject.GetComponent<GimmickObj>();
            if (fo != null || gio != null)
            {
                // 障害物に衝突している
                return false;
            }

        }


        isMove = true; // 移動中状態フラグを立てる
        var p = this.transform.position;
        p += movePos;

        MoveControl.Instance.Move(this.gameObject, p, tmpMoveTime, FinishMove);

        return true;
    }

    private void FinishMove()
    {
        isMove = false; // 移動完了でフラグを下ろす
    }

    public HIT_DIR HitDir
    {
        get { return hitDir; }
    }
}
Player

プレイヤーです。これも1つしかなかったが、実はFloor同様複数作ることが出来るようになってます。
プレイヤーを切り替えながら橋を架けるようなステージがあっても面白いかも。
と思いましたが、一旦保留に。

オブジェクトの移動

上でお見せしているCarryのソースコードを見れば一目瞭然ですが、
オブジェクトは前後上下左右でRayを飛ばしていて、

「今何とどのように接触しているか?」

を常に取得するようにし、それによって動作を変えるようにしました。
行動を起こす前にも同様にチェックして障害物が無いかを確認。

ここは最後まで悩みました。2Dだと

{0,1,1,0,0,1,0,1}
{0,1,1,0,1,1,0,1}
{0,1,1,0,1,1,0,1}
{0,0,0,0,0,0,0,1}

といったマップを持っておいて、
マップ情報を元に通れるか通れないかを判定することがありますが、今回は不採用。

また、移動処理だけ行うクラスを用意してそこで移動させています。
callbackを設定しておいて、完了通知が飛ばせるようにしてあります。

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

/**
 * objectの移動を司る
 */
public class MoveControl : SingletonMonoBehavior<MoveControl> {

    public void Move(GameObject obj, Vector3 targetPos, float moveTime , System.Action callback)
    {
        StartCoroutine(_Move(obj , targetPos , moveTime , callback));
    }

    private IEnumerator _Move(GameObject obj, Vector3 targetPos, float moveTime , System.Action callback)
    {
        // ある程度まで近づいたら
        while (Vector3.Distance(obj.transform.position, targetPos) > 0.01f)
        {
            obj.transform.position = Vector3.MoveTowards(obj.transform.position, targetPos, moveTime * Time.deltaTime);
            yield return new WaitForEndOfFrame();
        }
        obj.transform.position = targetPos; // 強制的に指定位置に
        
        callback(); // 完了通知

        yield return null;
    }
}


(今思えばSphereでRaycast飛ばして範囲に入ったら、お互いのforwardで内積でも取ればどこで接触しているか取れそう
だなーとは思った…今度やってみよう。)

ジャンプ

最初はありませんでした。ほんとに倉庫番というゲームまんまだったので。

オリジナル要素をもう少し入れたいなーと思った時に3Dらしく上下にも動かしたいなと。
入れてみたら思いの外面白かったのでそのまま採用へ。

MoveControlでMove -> Moveです。
なので二段以上の壁でジャンプすると上には行くが、前には進めないという現象が出るようになっています。
これはこれでカワイイのでそのままにしました。

f:id:ghoul_life:20171121135710g:plain

オブジェクトの再配置

オブジェクト、プレイヤーを画面外に落とすと、初期位置に戻ります。

これ実は「デバッグ機能」でした。

ステージから落ちれない

という仕様にするのが一般的ですが
実装を後回しにしてて、「落ちたら初期位置に戻しゃいいや」と適当スクリプトでガシガシ先に進めてた所
これを利用したステージを思いつき、コレ良いんじゃないかなとそのまま採用へ。

アセットについて

unitychanとSkyboxで使用しました。

周りを見るとみんな一杯使ってるんだなーと。
一週間だからどんどん使った方が良さそうではありますが。
勉強第一。DIY精神で自作頑張ります。(←unityちゃん使ってるヤツが言うセリフではない)

感想

仕事がずっと過渡期でして。
拘束十二時間超えがまれによくある会社とかおかしくないですか。

久しぶりにunity触って、過去の作品の実装とか思い出しながらやってましたが、
思ったよりも忘れてなかった。

自分はこういうゲームが好きだ。
ここ一週間、毎日通勤中にステージ考えて、
evernoteにガシガシアイデアメモってるのも楽しかった。
「家帰ったらUIガガッとやるぞ!」なんて思いながら仕事してました。

もし良かったらちょっとだけやってみて下さいー。
そんなに時間は取らせない…はず。

【Unity】Blenderで簡単にuv展開をする手順まとめ。そして作ったものをUnityで使いたい

お久しぶりです。ぐーるです。
ゾンビみたいな暮らしをしています。

随分と間が空いてしまったのですが、unity1weekがまた始まったので、
久しぶりにブログ更新しようと思いました。

枠付きのcubeを作りたい

枠付きのcubeはこんな感じのもの。

f:id:ghoul_life:20171115165349p:plain

オシャレですよね。ゲームに使うとちょっとしたアクセントになるのでは無いかと。
このぐらいなら自分でも作れるんじゃないか、と思い立ったのが今回になります。

Blenderでcubeを作ってuv展開して色を塗ればそれっぽくなるだろう。
初心者でも簡単簡単…。で、どうやるんだ?
という考えから手順をまとめてみました。

なお、クッソ初心者向けです。

BlenderでcubeをUV展開する手順

step1

Blenderを立ち上げます。

f:id:ghoul_life:20171115165405p:plain

そして適当に画面をクリックしてスプラッシュを消します。

f:id:ghoul_life:20171115165433p:plain

step2

いきなりですが、

メインビューの右上にあるツマミを左にグィーとドラッグ

して下さい。

f:id:ghoul_life:20171115165448p:plain

するとビューを2つにすることが出来ます。
この方が分かりやすいかと思いますのでこうしました。

f:id:ghoul_life:20171115165513p:plain

step3

左のビューは「編集モード」に
右のビューは「UV/画像エディタ」に
しておきます。

f:id:ghoul_life:20171115165532p:plain
f:id:ghoul_life:20171115165544p:plain

step4

シームを付けます。

シームとはuv展開をする時にどこに切れ込みを入れ、アジの開きのように展開するか?

という基準線になります。これを入れておかないとuv展開することが出来ません。

左ビューのcubeの頂点を全部選択しておいて(Aキー)
左ペインの「シェーディング/uv」 => 「シームを付ける」を押下します

f:id:ghoul_life:20171115165604p:plain

すると線に赤い色が付きます。これがシームになります。

step5

やっと展開です。
左ペインの「シェーディング/uv」 => 「展開」 => 「展開」
と選択します。

f:id:ghoul_life:20171115165620p:plain

右側に開いておいたuv画像エディタにuv展開された図が展開されるはずです。
上手く表示されない場合はシームが正しく付いていない可能性があるので、
step4からやりなおしてみましょう。

f:id:ghoul_life:20171115165638p:plain

step6

ここで終わりじゃなく、これを編集したいのです。
なので、エクスポートします。

右ビューの下ペイン「uv」 => 「uv配置をエクスポート」
で出力することが出来ます。適当な名前と場所に出力しましょう。

f:id:ghoul_life:20171115165700p:plain

step7

編集ツールでテキトーに編集します。
Windowsペイントでも全然OKですよ!

f:id:ghoul_life:20171115165716p:plain

step8

編集したら、その画像を適用します。
Blenderに戻って右側ビュー下ペインの「開く」を押下してstep7で編集した画像を選択します。

f:id:ghoul_life:20171115165729p:plain

右ビューに編集した画像が表示されます。

f:id:ghoul_life:20171115165740p:plain

step9

もう反映されています。
左ビューに行き、シェーディングをテクスチャにしてみましょう。

f:id:ghoul_life:20171115165802p:plain

すると編集した画像が適用されたcubeが見えるはずです。

  • 上手く表示されない?

なんか部分部分しか反映されないんだけど?
ライティングやカメラの関係が上手く表示されていないのかもしれません。
unityで使う関係上、ライティングやカメラは必要無いので、消しちゃいましょう

f:id:ghoul_life:20171115165846p:plain

「右クリック」 => 「削除」 などでOKです。(xキーでもいける?)

step10

unityで使ってみましょう。
fbxで適当に出力してD&Dで持っていけばOKです。
なお、テクスチャは別で入れる必要がありました。
「fbxとpngを入れる」 => 「pngからMaterialを作る」 =>「fbxをSceneに配置してMaterialを適当」
オブジェクトに適用すればOKのはずです。

f:id:ghoul_life:20171115165907p:plain
f:id:ghoul_life:20171115165915p:plain

fbxに画像を含めるには
nn-hokuson.hatenablog.com
ここに手順が!神さまーありがとうございます!

感想

ステップ10までありますが、実際やってみると15分程度で終わりました。
慣れればもっとスイスイ出来ると思います。

これが理解できればきっと複雑なモデルでもuv展開することが出来る…はず!
あー、それにしてもBlender難しい!

unity1weekの進捗はダメです。

f:id:ghoul_life:20171115171013g:plain