UnityでiOSとかでよくあるトグルスイッチを作りたい。

iOSとかである、こーいうトグルスイッチを作りたい!っと思って作ってみました。

まず画から。

こーいう緑色で楕円のONの画像に、
f:id:wkpn:20170623213239p:plain

こーいう灰色で楕円のOFFの画像を被せ、
f:id:wkpn:20170623213254p:plain

さらに白い丸いボタン画像を載せます。
f:id:wkpn:20170623213258p:plain

Scriptで、タッチするとボタンが右に行って、OFF画像が縮小して消え、もう一回押すとボタンが左に行ってOFF画像が拡大して現れるようにします。

ソースはだいたいこんな感じ↓(Tweenアニメするためにオレオレライブラリが入ってますが、そこはDoTweenとかのメジャーなやつに置き換えてもらえばうまく動くかと思います。)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using WK.Tween;

public class ToggleSwitchController : MonoBehaviour, IPointerClickHandler {
    [SerializeField]
    Transform button;

    [SerializeField]
    Transform inactiveBackground;

    [SerializeField]
    float duration = 0.1f;

    [SerializeField]
    UnityEvent onEvent;

    [SerializeField]
    UnityEvent offEvent;

    bool currToggle = false;
    public bool CurrToggle { get { return currToggle; } }

    private enum EState {
          idle
        , gotoOn
        , gotoOff
    }

    private EState currState    = 0;
    private EState nextState    = 0;
    private int    stateCounter = 0;

    static readonly Vector3 cOnPos  = new Vector3(  100.0f, 0.0f, 0.0f );
    static readonly Vector3 cOffPos = new Vector3( -100.0f, 0.0f, 0.0f );

    //------------------------------------------------------------------------------
    void Awake()
    {
        stateCounter = 0;
        currState    = EState.idle;
        nextState    = EState.idle;
    }

    //------------------------------------------------------------------------------
    void Update()
    {
        ++stateCounter; 
        if( currState != nextState )
        {
            currState    = nextState;
            stateCounter = 0;
        }

        switch( currState )
        {
            case EState.gotoOn:
                updateGoToOn();
                break;
            case EState.gotoOff:
                updateGoToOff();
                break;
        }
    }

    //------------------------------------------------------------------------------
    private void changeState( EState next_state )
    {
        nextState = next_state;
    }

    //------------------------------------------------------------------------------
    private void updateGoToOn()
    {
        if( stateCounter == 0 )
        {
            inactiveBackground.TwScale( Vector3.zero, duration );
            button.TwMove( cOnPos, duration ).OnComplete( 
                    () => {
                            changeState( EState.idle );
                            onEvent.Invoke();
                        }
                    );
        }
    }

    //------------------------------------------------------------------------------
    private void updateGoToOff()
    {
        if( stateCounter == 0 )
        {
            inactiveBackground.TwScale( Vector3.one, duration );
            button.TwMove( cOffPos, duration ).OnComplete( () => {
                            changeState( EState.idle );
                            offEvent.Invoke();
                        }
                    );
        }
    }

    //------------------------------------------------------------------------------
    public bool GoToOnImmediately()
    {
        if( currState == EState.idle || nextState == EState.idle )
        {
            inactiveBackground.localScale = Vector3.zero;
            button.localPosition = cOnPos;
            currToggle = true;
            changeState( EState.idle );
            return true;
        }
        return false;
    }

    //------------------------------------------------------------------------------
    public bool GoToOffImmediately()
    {
        if( currState == EState.idle || nextState == EState.idle )
        {
            inactiveBackground.localScale = Vector3.one;
            button.localPosition = cOffPos;
            currToggle = false;
            changeState( EState.idle );
            return true;
        }
        return false;
    }

    //------------------------------------------------------------------------------
    public bool GoToOn()
    {
        if( currState == EState.idle )
        {
            changeState( EState.gotoOn );
            return true;
        }
        return false;
    }

    //------------------------------------------------------------------------------
    public bool GoToOff()
    {
        if( currState == EState.idle )
        {
            changeState( EState.gotoOff );
            return true;
        }
        return false;
    }

    //------------------------------------------------------------------------------
    public virtual void OnPointerClick( PointerEventData event_data )
    {
        if( currState == EState.idle )
        {
            currToggle ^= true;
            if( currToggle )
            {
                GoToOn();
            }
            else
            {
                GoToOff();
            }
        }
    }
}

こちらにソース一式があります。
github.com

UnityでBGMのイントロ付きループに挑戦

最初に試したのはループ終端まで来たのを確認してAudioSource.timeをループ開始地点に飛ばす方法
www.shibuya24.info


次に試したのが、イントロ用とループ用でAudioSourceを2つ用意してループ用のAudioSourceをPlayScheduledでイントロ長分ずらして再生する方法。
qiita.com

