UE4 UnrealBuildTool *.Build.cs のコードを共有する

UE4 は C# で書かれた独自のビルドシステム UnrealBuildTool を使用しています。Makefile に相当するのが Module 毎に用意する *.Build.cs ファイルです。

例えば GameProject/Source/GameModule をビルドする場合下記のようなファイルを作ります。

// GameProject/Source/GameModule/GameModule.Build.cs
using   UnrealBuildTool;

namespace UnrealBuildTool.Rules
{
    public class GameModule : ModuleRules {
        public GameModule( ReadOnlyTargetRules Target ) : base( Target )
        {
        }
    }
}

この中で依存モジュールの宣言や、外部ライブラリの path 追加などさまざまな設定を行うことができます。

*.Build.cs の他に Project 全体の設定を記述する *.Target.cs もあります。

*.Build.cs Module 毎のビルド設定
*.Target.cs Project 全体のビルド設定

Target.cs はあくまで Project 単位に用意するため Plugin にはありません。また Build 対象に応じて Target.cs が変わるため複数存在します。

~.Target.cs Game
~Editor.Target.cs Editor
~Server.Target.cs Server
~Client.Target.cs Client

●他の Module を参照する場合

UE4 内部の Module 同士の依存関係は Module 名で管理されています。そのため Module については個別に Include path を与えたり Link するライブラリを指定する必要がありません。

例えば依存ファイルとして下記のような宣言を行えば、それぞれの Module への include も Link する Lib も自動で管理してくれます。

PublicDependencyModuleNames.AddRange( new string[] {
        "Core",
        "CoreUObject",
        "Engine",
        "Projects",
    });

Public と Private の違いはわかりにくいですが、依存している Module の Include path を追加するかどうかの違いとなります。

例えば GameModule を参照している他の MainModule があったとします。

// GameModule.Build.cs
using   UnrealBuildTool;

namespace UnrealBuildTool.Rules
{
    public class GameModule : ModuleRules {
        public GameModule( ReadOnlyTargetRules Target ) : base( Target )
        {
            PublicDependencyModuleNames.AddRange( new string[] {
                    "Core",
                });
        }
    }
}

// MainModule.Build.cs
using   UnrealBuildTool;

namespace UnrealBuildTool.Rules
{
    public class MainModule : ModuleRules {
        public MainModule( ReadOnlyTargetRules Target ) : base( Target )
        {
            PrivateDependencyModuleNames.AddRange( new string[] {
                    "GameModule",
                });
        }
    }
}

このとき GameModule の Core は PublicDependency なので、MainModule の include path に Core が含まれます。もし GameModule の Core が Private なら MainModule の include path に Core は含まれません。

つまり、他の Module に公開したいヘッダファイルがある場合、ヘッダ側で include している Module は Public にしておく必要があります。他の Module から参照されることがない GameProject のような Module (PRIMARY_GAME_MODULE) では、すべて Private 宣言にして構いません。

●外部ライブラリを参照する場合

UnrealBuildTool は “ThirdParty” というフォルダを特別扱いします。このフォルダは検索対象から外れており、ソースコードがあっても勝手にビルドを行いません。外部ライブラリは一般的に ThirdParty フォルダに入れておきます。Build.cs では下記のように相対的に ThirdParty のパスを求めます。

using   UnrealBuildTool;

