WindowsのSteamゲームをProtonでプレイする【Ubuntu, Windowsデュアルブート】

UbuntuWindowsデュアルブートをしている場合、Windows側に入っているゲームをUbuntuから遊びたいときがあるかと思います。
今の時代は便利なもので、それが可能です。

では手順を紹介します。
基本的に下記サイトの日本語訳になります。

anthonyvadala.me

前提として必要なもの

NVIDIAGPUのドライバーが必要です。 これはすでにいれてあるものとします。

バージョンの更新は↓を参考にしてください。

NVIDIA

  sudo add-apt-repository ppa:graphics-drivers/ppa
  sudo apt install nvidia-driver-418

AMD/Intel

  sudo add-apt-repository ppa:paulo-miguel-dias/pkppa
  sudo apt dist-upgrade
  sudo apt install mesa-vulkan-drivers mesa-vulkan-drivers:i386

Radeon R9 200/300 series

  echo "blacklist radeon" | sudo tee --append /etc/modprobe.d/blacklist.conf
  echo "options amdgpu si_support=1 cik_support=1" | sudo tee --append /etc/modprobe.d/amdgpu.conf
  sudo update-initramfs -u

Python3とPython Minimalも必要です

aptでインストールしておきましょう。

sudo apt install python3
sudo apt install python-minimal

Steamも入れておきましょう

公式サイトから簡単にインストールできます。

store.steampowered.com

ディスクを見つける

Windowsに入っているゲームを遊ぶには、Windowsが入っているHDDもしくはSSDをマウントするという作業が必要になります。
これはファイルアプリからもできるのですが、それだと問題が生じることがある(実際に筆者はゲームが起動できなかった)のでターミナルから設定を行います。

デュアルブートを一つのHDD/SSDで管理している場合は必要ないかもしれません。)

Ubuntuでターミナル(端末)を起動してください。
Windowsの入っているHDDもしくはSSDを探してきます。

下記コマンドを実行してください。

sudo fdisk -l

すると下記のような出力がされると思います。

...
Disk /dev/sda: 3.7 TiB, 4000787030016 bytes, 7814037168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 2E9F174D-53CA-42AD-9CD4-DF7CA90B0C7A

Device      Start        End    Sectors  Size Type
/dev/sda1      34     262177     262144  128M Microsoft reserved
/dev/sda2 264192 7814035455 7813771264 3.7T Microsoft basic data
...

ここで、/dev/sd**と書かれているのがディスクになります。
自分がマウントしたいディスクがどれか確認しましょう。

これはUbuntuのファイルアプリで確認することもできます。

f:id:tmls:20201125220826p:plain

ここでは/dev/sda2をマウントしたいとします。
UUIDを知るために、下記のコマンドを実行してください。

sudo blkid
このような実行結果が得られると思います。

...
/dev/sda1: UUID="0CAEEC94AEEC781A" TYPE="ntfs" PARTUUID="1e6ca286-01"
/dev/sda2: UUID="50E2054BE205372E" TYPE="ntfs" PARTUUID="1e6ca286-02"
...

今回は、sda2をマウントしたいのでUUIDは50E2054BE205372Eになります。

マウント設定

Ubuntuの起動時に自動でディスクをマウントするように設定します。

まずは、マウント先となるディレクトリを作成しましょう。

sudo mkdir /media/gamedisk

次に、自分のユーザーIDとグループIDを知っておく必要があります。
これは下記コマンドで確認できます。

ユーザーID

id -u

グループID

id -g

基本的にデフォルトでどちらも1000が設定されていると思いますが、一応確認しておいてください。

FSTAB

続いてマウントするために、/etc/fstabを編集します。
お好きなエディタで開いてください。

sudo vim /etc/fstab

ファイルの最後に下記の1行を追加してください。(UUIDやUID, GIDは自分のものに置き換えてください)

...
UUID=38CE9483CE943AD8 /media/gamedisk ntfs uid=1000,gid=1000,rw,user,exec,umask=000 0 0

保存したら、一度再起動を行います。

reboot

NTFS読み込みエラーへの対処

(筆者はここは飛ばしてしまったのですが... 今のところは問題ないです...)

Windowsが読み込めない文字はディスクエラーを引き起こす可能性があります。(ゲームが起動できない原因になります)
多くの場合自動で生成されるファイルに含まれる ; が問題になりやすいです。

この修正はシンプルで、Ubuntu/compatdataフォルダからマウントしたディスクにシンボリックリンクを作成します。