自分の実装が良くなかったのかもしれないのですが、どちらの方法でもstreaming再生した時に音がプツッと途切れる事案が発生して断念。

結局Assetを購入して解決。

https://www.assetstore.unity3d.com/en/#!/content/51095

$21.6しましたが、今のところ途切れることはなく順調に動いているので満足してます。

UnityでビルドしたiOS版アプリがcompute_class_bitmap: Invalid type 13 for fieldと言われて落ちる

UnityでビルドしたiOS版アプリでcompute_class_bitmap: Invalid type 13 for fieldと言われて落ちる事案が発生しました。

IL2CPPでビルドすれば大丈夫。けどMonoだとアウト。Monoのほうがビルドが爆速なので、私、実機で動作確認する際は結構Monoでビルドするのです。

調べていくとどうも自作のTreeコンテナクラスが悪いようでした。

public class TreeContainer<T>
{
    private List<TreeContainer<T>> children;

    ...

childrenのとこの括弧が二重になっているGenericが駄目みたいです。

https://forum.unity3d.com/threads/new-running-fine-in-mac-windows-player-but-crashes-in-iphone-player.111056/

こちらの記事によると、generic of genericはAOTコンパイルではうまくいかないんだそうです。なのでiOS以外では問題なく動いていたと。

waken.hatenablog.com

悩ましい。

Mac OS Sierraで「ろ」のキーにbackslashを割り当てる

今までKarabiner-Elementsでなんとかしようとしてたけどうまくいかなくて鬱屈とした日々を送っていたのですが、今日ひさしぶりにトライしてみようとググってみたら、こんな記事を見つけました。

ubutun.blogspot.jp

「英かな」というアプリを使うといけると。

⌘英かな

早速試してみたら簡単に導入できて快適。

元記事では直接バックスラッシュ指定していなかったけど、直接バックスラッシュ指定しても問題なさそうだったのでそういうふうにしています。

f:id:wkpn:20170625143544p:plain

こんな感じ↑

数字が徐々に変わって目的の数値に行くスクリプト

f:id:wkpn:20170623190340g:plain
ゲームのUIで、こんな感じの演出ってよくあると思います。スクリプトを書きました。

以下のスクリプトをTextコンポーネントを持ったオブジェクトに貼り付けて、SlideToNumber( from_number, to_number, duration )を呼べば、from_numberからto_numberへduration秒かけて遷移します。

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Text))]
public class SlideNumberEffectController : MonoBehaviour {
    public Action OnComplete = null;//終わった時のコールバック

    private Text  text;
    private float speed;
    private float number;
    private float targetNumber;

    private Coroutine playCoroutine = null;

#if UNITY_EDITOR
    [SerializeField]
    private int debugToNumber = 10;
    [SerializeField]
    private float debugDuration = 1.0f;
#endif

    void Awake()
    {
        text = GetComponent<Text>();
    }

    //
    // 数値をすぐにセット
    //
    public void SetNumber( int n )
    {
        number = (float)n;
        text.text = ( (int)number ).ToString();
        if( playCoroutine != null )
        {
            StopCoroutine( playCoroutine );
            playCoroutine = null;
        }
    }

    //
    // from_number から to_number に徐々に移行
    //
    public void SlideToNumber( int from_number, int to_number, float duration )
    {
        SetNumber( from_number );
        SlideToNumber( to_number, duration );
    }

    //
    // 今の値から から to_number に徐々に移行
    //
    public void SlideToNumber( int to_number, float duration )
    {
        targetNumber = to_number;
        speed = ( ( targetNumber - number ) / duration );

        if( playCoroutine != null )
        {
            StopCoroutine( playCoroutine );
        }
        playCoroutine = StartCoroutine( "slideTo" );
    }

    private IEnumerator slideTo()
    {
        while( true )
        {
            var delta = speed * Time.deltaTime;
            var next_number = number + delta;
            text.text = ( (int)next_number ).ToString();

            number = next_number;

            if( UnityEngine.Mathf.Sign( speed ) * ( targetNumber - number ) <= 0.0f )
            {
                break;
            }
            yield return null;
        }
        playCoroutine = null;;
        number = targetNumber;
        text.text = ( (int)number ).ToString();
        if( OnComplete != null )
        {
            OnComplete();
            OnComplete = null;
        }
    }

#if UNITY_EDITOR
    [ContextMenu( "Test" )]
    void Test()
    {
        SlideToNumber( debugToNumber, debugDuration );
    }
#endif

}

おわり。

Unityでビルドした後、XCode上でSiginingのTeamを指定するのがめんどくさかったけどUnity側で設定できた。

だいぶ前から使っていて、前回の記事でもすでに使っていたのですが、ブログにメモしていなかったので書いておきます。

以前からUnityでiOS用にビルドした後にXCode上でSiginingのTeamの部分を設定するのがめんどくさいなぁと思っていました。