namespace UnrealBuildTool.Rules
{
    public class GamePlugin : ModuleRules {
        private string ThirdPartyPath
        {
            get { return Path.GetFullPath( Path.Combine( ModuleDirectory, "../../../ThirdParty/" ) ); }
        }
~

Include path, Lib path, Lib file 等は個別に指定が必要で、さらに Platform も複数あるので外部ライブラリの参照は複雑になりがちです。

using   UnrealBuildTool;
using   System.IO;
using   System.Collections.Generic;

namespace UnrealBuildTool.Rules
{
    public class GamePlugin : ModuleRules {
        private string ThirdPartyPath
        {
            get { return Path.GetFullPath( Path.Combine( ModuleDirectory, "../../../ThirdParty/" ) ); }
        }
        private Dictionary PlatformTable= new Dictionary(){
            {   UnrealTargetPlatform.Win64,     "Windows/x64",  },
            {   UnrealTargetPlatform.Win32,     "Windows/x86",  },
            {   UnrealTargetPlatform.Linux,     "Linux/x64",    },
            {   UnrealTargetPlatform.Android,   "Android28",    },
            {   UnrealTargetPlatform.Mac,       "macOS/x64",    },
            {   UnrealTargetPlatform.IOS,       "iOS/arm64",    },
        };
        public GamePlugin( ReadOnlyTargetRules Target ) : base( Target )
        {
            PublicDependencyModuleNames.AddRange( new string[] {
                    "Core",
                    "CoreUObject",
                    "Engine",
                    "Projects",
            }
            if( !PlatformTable.ContainsKey( Target.Platform ) ){
                return;
            }
            bool debug= Target.Configuration == UnrealTargetConfiguration.Debug && Target.bDebugBuildsActuallyUseDebugCRT;
            string  config= debug ? "Debug" : "Release";
            string  PlatformName= PlatformTable[ Target.Platform ];

            PublicIncludePaths.AddRange( new string[] {
                    Path.Combine( ThirdPartyPath, "flatlib" ),
                });
            if( Target.Platform == UnrealTargetConfiguration.Android ){
                PublicLibraryPaths.AddRange( new string[] {
                        Path.Combine( ThirdPartyPath, "flatlib/lib", PlatformName, "arm7", config ),
                        Path.Combine( ThirdPartyPath, "flatlib/lib", PlatformName, "arm64", config ),
                        Path.Combine( ThirdPartyPath, "flatlib/lib", PlatformName, "x86", config ),
                        Path.Combine( ThirdPartyPath, "flatlib/lib", PlatformName, "x64", config ),
                    });
            }else{
                PublicLibraryPaths.AddRange( new string[] {
                        Path.Combine( ThirdPartyPath, "flatlib/lib", PlatformName, config ),
                    });
            }
            PublicAdditionalLibraries.Add( "flatCore" );
        }
    }
}

外部ライブラリを必要としている Module や Plugin があれば、それぞれの Build.cs に記述しておく必要があります。Makefile の include のように共通の設定を動的に読み込む仕組みがあれば良いのですが、特に用意されていないようです。

●UnrealBuildTool による Build.cs の読み込み方

*.Build.cs は UnrealBuildTool によって dll に変換されます。

 (1) Intermediate/Build/BuildRules/~.dll を読み込む

 (2) もし *.Build.cs が更新されている場合は動的にコンパイルを行い dll を Reload する

この dll の生成は Project 単位で行います。例えば GameProject の場合、下記のファイルをまとめてコンパイルし単一の GameProjectModuleRules.dll が作られます。

GameProjectEditor.Target.cs
GameProject.Build.cs
GameModule.Build.cs
MainModule.Build.cs

