20200118のUnityに関する記事は6件です。

UnityでAndroidビルドエラーUnityEditor.BuildPlayerWindow+BuildMethodExceptionが出た時のライブラリ競合解決が辛過ぎて泣いた

概要

Unityでスマホゲーム作っててiOS向けのリリースがひと段落したのでAndroid向けビルドしたらめっちゃエラー出て泣いた。
2020-01-18_20h49_42.jpg
つら…

環境

  • OS:Windows 10
  • Unity:2019.1.0f2

Macでも同様のエラー出たので、OS依存のエラーではないことは検証済み。

エラー内容を確認する

ビルド失敗時に表示されるUnity Editorのダイアログには、何もメッセージが表示されず無事死亡。
2020-01-18_20h47_54.jpg

Consoleを確認するとUnityEditor.BuildPlayerWindow+BuildMethodException: 145 errorsという素敵なログが出ていた。

UnityEditor.BuildPlayerWindow+BuildMethodException: 145 errors

  at UnityEditor.BuildPlayerWindow+DefaultBuildMethods.BuildPlayer (UnityEditor.BuildPlayerOptions options) [0x00242] in C:\buildslave\unity\build\Editor\Mono\BuildPlayerWindowBuildMethods.cs:194 

  at UnityEditor.BuildPlayerWindow.CallBuildMethods (System.Boolean askForBuildLocation, UnityEditor.BuildOptions defaultBuildOptions) [0x0007f] in C:\buildslave\unity\build\Editor\Mono\BuildPlayerWindowBuildMethods.cs:97 
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

エラー145個て。初代のポケモン図鑑ならもう少しでコンプやぞ。

UnityEditor.BuildPlayerWindow+BuildMethodExceptionでググると結構情報出てくるけど、PlayServicesResolverだけで解決するパターンばかり。どれも今回のエラーとはちょっと違いそうな雰囲気だ。

もう少し手前のログを見ると、Gradle buildでコケていることがわかった。

CommandInvokationFailure: Gradle build failed. 
C:\Program Files\Unity\Hub\Editor\2019.1.0f2\Editor\Data\PlaybackEngines\AndroidPlayer/Tools\OpenJDK\Windows\bin\java.exe -classpath "C:\Program Files\Unity\Hub\Editor\2019.1.0f2\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\gradle\lib\gradle-launcher-4.6.jar" org.gradle.launcher.GradleMain "-Dorg.gradle.jvmargs=-Xmx4096m" "assembleRelease"

