2022年8月2日火曜日

重複排除記憶装置および動画骨組み形成圧縮機構

圧縮方法の基本的な考えとして、テキストファイルであればアルファベットの出現率が高い方から短い符号を割り当てて行って辞書化(キー・バリュー)して圧縮する方法がある。
これは1文字(1バイト)ずつ見て行く方式であるが、英語であれば英語の、日本語であれば日本語での文字のつながりの特徴・パターンがある訳で、そこまで考慮すればより効率の良い圧縮ができるであろうことは自明である。(というか実装されてるはず。)
(1ファイル中で全部英語とか、全部日本語とかであれば良さそうだが、複数言語が入り乱れているとオーバーヘッドが発生しそうである。また文法とか単語がルールに則っている間は良いが、突然(敢えて)変な言い方をしたり、敢えてスペルミスをした文章を載せる場合は、そこだけ「生(raw)情報 」としてエスケープしたりと、こちらもオーバーヘッドがかなり発生しそう。と言うことで実装はかなり大変そうである。)


今回のアイデアとしてはまず、テキストファイルであってもバイナリデータとみなして、パターンをとらえて圧縮するようにする。
そして、それを記憶装置全体に適用する、と言う物である。

解消されるポイントとしては、
・ファイル種別によってヘッダとか「決まりきった」書き方の箇所とかの重複した無駄な容量を節約できる。
・人によって同じようなファイルが集まる傾向がある(はず)なので、重複した無駄な容量を節約できる。(その人の作業で発生するバックアップファイルとかは少なくとも存在するだろう)
・つまり記憶装置全体としては使用者の特性にあった圧縮ができる。
といったところ。(話を分かりやすく単純化しているが、実際はどこまでも応用可能である。)

そういった情報を集めていけば、「現時点における、この組織の特性」が抽出できるので、以降はそのパラメータ決め打ちで圧縮していけば良い。
あわよくばクラウド全体・全世界に拡張できる。

問題点としては、
・パターン抽出の「枠の大きさ」はどれくらいが適切か。また「入れ子」も許容するのか。
・この方式はある時点の記憶装置全体の状態を基にパターン抽出する発想のため、変化に弱い。つまり書き込みがある場合には向いていない。
といったところか。

まず「枠の大きさ」「入れ子」についてだが、簡単に言うと最小公倍数で括り出した方が良いのか、最大公約数で括り出した方が良いのか、と言うこと。
最小公倍数で括ると言うのは、最初に書いた例だと英字であればアルファベットのeが一番出現率が高いので一番小さいサイズの符号に置き換えて、次は…と言った感じである。
そして入れ子というのは、一回符号化して出来上がったデータを再度パターン抽出して、今度は例えば2バイトの枠に広げて同じパターンを発生頻度の高い順に小さいサイズの符号に置き換えていく、ということ。
1回目の符号化であれば、符号化したものが1バイトを超えてしまっては圧縮にならない。
2回目の符号化であれば、符号化したものが2バイトを超えてしまっては圧縮にならない。
また、符号化したものが1回目のものなのか2回目のものなのか識別できる必要がある。
これを3回、4回と入れ子にしていけば圧縮率は高くなるかもしれないが、その分読み出す時の復号化の処理に負荷がかかることになる。
(1マシンで考えると大いなる無駄だろうが、クラウドで考えれば「復号化サーバ」がいて、全ての符号化辞書情報をメモリ上のキャッシュとして確保できるのであれば、このサーバにサーバに復号化だけ任せれば良いが、でもやはり処理要求が多すぎてI/Oは追いつかなそう。)

ということで、枠が小さすぎると細かくなりすぎて復号化が大変になるし、枠が大きいと復号化は簡単だけどあまり圧縮の意味がないため、どの程度の枠が圧縮率・読み出し速度のバランスとして最適なのかは実際のデータを基に計測する必要がある。

−−−−−−−−−−
アイデアの発端としては、もう編集されることがない決定文書とかアーカイブファイルとか動画とか、I/Oで言うと「読み取り」しかされないファイルは最大限容量を節約して良いのでは?と思った点。現在のストレージ技術では、全く同じファイルとかかなり似通ったファイルでも律儀にディスクスペースを使うしかないため、そこのブレークスルーになれば良いかと思った。

また、現在のクラウドストレージサービスでは「容量無制限」サービスは存在しないが、それは、そんなことをすれば絶対に誰かがディスクを「食い尽くす」人が出てくるだろうし、そう言う人を防ぎようがないからであろう。
しかし、このアイデアが実現すれば、そういった輩がどれだけ同じ巨大ファイルをストレージに書き込もうとしようが、それは全て同じ「符号」になるだけなので、「あぁ意味ないな」と悟ればそんな無意味なことをする人もいなくなるだろう。
そして「容量無制限」サービスも出てくるであろう。
※このアイデアを本気で実現すれば、もしかすると「ファイル容量」と言う考え方も根本的に変わってきそうである。

更にいっておくと、先ほどのストレージを食い尽くそうとする人の例では、同じ巨大ファイルで無駄にネットワークの帯域を使う必要もないことに気づくだろう。最初にハッシュ値などを確認してサーバから「アーカイブ済みですよ」応答があれば、クライアントは何も送らな行くて良い。
といったようにネットワークにもとても優しくなる。
(一部だけ符号化済みの場合は「xバイト目〜xバイト目まで送ってね」と返す、などなど。とは言っても「ファイル全体のハッシュ値」では差分は分からないため、この辺は要検討)

−−−−−−−−−−
なお、記憶装置(ストレージ)についてのアイデアであるが、アクセス権限やセキュリティについては、更に細かい検討が必要である。(アクセス権をちょっと考えると、符号化した方の情報にも、情報の元となったファイル&アクセス権の辞書が必要になりそう?)
このアイデアの特性が「書き込みに向いてない(リアルタイムの変更に弱い)」と言うことから、コンシュマー向けにはあまりならなそうである。
しかしビッグデータを抱える企業としては、ストレージの大改善になるアイデアであろう。
(復号化サーバとして考えれば、アクセス権はサーバとクライアント間だけの問題であるので、符号化データのアクセス権もシビアに考えなくて良さそうである。ただ、もしもこの装置をPCとかコンシュマー向けにも使いたい、となった場合には本機能の拡張版としてアクセス権対応版が必要になるだろう。)

ビッグデータとして例えば動画配信サービスであれば、似たような動画や時として「全く」同じ動画が多数あるはずで、それらを全て括り出せればものすごい容量の節約になるだろう。
動画圧縮技術次第だが、現在の方式では動画圧縮したものをストレージに格納しているはずで、圧縮動画から似た映像・同じ映像を括り出せれば良いのだが、それが不可である場合は一旦非圧縮データに展開してから映像を括り出さないといけないかもしれない。
しかし非圧縮データに展開して増える容量よりも、括り出しによって節約される容量の方が遥かに大きいだろう。
(また括り出し後に、今度は括り出し可能な圧縮方式で圧縮すれば良い。)

そもそも動画圧縮技術は、「隣接したピクセルは似通った色であることが多いはずだ」「隣接した時間では前後のフレームは連続的に移動しているはずだ(今の技術では加速度とかまで使ってる?)」と言ったアイデアで不可逆変換が主流であろう。

不可逆データ前提であるならば、括り出し時も「曖昧さ」パラメータによって、どれくらい似通っていれば同じ符号として置き換えて良いか調整ができそうである。曖昧さ:0 なら完全一致じゃないと許容しない。曖昧さを大きくするにつれて、似通った映像でも許可するが復号化した時にちょっと不自然になる、と言った感じだろう。

