【Unity】インターフェースのデフォルト実装はビルド後の動作が違う話

対象読者

  • UnityのWebGLでのビルド後の動作が変わって困っている人

記事の内容

  • 特定の条件下でインターフェースのデフォルト実装を使用したとき、WebGLビルド後の動作が変わる原因の解説をする

実際に問題が発生するコード


public interface IHogeValue
{
   int Value { get; set; }

   int GetValue()
   {
      return Value;
   }
}

public class HogeParent : IHogeValue
{
   public int Value { get; set; }

   public HogeParent()
   {
      Value = 1;
   }
}

public class HogeChild : HogeParent, IHogeValue
{
   new public int Value { get; set; }

   public HogeChild()
   {
      Value = 2;
   }

}

public class Logger : MonoBehaviour
{
   void Start()
   {
      IHogeValue child = new HogeChild();
      Debug.Log(child.GetValue());
      Debug.Log(((IHogeValue)child).GetValue());
      Debug.Log(child.Value);
      Debug.Log(((IHogeValue)child).Value);
      Debug.Log(((HogeParent)child).Value);
   }
}

実際に動作させると、Loggerの出力結果は、Windowsビルドの場合は「1 1 2 1 1」、WebGLビルドの場合は「2 1 2 1 1」となります。

2つのビルドにおいて、変数「child」が保持している変数の値は同じで、プロパティから呼び出しても動作は同じですが、関数からプロパティを呼びだしときだけ、動作が変わります。

予防法

  • 曖昧なプロパティを作らない

今回の場合だと、「HogeChildのValue」が曖昧なプロパティです。

このプロパティを消すと、WindowsとWebGLで動作は同じになります。

問題が発生する条件

  • 子クラスと親クラスに付けられたinterfaceは異なるが、同名の関数を持っている
  • 継承しているinterfaceにデフォルト実装がされている
  • 同名関数内で同名プロパティを使った処理をする
  • 子クラスから、その関数を呼び出す

まとめ

  • WebGLビルドをするなら、インターフェースのデフォルト実装の使い方に注意
  • 普通の書き方をすれば、殆ど問題は発生しない
  • 特殊な書き方をしても、「曖昧な関数やプロパティ」を消す事で問題が解消できる場合がある

【javascript】async、awaitの正確な仕様を理解する

前提知識

Microtask queueMacrotask queueー非同期処理が格納されるキュー。setTimeoutはMacrotask、Promiseのコールバック関数はMicrotask queueである。

・処理の優先順位は、コールスタックに入れられた関数>Microtask queue>Macrotask queue。コールスタックが空になったとき、Microtask queueが呼ばれ、その両方が空になったとき、Macrotask queueが呼ばれる。

・要するに、大まかに言うと、処理の順番は、「非同期じゃないコード」→「Microtask queueに入れられた関数(キュー入れられた順)」→「Macrotask queueに入れられた関数(キューに入れられた順)」である。(途中で優先度の高い処理が追加されると前に戻る)

仕様① awaitが読み込まれたときの挙動

・awaitの行に差し掛かった時、awaitの右に書かれた処理を実行した後、async関数を中断し、async関数を「Microtask queue」に入れる。

以下はコード例。

async function hello()
{
    console.log("hello")
    var b = await 1
    console.log("hello"+b)
}

console.log("start")
hello()
console.log("end")

上を実行すると、start→hello→end→hello1の順でログが出力される。

仕様② async関数の返り値

・async関数の返り値Promiseオブジェクト。

・async内の処理が終わっていない場合は、stateが「Pending」であるPromiseを返す。

・async内の処理が終わっている場合は、stateが「Fulfilled」であるPromiseを返す。

returnは関数の返り値とはならない。返り値は常にPromiseオブジェクトで、その中にreturnの結果が保存される。

以下はコード例。

async function hello()
{
    var b = await 1
    console.log("resume!")
    return b
}

a = hello()
console.log(a)

window.setTimeout(() => {
    console.log(a)
}, 1000)

上を実行すると、stateが「Pending」であるPromiseオブジェクトが出力された後、「resume!」が出力され、1秒後にstateが「Fulfilled」であるPromiseオブジェクトが出力される。

仕様③ 実行順序に気を付ける

・実は、仕様②で書いたコードのsetTimeoutの秒数を0にしても、結果は同じです。