stderr[
D8: Program type already present: com.facebook.unity.FBDialogUtils

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':transformDexArchiveWithExternalLibsDexMergerForRelease'.
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 
...

以下、つらつらと導入ライブラリの一覧が羅列されていた。
stderrにはFacebookのFBDialogUtilsがすでに存在しているとのメッセージが。

D8: Program type already present: com.facebook.unity.FBDialogUtils

Facebook SDKで何か引っかかっているのかな?
正直これだけでは具体的な原因が特定できない。

とりあえず現状得られたエラーメッセージでググってみると、SDKの重複に起因して発生するエラーっぽい情報がでてきた。
D8: Program type already present: com.facebook.unity.Constants - Unity Answers
Program type already present error when gradle build - Unity Forum

そういえばAndroidアプリのビルドでいろんなSDK入れると、バージョン違いの重複ライブラリが存在してしまい「どっち見ればええねん」的なエラーが出るのを何度も経験していた…

ライブラリの依存関係を調査する

おおよその目星がついたので深掘ってみる。Androidプロジェクト吐いてgradlew dependenciesコマンド叩くと、ライブラリの依存関係を確認できるらしい。
【Android】ライブラリの依存関係を調査する方法(gradle) - テクノモンキーのアプリ開発日記

知らなかった。

Build Settingsで [Export Project] にチェックを付けてAndroidプロジェクトを出力。
2020-01-18_21h39_22.jpg
Android Studioでプロジェクト開いてgradlew sync実行。エラー出てたら解消させとく。

コマンドプロンプト(Macの場合はターミナル)でAndroidプロジェクトのフォルダに移動してgradlew dependenciesコマンドを叩く。ライブラリ一覧や依存関係がズラリと表示される。

2020-01-18_21h51_00.jpg
2020-01-18_21h52_01.jpg

細かい部分はスルーするが、同名のバージョン違いが存在したりplay-services系で被っているものがいくつかあった。

ライブラリの競合を解決する

おそらく以下3つのライブラリが重複対象として該当。

  • play-services-ads-identifier
  • play-services-basement
  • facebook-android-wrapper

対応方法としては

  • バージョンを合わせる
  • 依存関係を無効化する
  • 重複ライブラリを除外する

といった案が考えられるようだ。

重複ライブラリを除外する

Androidプロジェクト上ではbuild.gradledependenciesから対象のimplementationを削除することで、ビルドエラーが解消されapkの生成ができた。

build.gradle
dependencies {
...
    implementation(name: 'play-services-ads-identifier-16.0.0', ext:'aar')
    implementation(name: 'play-services-basement-16.0.1', ext:'aar')
    implementation(name: 'facebook-android-wrapper-7.16.1', ext:'aar')
...
}

とはいえ毎回UnityからAndroidプロジェクト吐いてAndroid Studioでビルドするのはダル過ぎる。そんなのはiOSだけで十分です。

軽く調べてみると、Unityプロジェクト上ではmainTemplate.gradleを自前で用意して依存関係を書かなければならないらしい。

ちょっと時間も無くて「なるほどわからん」って感じだったので、重複してるaarファイルのバージョン低い方を削除するという力技でUnity上でもビルドエラーを解決(^ω^)

その後、対象ライブラリが関係している広告まわりを実装。ひと通りの動作確認でも問題なさそうだったのでクローズとした。

Androidのビルドエラー解消しんどい

Androidのライブラリ競合問題、5年くらい前から悩まされてた気がするけど、未だにサクッと解消できなくてつらい。はやくAIに仕事奪われたい。

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

【Unity】Timelineの再生位置をスライダーで指定する

こんにちはっ?八ツ橋まろんですっ!

ARアプリを作っているときに、Timelineをスライダーで操作したくなったのでスクリプトを書きました。本記事ではそのコードと解説をします。

↓↓↓↓このコードによってこんなことができます↓↓↓↓(GIF)
(このGIFではアニメーションだけが入ったTimelineを使用しています)
Qiita.gif

・再生ボタン/一時停止ボタンの実装
・Timelineの進行度合いがスライダーに反映される
・スライダーの位置をクリックして変えるとTimelineの再生位置も連動する

以下、完成コード

TimelineUiController.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Playables;
using System; // Actionに必要

public class TimelineUiController : MonoBehaviour
{
    [SerializeField]
    PlayableDirector timeline;

    [SerializeField]
    Slider slider;

    [SerializeField]
    GameObject playButton;

    [SerializeField]
    GameObject pauseButton;

    double length;

    float sliderValue;
    float sliderValueStored;
    float time;

    private void Start()
    {
        length = timeline.duration;
        Play();
    }

    void Update()
    {
        sliderValue = slider.value;
        time = ConvertDtoF(timeline.time);

        if(time >= 0.995 && timeline.extrapolationMode != DirectorWrapMode.Loop)
        {
            playButton.SetActive(true);
            pauseButton.SetActive(false);
        }

        if (sliderValue != sliderValueStored)
        {
            sliderValueStored = sliderValue;
            time = sliderValue;
            SetTimelineTime(time);
        }
        else if(time != sliderValueStored)
        {
            slider.value = time;
            sliderValueStored = time;
        }
    }

    public void Play()
    {
        timeline.Play();
        playButton.SetActive(false);
        pauseButton.SetActive(true);
    }

    public void Stop()
    {
        timeline.Stop();
        PlayOneFrame();
    }

    public void Pause()
    {
        timeline.Pause();

        time = ConvertDtoF(timeline.time);
        sliderValue = time;
        sliderValueStored = time;
        playButton.SetActive(true);
        pauseButton.SetActive(false);
    }
    public void LengthReset()
    {
        length = timeline.duration;
    }
    void PlayOneFrame()
    {
        Play();

        //1フレーム後にPauseする
        StartCoroutine(DelayMethod(1, () => {Pause(); }));
    }

    private IEnumerator DelayMethod(int delayFrameCount, Action action)
    {
        for (var i = 0; i < delayFrameCount; i++)
        {
            yield return null;
        }
        action();
    }

    void SetTimelineTime(float f)
    {
        double d = ConvertFtoD(f);
        timeline.time = d;
        PlayOneFrame();
    }

    double ConvertFtoD(float f)
    {
        double d = (double)(f * length);
        return d;
    }

    float ConvertDtoF(double d)
    {
        float f = (float)d / (float)length;
        return f;
    }
}


使い方

[SerializeField]
PlayableDirector timeline;

[SerializeField]
Slider slider;

[SerializeField]
GameObject playButton;

[SerializeField]
GameObject pauseButton;

上記にPlayableDirectorとスライダー、再生ボタン、停止ボタンをあてがってください。

解説

Timelineの総時間は

length = timeline.duration;

で取得できます。ただし、これはdouble型で得られるため、float型に変換しないといけないので注意。

float ConvertDtoF(double d)
{
    float f = (float)d / (float)length;
    return f;
}

timeline.timeで現在の再生位置をdouble型で取得し、総時間で割ったあとにfloat型に変換して、0~1にしています。

time = ConvertDtoF(timeline.time);

スライダーを触ってtimelineの再生位置を変更する場合、timeline.Pause();してtimeline.time = xxx;だけではスライダー位置が動かないため、その後に1フレームだけ再生することでスライダー位置を動かすようにしています。

おわりに

Timelineの再生位置制御の需要は、なかなかないと思いますが、Animation制御を越えてカメラワークやエフェクトの巻き戻しとかを任意でやりたい場合に使えます✨よかったら使ってみてください。

八ツ橋まろん

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

uGUIで線を引く~その3:線の端は垂線にする~

はじめに

前回の続きです
uGUIで線を引く~その2:線の描画編~
線の先端が縦固定になってしまっているため、線の角度によって、変更するようにします

流れ

①実際に引きたい線(以下、本線という)の単位垂直ベクトルを求める
②本線の端座標から、垂直線を引く
③本線の端座標から、垂直線上の双方向にweight/2分ずらした座標を、メッシュ描画時に使用する

垂直ベクトルを求める

垂直のベクトルは内積が1なので、本線の傾きを表すベクトルを(x,y)、垂直ベクトルを(a,b)とすると
x * b + y * a = 1
が成り立つ。
手順③を考えると、単位ベクトルがほしいので、
a * a + b * b = 1
も成り立つようにする。
上記2つの式から、aとbを求めることができる。
....が!!ただ、実際計算したら、ルートを含むごちゃごちゃした式が出てきたので、シンプルに以下のようなコードにしてみた

①一度単位ベクトルにせず、a = 1として垂直ベクトルを取得
②Vector2のnormalizedプロパティで単位ベクトル取得

    private Vector2 CalcurateVerticalVector(Vector2 vec)
    {
        Vector2 v = new Vector2(1.0f, -vec.x / vec.y);
        return v.normalized;
    }

これを使って、以下のようなコードになりました

using UnityEngine;
using UnityEngine.UI;
public class UIOneLine : Graphic
{
    [SerializeField]
    private Vector2 _position1;
    [SerializeField]
    private Vector2 _position2;
    [SerializeField]
    private float _weight;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        // (1)過去の頂点を削除
        vh.Clear();

        // (2)垂直ベクトルの計算
        var pos1_to_2 = _position2 - _position1;
        var verticalVector = CalcurateVerticalVector(pos1_to_2);

        // (3)左下、左上のベクトルを計算
        var pos1Top = _position1 + verticalVector * -_weight / 2;
        var pos1Bottom = _position1 + verticalVector * _weight / 2;
        var pos2Top = _position2 + verticalVector * -_weight / 2;
        var pos2Bottom = _position2 + verticalVector * _weight / 2;

        // (4)頂点を頂点リストに追加
        AddVert(vh, pos1Top);
        AddVert(vh, pos1Bottom);
        AddVert(vh, pos2Top);
        AddVert(vh, pos2Bottom);


        // (5)頂点リストを元にメッシュを貼る
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(1, 2, 3);
    }
    private void AddVert(VertexHelper vh, Vector2 pos)
    {
        var vert = UIVertex.simpleVert;
        vert.position = pos;
        vert.color = color;
        vh.AddVert(vert);
    }

    private Vector2 CalcurateVerticalVector(Vector2 vec)
    {
        Vector2 v = new Vector2(1.0f, -vec.x / vec.y);
        return v.normalized;
    }
}