あと更に応用だが、動画であれば映像内部の一部の領域(静止画)についても同じアイデアが適用できそうである。つまり動画中で似通った・全く同じ静止画があるならばそこを括り出せる、と言うものである。(フレームに飾られた絵画とか)。ただしこちらは、たとえ全く同じ静止画であっても次のフレームではちょっと角度が変わったり、影がかかったりと、むしろ括り出し処理の方が難しそうである。
(余談だが動画も究極を言えば、元となった4次元データがあれば一番正確に再現できるのである。(当たり前か^^)。つまり上記で言ったフレームに飾られた絵画も角度が変わったり影が差したりと言ったことも、「絵画」データは1枚でよくて変化するのは絵画を入れているフレームの角度が変わったり、外部から影が差したりしているだけで、3Dで計算すれば良いだけの話、と言うこと。)
余談で書いたが、これも良いアイデアなので記録として残しておくこととする。
アップロードされた動画から3次元データを作成して、それを骨組みとすることで角度変化や影の差し具合などは「演算化」してしまうことによって圧縮する技術である。

なんと言うか、そこまで考えると、動画作成時に3D情報があるのであれば、わざわざ3D情報を削除することもないように思えてくる。(なんでせっかくの大切な情報をあえて削ってるの?と言うこと)。将来的には動画には3D情報もくっつけるのが主流になりそうである。

(画像認識技術でも、行き着くところは「欠落しているのは3D情報だな」と「悟った」人はたくさんいると思うのだが、画像に3D情報が付いているのが当たり前になれば「これまでの悩みは何だったの?」となるだろう。しかしこういった積み重ねれこそが技術革新なのであろう。)
(「ゲームエンジンのように画像全体をレンダリングをすると言うこと?」と思われるかもしれないが、そうではない。そんなことをしたら読み出し処理が追いつかない。
静止画や動画とレンダリングの最適解があるはずで、このアイデアをベースに最適解を探っていきましょう、と言うことである。(圧縮率、品質、エンコード負荷、デコード負荷などの総合的な最適解。圧縮技術の側面から3Dゲームエンジンを見ると、「テクスチャは容易には変わらない」と言う前提で圧縮していると解釈できたりする。テクスチャで解決できないもの、例えば髪の毛であっても最近は物凄い精度の良いモデルもあるわけで、これは言わば擬似的な近似値であり、髪の毛全てをシミュレーションしている訳ではないが、見る側としては実物と遜色のないものとして見えるため、または見えるように技術進歩してきた訳である。動画圧縮の前提として不可逆変換を前提にしている場合、つまりそれは各フレームの画像は「近似値」になっている訳であるから、本アイデアでも極端な話をすると3D化する時に髪の毛も「髪の毛近似モデル」に置き換えてしまってもいいのかもしれない。細かいパラメータは当然あると思うが。そもそも3D情報もゲームエンジンで作った場合であれば正確な情報だろうが、現実世界を撮った動画から3D情報を生成した場合は、その時点で近似値であるわけで、「近似値で良いのか?」と言う問いに対しては「では【正確な値】とは何か?」と問い直すことができるのである。それこそ映像の世界でも本当にただのレンズをそのまま撮ったのでは歪みとかぼやけとか手ぶれとか色々あるわけで、各種培われた「補正」が入っている訳である。補正をしてしまった時点でそれは既に「近似値」といえよう。物理学的な話になってきたが、実際に【正確な値】を計測することはとても大変なのである。また、もしも【正確な値】が取れたとしても、それを実際に人が「自然に」見えるようにするためには、今度は逆の手順が必要で、復元した情報を映像化する時に再度歪みとかぼやけとか手ぶれとかを結局は補正しなければならないのである。(ご存知の通り人が見る「色」の特性一つとっても奥が深い。プロから見ると「何だこの色使いは。というか何も補正してないのか?」と思われたりするのだが、では色を補正した画像は「本物」なのだろうか?)
ちょうど「本物か嘘か」と言うポイントが出てきたため、ちょっと書いておくと、
・本物を土台にして「近似」したもの
・本物のデータの一部または全てを、近似(補正)したり、モデルまたはシミュレーション(架空)データで置き換えたもの
を考えた場合、前者は本物または本物ベースのもの、
後者は嘘、そこまで明確に言わなくとも架空データで本物を模擬したもの、
と言えるだろう。
動画を楽しむ分には、そんなのどちらでも良いのかもしれないが、どの分野にも「完璧な正確性」を求めたり、求められたりする領域がある訳で、やはりどこかで真面目に議論しなければならないことである。例えば写真やビデオが証拠になったりする訳だが、担当者が気を利かせて色補正をしてしまったら、それは「本物」として扱っていいのか?と言うこと。先ほども書いたが、そもそも現代の最先端カメラでは写真を撮る時点で映像のプロによって培われた各種の補正が入ってしまっている訳で、そもそも「本物」として扱ってはいけないのではないか?と言う話になってくるのである。カメラについてはメーカーがどこで型番が何で、と言う情報で写真の特性も分かるため合わせ技で現代では「正確な」証拠として取り扱っている、と言うだけのことであろう。
しかし補正が入っていると言うことは、ある情報は強化され、ある情報は弱まったり省略されていると言うことなので、その特性を悪い方に活用されると極端な話「見えていたはずのものも見えない」写真や「見えてないはずのものが見える」写真も作れるはずである。そして現代ではそれさえも「正確な」証拠になってしまうのである!と言うこと。現代では「実際のカメラでそんな映像が撮れることは確率的には著しく低いため、取るに足らない」と割り切っているだけなのである。
上記はシビアするぎる話だが技術と共に成長していかなければならない問題である。
もう少し話を緩めると、例えば広告・虚偽広告が挙げられる。例えば本物の映像を近似値で補正するのはいいけど、シミュレーションデータに置き換えた場合、それは許されるのか?といった問題。どちらかというと倫理的な問題であろう。この辺は勿論経済の力学に沿って、Googleとかが先手を打って倫理観とかをまとめていくのであろう。(先手を打ってというか、大きいだけに何でも最初に槍玉に上げられる、と言ったほうが正確だろうか。))

3D情報ベースで書いたが、成果物ベースからもう一度見直せば、最終的な2D画像に直接関係しない3D情報は無論省略可能である。ただしこれくらい進化すれば、最終成果物として1本の動画だけにするのは勿体無い気がする。既に360°動画もあるわけだが、本アイデアは2D動画ベースなので、ちょっと違ってて、例えば2D動画なんだけどちょっとだけ視線を変えられたり、3Dカメラで撮影しなくても3D映像化ができる、とかであろうか。)

−−−−−−−−−−
ちなみに動画配信については以前に「ホログラムストリーミング」を発表済みである。

2022年7月28日木曜日

小説になるマンガ

最初はマンガなんだけど、説明文がどんどん長くなっていき、
気づいたら小説になってしまうマンガ。というアイデア。
本の枠だけ漫画になっているアイデアもアリ。
または漫画の中の人が漫画を読んでて、いつの間にかそっちがメインになるアイデアもアリ。
(くだらないけど、面白いのでアイデアとして残しておく)

2022年5月24日火曜日

Unreal Engine 5でAndroidパッケージ作成 on MacBook Pro M1

MacBook Pro M1上のUnreal Engine 5でAndroid用パッケージ(apk)を作成する環境を構築した。
その時に発生したエラーと対処法について。
※最後に詳細を書いてますが、エラー②についてはWindowsユーザの方も参考になるかも知れません。

