UnityのエディタはWSLのVimが最強だということ

Unityのコード編集

Unityのコード編集ツール候補には次のような選択肢がある.

IDE

単にコードを書く支援だけじゃなく,デバッグ機能や便利ツール全盛り!というIDE統合開発環境)がある.
これは,すごく便利で何も設定しなくてもすぐ使えるし,最近はUnityとの連携も強固になってきた.

Unityで有名なIDEは,Visual StudioとRider.

Visual StudioはUnityのデフォルト設定になっている.
無料ですぐ使えるから,デフォルトになってるのも頷ける.

Riderは,Visual Studioとほぼ同等の機能が使える上,コード補完がより賢くなっていて,もっと色んなおせっかいをやいてくれる.
例えば,Unityのnull比較はやめた方が良いと教えてくれたり,命名規則にやたら厳しかったりする.
有料(学生無料)だけどね.

そんな便利なIDEだが,性能が良い分,動作がやや重い.
それから,Vimプラグインを入れても,もとからあるキーバインドと衝突したり,コード編集画面外に移動すると戻れないみたいな地味に嫌な問題がある.

エディタ

IDEではない,いわゆるエディタは,コードを書くための必要最小限を提供してくれる.
最近はプラグインを入れまくるとIDEみたいになるけど,それはおいておこう.

エディタは星の数ほどあるけれど,Unityで有名なのは,Visual Studio CodeVisual Studioとは違うよ).
エディタの場合は,最初にちょっとした環境構築が必要なので,やや中級者向け.

コード補完や機能は,IDEには及ばない.
ただしその分動作が軽い.
無駄な機能がないとも言えるわけだ.

ある程度慣れてくると,こっちの方が好きな人も結構いる.

Vim

古より伝わりしCUIで動くエディタ. 快速,無限のカスタマイズ. 狂信者多数.

Visual Studio Codeと同じくエディタなのだが,動作する環境がCUI
初めてVimを触ったら意味不明で使いにくいと思うが,カスタマイズしまくってキーバインドになれると,マウスを一切使わずに爆速コーディングが可能になる.

最近は,NeoVimやLSPといったVimIDEさながらの便利さにしようという試みもある.

Emacs

Vimを書いてしまったのでEmacsも載せなくてはならない.
が,筆者は使ったことが無いので詳しい事が書けない.申し訳.

Vimよりももっと自由度が高く,コード編集に限らずあらゆることがEmacs上でできるらしい.
利用者数はVimの方が多いものの,こだわる人はEmacsを使っているイメージ.

VimEmacsはよく戦争になるので,気軽に口に出さないのが吉.

Unityのエディタに最適なのは?

初心者は間違いなくVisual Studio
慣れてきたら好みによって,RiderやVisual Studio Code
めちゃくちゃ慣れて,Vimに興味ある・Vimmerの方は,Vimを使おうぜっていうのがこの記事.

IDEより早く,VSCodeよりマウスを使わないVimが最強です.(あ,Emacsでもいいと思います.)

問題が・・・

よっしゃVim使うぜってなっても,2つ問題がある.

1つは環境構築の難易度の高さ.
Vimを初めて使うならそもそもVimの環境作らなきゃだし,そうでなくともUnity連携は割と面倒.
まあでもこれは頑張ればなんとかなる.

2つ目は,UnityはWindows環境で使いたいということ.
多くのアプリ開発Macが使われるが,ゲームはやや特殊.
クオリティの高いゲームを作るほど,より高性能なGPUが欲しくなる.
するとどうしてもMacではなくWindowsになってしまう.

WindowsVim...?
VimCUIで動くツールだ(GVimは一旦忘れよう).
WindowsCUIは,コマンドプロンプト.これでVimはちょっときつい.
色々工夫すれば,Windows上でもVimが使えるが,結局環境がWindowsなので思うように使えないことが多い.

WSL

Windows Subsystem for Linux(WSL)は,Windows上でLinuxを使う機能.
この機能は公式サポートされている.

これでVimを使うことで,Windowsで組んだ最強PCでUnityを動かしつつ,Linux環境のVimで爆速コーディングが可能になる.
WSLを使ったVimの環境構築については,別の記事で書いたから興味ある人はそっちをみてくれよな.

tmls.hatenablog.com

結論

つまるところで,UnityのエディタはWSLのVimが最強だということです.

WindowsのUnityでVimを使う手順

UnityはWindowsで動かしたいけど、Vimが使いたいので、WSLでVimを使うことにした。