結果

できました!
VerticalOneLine.gif

おわりに

気が向いたら、LineRendererみたいに配列でもらった座標をもとに折れ線を作れるようにします

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

uGUIで線を引く~その3:線の端を垂線にする~

はじめに

前回の続きです
uGUIで線を引く~その2:線の描画編~
線の先端が縦固定になってしまっているため、線の角度によって、変更するようにします

流れ

①実際に引きたい線(以下、本線という)の単位垂直ベクトルを求める
②本線の端座標から、垂直線を引く
③本線の端座標から、垂直線上の双方向にweight/2分ずらした座標を、メッシュ描画時に使用する

垂直ベクトルを求める

垂直のベクトルは内積が1なので、本線の傾きを表すベクトルを(x,y)、垂直ベクトルを(a,b)とすると
x * b + y * a = 1
が成り立つ。
手順③を考えると、単位ベクトルがほしいので、
a * a + b * b = 1
も成り立つようにする。
上記2つの式から、aとbを求めることができる。
....が!!ただ、実際計算したら、ルートを含むごちゃごちゃした式が出てきたので、シンプルに以下のようなコードにしてみた

①一度単位ベクトルにせず、a = 1として垂直ベクトルを取得
②Vector2のnormalizedプロパティで単位ベクトル取得

    private Vector2 CalcurateVerticalVector(Vector2 vec)
    {
        Vector2 v = new Vector2(1.0f, -vec.x / vec.y);
        return v.normalized;
    }