【環境】
MacBook Pro M1 Pro (2021年モデル) macOS Monterey
Unreal Engine 5.0.1
Android Studio Chipmunk | 2021.2.1 ※Android Studio Bumblebeeでも同じだと思う。

【経緯及びエラー内容】
Unreal Engineの以下のページを参照すると、「Android Studio version 4.0」とか古いAndroid Studioしか正式には対応してなさそう。

実際には、最新の「Android Studio Chipmunk」でもできたのだが、
正式手順ではないため、あくまでも自己責任でお願いします。

環境的には、Android Studio Chipmunkインストール済みの環境で、FlutterでAndroidアプリ開発を行なって、アプリをGoogle Play Storeにリリースまでしている環境。つまりそれまではUnreal Engineは入れてなかった環境にUnreal Engine 5 を入れた状態。(なのでAndroid Studio version 4.0とか古いものは入れたくなかった。)

よって、上記リンクで言うところの
1.Android Studio をインストールする
2. Android Studio を設定する (初回ユーザーの場合)
※2.10 Android SDK Command-line Tools についてもインストール済みだった。
はスキップして、
3. Setting Up Android NDK
から実行。
なお、mac上のSetupAndroid スクリプトの場所は以下。
/Users/Shared/Epic Games/UE_5.0/Engine/Extras/Android/SetupAndroid.command

【エラー①】
SetupAndroid.command  67行目あたりで、JAVA_HOMEを設定しているが、これがAndroid Studio 4の古い設定(Java 1.8用)になっている。新しいAndroid StudioだとJava 11。
export JAVA_HOME="$STUDIO_PATH/Contents/jre/jdk/Contents/Home"

対策①: 新しいAndroid Studioの付属jdkを既にJAVA_HOME環境設定に設定済みの場合
つまりprintenvで以下の結果になる場合
> printenv |grep JAVA_HOME
JAVA_HOME=/Applications/Android Studio.app/Contents/jre/Contents/Home

この場合は、単純にSetupAndroid.commandのexport JAVA_HOMEをコメントアウトすれば良い。
export JAVA_HOME="$STUDIO_PATH/Contents/jre/jdk/Contents/Home"
# export JAVA_HOME="$STUDIO_PATH/Contents/jre/jdk/Contents/Home"

対策②: JAVA_HOME環境変数が未設定の場合
この場合は、SetupAndroid.commandのexport JAVA_HOMEのパスを修正すればよい。
export JAVA_HOME="$STUDIO_PATH/Contents/jre/jdk/Contents/Home"
↓
export JAVA_HOME="$STUDIO_PATH/Contents/jre/Contents/Home"

※なお、SetupAndroid.commandを実行すると、
SetupAndroid.command: line 20: rem: command not found
が出力され、実際にスクリプトを見てみると確かにrem、つまりDOSコマンドの「コメントアウト」キーワードが使用されている^^ しかしエラーで止まるわけでもなくスルーされるだけなので気にしなくて大丈夫(後でEpicに教えるだけ教えておこう^^ Unreal Engine 4.x の頃からこのままだっただろうし、ずっと放置されてきたのだろうから、Mac + Unreal Engine + Android開発はものすごくマイナーなのだと、逆に知ることができるのである^^)

P.S. 2022.10
Unreal Engine 5.1.0 (現時点ではPreview版) にて、上記remは修正されました!よかったよかった👍
rem hardcoded versions for compatibility with non-Turnkey manual running
↓
# hardcoded versions for compatibility with non-Turnkey manual running


【エラー②】
上記修正をして、再度SetupAndroid.commandを実行すると今度は以下のエラーが発生。
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema

色々調べたところ以下のようにSetupAndroid.commandを修正すればよかった。
(83行目あたり) 
(修正前)
SDKMANAGERPATH="$STUDIO_SDK_PATH/tools/bin"
if [ ! -d "$SDKMANAGERPATH" ]; then
        SDKMANAGERPATH="$STUDIO_SDK_PATH/cmdline-tools/latest/bin"
        if [ ! -d "$SDKMANAGERPATH" ]; then
                echo Unable to locate sdkmanager. Did you run Android Studio and install cmdline-tools after installing?
                ${PAUSE}
                exit 1
        fi
fi

↓
(修正後)
#SDKMANAGERPATH="$STUDIO_SDK_PATH/tools/bin"
#if [ ! -d "$SDKMANAGERPATH" ]; then
        SDKMANAGERPATH="$STUDIO_SDK_PATH/cmdline-tools/latest/bin"
        if [ ! -d "$SDKMANAGERPATH" ]; then
                echo Unable to locate sdkmanager. Did you run Android Studio and install cmdline-tools after installing?
                ${PAUSE}
                exit 1
        fi
#fi

つまり、使用するsdkmanagerが違っていた問題。(これもJava 8 → 11 問題か)
$STUDIO_SDK_PATH/tools/binの方をコメントアウトして、追加で入れたCommand Line Toolsの方を使うように修正すればエラー発生しなくなった。

ーーー
エラー①のJAVA_HOMEについては、Windows用とMac用とで、JAVA_HOME環境変数の判定処理が異なっている。
Windows用(SetupAndroid.bat): 最初にJAVA_HOME環境変数が定義されてるかチェック
Mac用(SetupAndroid.command): 最初に強制的にJAVA_HOME環境変数を定義(export)

ただし、エラー②については、Windows用とMac用とで同じ処理内容だったため、エラー②の問題についてはWindowsユーザの方も参考になるかも知れない。
ーーー

【エラー③】
これで再度Unreal Engine 5 でAndroidのパッケージ作成( Platforms → Android → Package Project)を行うと、最後のapk作成部分で以下のエラーが発生する。
…
Making .apk with Gradle...
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/6.1.1/userguide/gradle_daemon.html.
Daemon will be stopped at the end of the build stopping after processing

FAILURE: Build failed with an exception.

* Where:
Settings file '/プロジェクトフォルダ/Intermediate/Android/arm64/gradle/settings.gradle' line: 9

* What went wrong:
A problem occurred evaluating settings 'app'.
> AFSProject/gradle.properties (No such file or directory)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

対応方法としては、上記* Where: に書かれているsettings.gradleファイルをエディタで開き、以下の修正をしてファイルを保存する。
new File('xxx')
↓
file('xxx')

※「new F」を「f」に置換すると簡単。
settings.gradleファイルでは「new File('xxx')」を使ってる箇所が8箇所あるので、8箇所とも修正する。

※Unreal Engineしか知らない人からすると「Gradle」が突然出てきて「何だそれは?」と思われるかもしれないが、AndroidとかJavaとか開発してる人からすると日常的にお世話になっているシロモノ。AndroidとかJavaに詳しい人に聞いてみましょう。

エラー内容としては、gradleで「new File('xxx')」の書き方をすると、ビルドフォルダからの相対パスではなく、gradleデーモンのワーキングディレクトリからのパスと解釈されてしまうらしく、よって「AFSProject」という親フォルダが存在しないので、「gradle.properties」ファイルも作成できませんよ、というエラー。
なお、gradleデーモンのワーキングディレクトリ及び実際に作ろうとしてたファイルパスは私の環境では以下だった。
/Users/user_x/.gradle/daemon/6.1.1/AFSProject/gradle.properties
よってこれを「file('xxx')」関数にしてあげれば、ビルドフォルダからの相対パスとして正常処理できるようになる。
※ただしこれもgradleというか、Android Studioに紐づいたgradleのバージョンの問題かも知れない。(流石に一度も動かないスクリプトを上げるわけないと思うのでAndroid Studio 4ではうまく行くのだろう。でもgradleバージョンによってnew File()の起点が変わるのは結構破壊的変更なので、gradleの環境周りとかもっと深い話かも知れない。要はその辺の微妙なところがバージョンの違いでうまく噛み合っていないっぽい。)