  → GameProject/Intermediate/Build/BuildRules/GameProjectModuleRules.dll

Engine も同様で、下記の cs ファイルから単一の dll にコンパイルします。

Engine/Source/UE4Editor.Target.cs
Engine/Plugins/~/*.Build.cs
Engine/Source/~/*.Build.cs

  → Engine/Intermediate/Build/BuildRules/UE4Rules.dll

よって動的な include はできないものの、他の Build.cs や Target.cs で宣言したコードを呼び出すことが可能です。例えば代表 Module を 1つ決めて、Build.cs に共通で使いたいコードを書いておきます。 

// GameProject.Build.cs
using   UnrealBuildTool;
using   System.IO;
using   System.Collections.Generic;

namespace UnrealBuildTool.Rules
{
    public class ThirdPartyCommonLibrary {

        ~

        public static ThirdPartyCommonLibrary Create( ModuleRules rules, ReadOnlyTargetRules target, string path= null )
        {
            ~
        }
        public void SetupFlatlib()
        {
            ~
        }
    }

    public class GameProject : ModuleRules {
        public GameProject( ReadOnlyTargetRules Target ) : base( Target )
        {
            PublicDependencyModuleNames.AddRange( new string[] {
                    "Core",
                    "CoreUObject",
                    "Engine",
            }
            var lib= ThirdPartyCommonLibrary.Create( this, Target );
            lib.SetupFlatlib();
        }
    }
}

他の Module の *.Build.cs からも参照できます。これでメンテしやすくなりすっきりしました。

// GameModule.Build.cs
using   UnrealBuildTool;

namespace UnrealBuildTool.Rules
{
    public class GameModule : ModuleRules {
        public GameModule( ReadOnlyTargetRules Target ) : base( Target )
        {
            PublicDependencyModuleNames.AddRange( new string[] {
                    "Core",
                    "CoreUObject",
                    "Engine",
            }
            var lib= ThirdPartyCommonLibrary.Create( this, Target );
            lib.SetupFlatlib();
        }
    }
}

Python : zip にアーカイブした pyz を直接実行できる専用 exe を作る

実行ファイルの置き換えに python を使用したときのメモです。Python 3 はプログラムコードを zip にアーカイブしたまま実行することができます。

●(1) Library として読み込む場合

テストコードを appmain.py, testmodule.py とします。

# appmain.py
import  testmodule

def main():
    testmodule.app()

if __name__ == '__main__':
    main()

# testmodule.py
def app():
    print( 'testmodule' )

そのまままとめて zip 圧縮可能で、Libpath に追加すると読み込むことができます。

### zip file を作る
import  zipfile

src_list= [
        'appmain.py',
        'testmodule.py',
    ]

with zipfile.ZipFile( 'applib.zip', 'w' ) as fo:
    for src in src_list:
        fo.write( src )


### zip file 内の code を実行する
import sys

sys.path.append( 'applib.zip' )

import  appmain
appmain.main()

embeddable 版の python3x.zip と同じように事前にコンパイルしておくこともできます。

●(2) zipapp (pyz) に変換してから実行する場合

zip ではなく zipapp (pyz) に変換すると script のように直接実行できるようになります。

予めサブフォルダ app を作って、その中に appmain.py, testmodule.py を入れておきます。下記のコードを実行すると pyz にまとめることができます。create_archive() 最初の引数 ‘app’ はフォルダ名です。

import	zipapp

zipapp.create_archive( 'app', 'app.pyz', '/usr/bin/env python3', 'appmain:main' )

変換出力した app.pyz はただの zip ですが、エントリポイントとして __main__.py が挿入されています。この pyz ファイルはそのまま python で実行できます。

python app.pyz

また unix 系 os の場合は実行属性をつけることで直接実行することができます。

$ chmod 755 app.pyz
$ ./app.pyz

Windows の場合そのまま実行することはできませんが、pyz 専用の exe を作る方法が zipapp のマニュアルで紹介されています。

●(3) pyz の起動用に exe を作る

zipapp — Manage executable Python zip archives

上記ページに載っている zastub.c をそのままコンパイルします。

// zastub.c
#define Py_LIMITED_API 1
#include "Python.h"

#define WIN32_LEAN_AND_MEAN
#include 

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* handle to current instance */
    HINSTANCE hPrevInstance,  /* handle to previous instance */
    LPWSTR lpCmdLine,         /* pointer to command line */
    int nCmdShow              /* show state of window */
)
#else
int wmain()
#endif
{
    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    myargv[0] = __wargv[0];
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
    return Py_Main(__argc+1, myargv);
}

コンパイル方法もマニュアル通りです。下記のコードを実行してコンパイルできます。

# compile.py
from distutils.ccompiler import new_compiler
import distutils.sysconfig
import sys
import os
from pathlib import Path

def compile(src):
    src = Path(src)
    cc = new_compiler()
    exe = src.stem
    cc.add_include_dir(distutils.sysconfig.get_python_inc())
    cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
    # First the CLI executable
    objs = cc.compile([str(src)])
    cc.link_executable(objs, exe)
    # Now the GUI executable
    #cc.define_macro('WINDOWS')
    #objs = cc.compile([str(src)])
    #cc.link_executable(objs, exe + 'w')

if __name__ == "__main__":
    compile("zastub.c")

zastub.exe が作られるので、(2) で作った pyz とそのまま合成します。単純につなげるだけです。

copy /b zastub.exe + app.pyz app.exe

これで pyz を実行可能な app.exe ができました。そのまま実行できます。(python3.dll が必要)

app.exe

プログラムの内容によっては python の import でモジュールが見つからない場合があるかもしれません。その場合は予め検索パス設定しておいてください。環境変数 PYTHONPATH でも構いませんし、zastub.c の中で Py_SetPath() を使ってパスを与えることもできます。

zastub.c は python.exe 相当です。これが引数を何も加工していない状態になります。

int wmain()
{
    return  Py_Main( __argc, __wargv );
}

zastub.c では 1番目の引数に自分自身のパスを複製して挿入します。例えば引数が “arg1” なら「app.exe arg1」→ 「app.exe app.exe arg1」となります。自分自身である app.exe には app.pyz の内容がそのまま含まれているので、app.exe を pyz とみなしてそのまま実行できるわけです。

なお 1番目の引数を正しく渡すことができるなら、必ずしも exe に pyz を合成する必要がありません。例えば下のように直接 “app.pyz” を渡すことができます。

int wmain()
{
    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
    myargv[0] = __wargv[0];
    myargv[1] = L"app.pyz";
    return Py_Main(__argc+1, myargv);
}

ただし若干問題もあります。上のコードはカレントディレクトリでなければ app.pyz を読み込むことができません。例えば他のフォルダからフルパスで app.exe を実行しようとすると app.pyz が見つからずにエラーになります。もし app.pyz を実行ファイルと同じ場所に置くなら、__wargv[0] や GetModuleFileNameW() を利用して app.pyz のパスを求めることができます。

↓ __wargv[0] から app.pyz のパスを求める。

#define Py_LIMITED_API 1
#include    

#define WIN32_LEAN_AND_MEAN
#include    

#ifdef WINDOWS
int WINAPI wWinMain(
    HINSTANCE hInstance,      /* handle to current instance */
    HINSTANCE hPrevInstance,  /* handle to previous instance */
    LPWSTR lpCmdLine,         /* pointer to command line */
    int nCmdShow              /* show state of window */
)
#else
int wmain()
#endif
{
    wchar_t *PYZ_NAME= L"app.pyz";      
    size_t  NAME_LENGTH= wcslen( PYZ_NAME ) + 1;
    size_t  length= wcslen( __wargv[0] ) + NAME_LENGTH;
    wchar_t *pyz_path= _alloca( (length + 2) * sizeof(wchar_t) );
    wcscpy_s( pyz_path, length, __wargv[0] );

    wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
    myargv[0] = __wargv[0];
    memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));