ln -s ~/.steam/steam/steamapps/compatdata /media/gamedisk/Games/Steam/steamapps/

もしマウントしたディスクに 既に/compatdataが存在するなら、シンボリックリンクを作る前に削除しておいてください!

Steam Protonを有効化

Steamを開いてください。 左上のSteam > 設定 > Steam Play をクリック。

下記の項目にチェックが入っていることを確認してください。

  • サポートされたタイトルでSteam Playを有効化
  • 他のすべてのタイトルでSteam Playを有効化

Steamライブラリフォルダを追加

既にWindowsに入っているゲームを参照できるようにします。
Steam > 設定 > ダウンロード > STEAMライブラリフォルダ をクリックしてください。

続いて、ライブラリフォルダを追加します。
自分のSteamフォルダを指定してください、私の場合は/media/gamedisk/Program Files (x86)/Steam/でした。
そのまま、今追加したフォルダを右クリックしてデフォルトフォルダに設定しておいてください。

OKを押して、Steamを再起動すると全ゲームで多少のダウンロードが始まります。
ダウンロードが完了するとついに、Windowsでインストールしていたゲームが遊べるようになります!

どのゲームが動くのか

↓のウェブサイトでは、ゲームがどれくらい正しく動作するかが募られています。

www.protondb.com

もし自分の手を汚すのを気にしないなら、バグを修正したりパフォーマンスを出せる設定が書かれていたりしますよ。

PCが突然フリーズしたりシャットダウンするので解決した

イベントビューアーを確認したところ、下記のエラーを発見。

f:id:tmls:20200924181024p:plain

f:id:tmls:20200924173016p:plain

ドライバーの再インストール。

support.borndigital.co.jp

効果なし。

マルチモニターからシングルモニターに切り替え。 どちらにしても効果なし。

画面の切り替え時、スリープからの復帰時にシャットダウンが発生する事が多い事に気づく。
例えば、YouTubeの全画面表示の終了時、カーソルで別ウィンドウをクリックしたとき、ディスプレイの黒い画面からの復帰時など。

Windowsのテーマをカスタムする際にレジストリをいじったりしたので、それも疑った。
この時同時に色々導入したので、それも疑った。

が、BugCheckCode: 278はそれより前から出ていたのでおそらく関係ないと判断。

moshi-nara.com

こちらに従い、ダンプファイルの出力をOFFに変更。

KP41発症。 ただしBugcheckCodeは0。
ダンプファイルの出力をOFFにしたから?

f:id:tmls:20200925123407p:plain

ローカルセキュリティポリシーから、シャットダウン時にページファイルを削除するように設定。
システムの詳細設定から仮想メモリをなしに設定。
さらに高速スタートアップの無効化。

freepc.jp

これでシャットダウンを行ったら、画面は消えるけどPowerのライトが消えないという、シャットダウンしきらない状態が発生。

一度電源ボタンを押して、完全にシャットダウンさせた。
その後、電源ケーブルを抜いて放電。
BIOS画面でFast BootをDisableに変更した。
ちなみに私のBIOSUEFIで、起動中にF2ボタンを連打したら開けた。

ダメ。 やはりしばらく無操作状態からの復帰時に落ちる事が多い気がする。

↓のサイトに従い、省電力設定を変更。

freesoft.tvbok.com

ついに解決した。

Unityの最適化に関する用語・リンクまとめ

最終更新2020/8/30 ※随時更新予定

デバッグ、分析ツール

Stats

Profiler(プロファイラー)

Frame Debugger(フレームデバッガー)

描画に関する詳細を確認できる。 バッチングがどうなってるとか、なぜできなかったとかも分かる。

Physics Debug

Development Build

スクリプト

GC(Garbage Collection)

GameObject.Find系

FindWithTag(軽い)

Find(普通)

FindObjectOfType(重い)

描画

バッチ処理(バッチ、バッチング)

docs.unity3d.com

SRP Batcher

blogs.unity3d.com

シェーダー

マルチパスシェーダー

シェーダーバリアント

SetPass calls(ドローコール)

フィルレート

Canvas(uGUI)

Canvasを使った描画は、動かない限り基本的にSprite Renderer等より軽い。
逆に動く場合(Transform, Animation等)は注意が必要。

Camera - Render Mode

Screen Space - Overlay
画面にオーバーレイするように描画する。 基本的に軽い。