P.S. Gradleリファレンスで以下を見つけた。

やはりnew File()だとCurrent Working Directory(CWD)からの相対パスになってるとのこと。そうすると、Android Studio 4 バージョンだった時の可能性としては以下があげられる。
可能性①: 以前のgradleバージョンではnew File()もfile()同様、ビルドフォルダからの相対パスになっていた。(つまりどこかでnew File()の破壊的変更があった。)
可能性②: gradleバージョン差異の影響で、change directory(cd)が処理されておらずCWDが変更されてなかった。(特にエラーも発生せずに)。またはgradleのchange directoryの解釈が変わったりしてCWDとしては変わっていなかった。
可能性③: やっぱりAndroid Studio 4でもこの箇所でエラーになる^^(つまり一度も動いてないスクリプトだった^^)

可能性は絞れたが、ここではそこまでは追わない。。



次の手順が注意が必要であるが、上記修正&保存をしたら、ターミナルを開いて自分でgradleを動かしてあげる必要がある。
理由:UE側でAndroidのパッケージ作成( Platforms → Android → Package Project)を行うと、修正したファイルが毎回上書き更新されてnew File('xxx')に戻ってしまうため。ちょっとだけ追ってみたところ、該当settings.gradleはUE側のスクリプトで生成しているようです。
> cd /プロジェクトフォルダ/Intermediate/Android/arm64/gradle/
> ./gradlew assembleDebug
「assembleDebug」はデバッグ用apkを作成するタスク(コマンド)。目的に合わせてタスクは変えて下さい。タスク一覧は「./gradlew :tasks」で確認可能。どのタスクを使うとかはAndroid Studioの話になってくるので、Android Studioリファレンスを参照して下さい。

これで無事にapkが作成できた!ただしapk出力パスはUnreal Engineで指定した場所ではなかったため注意。私の環境の場合は以下になっていた。
/プロジェクトフォルダ/Binaries/Android/test_app-arm64.apk
※apkの出力先が異なっているため、手動でgradlewを実行する時のパラメータが足りてないかも知れない。assembleDebugしか試してないが、Play Store用にApp Bundleを作成する場合は証明書が必要となるため、その辺のパラメータもUnreal Engine側で設定した値は正常に引き継がれないかも知れない。ということで、App Bundle作成時はもっと複雑な問題が出てくると思われる。gradlewの引数を含めた実行コマンドが不明なので何とも言えないが、Unreal Engine側で動的に渡している場合は、もう人の手で補えるレベルのものではないかも知れない。(最後に結論として書くが、素直にAndroid Studio version 4を別途入れた方が良いかも^^)

これで、Androidエミュレータとか実機Android端末をつないで、adbコマンドでインストールすれば実行可能!
> cd /プロジェクトフォルダ/Binaries/Android
> adb install test_app-arm64.apk
Performing Streamed Install
Success

※ちなみにUnreal Engineでデフォルト設定でAndroidアプリ作って動かした時に「No Google Play Store Key. No OBB found...」エラーが発生する問題があるのだが、それはまた別問題なので、各種Q/Aサイトをご参照ください。



私の場合は、Unreal Enginの以下の設定をして上記エラーが発生しなくなり、アプリ起動に成功した。
Project Settings
 Platforms
  Android
   Package game data inside .apk? : Yes/Checked
   Disable verify OBB on first start/update. : Yes/Checked
   Force small OBB files. : Yes/Checked

 Project
  Packaging
   Full Rebuild : Yes/Checked
上記変更後は、Unreal Engine側で再度Android Package Projectが必要。そうすると、settings.gradleは毎回上書きされてしまうため、再度settings.gradleでエラー発生するので、「new File()」を書き換えて、手動でgradle実行となる。

もう一つの注意点としては、「adb install」する前に対象Android端末からは一回アプリをアンインストールしておいた方が良い。
(私は最終的に色々試したapkで、上書きadb installだとダメだったが、アンインストール後に全く同じapkで「adb install」したら正常起動できた。なので、Unreal Engine側の設定で一体どの項目がクリティカルだったのかは不明。)

こちらが実際にAndroid端末で動作したテストアプリの画面キャプチャー。黄色いでかいキャラクターは世界的人気ゲーム「ROBLOX」の「NOOB(ヌーブ)」です^^一体何のゲームを作ろうとしてるのか?って感じですね。

という訳で、無事にMac + Unreal Engine 5.0 + Android Studio Chipmunk でAndroidアプリのパッケージ作成が出来たわけだが、こんなに煩雑なことをするのなら、素直にAndroid Studio version 4.0 を入れた方が手っ取り早そうである^^という結論に達した。
(おそらく)Unreal Engine側としても最新Android Studio対応はしてくれてる/念頭にはおいてくれてると思うので、最新Andorid Studio連携はそれを待っても良いかも知れない。(最初に載せたリンクも「Unreal 4.25 以降は」となっているが、まだUnreal Engine 5.0 専用のページはない。)

ーーー
P.S. 2022.10 再度リンク先ページを確認したところ「Unreal 4.25 以降は」の表記が消えていた。そして内容はAndroid Studio 4.0のままだった。つまりUnreal Engine 5.0 ではやはりAndroid Studio 4.0のまま据え置く結果になった模様。。「最新Andorid Studio連携を待ったほうが良いかも」と書いたがUE5では可能性が薄れ、当分先になりそうなので、諦めて公式リファレンス通りAndroid Studio 4.0 を使いましょう!^^
ーーー


「どうしてもAndroid Studio環境を汚したくない!」という方は参考にしていただければ幸いである。(Unreal Engine側でNDKとか入れるので影響は少なからずあるのだが。。)

あと何点か、Mac + Unreal のマイナスポイントを上げておく。
・そもそもWindows用にパッケージ作成できない(ですよね?ちょっと調べただけだけど。どうしてもDirect Xの壁があるからそりゃ無理なのかな?逆にWindows版Unrealだとios版が作れる?ので羨ましい。)
・Unreal Engineライブラリで使えるものが少ない。ほとんどWindows用ばかり。UE5リリース時に話題になってた映画Matrixの「Cityサンプル」も、Microsoftの音響システム「 Project Acoustics」 もWindows版だけとか。せっかく「いいな」と思ったものを見つけてもMacだと試せない。。MacではNatiteは対応されていないし対応予定もないので、Cityサンプルを試すのはやはり無理っぽい。
There is no Mac OS support for Nanite, nor is any likely in the future due to 
graphics API limitations.
(Google翻訳)
Nanite に対する Mac OS のサポートはありません。また、グラフィックス API の制限により、
今後もサポートされる可能性はありません。

NANITE FOR EDUCATORS AND STUDENTS より抜粋
※Mac環境&UE5.1 で影が以前より濃くなる現象に直面した。根本解決ではないが、とりあえずの回避策を見つけたので、こちらを参照。

ーーー
そして、こちらもそもそも話だが、ゲームを「遊ぶ」側としてもEpic Games StoreのゲームはWindows版ばかりである。