これを使って、以下のようなコードになりました

using UnityEngine;
using UnityEngine.UI;
public class UIOneLine : Graphic
{
    [SerializeField]
    private Vector2 _position1;
    [SerializeField]
    private Vector2 _position2;
    [SerializeField]
    private float _weight;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        // (1)過去の頂点を削除
        vh.Clear();

        // (2)垂直ベクトルの計算
        var pos1_to_2 = _position2 - _position1;
        var verticalVector = CalcurateVerticalVector(pos1_to_2);

        // (3)左下、左上のベクトルを計算
        var pos1Top = _position1 + verticalVector * -_weight / 2;
        var pos1Bottom = _position1 + verticalVector * _weight / 2;
        var pos2Top = _position2 + verticalVector * -_weight / 2;
        var pos2Bottom = _position2 + verticalVector * _weight / 2;

        // (4)頂点を頂点リストに追加
        AddVert(vh, pos1Top);
        AddVert(vh, pos1Bottom);
        AddVert(vh, pos2Top);
        AddVert(vh, pos2Bottom);


        // (5)頂点リストを元にメッシュを貼る
        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(1, 2, 3);
    }
    private void AddVert(VertexHelper vh, Vector2 pos)
    {
        var vert = UIVertex.simpleVert;
        vert.position = pos;
        vert.color = color;
        vh.AddVert(vert);
    }

    private Vector2 CalcurateVerticalVector(Vector2 vec)
    {
        Vector2 v = new Vector2(1.0f, -vec.x / vec.y);
        return v.normalized;
    }
}

結果

できました!
VerticalOneLine.gif

おわりに

気が向いたら、LineRendererみたいに配列でもらった座標をもとに折れ線を作れるようにします

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

Oculus Questアプリが画像として表示され、起動が不安定になった時の対策

症状

  • Oculus Quest上で起動するが、VRアプリがただの画像として表示される、または黒い画像が表示される。
  • Oculus TVが落ちたり、繰り返し起動される。