WSLのインストール

WSL2を使っても良かったが、追加のコンポーネントが必要とのことで、ひとまずWSL1で利用することにした。 WSL2でも問題ない。 https://qiita.com/matarillo/items/61a9ead4bfe2868a0b86

Windows Terminal

インストールして適当に設定。

Windows Terminalのベルを消す

Windows TerminalでESCキーを押すと、Windowsの警告音がなってうるさいので、消す。

echo 'set bell-style none' >> ~/.inputrc

NeoVim

ひとまずアプデ。

sudo apt update
sudo apt upgrade

リポジトリ追加&インストール。

sudo apt-add-repository ppa:neovim-ppa/stable
sudo apt update
sudo apt install neovim

https://sig9.hatenablog.com/entry/2020/05/02/000000

vim-lsp

VimIDEのように使うには、LSPを導入する必要がある。 導入方法はいろいろあるが、私はvim-lsp-settingsを使っている。

https://github.com/mattn/vim-lsp-settings

omnisharpを機能させる

vim-lsp-settingsは、C#のLSPにomnisharpをインストールする。 これと同時にデフォルトでmonoもインストールされるのだが、このmonoだとうまくいかないケースが割とある。 そのため、最新のmonoをインストールして、それを使うように変更する。

monoのインストール https://linuxize.com/post/how-to-install-mono-on-ubuntu-20-04/

vim-lspでインストールされたomnisharpサーバーのmonoのパスを変更する。 ~/.local/share/vim-lsp-settings/servers/omnisharp-lsp/runmono_cmdの行を↓のように書き換える。

修正前:mono_cmd=${bin_dir}/mono
修正後:mono_cmd=/usr/bin/mono

Unityプロジェクトを一度Visual Studioで開く

初めてプロジェクトを開く場合、一度Visual Studioで開いてソリューションファイルを作成する必要がある。(もしかしたらUnityの起動だけで作られるかもしれないが) プロジェクトのルートフォルダに.slnファイルと.csprojファイルがあるのを確認したら、Visual Studioは閉じてしまう。

Unityのソリューションファイルを読み込めるようにする

いざ、WSLからUnityのC#ファイルを開くと、ソリューションファイルが読み込まれる。 しかし、表示された画面は真っ赤で、MonoBehaviourがundefinedとか言われる。

これは、UnityのソリューションファイルがWindows用に生成したものであり、Windows以外からアクセスすると間違ったパスになるのが原因だった。 具体的には、Unityプロジェクトのルートフォルダにある*.csprojの中が↓のようになっているのがまずい。

<Reference Include="UnityEngine">
  <HintPath>C:/Program Files/Unity/Hub/Editor/2019.4.23f1/Editor/Data/Managed/UnityEngine/UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AIModule">
  <HintPath>C:/Program Files/Unity/Hub/Editor/2019.4.23f1/Editor/Data/Managed/UnityEngine/UnityEngine.AIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ARModule">
  <HintPath>C:/Program Files/Unity/Hub/Editor/2019.4.23f1/Editor/Data/Managed/UnityEngine/UnityEngine.ARModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AccessibilityModule">
  <HintPath>C:/Program Files/Unity/Hub/Editor/2019.4.23f1/Editor/Data/Managed/UnityEngine/UnityEngine.AccessibilityModule.dll</HintPath>
</Reference>
...

HintPathにC:がついていることが問題で、このファイルをWSLで読み込んでも、C:は認識できない。 これは、C:の部分を/mnt/cに置き換えることで解決できる。

↓のシェルスクリプトを実行すれば、すべての.csprojC:/mnt/cに置き換える。

#!/bin/bash

dir="${PWD}"

for f in *.csproj
do
    sed -i "s/C:/\/mnt\/c/g" $f
done

これで再びC#ファイルを開けば、ついに正しく読み込めるはずだ。

.csprojが自動再生成される問題

Unityの再起動時等に.csprojファイルが再生成されることがある。 すると、せっかく置き換えたC:がまた元に戻ってしまう。

この問題の解決策として、次の2つを考えているがまだうまくいっていない。 うまくいったらまた追記したい。 しばらくは毎度シェルスクリプトを実行することで耐えようと思う。

  • Unityに.csprojファイルを再生成させない
  • WSL用のソリューションファイルを別フォルダに移し、vimではそのソリューションを開くよう設定する

【Unityシェーダ】ZWrite Onなのに描画順が崩れる場合の対応

問題

サーフェスシェーダで、半透明なシェーダを作っている際に描画順が崩れた。  