ということで、本格的にゲーム開発する人は当然Windowsマシンで開発するのだろう。
「Windowsのゲームを本格的に開発したいので、Macを買いました」という人がいたら「なんでそうなるの?」となるだろう。
ゲーム + Mac + Unreal はモバイルゲーム開発とか、またはMacが好きでMac用ゲームを作りたいとかのパターンが考えられる。しかしこれも現時点での私個人の主観でしかないし、最近ではモバイルからPCへの逆パターンもあったり、MacからWindowsとかの流れもなきにしもあらずだと思う(少なくともJavaのEclipseとかか始まり、今ではAndroid Studio然りUnityやUnreal Engine然りというように「IDE」はWin&Mac両対応が当たり前になっている)ため、これもまた自己責任でこの辺の動向はウォッチしていって自分に合ったやり方・環境を見つけてくれればと思います。
(会社とか組織規模で考えれば、会社内にWindowsマシンもある訳で最終的なapk(aab)出力はWindows環境でやれば良い訳で、別にMacでapk(aab)出力を出力することにこだわることはないのかも知れない^^
と、そこまで話が遡ってしまうと、今回の話も、またUnreal EngingeがMac用にもAndroidパッケージ出力方法(手順)を作っている意味も無くなってしまうので、私も含めて「それしかない環境」でやっている人もいる訳で、また大きく言えばプラットフォームの(純粋な意味での)垣根を越えるという点に寄与しているという点において、意味はあるのであろう。※「純粋な意味での垣根を越える」とは、お互いにWin-Win関係になれる、という意味。
ちょっと湾曲的すぎて何を言ってるのかわけがわからないと思うので、簡単にいうと「クリエーターからするとEpicとAppleの訴訟なんて知ったこっちゃなくて、せっかく素晴らしい3DCGエンジンがあるのだから、グラフィックとかアート分野に強いMacでも使わせて欲しい!」と言ったところだろう。それこそFortniteとかちゃちい話(失礼!)ではなく、Win-Winになれると思うのだが。。
かと言って、実際問題としてこのように影響があるのだから、訴訟の話も無視するわけにはいかず、話はぐるぐる回るわけである。。)

2022年4月22日金曜日

無駄に段落分けしたサイト

最近調べ物をしていると、無駄に段落分けされたサイトがよく出てくる。
おそらく皆さんも同じことを感じているだろう。

内容的には1つの段落で十分かけることであるのに、わざわざ章立てしていたりして
いくら下に進んでも答えが出てこない。
(挙げ句の果てに「続きは次ページで」になってたりする。そんなに壮大なことを書いているのだろうか?その壮大さに気付けずにすいませんとでも言って欲しいのだろうか?)

これがインターネットの発展に寄与していると言えるのか?
その判断はGoogleなど検索サイト次第であるが、ユーザのためを思って
あまり上位には持ってきて欲しくないものである。
(最低限そのページを作った人は「素晴らしく人類に寄与しているサイトだ!」と思ってるはずだが、本当に自分の目で見て確かめてそう思っているのだろうか?)

ーーー
一応上記のようなサイトをフォローしておくと、そのサイトの規則的に
必ず章立てすることとか決まってて、書いてる人は「しょうがなく」
書いているのかもしれない。
ではそのサイトの管理者はなぜ、どんなに薄い内容でも無駄な章立てを
強要しているのか、となってくるが、もしもそれが広告表示回数を上げるため
であるならば、それこそGoogleなど広告提供側から厳しくチェックされる
ことになるだろう。
(ある意味自業自得で放っておけば勝手に検索順位は下げられそうなので
 まぁいいか。。)

ーーー
P.S. 2022.8
ついにGoogleが検索にておいて、低品質な結果をあまり表示しないように更新するとニュースが出てました。ようやく私を含めた皆さんのお怒りのようなものが伝わったんですかね^^
(本当はGoogleが主体的に日々検索エンジンを「有益な」ものにするよう改善してるのだと思いますが。)

これで広告収入のために頑張って章分けをしていたサイトは上位には出てこなくなると思います。まぁ彼らも「こんなウマイことはない」と思いながらやってたでしょうから、短期間でも稼げてよかったのではないでしょうか?
(ただし、新しいエンジンの特性を見抜いて対処するというイタチごっこは続くかもしれませんが。。)

2022年4月19日火曜日

天の川銀河・ミルキーウェー銀河・Milky Way Galaxy について

天の川銀河・Milky Way Galaxy というのは、地球人が我らの銀河を
内側から見た時に白い帯が天の川のようだからそう呼ばれているためである。

一方で我らの銀河以外の銀河については、地球から見えた時の色や形で
愛称をつけたりしている。

そこで、将来的なことを踏まえて、この天の川銀河・Milky Way Galaxy という
名称について考察しておこう。

簡単に考えると次の2段階。
①我らが銀河内の別の生命社会が存在したとして、彼らが我らが銀河を何と呼んでいるか。
彼らと我ら(地球人)で名称を統一する話になった場合に何になるか。
我ら地球人は、地球上で見ると、我らが銀河は白く見えるのだが、彼らの環境だと違う
ように見えてまた違った発想の呼び方かもしれない。

②我らが銀河の別の生命社会が存在したとして、彼らが我らが銀河を何と呼んでいるか。
彼らと我ら(地球人)で名称を統一する話になった場合に何になるか。

①で地球外の生命社会が存在しなかった場合とか、存在したとしても諸事情で①は
スキップするかもしれない。
また、いずれ②まで考慮することになる訳であるから②の方向で考えてしまった方が
手っ取り早くもある。
もう一点、「天に見える川」とか、「空に見えるミルクのように白い川・道」というのは地球以外の星でも同じような呼び方をしている可能性が高くて、①とか②とかになった時に彼らと我らで呼称が被ってしまうと思われる。

という訳で、他の分野でもそうだが、あくまでも「客観的に」自分を見つめて
愛称をつけた方がよさそうである。

果たして我らが銀河は、外から見るとどのように見えるのだろうか?

ーーーーーーーーーーーーーーーーーーーーーーー
かなりの取り越し苦労ではあるが、一方で最近は「物理的にそこへ行くことよりも
そこに行ったことを仮定する」方が重要で有用であると、人類は気づき始めているため、
意外と早くこの点も考慮される日が来るかもしれない。

ーーーーーーーーーーーーーーーーーーーーーーー
客観的な特徴とは何だろうか?
例えば銀河が可視光領域で全体的に赤っぽいのであれば「赤い」のが特徴。
しかしその赤色の原因は、その銀河が含んでいる物質的なものなのか、赤方偏移によるものなのか。
前者はその銀河の特徴となり得そうだが、後者はやはり観測される側と観測する側という相対的な要因を孕んでしまう。

特徴を述べる時にはもっぱら視覚情報によるものが多い。
これは視覚情報が情報として多く、特徴を共有するのに適しているからとも言えるだろう。
しかしこれまでの考察の通り、銀河レベルで「公平な」特徴を言う時はどうやら限界がありそうである。
現在までのところは共有相手は地球内であるため、地球から見える色とか形で一向に問題はない。※形についても地球から見た角度だと、そう見えるだけであって、地球と反対側の人(宇宙人)に伝える時点で「特徴」情報としてふさわしくないことに気付く。

よって「公平な」特徴は、極論すればその銀河内の全ての情報を元に導き出されるものの方が良さそうである。
すでにここまでの話でも端々に出てきているが、特徴とは別個体との情報共有とか、または対象を「大雑把に」把握するためのものであり、つまり外部からの「観測」によるものである。観測で使われるのは地球人の場合は所謂五感(視覚、聴覚、触覚、味覚、嗅覚)であるのだが、宇宙生命体ではもっと多くの感覚があるかもしれない。(例えば重力波も知覚できる感覚など。これは突拍子もないアイデアで全くの空想であるが、例えば惑星とかそれこそ銀河規模の生命体が「いた」として、その生命体は一体何を知覚するのだろうか。地球上の生物はなぜ知覚するかというと生命維持が大きな目的、最初の出発点になっているのだが、宇宙規模の生命体は純粋に知覚することを目的としてそうである。生命の何たるかが解明されて、人工的に生み出すことができるとすれば、それら宇宙規模の「感覚を持った生命」も生成されうる、と言う思考実験。)