    wchar_t *eptr= pyz_path;
    for( wchar_t *ptr= pyz_path ; *ptr ; ptr++ ){
        if( *ptr == L'/' || *ptr == L'\\' ){
            eptr= ptr + 1;
        }
    }
    wcscpy_s( eptr, NAME_LENGTH, PYZ_NAME );

    myargv[1]= pyz_path;
    return Py_Main(__argc+1, myargv);
}

同じようにして exe の場所からライブラリの位置を特定し、Py_SetPath() することもできます。例えば embeddable 版を使うなら、Py_SetPath() に python3x.zip と dll/pyd フォルダの 2つを与えれば OK です。

VR HMD の分類表

VR 機器の種類が増えたのでわかっている範囲で大まかに分類してみました。

(1) 頭 ポジショントラッキングあり 回転のみ
(2) 手 ポジショントラッキングあり 回転のみ
(3) 外部に PC (PS4) が必要 スタンドアロン (Mobile SoC)
(4) 外部センサーあり 外部にセンサーを設置する必要なし
Oculus Rift Quest Go
HTC Vive Vive/Pro (*1) Cosmos Focus Plus Focus
Sony PSVR
Samsung Odyssey GearVR
MS Windows MR
Google Mirage Solo Daydream

(1)/(2) ポジショントラッキングがあるかどうか