ここ↓
f:id:wkpn:20170623133038p:plain

Unity5.6になったときに気づいたんですが、Player SettingsにAutomatically SignっていうのとAutomatic Sigining Team Idってのがあるんですね。

これ↓
f:id:wkpn:20170623133110p:plain


うっほほーいと思って、Team IdのところにXCode上で表示される自分のTeam名(Ken Watanabe)を入れてみたもののうまくいかない😨

調べてみると、こちらに同様の質問がありました。
https://forum.unity3d.com/threads/ios-developer-team-id-how-does-it-work.435411/


Sign in with your Apple ID - Apple Developer
に飛んでMemberのところに出てくるTeam IDを入れればOKです。

これ↓
f:id:wkpn:20170623133333p:plain

おわり

UnityアプリのiOS版にアドフリくんを導入する際にXCode上での操作をしたくないので頑張ってみた。

アドフリくんとは

アドフリくんとは、俗に言うSSPというものです。

SSPとはSupply Side Platform(サプライ サイド プラットフォーム)の略語で、簡単に言うといくつかあるアドネットワークから適したものを自動で選んでくれて収益を最大化してくれるツールです。

今回色々と事情があって自作のUnityアプリにアドフリくんを入れてみることにしました。

基本的には丁寧なマニュアルが用意されているので、それに従えば導入できます。

しかしiOS版の導入の仕方が、マニュアルそのままだとXCode上での操作がありちょっと手間なのでUnity上で全部やってしまえるように多少工夫しました。

それをここにメモしておきます。

ADFMovieReward.framework を追加

マニュアルでは、ADFMovieReward.framework をXcodeにドラッグ&ドロップします。

と書いていますが、ビルドのたびにいちいちXCode上で操作するのはめんどくさいです。Unity Cloud Buildもできなくなっちゃうし。

なので、ADFMovieReward.frameworkをAssets/Plugins/iOSの下にぶっこみます。

しかし、そのままだとエラーが出てしまいました。

'ADFMovieReward/ADFmyMovieReward.h' file not found

と言われてしまいます。

ファイルがどこにあるかわかんないのでエラーになってるようです。

ぶっこんだファイルがどこにいったかというと、$(PROJECT_DIR)/Frameworks/Plugins/iOS の下にあります。

なので、これをSearch Pathに入れてあげるため、以下のようなコードを書きます。

using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public static class XcodePostProcessBuild
{
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string path)
    {
        if (target != BuildTarget.iOS)
        {
            return;
        }

        var project = new PBXProject();
        project.ReadFromFile(PBXProject.GetPBXProjectPath(path));

        //Search Framework pathを追加
        string target_name = project.TargetGuidByName("Unity-iPhone");
        project.AddBuildProperty(target_name, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks/Plugins/iOS");
        project.WriteToFile(PBXProject.GetPBXProjectPath(path));
    }
}

これをXcodePostProcessBuild.csなどと名前をつけて、Assets/Editor以下に置いておきます。

-ObjC -fobjc-arc フラグを追加

Other Linker Flagsに-ObjCと-fobjc-arcを指定とあるので、これをframeworkを選択してinspectorから設定します。
Compile flagsと書いているけれどもOther Linker Flagsにちゃんと追加されるみたい。

f:id:wkpn:20170621212513p:plain


他のAdNetworkのframeworkも容赦なくぶっこむ

ADFMovieReward.frameworkを入れただけでは、各社の広告は表示されません。各社のAdNetworkプラグインもインストールする必要があります。

これもマニュアルによると、XCode上でドラッグ・アンド・ドロップというふうに書かれていますがめんどくさいのでAssets/Plugins/iOSに予めぶっこんどきます。

フォルダ階層が深いものもありますが、中から引き出してぶっこみます。

こんな感じ。

f:id:wkpn:20170621212439p:plain


Privacy Descriptionの追加

このままで来たバイナリをAppleに提出すると、Privacy Descriptionがないよっと怒られます。
Cameraに関するDescriptionは最近のUnityではPlayer Settingsで記述できますので、そちらで書いておきます。

こんな感じ。

f:id:wkpn:20170621212549p:plain

Calendarsにもアクセスが入るようです。そちらに関してはスクリプトで追加します。先のOnPostProcessBuildに以下を追加します。

// Add Privacy Description For Adfurikun
var plistPath = Path.Combine(path, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
plist.root.SetString("Privacy - Calendars Usage Description", "Advertisement would like to use Calendars.");
plist.WriteToFile(plistPath);

まとめ

以上で完了。なんとかXCode上で何もしなくて良くなりました!

その他

この作業をしている途中で、

ld: library not found for -lPods-Unity-iPhone

というエラーに悩まされたのですが、Unityを最新版にしたら解決しました。
Unityを最新にせずに、XCodeのバージョンを上げたのが問題だったのかも...