脱線してしまったが、銀河の特徴を抽出する際に、観測者の(地球人でいうところの)五感の情報をフルに使って特徴抽出した方が良いだろう、ということである。少なくとも公平度合いは増すだろうということ。

得られた情報を3次元なり4次元空間にプロットしてみて、座標をぐりぐり回して眺めたりして、どの方角から見ても、というか「全体」として何かにそっくりだったとすれば、その名前を拝借するとかが考えられる。例えば3Dに可視光または何かの電磁波をプロットしてぐりぐり見てみたら、地球上の蝶にそっくりだったとすれば「地球上の蝶」銀河と名前をつける、とかである。ここでの例は結局視覚による認識になってしまっているが、あくまで分かりやすい例え話のためである。しかし人が知覚できない電磁波領域の特徴を掴んだとしても、共有相手も人と同じ可視光領域しか持たない生物だったとしたら、果たして伝えるときの「特徴」として相応しいのかどうか?という問題点もある。

そもそもの話に戻り「そもそも特徴というのは個体間の情報共有のためのものであり、厳密な意味はないので、考えるだけ無駄」と言うのであれば、振り出しに戻るのであるが、そうすると今度はやはり来るべき銀河時代にどうやって情報共有するのか?と言う話に戻るのである^^
「そういった時代になれば、その時の人が勝手に色々考えるだろう」と言うのであれば、最初に書いた通りのただの取り越し苦労と言うだけの話なのであるが、だからと言って人のこういった知的探求は誰にも止められないのである。と、体裁よく話をまとめて終わりとしよう。

ーーーーーーーーーーーーーーーーーーーーーーー
やはりもう少しだけ書いておこう^^
「地球人の所謂五感」と書いたが、大抵の場合は系の中心に恒星があってそれが重要なエネルギー源になっていたり、また現代の科学情報ではどうやら生命の起源には「水」が重要そうで液体としての水が大量に存在し得る条件が大切らしい。そこで「生命」という枠だけで、しかも「知覚」とうことだけで考えれば、もしかすると地球外・太陽系外・天の川銀河外の生命体もある程度は同じような感覚を発展させるのかもしれない。(もちろん地球上にも深海には視覚は弱まって触覚とか別器官で生存する生物もいると言う点は考慮が必要。)

また、逆に言うと生命というものは自分が生存する環境に適するように、つまり重要な情報を知覚できるように進化の発展を遂げていると言えるわけであるから、その環境の特徴とはつまりその環境に住む生物がどういった知覚器官を備えたり発達させているかを知ることで分かる、とも言えるだろう。

2022年3月23日水曜日

MacBook Pro M1 Pro (2021年モデル) に買い換えた話 (新旧比較表付き)

と言うわけでついにMacBook Proを2009年モデルから2021年モデルに買い換えた!

「どう言うわけか?」と言う方はこちらをどうぞ。

せっかくなので比較しておこう。


BeforeAfter
ModelMacBook Pro
13-inch, Mid 2009
MacBook Pro
14-inch, 2021
Display13.3インチ
クリアワイドスクリーンディスプレイ
14.2インチ
Liquid Retina XDRディスプレイ
CPUIntel Core 2 Duo
2.26GHz
Apple M1 Pro
10 Cores
GPUNVIDIA GeForce 9400M 256MBApple M1 Pro
16 Cores
Memory2GB DDR3
→ 8GBに交換
 2016年 ¥9,300
16GB
LPDDR5
Storage160GB HDD
→240GB SSDに交換
 2016年 ¥10,260
1TB SSD
Battery60Whリチウムポリマーバッテリー70Whリチウムポリマーバッテリー
Height2.41 cm1.55 cm
Width32.5 cm31.26 cm
Depth22.7 cm22.12 cm
Weight2.04 kg1.6 kg
Keyboard"Scissor" Keyboard(※1)バックライトMagic Keyboard, Touch ID
CameraiSightカメラ1080p FaceTime HDカメラ
Wi-Fi802.11n/a/b/g802.11ax Wi-Fi 6
BluetoothBluetooth 2.1 + EDRBluetooth 5.0
Power Adapter60W MagSafe電源アダプタ96W USB-C MagSafe 3電源アダプタ
USBUSB 2.0ポート(最大480 Mbps)×2Thunderbolt 4 (USB-C)ポート x 3
Display OutputMini DisplayPortHDMIポート
Audio I/O3.5mmヘッドフォンジャック3.5mmヘッドフォンジャック
SD Card SlotSD Card SlotSDXC Card Slot
CD Drive8倍速SuperDrive
(DVD±R DL/DVD±RW/CD-RW)
-
FireWireFireWire 800-
※1 … キーボードも Scissor Keyboard (シザーキーボード) から Butterfly Keyboard (バタフライキーボード) の一悶着があって、Scissor Keyboardが復活&改良されてMagic Keyboardとなったらしい。もちろん私はButterfly Keyboardは知らない…
バタフライキーボード: 2015年〜2019年

2009年モデルのスペック及びマニュアル
2021年モデルのスペック及びマニュアル
※マニュアルがかなり簡略化された^^

この後に写真比較も載せるが、昔の13インチモデルが今の14インチモデルと同じ大きさなのも面白い。

2016年にメモリ増強(¥9,300)とSSD化(¥10,260)合わせて¥19,560で6年も延命できたのだから安上がりであった。(というか使おうと思えばまだまだ使える!)
メモリは最初の2GBのものを外して、4GBを2枚挿しにした。
当時の記憶では検索をミスったのかメモリは最大で4GBまでと勘違いしていた。(MacBook 13-inch late 2009モデルと勘違いしてた?)
勘違いしていたため8GBまで乗せれるのか色々検索し、どなたかが8GBでもちゃんと認識されたとか見つけて、大丈夫ということを確信した上で8GBにした様な気がする。証拠を載せておく。(前記事の通り、2009年モデルは OS X El Capitan (10.11)。About This Mac 画面は当時からあまり変化していないことが分かる。)

P.S.2022.10.9
私が「About This Macが少なくとも2009年から10年以上も変化していない」と言ったことから、ついにmacOS Ventura 13.0 にて変更されました!私の指摘がよほど気になったようですね^^またはMacBook Pro 2009年モデルを10年以上も大切に愛用した私の意見を尊重しれくれたんですね。(そんな訳はない。。VenturaにてSystem Setting(システム設定)が ios 風に統合されたので、その一環でAbout This Macも見直されたのでしょう。)



それでは比較写真に移ろう。
トップ 左:2009年モデル 右:2021年モデル※プラスチックカバーを付けてます。


フロント 下:2009年モデル 上:2021年モデル
2009年モデルについては、右から赤外線レシーバ、スリープインジケータ
2021年モデルがコンパクトになった。2021年モデルの方が軽いのだが、手で持って比較すると2021年の方が「詰まってる感」がする。
本記事を書いててわかったが、2009年モデルには赤外線レシーバがあったんだ。。この機能だけは使わずじまいだった。。