元コード↓

Shader "hoge/hoge"
{
    Properties
    { 
    }
    
    SubShader
    {
        ZWrite On

        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }
        
        CGPROGRAM
        
        #pragma surface surf Lambert alpha
        
        struct Input
        {
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
        }
        
        ENDCG
    }
    
    Fallback "Diffuse"
}

解決手段

下記のようにZWrite Onを記述していたところを、Passで囲って下記のようにする。

Pass {
    ZWrite On
    ColorMask 0
}

コード全体

Shader "hoge/hoge"
{
    Properties
    { 
    }
    
    SubShader
    {
        Pass {
            ZWrite On
            ColorMask 0
        }

        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }
        
        CGPROGRAM
        
        #pragma surface surf Lambert alpha
        
        struct Input
        {
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
        }
        
        ENDCG
    }
    
    Fallback "Diffuse"
}

ffmpegで動画のアスペクト比を変更する

下記のように利用する.

$ ffmpeg -i input.mp4 -vf scale=1920:1080,setsar=1 output.mp4

画像等の場合はsetsar=1が不要だが,動画では必要となる.

1920x1080(iPhone 8+)の動画を,2688x1242(iPhone 11 Pro Max)にリサイズした結果は以下.

f:id:tmls:20210412213313p:plain
1920 x 1080

f:id:tmls:20210412213724p:plain
2688 x 1242

uGUIのTextにルビをつける【Unity】

ルビ振り問題

uGUIで漢字にルビをつけたいとき,Unityでは結構面倒です.
普通にやると,新しくTextオブジェクトを作成して,漢字の上に位置を合わせるしかありません.
これを全部の漢字にやっていては,あまりに時間がかかる上に,変更に耐えられません.
そこで,ルビふりをもっと楽にする方法を紹介します.

使い方

[漢字]<かんじ>という形式で書くことで,ルビを設定できます.

f:id:tmls:20210411134456p:plain

f:id:tmls:20210411134545p:plain

導入方法

  1. Unityに後述のスクリプトをインポートもしくは,コピペする
  2. uGUIのTextがついたプレファブを作成する(ルビ用のTextとして利用)
    • TextのAlignmentは縦横どちらも中央寄せに
    • Textのフォントサイズは小さめに
  3. ルビをふりたいTextに,1のスクリプトをつけ,2のプレファブを設定する
// BEGIN MIT LICENSE BLOCK //
//
// Copyright (c) 2017 dskjal
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//
// END MIT LICENSE BLOCK   //
/*
 * *注意*
 * 改行の処理はしてない.ルビを必要とする部分が途中で改行されないよう処理する必要がある.
 * http://dskjal.com/unity/detect-unity-ugui-break-pos.html を参照.
 */
/*
 * 設定方法
 * uGUI の Text にこのスクリプトをつける.
 * テキストのセンタリング設定をした uGUI の Text のプレハブを作り TextPrefab にセット
 * プレハブのフォントサイズをテキストのフォントサイズの 1/2 ぐらいにする
 */
using UnityEngine;
using UnityEngine.UI;
using System.Text.RegularExpressions;
using System;
using System.Collections.Generic;

[RequireComponent(typeof(Text), typeof(RectTransform))]
public class RubyText : MonoBehaviour {

    Text text;
    RectTransform rt;
    public GameObject TextPrefab;  // テキストはセンタリングしておくこと
    private const float rubyHeight = 0.7f;

    class RubyPos {
        public int start;   // ルビの開始インデックス
        public int end;     // ルビの終了インデックス
        public string ruby; // ルビ
        public RubyPos(int start, int end, string ruby) {
            this.start = start;
            this.end = end;
            this.ruby = ruby;
        }
    }
    void Start() {
        text = GetComponent<Text>();
        rt = GetComponent<RectTransform>();

        var rubyPos = new List<RubyPos>();
        var matches = Regex.Matches(text.text, @"\[(.*?)\]<(.*?)>");
        // removed text count
        var rmtxt = 0;
        foreach(Match match in matches){
            var match1 = match.Groups[1];
            var match2 = match.Groups[2];
            // remove markdown texts and insert match1
            text.text = text.text.Remove(match.Index - rmtxt, match.Length);
            text.text = text.text.Insert(match.Index - rmtxt, match1.Value);
            // remove text count 1([)
            rmtxt += 1;
            rubyPos.Add(new RubyPos(match1.Index - rmtxt, match1.Index - rmtxt + match1.Length - 1, match2.Value));
            // removed text count is 2(]) + 2(<>) + (ruby count)
            rmtxt += 1 + 2 + match2.Length;
        }

        var generator = new TextGenerator();
        // テキストのレンダリング位置の計算
        var settings = text.GetGenerationSettings(rt.sizeDelta);
        settings.scaleFactor = 1;
        generator.Populate(text.text, settings);

        // 各文字のレンダリング位置を記録した文字配列の取得
        var charArray = generator.GetCharactersArray();
        foreach (var ruby in rubyPos) {
            var start = charArray[ruby.start].cursorPos;
            var end = charArray[ruby.end].cursorPos;
            end.x += charArray[ruby.end].charWidth;

            PlaceRuby(start.x + (end.x - start.x) / 2f, start.y + charArray[ruby.start].charWidth / 2 * rubyHeight, ruby.ruby);
        }
    }