VR 空間を自由に歩く Room Scale に対応するにはポジショントラッキングが必要です。外部にトラッキング用のセンサー設置が必要なタイプと、内蔵のカメラだけでトラッキングできるインサイドアウト方式の 2種類があります。

(3) PC が必要かどうか

外部に PC が必要なタイプはモニタ相当です。ケーブルで Host PC (PS4) に接続する必要があります。性能が高く画質も良いですがプレイ中はケーブルがじゃまになりがち。

スタンドアロンタイプはケーブルが不要で PC と繋がなくても単体で使うことができます。手軽に使える半面、描画などの処理能力はスマートフォン相当になります。

(4) 外部にセンサーの設置が必要かどうか

「外部センサーあり」の場合は、トラッキングためにカメラなどのセンサーを部屋に設置しておく必要があります。HMD だけでなくセンサーのためのケーブルも必要で、事前の準備に少々手間がかかります。

(*1) HTC Vive / HTC Vive Pro は同じグループの中でも少々別格です。部屋にベースステーションの設置が必要ですが厳密にはセンサーではなく、どちらかといえばマーカーに近いものです。そのため PC とのケーブル接続が不要で、バックパック型 PC を使うとスタンドアロンタイプのようにも使えます。またポジショントラッキング対応のコントローラを 10個以上同時に利用することができます。手だけでなく足など全身のトラッキングも実現できます。

関連ページ
HMD VR / AR Device spec 一覧

関連エントリ
Gear VR のヘッドセットの種類のまとめ
各種 VR HMD と Volume 調節の仕方まとめ
VR で物が大きく見えたり小さく見えたりするわけ
Oculus Go は VR ができる新しい携帯ゲーム機
Oculus Go と VR デバイスの種類
Windows MR で Fallout 4 VR (Dell Visor)
Google Daydream View (2017) 新型
Windows Mixed Reality Dell Visor (VR HMD)
ASUS ZenFone AR (Daydream/Tango)
Gear VR Controller
HTC Vive のセットアップと PC スペック、遅い PC で動くかどうか
HTC Vive (VR ヘッドマウントディスプレイ) の接続

UserLAnd : Android 9.0 で Ctrl + SPACE を使えるようにする

以前こちらで書いたように Android 9.0 Pie 以降は、外付けキーボード利用時に Control + SPACE のキー入力ができなくなっています。「キーボードレイアウトの切り替え」操作に割り当てられているためです。

ソフトウエアによっては Ctrl + SPACE を使いたい場合も多いので、無理やり使えるようにしてみました。

Ctrl + [SPACE] を乗っ取っているのは Android なので、Android 側で Ctrl を利用頻度の低いキーと交換してしまいます。例えば [F12] など。ただし入れ替えは UserLAnd + Linux (xmodmap) で識別可能なキーに限ります。本当は Caps が使えればよかったのですが Linux 側でうまく識別できませんでした。

もちろん Android 側では Ctrl キーが [F12] に置き換わってしまうため、ハードウエアキーボードで Ctrl を使った操作が困難になります。また xmodmap を使ったキー入れ替えは VNC/XSDL のみ有効で UserLAnd の SSH では使えません。

Step 1 : Android 側の入れ替え (kcm)

 [Ctrl] ←→ [F12]

・Android 側の「キーボードレイアウト切り替え操作」 は [F12]+[SPACE] になります。