右側面 下:2009年モデル 上:2021年モデル
ポートは前方から
2009年モデル:盗難防止用ロックスロット※USB-Cではありません!, CDドライブ
2021年モデル:SDカードスロット, USB-C, HDMI
そう言えば2021年モデルには(というかいつからか)、盗難防止用ロックスロットが無くなった気がする。店舗展示とか会社の事務所に置きっぱなしにする場合にワイヤーチェーンで机とかに結び付けて盗難されない様にするものだが、この辺にも世界標準的に考え方の変化があったのだろうか?ISOとかでリスクアセスメントを理詰めでやると大抵はワイヤーチェーンなどに行き着くのだが、「現実問題との乖離」問題もあるわけで、世界標準もこの辺の乖離に一歩踏み込んで次のレベルの現実解まで考察し始めてくれたのであれば幸いである。
(現実問題との乖離として、何よりもズレているのは、大盗賊であれば当然ワイヤーカッターを持って盗難に来る、ということである^^大盗賊と言わずとも、よほど頭が弱くて世情を知らない盗賊でもなければ、「ワイヤーチェーンで結ばれてるかも知れないからワイヤーカッターも準備しておこう」となると思う。そうするとワイヤーチェーンの主たる目的はむしろ「抑止力」なのかも知れない。)


そして何よりもCDドライブ!当時インターネットは普及していたとはいえソフト購入時の主なメディアだったり、マシン故障時のリカバリメディアだったりした訳で「必要」だったのである。
ソフト購入について、多分どこかでちゃんと考察されているのだろうが、また私を含め一般コンシューマーの多くも心に思ったことだと思うが、スマホが世に出てAppleのApp StoreやGoogleの Play Store が出来てお客さんはバンバンアプリを購入する様になって、AppleとかMicrosoftは「あぁ何でもっと前からPC版Storeを真剣に作らなかったのだろう」と思っていることだろう。順当に考えるだけならスマホ効果でPC側に逆輸入しただけとも捉えられる。まさに黎明期である。

マシン的な話に戻すとこの物理的稼働装置を無くせたことは大きい。後で書こうと思ったがここで書いてしまうが、また前記事でもちょっとだけ触れたが、ポートを最小限まで切り詰めたり、バタフライキーボードにしたり、ファンクションキーをTouch Barにしたりという一連の流れは「現在の技術で最小化、コンパクト化、物理稼働装置の撤去・デジタル化・仮想化をできるところまでしてみよう」的ないわば一大ムーブメントがあった気がする。「今」というのが難しく、本当の現在の最新技術であれば基盤からポートから最新のものを使えば良いのだが、世に出ているものと次世代規格が同時進行で時間は流れている。製品化するには「今」という断面を切らなければならないということ。
当時Appleが目指した「シンプル化・最小コンパクト化」も「現在」技術であれば簡単に到達できるのだろうが、目指していたことはそういうことでは無論あるまい。むしろ一回リスクを負って、シンプル化・最小コンパクト化を目指して、それを実際にやってみないとそこからの景色は見えない、という事であろう。
(世の単純比較からは「Appleは後悔して前の時代に戻った」とか散々言われているが、ここで得たAppleの知見は計り知れない。また、最大の「外せない」物理装置としてキーボードは常に念頭に入れているはずである。誰しもが憶測しただろうが、おそらくキーボードを薄くしていって、最後はスマホのように仮想キーボードでいいんじゃない?に持っていきたかったのだろう。でもキーボードは「反応(レスポンス)」「押した感覚」を人は求めているということ。かといってトラックパッドのクリックは物理的押し込みはしなくてもいいんじゃないかと思う。塩梅が難しい。まさにこういったことの積み重ねなのだろう。)

右側面 下:2009年モデル 上:2021年モデル
ポートは前方から
2009年モデル:ヘッドフォンジャック, SD Card Slot, USB 2.0 × 2, Mini Display Port, FireWire 800, Ethernet, MagSafe電源ポート
2021年モデル:ヘッドフォンジャック, USB-C × 2, MagSafe電源ポート
左右のポートを比較してみると、さすが私が買い替えるために開発された(前記事参照)だけあって、ポート構成が似ている。
・ヘッドフォンジャック復活。(あれ? MacBookだとヘッドフォンジャックは無くなったことはない?iPhoneの話か。)
・四角いUSBから、丸いUSB-Cへ。
・Mini Display PortからHDMIへ。
MagSafe電源ポートは平べったくなった。
・EthernetとFireWireが無くなった。FireWireが懐かしい。

おまけ:2009年モデルのバッテリーインジケータ
1個前の比較画像だと分かりづらいが、2009年モデル前方にはバッテリーインジケータがあった。ボタンを押すとバッテリー残量がわかる。

続いて開いた状態。

左:2009年モデル 右:2021年モデル
比較して気づいたが、トラックパッドが若干大きくなってる。

2009年モデル キーボードとトラックパッド
2009年モデルは訳あって英語配列キーボードにした。

2021年モデル キーボードとトラックパッド ※キーボードカバーを付けている。
右上の2009年モデルではCDイジェクトボタンだったところが、2021年モデルではTouch IDになった。
またキーボードの話になるが、2009年モデルの方がストロークが深いため押しごたえがある。特に矢印キーについては2009年モデルの方が使いやすい。人差し指、中指、薬指を置いた時に手の感覚でどのキーを押しているのかが分かって安心するからだろう。
2021年モデルくらい薄くなると、ちゃんと指が正しい矢印キーを押せているのか何となく不安になる。(慣れの問題かもしれないが。)
この辺の
・英数字キーは「F」と「J」に人差し指を置いたホームポジションからの相対的な感覚
というのと、
・矢印キーはゲームパッドなどと同様に指先の感覚
という点で性質が異なるということである。(人間工学メモ)

背面 左:2009年モデル 右:2021年モデル
2021年モデルにはプラスチックカバーを付けている。

せっかくなので電源アダプターも載せておく。

上:2009年モデル 下:2021年モデル
デザインやサイズは前から変わっていない。2009年モデルではコード一体型だったため巻き取り用ウィング(正式名称不明)があって便利だった。ただし写真のように巻き取った後の格好については、Appleデザイン的に合致しているかどうかは不明である。(巻き方が汚いだけ?)

プラグ部分を取り外した状態
左:2009年モデル 右:2021年モデル
プラグ部分は全く一緒のように見えた。多分2019年モデルのものは2021年モデルにもぴったりとくっ付くと思うが、壊れたら嫌なので試してない。
※写真ではプラグ部分の大きさが違うように見えるかも知れないが、被写体の置き方や角度が違うだけで、つまり私の写真の撮り方が下手なだけである。

以上が比較結果である。こんなに世代がかけ離れたマシンの比較結果内容で買い替えを検討する方はいないと思うが、読み物として読んでいただければ幸いである。

なおFlutter開発について本MacBook Proを以って、ようやく念願のiOS版もリリースすることができた!
ネットを調べるとApple Developer Program購入やアプリの審査など時間がかかると書いてあったが、私の場合は3/15 にApple Developer Programを購入実施、完了して、3/16にはApp審査も通ってリリース(App Store公開)までできてしまった!(結構最小日数記録だと思う。)
これも2009年モデルMacBook ProというApple製品を大切に使ってきたおかげであろう。

2022年3月13日日曜日

Flutter: Androidの場合、showDialog(AlertDialog)内でWebViewを使用するとボタンが見えなくなる現象の対処法

Androidの場合、showDialog(AlertDialog)内でWebViewを使用するとボタンが見えなくなり困っていたが対応できた。

Flutterバージョン
> flutter --version
Flutter 2.10.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7e9793dee1 (10 days ago) • 2022-03-02 11:23:12 -0600
Engine • revision bd539267b4
Tools • Dart 2.16.1 • DevTools 2.9.2