Camera Space - Camera
画面に映り込むようにCanvasの位置を調整して表示する。Cameraの子オブジェクトのように一緒に動くイメージ。
Overlayと違ってz座標や光処理、ポストプロセスなどを反映可能。カメラが動くとめちゃくちゃ重い。

World Space
ワールド座標を使って表示。 やはり動くと重い。

ポストプロセス

Post Processing Stack V2

SRP (Scriptable Render Pipeline)

www.slideshare.net blogs.unity3d.com

Material

Material Property Block

tsubakit1.hateblo.jp

sharedMaterial

tsubakit1.hateblo.jp

Instance

Sprite Atlas

頂点カラー

参考

esprog.hatenablog.com docs.unity3d.com

水の軽量化

Nvidia Flexの水をカスタマイズしたときの最適化に関するメモ

z軸固定

全パーティクルについてfor分を回して、GetVelocity SetVelocityは重い。
全パーティクルは3000程度いくのできっつい。
新しくループを作るのではなく、もとのNvidiaを書き換える事で対応。
→ 書き換えたのは、新規に生成された水粒子に関してのみだったのでダメだった。
新規の水粒子以外でSetVelocityが呼ばれているのは、ApplyImpulseTeleportParticlesだが、DebugLogしてもどちらも呼び出されていないようだった。

しかし、CustomWater.cs内のOnFlexUpdateGetVelocityするとzの値が0ではなくなっている。
つまりSetVelocity以外の部分でVelocityの変化が起きていることになる。
原因を探ったがよく分からず(ネイティブプラグイン内かも)、その過程でSetVelocities関数を発見した。
SetVelocityをfor分で回すより効率が良いと考え、利用してみた。

var velocities = new Vector3[indices.Length];
particleData.GetVelocities(indices[0], indices.Length, velocities);
for(var i = 0; i < velocities.Length; i++)
    velocities[i].z = 0;
particleData.SetVelocities(indices[0], indices.Length, velocities);

結果、ほぼ無視できるレベルに軽量化に成功した。
が、代わりにとんでもないGCが発生した。
原因はGetVelocitiesの時のnew Vector3[]だろう。
f:id:tmls:20200917064018p:plain

ちなみに、SetVelocityを使った場合との比較は↓である。
f:id:tmls:20200917064513p:plain

手法 Total GC
SetVelocity 2.90ms (7.6%) 29.5kB
SetVelocities 0.03ms (0.1%) 約60kB (スパイク時:117.7kB)

SetVelocitiesを使う場合はindicesはスタートだけあればよいので、 int[] indices = new int[instance.numParticles];int[] indices = new int[1]; に変える事でnewを減らすことに成功。
GCを60kB → 44kB (スパイク時:88kB)まで減らした。

ここでのスパイクはFixedUpdateが1フレーム内で2回呼ばれた時の話だと考えられる。

f:id:tmls:20200917071059p:plain

さらに、_velocitiesをクラス変数にして初期化する事でnewを呼ぶ回数は最初の初期化時に限定。
結果、GCを44kB → 0.072kB (72B) に減らすことに成功!

private Vector3[] _velocities;

private void Start(){
    _velocities = new Vector3[_flexActor.asset.maxParticles];
}

private void OnFlexUpdate(FlexContainer.ParticleData particleData){
    particleData.GetVelocities(indices[0], instance.numParticles, _velocities);
        for(var i = 0; i < _velocities.Length; i++)
            _velocities[i].z = 0;
        particleData.SetVelocities(indices[0], instance.numParticles, _velocities);    
}

f:id:tmls:20200917072339p:plain

これにて、Z軸固定は完了した。

当たり判定及びAddForce

上記同様に、GetVelocities 及び GetParticlesによる軽量化を行った。

さらに、IsInCollider(Vector3 pos) 内の targetCollider.ClosestPoint(pos)が非常に重い。 これは水粒子がコライダー内部にあるかの判定に利用していた。

そこで、コライダーをSphereColliderに限定して、水粒子のコライダー内部判定を(particle - colPos).magnitude < radiusで置き換えた。
汎用性は下がるが、単純なVector3の演算のみになる。 実際にこれでかなりの軽量化に成功した。

しかしながら、magnitudeの計算等でそれなりに処理時間を要していた。 そこで今回はz軸方向を利用しないという点を活かして、Vector3の演算からVector2の演算に変更を行った。 これでさらに若干軽くなった。