・何故なら、「Microtask queue」→「Macrotask queue」の順に実行されるからです。

【javascript】厄介なビット演算の仕様を完全に理解する

導入

かなり厄介なので、正確な動きをかいておきます。

前提知識

・javascriptでは、数の型はNumberのみ。整数も小数も関係なくNumber型。

・Number型は、倍精度浮動点小数点数型であり、IEEE 754という規格が使われている。

・C#でいえばdouble。

・Number型の整数の精度は53bit。つまり、-2**53+1以上2**53-1以下の整数は正確に表記できる。(例えば、2*53+1のbit表記は2**53と同じ。)

・int32のbit表記には、補数表現が使われている。

javascriptのビット演算

ビット演算を行うときのみ、int32として扱われる。(>>>を含む計算は、uint32として計算されます。)

ビット演算が終了すると、int32(またはuint32)から通常のNumber型に戻る

Number→intへの変換

①小数の場合は、近い整数に変換される。

②整数x を ((x % 2**32)+2**32) % 2**32に変換する。簡単に言えば、mod 2**32において等しい、0以上2**32-1の整数に変換する。

③その整数の2進数表記をint32とみなす。(演算が>>>の場合はuint32としてみなす。)

int32→Numberへの変換

・int32における整数nの値は、そのまま、Number型におけるnに変換される。

(bit表記は変わるが、数の世界では変化なし。)

整理

・>>>が含まれない場合、-2**31以上2**31-1以下の整数は、ビット演算の前と後では変化しない。

・-2**31-1以下の場合や2**31以上の整数の場合、値は変化する。

整数の精度に注意

-2**53以下の整数と2**53以上の整数を扱う場合、意図していない値になるので注意。

例えば、(2**53+1)^0は、上の変換通りに計算すると、変換後は1になりそうですが、実際は0になります。これは、2**53+1のbit表記が2**53である為です。

・-2*31^0は、-2**31 mod 2**32 = 2**31より2**31に変換され、これを2進数を表すと、100…00(32bit)になる。これをint32で解釈すると-2**31となるので、計算終了後はNumber型の-2**31になる。

・2**31^0は2進数で表すと、100…00(32bit)になる。これをint32で解釈すると、-2**31となるので、計算終了後はNumber型の-2**31になる。

・-2**31^0は、-2**31-1 mod 2**32=2**31-1より2**31-1に変換され、これを2進数で表すと、01111…1111(32bit)になる。これをint32で解釈すると、2**31-1となるので、計算終了後はNumber型の2**31-1になる。

・-1^0は、2**32-1に変換され、これを2進数で表すと、111…111(32bit)になる。これをint32で解釈すると、-1となるので、計算終了後はNumber型の-1になる。

・(2**31^0) + (2**31^0)は、-2**32になる。

参考文献

↓ かなり分かりやすいjavascriptの数値型に関する記事です。

https://qiita.com/uhyo/items/f9abb94bcc0374d7ed23

【javascript】サイト上で音を鳴らす方法【2024年】

導入

2018年、Googleのポリシーが変更になり、javascriptでただAudioオブジェクトを作って、play()を呼ぶだけでは、音を鳴らすことができなくなりました。

様々なサイトで、すでに無効になった上記の方法が紹介されていて、2018年以降の方法を紹介しているサイトがあまりなかったので、ここに方法を書いておきます。

方法

①ユーザーにサイトをクリックをさせる。

②AudioContextオブジェクトのresume()を呼ぶ。

③その後に音を鳴らす。

(①と②は逆でも良いです。)

コード例

const atx = new AudioContext();
atx.resume();
const snd = new Audio("オーディオファイルへのパス.mp3");
document.addEventListener("click",() => {
     snd.currentTime = 0;
     snd.play();
})

上のコードは、クリックすると音が鳴るコードです。

currentTime = 0は、再生場所を設定しています。再生場所を元に戻さないと、連続再生しても、前の音が終わるまで次の音が鳴りません。

また、AudioContextのstateというプロパティで「音声が許可されたかどうか」が確認できます。stateが「running」の場合は許可あり、「suspended」の場合は許可なし、です。

ブラウザゲームを作る場合は、ミュートをデフォルトの設定するか、クリックするとゲームが始まるように設計すると良さそうです。

参考文献

https://developer.chrome.com/blog/autoplay