対応前            →  対応後
 


使用しているWebView
webview_flutter: ^3.0.0
※執筆時最新バージョンは 3.0.1 

対処法
initState() に WebView.platform = AndroidWebView() を追加。
@override
void initState() {
  super.initState();

  // Enable virtual display.
  if (Platform.isAndroid) WebView.platform = AndroidWebView();
}

詳細
対応前は、ボタンは見えてないだけで、ボタンがあるあたりをタップするとちゃんとタップイベントが動作していたため、「見えてない」だけのようである。

また不思議なことに、次のページに遷移してそちらでもshowDialog()でダイアログ内にWebViewを使用しているのだが、こちらはちゃんとボタンが表示されていた。

なお、アプリ作成時(2019年3月ごろ)は、全画面ともダイアログのボタンは表示されていたため、webview_flutterをバージョン3.0.0に上げてからの症状だと思われる。

ネット上でもWebView関連の症状は結構あるようなので、その辺の問題であって、Flutterライブラリの更新はまだまだ目まぐるしい。
Flutterはその様に目まぐるしい状況なので、調査される方はあまり古い情報にハマらない様ご注意ください。(正攻法は「最新ライブラリのリファレンスを読む」ことなのだが、どうしても急いで対処療法を探すとネットに頼ってしまうこともあるかと思うため、念の為。)
(加えて言うならば、例えば.NET やJava だともう大きく変化しない箇所があって、「こういう時は、こう対応」みたいなものができていて、検索エンジンもそういったものを上位に持ってきてくれるので、古い情報でも気にせずに検索上位から当たっていく、みたいなやり方もアリといえばアリなのだろうが、マルチプラットフォーム開発の期待の星・または大本命であるFlutterはまだまだガンガン発展・進化を遂げていくであろうから、前者のやり方に慣れてしまった方は注意である。例えば.NETやJavaから、いきなりFlutter担当になった方など。「基本は本家リファレンスをあたる」という基本習慣が身について良いかも知れない。)

ちなみに、私のアプリもwebview_flutterをバージョン3.0.0にした時に、描画されるHTMLが全体的に小さくなったため、HTML側で以下を追加して対応してた。
<meta name="viewport" content="width=device-width, initial-scale=1.0">

webview_flutterバージョン3.0.0の変更履歴を見ると、BREAKING CHANGE(破壊的変更)とあって、ちょうどこの箇所が関係していたようだ。
BREAKING CHANGE: On Android, hybrid composition (SurfaceAndroidWebView) is 
now the default. The previous default, virtual display, can be specified with 
WebView.platform = AndroidWebView()
(出典: pub.dev webview_flutter)
それにしてもSurfaceAndroidWebViewがデフォルトになることで、なぜWebViewがダイアログのTextButtonに影響しているのかは不思議である。(私はこの症状にはなったことはないが、ネット上に「showDialogでWebViewを使うとWebViewがダイアログをはみ出す」とかもあったので、やはりWeb画面は色々できるので強力であり特殊なのだろう。)

※そもそもアプリの更新を半年くらいしてなかったら、DartがNull Safety正式版になってたりとか、ライブラリもWebView含め、Firebaseとか結構な変更があったので諸々対応して大変だった^^
 みなさんも放置しすぎない様に注意しましょう!(バージョンアップ対応に追われて、なかなかやりたかった更新作業に辿り着けない。。。)


(おまけ) コード抜粋(buildの骨組みだけ)
※Null Safetyは突貫で直しているので、_doShowDialog の String? contentStr は、String contentStrでいいんじゃないか、とかはご愛嬌^^
①問題が発生した画面
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(_appTitle),
          actions: <Widget>[
            PopupMenuButton(
              itemBuilder: (BuildContext context) {
                return [
                  PopupMenuItem(child: Text(AppLocalizations.of(context).aboutXxxTitle), value: 1),
                  PopupMenuItem(child: Text(AppLocalizations.of(context).aboutYyyTitle), value: 2),
                ];
              },
              onSelected: (menuId) {
                // ダイアログ表示
                if (menuId == 1) {
                  _doShowDialog(AppLocalizations.of(context).aboutXxxTitle, AppLocalizations.of(context).aboutXxx);
                } else if (menuId == 2) {
                  _doShowDialog(AppLocalizations.of(context).aboutYyyTitle, AppLocalizations.of(context).aboutYyy);
                }
              },
            ),
          ],
        ), // AppBar
        body: SafeArea(
          ...(省略)

  // ダイアログ表示
  _doShowDialog(String title, String? contentStr) {
    showDialog(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: Text(title),
          content: Container(
            width: MediaQuery.of(context).size.width - 10,
            height: MediaQuery.of(context).size.height - 350,
            // 以下のWebViewは実際は別クラスにしてます。リソース(assets)テキストを表示するだけのWebView。
            child: WebView(
              onWebViewCreated: (WebViewController webViewController) async {
                // return the uri data from raw html string.
                String htmlString = '<!DOCTYPE html><body>' + contentStr! + '</body></html>';
                await webViewController.loadUrl(Uri.dataFromString(htmlString, encoding: Encoding.getByName('utf-8')).toString());
              },
            ),
          ),
          actions: <Widget>[
            // ↓これが見えなくなったボタン
            TextButton(
              child: Text("OK"),
              onPressed: () => Navigator.of(context, rootNavigator: true).pop(context), // これでダイアログが閉じられる
            ),
          ],
        );
      }
    );
  }

②問題が発生しなかった画面
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(
            child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
              // ページデータを読み込む
              stream: _pageData,
              builder: (BuildContext contextPageData,
                AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
                  if (snapshot.hasError) {
                    return Center(child: Text('Error: ${snapshot.error}'));
                  } else if (snapshot.connectionState == ConnectionState.none ||
                    snapshot.connectionState == ConnectionState.waiting) {
                    // ロード中
                    return _getLoadingText();
                  } else if (!snapshot.hasData) {
                    return _getLoadingText();
                  }

                  switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return _getLoadingText();
                  default:
                    // ページ情報
                    List<DocumentSnapshot<Map<String, dynamic>>> pages = snapshot.data!.docs;
                    return DefaultTabController(
                      length: pages.length,
                      initialIndex: _currPage!,
                      child: StatefulBuilder(
                        builder: (BuildContext contextTab, setState) =>
                          WillPopScope(
                              onWillPop: () {
                                // 前画面へ戻る
                                Navigator.of(context).pop();
                                return Future.value(false);
                              },
                              child: Scaffold(
                                appBar: AppBar(
                                  title: Text(widget.textDoc.data()!["title"]),
                                  actions: <Widget>[
                                    IconButton(
                                      icon: const Icon(Icons.info, color: Colors.white, size: 30,),
                                      onPressed: () {
                                        // 説明をダイアログ表示
                                        _doShowDialog(widget.textDoc.data()!["title"], widget.textDoc.data()!["text_info"]);
                                      },
                                    ),
                                  ],
                                ),

おまけなので、アプリの詳細は割愛するが、
①問題が発生した画面 では、appBarまたは、bodyの情報ボタンからダイアログを表示している。
②問題が発生しなかった画面 では、appBarからダイアログを表示している。
やっていることは、_pageDataとして外部からページデータを取得して、StreamBuilderでタブに表示している。そこそこありがちな構成だと思う。

構造としては、①だと Scaffoldだけだが、
②だと SafeArea → Expanded  DefaultTabController  StatefulBuilder  WillPopScope  Scaffold
となっており、この辺の構造も関係しているのだろうか?
ネット上にもScaffoldがどうのこうとと書いてあった様な。。。