f:id:tmls:20200918061952p:plain

f:id:tmls:20200918063709p:plain

手法 Total GC
WaterCollider 22.50ms (37.0%) 67.4kB
WaterSphereCollider 1.70ms (6.6%) 36B

f:id:tmls:20200918064001p:plain

f:id:tmls:20200918065414p:plain f:id:tmls:20200918065532p:plain

f:id:tmls:20200918070035p:plain f:id:tmls:20200918070050p:plain

f:id:tmls:20200918070843p:plain

Unityで等速に移動する方法まとめ

基本的に全部右(1, 0, 0)に移動する場合について考えます。
その他の方向についても同様に出来ると思います。

Unityのバージョンは2019.3です。

Transform

Transform.positionの書き換え

単純に座標の足し算です。
これらの処理はfpsの影響を受けるのでUpdateで書く際にはTime.deltaTimeを掛けましょう。(そうしないとfpsによって速度が変わります)

private void Update()
{
    transform.position += Vector3.right * Time.deltaTime;
}

また、FixedUpdateで書く場合はTime.deltaTimeは必要ありません。

private void Update(){
    transform.position += Vector3.right * 0.02f;
}

Transform.Traslate

Transform.positionの書き換えと原理はほぼ同じですが、Unityの関数を利用します。
fpsの影響を受けるという点でも同じです。

private void Update(){
    transform.Translate(Vector3.forward * Time.deltaTime);
}
private void Update(){
    transform.Translate(Vector3.forward * 0.02f);
}

違いとしては、引数に指定する数値がワールド座標ではなく対象のオブジェクトからみた進行方向で指定するという事です。
Vector3.forwardを指定すれば前方(向いてる方向)に進み、Vector3.rightを指定すれば右に進みます。

このようにTransformの利用はシンプルで分かりやすいです。
しかしRigidbodyやColliderとの相性が悪かったりしますよね...。 (例えば、Rigidbody.velocityの値は変わりません)

Rigidbody

ここから先はRigidbody及びColliderを利用する場合の話です。 Rigidbodyを利用した物理演算に関する処理は必ずFixedUpdateで行います。

velocityの値を書き換える

Rigidbodyがもつ速度のパラメータであるvelocityの値を上書きします。

private void FixedUpdate()
{
    GetComponent<Rigidbody>().velocity = Vector3.right;
}

Rigidbodyを使った一番シンプルな方法かと思います。
ただしこのようにvelocityを直接書き換えてしまうと、他の物理演算の影響を受ける時に問題になる事が多いです。

AddForce + velocity を使う

velocityの書き換えは問題になることが多いので、AddForceを利用します。 しかし単純にAddForceを繰り替えすだけでは、ひたすら加速してしまいます。

そこで速度の遅い時には力を強めて速い時には弱める事で、等速を維持します。
自転車を漕ぐときに最初は力を入れて踏み込み、スピードに乗ったら徐々に力を抜くのに似ていますね。

これをUnityで実装してみます。
自身の速度はrb.velocity (rbはRigidbody)で取得できます。
これを減算する事で、遅い時は強く、速い時は弱い力を与える事ができます。
(つまり厳密には最初から等速ではなく、凄い速さで加速するだけで"ほぼ"等速と言えます)

private void FixedUpdate(){
    var rb = GetComponent<Rigidbody>();
    const float targetVelocity = 2;
    const float power = 20;
    rb.AddForce(Vector3.right * ((targetVelocity - rb.velocity.x) * power), ForceMode.Acceleration);
}

f:id:tmls:20200923190634g:plain

これで等速になりました。

しかしtargetVelocity = 2としたのに実際には1.4しか出ていないのは気になります。
これは摩擦が影響していて、↓のように摩擦0のPhysic Materialを作成して、

f:id:tmls:20200923191226p:plain

Cubeのコライダーに設定すれば、

f:id:tmls:20200923191346p:plain

velocityが2になっている事を確認できます。

f:id:tmls:20200923191737g:plain

現状、私はこの方法がベストなのではないかと思っています。

AddForce + 空気抵抗 を使う

Rigidbodyのdragの値は抵抗の値を表しています。
抵抗はF = kvの式で表され、速度に比例して変化します。

なので終端速度というものが存在し、徐々に加速して一定の速度に達するとそこからは等速になります。
雨粒がどれだけ高いところから落ちてきても、全部同じ速度なのと同じ原理です。