Step 2 : UserLAnd Linux 側の入れ替え (xmodmap)

 [F12] ←→ [Ctrl]

・[F12] に偽装した本来の [Ctrl] キーを Ctrl として使えるようになります。

● (1) kcm キーレイアウト作成

Android 側でキー入れ替えを行うには kcm ファイルを作成し、新しいキーボードレイアウトを登録します。今回は下記 nakajit さんの code を利用させていただきました。

GitHub: nakajit/InputDevices4J

InputDevices4J/res/raw/keyboard_layout_japanese_ime_cc_ek.kcm を下記のように書き換えました。(Caps ←→ Ctrl, ESC ←→ 半全 が不要な場合は他のファイルを使用してください)

~
map key 58  CTRL_LEFT           # CAPS_LOCK
~

↓ 該当部分を下記のように修正

~
map key 58  F12                 # CAPS_LOCK
map key 88  CTRL_LEFT           # F12
~

● (2) 書き換えたプロジェクトを apk にビルドしてインストールします

● (3) 設定からレイアウトを選択

 設定→システム→言語と入力→物理キーボード→キーボードレイアウトの設定

keyboard_layout_japanese_ime_cc_ek.kcm の場合は「日本語 106/109 (IME用, C-C, E-H/Z)」を選びます。

選択すると Caps キーが [F12] に置き換わるので注意。

● (4) xmodmap での設定

UserLAnd から VNC を起動して xmodmap ファイルを作ります。

remove Control = Control_L
keysym Control_L = F12
keysym F12 = Control_L
add Control = Control_L

↑この内容を仮に .Xmodmap-F12 で保存したら

$ xmodmap .Xmodmap-F12

を実行します。

これで Android 9.0 でも UserLAnd (X11) 上で [Ctrl]+[SPACE] が使えるようになりました。

同じように、[半角/全角] キーや [変換] キーなども [F11], [F10] とか割り当ててしまえばおそらく UserLAnd 上で使用できるようになると思われます。

関連エントリ
Android Termux で日本語入力を行う / UserLAnd との併用
Android 9.0 と Bluetooth Keyboard による日本語入力
Android で動く Linux 環境 UserLAnd が XServer XSDL に対応
Oculus Go を文章書き&開発マシンにする
UserLAnd とブラウザ
Android 上の開発環境と UserLAnd
OS の中の Linux (WSL/Chrome OS/Android UserLAnd)
ARM CPU 上の開発環境とコンパイル時間の比較 (2) Pixel 3/UserLAnd

Android Termux で日本語入力を行う / UserLAnd との併用

普段使っている Note PC よりもハイエンドスマートフォンの方が性能が良いことが判明してから、Android を Note の代わりに活用する方法をいろいろ試しています。主な用途は外出時の議事録などのメモ取りとちょっとしたプログラミングです。

Android 上で root なしに利用できる開発環境として Termux と UserLAnd があります。

UserLAnd は Android 上に PC とほぼ同等の Linux 環境を構築可能で日本語入力もできます。その代わり proot のオーバーヘッドがありファイルアクセスが遅くなっています。また GUI が使えるものの GPU が使われておらず、PC と比べると画面描画は低速です。

Termux はファイルアクセスのオーバーヘッドがなく高速ですがパッケージがあまり多くありません。日本語入力にも非対応。

機能面では UserLAnd だけで済むのですが、速度面で少々難があるので工夫が必要です。下記はライブラリのコンパイル時間の比較です。