    // TextPrefab をインスタンス化して配置する
    void PlaceRuby(float x, float y, string text) {
        var o = GameObject.Instantiate(TextPrefab);
        o.name = text;
        o.transform.SetParent(this.transform);
        var prt = o.GetComponent<RectTransform>();
        prt.localPosition = new Vector3(x, y, 0f);
        prt.localScale = new Vector3(1, 1, 1);

        o.GetComponent<Text>().text = text;
    }
}

このスクリプトは,↓の方が公開しているものをもとに,改良を加えています.

dskjal.com

最終的に↓のように設定されていればOKです.

f:id:tmls:20210411134411p:plain
最終的なコンポーネント設定例
f:id:tmls:20210411140554p:plain
ルビとなるプレファブ(手順2)の設定例

UnityのビルドをSteamにアップロードする方法

公式サイトからSDKをダウンロード

↓のサイトから最新のSteamworks SDKをダウロードします. zipを解凍後,toolsフォルダにあるSteamPipeGUI.zipも解凍して,SteamPipeGUI.exeを起動します. sdk > tools > ContentBuilder > content 内にUnityのビルドで出力されたファイルを入れておきましょう.

https://partner.steamgames.com/dashboard

f:id:tmls:20210321201552p:plain

f:id:tmls:20210321201601p:plain

App IDを入力します.
これはSteamworksで,ゲームタイトル名(App ID)という表記で確認できます.

App Descriptionには好きな言葉を書きます.

Add Depotを押して,Build Pathの横のBrowseを押し,先ほどビルドファイルを入れたcontentフォルダを指定します.
Steamworks SDK Content Builder PathにContentBuilderフォルダを指定します.
SteamworksのアカウントIDとパスワードを入力します.

中央右あたりにあるUploadボタンを押します.
SteamGuardコードを聞かれたら,メールに届いてる文字を入力しましょう.

f:id:tmls:20210321235359p:plain

アップロードの確認

f:id:tmls:20210321210511p:plain

Steamworksに移動し,SteamPipe > ビルドを選択します.
うまくいっていれば,ここにビルドが表示されるはずです.

「アプリブランチを選択」を押し,defaultに変更して,変更をプレビューをクリック.
「今すぐビルドをライブに変更」を押すことで,ビルドを有効化できます.

起動オプションの設定

Steamworksにて,インストール > インストール全般 に移動します.
起動オプションの欄で,「新しい起動オプションを追加」を選択します.
実行可能ファイルの項目に,Windows desktop 64-bit.exeのようにゲームの実行ファイルを指定します.

テスト

公開タブに移動し,「公開の準備」→「Steamに公開」→「本当に公開する」と押していきます. これはビルドが公開されるだけなので,まだリリース前であれば自分のアカウント限定で公開されます.

βテスト

Steamworksのアカウント管理者以外の人がプレイするためには,インストール用のキーを発行します.
これはSteamworksの「キーの管理」から行います.
説明に従っていけば問題なく作れると思います.
キーの発行は申請をだしてから2~3日かかると書いてありますが,実際には6時間後くらいに承認,発行されました.

あとは,発行されたキーをSteamで入力すればインストールできます.

公式ドキュメント

partner.steamgames.com

Windowsの個人用ファイルを保持した初期化の挙動

消えるもの

  • インストールしていたものはアンインストールされた
  • アカウント情報も初期化された
  • WSLは消えた
  • Windows Terminalも消えた

消えないもの

  • ドキュメントフォルダはもちろん,ダウンロードフォルダやC直下のファイルも全て残っていた
  • Windows Insider Programも入ったまま