対応

  1. Build Settings> Build Systemを internalからGradleに変更。
    (公式ドキュメントによると、internalは非推奨とのこと。https://docs.unity3d.com/ja/current/Manual/android-BuildProcess.html)

  2. Android StudioでインストールしたAndroidのバージョンに合わせて、player setting> minimum API levelを変更した。

最低APIレベルをどう変更したか

  • Android StudioでAPI level 21-29 インストール済み。
  • minimum API level 19から21に変更

3.再度ビルド&実行

version

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

UnityでListの最小値を取得する最速の方法

A*経路探索のコードを書いていてListの最小値を取得する最速の方法が気になったので検証してみました。
前回、速度比較のやり方を模索してみたのはこの為だったりします。

for文 vs List.Min

普通にfor文で全要素を一つづつ比較していくのとList.Minではどっちが速いんでしょうか?
ついでにLinqのOrderByしてFirstした場合も比較してみます。

コードは以下です。
0~100000のランダムな整数の入った要素数10万のListを宣言してその中から最小値を取得、
Profiler.BeginSample/EndSampleで、最小値を取得する部分だけを計測しています。

public class MinTest {

    //for文のやつ
    [Test]
    public void UseFor() {
        var randamValues = GetRandamValues();
        Profiler.BeginSample("MinTest.UseFor");
        var min = randamValues[0];
        for (int i = 1; i < randamValues.Count; i++) {
            min = randamValues[i] < min ? randamValues[i] : min;
        }
        Profiler.EndSample();
        Debug.Log(min);
    }

    //ListMin
    [Test]
    public void UseListMin() {
        var randamValues = GetRandamValues();
        Profiler.BeginSample("MinTest.UseListMin");
        var min = randamValues.Min();
        Profiler.EndSample();
        Debug.Log(min);
    }

    //OrderByしてFirst
    [Test]
    public void UseOrderByFirst() {
        var randamValues = GetRandamValues();
        Profiler.BeginSample("MinTest.UseOrderByFirst");
        var min = randamValues.OrderBy(x => x).First();
        Profiler.EndSample();
        Debug.Log(min);
    }

    List<int> GetRandamValues() {
        var randamValues = new List<int>();
        for (int i = 0; i < 100000; i++) {
            randamValues.Add(Random.Range(0, 100000));
        }
        return randamValues;
    }
}

結果

GC Alloc Time
UseFor 0B 1.18ms
UseListMin 40B 1.49ms
UseOrderByFirst 136B 3.92ms

for文 vs List.Minは速度には気にするほどの差は出ませんが、List.MinはGC Allocが発生してしまいますね。
Linqは普通に遅かった・・・

条件分岐の書き方で差は出るの?

やっぱり普通にfor文使うのが速いですね。
しかし、もう一つ気になることがあります。
条件分岐の書き方で差が出るかどうかです。

//for文のやつ
    [Test]
    public void UseFor() {
        var randamValues = GetRandamValues();
        Profiler.BeginSample("MinTest.UseFor");
        var min = randamValues[0];
        for (int i = 1; i < randamValues.Count; i++) {
            min = randamValues[i] < min ? randamValues[i] : min;//←ここの書き方で差は出るの?
        }
        Profiler.EndSample();
        Debug.Log(min);
    }

以下の3つを試してみます

①if文

if (randamValues[i] < min) min = randamValues[i];

②条件演算子

min = randamValues[i] < min ? randamValues[i] : min;

③条件演算子(条件式を反転)

min = randamValues[i] >= min ? min : randamValues[i];

結果

誤差で結果が上下するので、1000回の平均を計測しました。
微妙に差が出ますが、気にするレベルではないですね。

GC Alloc Time
①if文 0B 1.28120ms
②条件演算子 0B 1.26898ms
③条件演算子(条件式を反転) 0B 1.24765ms

まとめ

普通にfor文使うのが速いしGC Allocも発生しない!

ご意見ご感想、多分これが一番速いと思います等ありましたらコメントを頂けますと幸いです!

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