Google Pixel 3 (Android 9.0 Pie) Snapdragon 845
実行環境 Compiler Time (秒)
(1) Termux clang-7.0.1 32.6
(2) UserLAnd Debian + Termux VNC clang-7.0.1 34.5
(3) UserLAnd Ubuntu 18.04 SSH clang-6.0.0 72.9
(3) UserLAnd Debian Stretch VNC clang-3.8.1 220.4
(3) UserLAnd Debian Stretch SSH clang-3.8.1 219.0
Essential Phone PH-1 (Android 9.0 Pie) Snapdragon 835
実行環境 Compiler Time (秒)
(1) Termux clang-7.0.1 38.4
(2) UserLAnd Ubuntu + Termux VNC clang-7.0.1 40.5
(2) UserLAnd Debian + Termux VNC clang-7.0.1 41.7
(3) UserLAnd Debian Stretch VNC clang-3.8.1 423.6
(3) UserLAnd Debian Stretch VNC clang-4.0.1 456.9
(3) UserLAnd Ubuntu 18.04 VNC clang-6.0.0 95.6
(3) UserLAnd Arch SSH clang-7.0.1 107.0

・Time (秒) の値が小さい方が高速

(1) = Termux のコンソールから直接実行
(2) = UserLAnd から ssh で Termux に login して実行
(3) = UserLAnd 上で直接実行

Termux (1) が一番速く、UserLAnd 経由で Termux を利用した場合 (2) もほぼ速度が落ちずに済んでいます。

Termux と比べると UserLAnd は 2〜12倍くらい遅くなっています。かなりばらつきがあり Debian が極端ですが原因はわかっていません。

UserLAnd は Desktop アプリの利用や日本語入力などの環境として利用して、Termux でできる作業は Termux で行うのが効率良いことがわかります。

上の結果 (2) では Desktop 上の LXTerminal から Termux に ssh 接続していますが、ウィンドウ上の Terminal の描画は遅くなります。パフォーマンスが低いデバイスでは X11/VNC ではなく UserLAnd の SSH から Termux にログインすることをお勧めします。こちらの方が Terminal の描画は高速で、uim-fep の日本語入力が使えるようになります。

● Termux で日本語入力を行う方法

1. UserLAnd に任意の Distribution をインストールして日本語環境を設定
2. Termux 上で sshd を起動
3. ssh で Termux にログイン

● 1. UserLAnd の設定

4通りの方法を wiki にまとめました。

Ubuntu + SSH コンソールでの日本語入力 (uim-mozc)
Debian + SSH コンソールでの日本語入力 (uim-mozc)
Ubuntu + VNC デスクトップでの日本語入力 (fcitx-mozc)
Debian + VNC デスクトップでの日本語入力 (uim-mozc)

その他 UserLAnd 関連はこちら
Android の上の開発環境: UserLAnd

● 2. Termux の設定と sshd の起動

Termux では予め sshd を起動しておく必要があります。手順は下記の通り。ssh でログインするためにはパスワードの設定が必要です。

$ pkg update
$ pkg install openssh
$ passwd
パスワードの設定
$ sshd

● 3. Termux へのログイン

UserLAnd から Termux へのログイン方法。VNC (LXDE desktop) を使う場合は LXTerminal を使ってください。

$ ssh localhost -p 8022

●実際に使用してみて

Snapdragon 845 クラスのスマートフォンでは Ubuntu + VNC + Termux で十分だと感じます。議事録などのメモ用途で使う場合は、Bluetooth Keyboard があれば Note PC 代わりになりますし、外出時のコードの確認にも使えます。

どこでも作業空間を作り出せる Oculus Go も非常に魅力なのですが、VR にパワーが取られてしまい VNC は重くなります。テキストエディタの作業は SSH Console の方をお勧めします。もっとパワーのあるスタンドアロン機種や、ハイエンド機種 + GearVR/Daydream だったらもう少し快適な作業ができるかもしれません。

関連ページ
Android の上の開発環境: UserLAnd
Android の上の開発環境: Termux

関連エントリ
Android 9.0 と Bluetooth Keyboard による日本語入力
Android で動く Linux 環境 UserLAnd が XServer XSDL に対応
Oculus Go を文章書き&開発マシンにする
UserLAnd とブラウザ
Android 上の開発環境と UserLAnd
OS の中の Linux (WSL/Chrome OS/Android UserLAnd)
ARM CPU 上の開発環境とコンパイル時間の比較 (2) Pixel 3/UserLAnd