この終端速度を使って等速を表現します。
まず下記のようなスクリプトを作成し、

private void FixedUpdate(){
    const int power = 25;
    GetComponent<Rigidbody>().AddForce(Vector3.right * power, ForceMode.Acceleration);
}

続いてRigidbodyのdragの値を大き目に設定します。

f:id:tmls:20200923175835p:plain

これで動き始めは加速、しばらくすると等速になるかと思います。

f:id:tmls:20200923175415g:plain

徐々に加速したい場合には良いかもしれません。

摩擦を0にする

コライダーに設定可能なPhysic MaterialはデフォルトでNoneになっていると思いますが、これは摩擦が0であるわけではありません。
Noneの時には、デフォルト値としてDynamic Friction = 0.6 Static Friction = 0.6が設定されています。

従ってこれを上述の方法でDynamic Friction = 0 Static Friction = 0にして例えば、

private void Start()
{
    GetComponent<Rigidbody>().velocity = Vector3.right;
}

このように初速のみを与えれば、あとは速度が維持され等速で動き続けることになります。 ただしこれは氷の上を滑ってるのと同じなので、障害物に当たれば減速したり止まったりします。

AddForce + 摩擦 で等速にすることは可能か?

AddForceと摩擦を使うと、何となく等速に出来そうな気がしますが基本的には出来ません。

摩擦力はF = μNの式で表され、垂直抗力に比例します。 垂直抗力が重力のみの場合、摩擦力は常に一定の力を加え続けます。 従って、空気抵抗のように終端速度が存在しません。

ただし一応、摩擦力と全く同じだけの力を加え続ければ理論上は等速に移動が可能です。 摩擦分を打ち消すだけの力を加える事で、実質的に摩擦力が0にするという話です。

下記のコードは、その一例です。

        private Rigidbody m_rb;
            
        private void Start()
        {
            m_rb = GetComponent<Rigidbody>();
            m_rb.velocity = Vector3.right;
        }

        private void FixedUpdate()
        {
            var col = GetComponent<Collider>();
            Vector3 g = Physics.gravity;
            float m = m_rb.mass;
            float u = 2 * col.material.dynamicFriction;

            float F = u * m * g.y;
            rb.AddForce(Vector3.right * -F, ForceMode.Acceleration);
        }

今回はuの計算を自身のコライダーに設定されたDynamicFrictionの値であると仮定しています。 実際にはこの条件が成立しないこともあるので、上記のコードは常に動くとは限りません。

このように、摩擦を用いた等速の表現は非常にシビアで汎用性が低い事が分かります。 また今回紹介してきた方法は全て摩擦の影響があっても無くてもpowerの値が十分に大きければ、正しく動作するはずですので、摩擦については自由に設定して良いかと思います。

まとめ

Unityで等速に移動する方法を見てきました。
どの方法を使うかは、ケースバイケースですので合ったものを利用いただければと思います。

私のおすすめとしては、物理演算の影響を受けられ即座に等速にすることができる「AddForce + velocity」が汎用性が高いと思うので困ったら試してみてはどうでしょうか。

bug.nの設定

よく使うもの

キー 動作
win k アクティブウィンドウを一つ上に切り替え
win j アクティブウィンドウを一つ下に切り替え
win h メインウィンドウを左方向にリサイズ
win l メインウィンドウを右方向にリサイズ
win shift h ウィンドウサイズを大きくする
win shift l ウィンドウサイズを小さくする
win shift c 現在のウィンドウを閉じる
win , 左のモニターの切り替え
win . 右のモニターの切り替え
win shift , 現在のウィンドウを左のモニターに移動
win shift . 現在のウィンドウを右のモニターに移動
win ctrl o 元のウィンドウに戻す(最大化の解除など)
win num numの仮想モニターに移動する

たまに使うもの

キー 動作
win ctrl メインウィンドウの数を増やす
win ctrl メインウィンドウの数を減らす
win ctrl i ウィンドウの情報を表示する
win d ウィンドウの入れ替え
win i ウィンドウの入れ替え

iniファイル

↓にiniファイルを置いた。

github.com

主に下記サイトのサンプルで、joten'sの見た目とdwm look-alikeのキーコンフィグを融合させている。 github.com

また、このキーコンフィグの際にWin + lの組み合わせがWindows側で設定されており、さらにAutoHotKeyを使っても上書きできなかった。
なので、↓のサイトを参考にレジストリを直接いじって無効にした...。

okwave.jp