kumak1’s blog

kumak1のイラストや技術ログ

M1 搭載 Mac mini を購入したので Unity のビルド時間とか測ってみた

総評

ワットパフォーマンス・コストパフォーマンス・静音性(ほぼ無音)がとんでもなく良い。 (比較用2016, 2019 のMacBook Pro はそれぞれ30万クラス。自作PCも20万弱程度で組んだハズ。一方、今回購入したMac mini は11万円)

このため、メイン開発環境はもちろん、常時起動のビルドサーバ用にとりあえず買う、のも大いにアリだと思う。 (Webエンジニアとしては、Docker対応されるまでintelさんにはお世話になるけども)

計測

手元にあるモノで色々とベンチマークとってみた。

端末 自作PC MacBook Pro 2016 MacBook Pro 2019 Mac mini 2020
CPU Intel Core i5 4670
4core
Intel Core i7 6700HQ
4core
Intel Core i9 9880H
8core
M1
4core + 4core
GPU GeForce GTX 1060 Radeon Pro 460 Radeon Pro 560X M1
RAM 32GB 16GB 16GB 16GB
消費電力
(idle)
83W 8W 17W 2.5W
消費電力
(Cinebench Multi)
117W 61W 91W 22W
Cinebench R23
( Single Core )
881 821 1113 1519
Cinebench R23
( Multi Core )
2796 3962 7468 7815
Unity 2020.2.0b12
iOS (IL2CPP) Build Time
( 3D Game Kit )
- 00:09:16 00:05:17 00:05:16
Unity 2020.2.0b12
Win (Mono) Build Time
( 3D Game Kit )
- 00:08:59 00:05:12 00:04:46
Unity 2020.2.0b12
Win (IL2CPP) Build Time
( 3D Game Kit )
00:12:14 - - -
Unity 2020.2.0b12
Linux (IL2CPP) Build Time
( 3D Game Kit )
00:29:36 00:24:42 00:13:37 00:13:09

Unityのビルド時間は、大方 Cinebench スコア比の通りの差になっている。 Unity 2020.2.0b12 ではまだ Apple Silicon にネイティブ対応したアプリケーションではないため、対応版だともうちょっと短縮されるのかもしれない。

余談

CPU と高速ストレージが上手いこと噛み合っているからか、アプリケーションのインストールや、アセットのインポートなどがガッツリ早くなっている。 Jetbrains Rider や Intellij IDEA の挙動も良い。Karabiner, 1Password, Alfred, Magnet といったユーティリティの動作も問題ないので、ゲーム開発する端末はもう移行しちゃおうと思う

Unity の Package Manager で GitHub の Private Repository を使う

2020/11/12 追記

manifest.json にtokenを直書きしたものをcommit・運用するのもよくないので、もんりぃ先生のいう通り git+ssh プロトコルで通信しよう!

コピペ用 リポジジトリ直下にasmdefファイルがある場合

git+ssh://git@github.com/[orgs]/[repo].git

asmdefの格納されているディレクトリを指定したい場合

git+ssh://git@github.com/[orgs]/[repo].git?path=[path/to/dir] 

結論

Personal access tokens を使った認証をしたらつかえる!

こばなし

Unity2020 にバージョンアップ Unity エディタ上から AssetStore は見れなくなり「Package Manager を使ってね」という圧がかけられるようになりました。なるほど。 購入アセットが100を超えてるので、一覧性がキビしい事を除けば、色々とできることが増えてて良さそうですね。

Package Manager 用の自作ライブラリの作り方

どうやら AssetStore で購入したライブラリだけでなく、自作ライブラリもお作法に則って作成・公開すれば、Package Manager で取り込みできるようになるみたい。大変便利だ。

PackageManagerで自作ライブラリを作成する方法 - Qiita

Package Manager で使えるライブラリのホストについて

GitHub の Public Repository が使えるのは上のQiitaの記事でも確認し、実践済み。 が、Private だとかで認証使いたい場合、他の人は npm サーバーを自分で建てたりしている。

UnityのPackageManagerプライベートリポジトリの調査 - 渋谷ほととぎす通信

Verdaccioでローカルのnpmサーバーを立ててUnityPackageManagerにプライベートリポジトリを使う - 渋谷ほととぎす通信

Unity Package Manager が認証に対応しました! - もんりぃ is undefined.

しかし、個人プロダクトのセキュリティを自分自身で面倒みるのは、正直コスパが悪すぎる。 このため、GitHub Packages を利用するのが適切・・なのだが、Private な Packages 0$運用するにはストレージ容量とデータ転送量が限られる。 パーソナルアクセストークンを使用するアクセスは無料なので、開発メンバーが1人だけで500MB以下の運用なら積極的に使っていこう。 スケールしてもお金で殴れば解決するし。

GitHub Packagesについて - GitHub Docs

GitHubパッケージの支払いについて - GitHub Docs

Package Manager で GitHub の Private Repository を使う設定

「Packages にするほどでもないけど、金かけたくないし、なんか雑に試したいし、認証付きで公開したいんじゃい!」という事もあると思う。 この場合 Personal access tokens を使った認証をしたら、すんなり利用できた。

自身のUnity プロジェクトの manifest.json に以下のフォーマットで読み込みたいリポジトリを指定するだけ。

{
  "dependencies": {
    [中略]
    "{package_name}": "https://{token}:x-oauth-basic@github.com/{owner}/{repo}.git"
  }
}

{package_name} {token} {owner} {repo} はよしなに置き換えてください。 ちなみにアクセストークンは以下リンクから発行できる。

https://github.com/settings/tokens

開発環境をうまく使いこなして、快適にすごそう

Unity の Command Buffer 用の Shader を雑に試せるスクリプト書いた

会社でお昼ご飯食べながら Amazon.co.jp: ディジタル画像処理 [改訂第二版] eBook: ディジタル画像処理編集委員会: Kindleストア を読む会をすることになった。

内容をパラっとめくってみるとどうやら、理論・数式・画像処理前後のサンプル画像は載っているが、実コードには落とし込まれていないようだ。 (汎用性を持たせるために、この構成は正しいと思う)

「より理解を深めるために実際にコードを書きたい!できればShaderで!」 「処理対象の画像は手軽に作り出したい!」

と思ったので、Unity のカメラへ手軽にShader(Material)を重ね掛けできるコンポーネントを作った。

GitHub - kumak1/CommandBufferTester

利用方法は、Readmeの通りに Unity Package Manager で Package を追加し、Camera オブジェクトにコンポーネントを追加するだけ。 Materials パラメータに自作の Shader(Material)をポイポイ登録すればOK。 (Shader の作成は https://github.com/kumak1/CommandBufferTester/blob/master/shader/Grab.shader を見るとお約束がわかると思う)

f:id:kumak1:20200920000609p:plain

捗る準備はできたので、勉強すすめよ〜

Unity の Assembly Definition の依存関係を PlantUML で出力する雑スクリプト書いた

Unity - Manual: Assembly definitions 使ってますか?

qiita.com

上記の記事のような完全に理解したツワモノのおかげで、わたしも色々恩恵をいただいております。 肥大化した自作コードを分割できたり、エディタービルド時間を削減できたりして便利ですね。

f:id:kumak1:20200913234032p:plain

コードで上で縛るのはもちろん、このように単体の依存関係を目で確認しやすいのも良い所ですね。

ここまでできると「プロジェクト全体など任意のスコープで俯瞰した図をみたい」「Docusaurusなどのドキュメントでも俯瞰図みたい」と欲が深くなったので、指定ディレクトリ配下を検索して PlantUML を出力できるようなスクリプトを書いてみました。

jqyq を利用してるので注意、と、よしなに改変してご自由に利用ください。

#!/usr/bin/env bash

JQ=/usr/local/bin/jq
YQ=/usr/local/bin/yq

TARGET_FILE=\*.asmdef
WORK_DIR=$1
UP_ARROW="-up->"

# オプションがなければカレントディレクトリが処理対象。
# 空白のあるディレクトリ名・ファイル名を考慮してないので注意
if [ -z "${WORK_DIR}" ]; then
  WORK_DIR="."
fi

ITEMS=$(find "${WORK_DIR}" -name "${TARGET_FILE}")
ROOT="\"Assembly-CSharp\""
TEXTS=()

echo "@startuml"
echo "component ${ROOT}"

for ITEM in ${ITEMS}; do
  NAME=$(cat $ITEM | $JQ '.name')

  # コンポーネントを宣言
  echo "component ${NAME}"

  # Assembly-CSharp への依存関係を明示
  if [ $(cat $ITEM | $JQ -r '.autoReferenced') = "true" ]; then
    echo "[${NAME}] ${UP_ARROW} [${ROOT}]"
  fi

  # その他ライブラリへの依存関係をいったん控える
  for GUID in $(cat $ITEM | $JQ -r 'select(.references != null) | .references[]' | sed s/GUID://); do
    TEXTS+=("[${NAME}] ${UP_ARROW} [${GUID}]")
  done
done

# bash 3.2 利用想定なので非常に非効率(ハッシュが使えない・・)
# 利用しているシェルに合わせて、GUID -> Assembly Definition File Name への置換処理をチューニングしましょう
for ((i = 0; i < ${#TEXTS[@]}; i++)) {
  SHOW=false

  for ITEM in ${ITEMS}; do
    NAME=$(cat $ITEM | $JQ '.name')
    GUID=$($YQ read $ITEM.meta 'guid')

    if [ "`echo ${TEXTS[$i]} | grep ${GUID}`" ]; then
      echo ${TEXTS[$i]} | sed s/${GUID}/${NAME}/
      SHOW=true
      break
    fi
  done
  
  if ! "${SHOW}"; then
    echo ${TEXTS[$i]}
  fi
}

echo "@enduml"

これを実行すると PlantUML が標準出力されるので、クリップボードにコピーしてよしなに利用しましょう。 JetBrains IDE 利用者は PlantUML integration - Plugins | JetBrains を使うと scratch で作成できるし、プレビューも画像保存も手軽で良い。

で、実際に私が出力してみたものがこんな感じ。

f:id:kumak1:20200913235612p:plain

どう依存してるかは俯瞰してみれるようになったが、なんだか大変煩雑な図ができあがってしまった。。コードをもっと整理しないとな・・ (図内でGUIDが直接出力されているのは、対象ディレクトリ外に Assembly Definition File があるため。Rewired や Behavior Designer などの asset だ)

なにはともあれ、こういった問題点も可視化できて便利だな?

Unity で BehaviorDesigner を導入する際の役割分担など

設定値の管理ってむずかしい。 最近ようやっと Behavior Designer を本格導入し、「Behavior Designer 内に専用 variable あるのか・・」と概念の広がりを感じたのでなおのこと。

assetstore.unity.com

Unity で個人制作をしている場合、当然ながら「作業者は全部俺!」となるので全ての領域に対してアクセスできてしまう。 このため、開発中は「UnityEditor から MonoBehavior の public 変数にアクセスできるから、とりあえず急ぎ開発中だしココで!」とかしてしまってた。 くわえて「クラスの中にマジックナンバー(あるいは定数)埋め込んだ・・けど、どこだっけ。どんな名前だっけ」とかにもなってた。

全部が全部そうしていたわけではないが、焦っている時などは前述の行動をとりがちだったのだ。 ので、自戒の意味も込め、迷子にならないように自分用の地図をメモしておく。

f:id:kumak1:20200723155455j:plain

左上の丸、世界・環境の設定

レベルデザイン。地形・照明・3Dオブジェクトの配置や、NavMesh生成・調整などなど。 sceneで管理するもよし、シングルトンとScriptableObjectでゴリゴリ管理するもよし。

中心の丸、人物・モノの思考や経験の設定

バランス調整。体力・移動速度といった変数の調整や、BehaviorTree の思考の組み替えをおこなう。 ゲームプレイ中は不変だが、ゲーム作成中は柔軟に調整したいものをこの領域におく。 難易度調整はここだけ触ればOK、になれるとよし。

右下の丸、人物・モノが自身・世界へ影響を与える行動の設定

意思決定は中心の丸に全て委ねるので、ここではActionの内容作ればよい。 攻撃の場合だと、 直前の行動はなにか 大・小どんな攻撃が選択されたか ブレンドするアニメーションはなにか などから判断をしてアトミックな行動を演出する。 これらは Asset のコンポーネントだったり、自身が作ったクラスだったりよしなに。

Unity で三角比を使って辺の長さや角度を求める

3D空間で、Shaderのテカリを制御したり、カメラやオブジェクトの挙動などを制御したい際には、とにかく角度や2点間の距離を求めることが多い。

そして残念ながら、使う側の人間(私)は度数法に染まりきっているため、ラジアンだとどれくらいの角度かパッとイメージできない。くやしい。 (「最大角度 0.2 rad まで許容」みたいな入力項目には「具体的にどんくらいだよ・・」ってなる残念な頭である。とはいえ、物理カメラの画角なども一般的には度数法で表すので、一般的・・のハズ)

なので、わたしでもビャッと使える関数群を用意してみた。よかったらお使いください(意外とネットにはこういったサンプルコードが転がってなかったので・・)

using UnityEngine;

public static class Util
{
    // 斜辺
    public static float HypotenuseByBoAn(float bottom, float angle) =>
        bottom / Mathf.Cos(angle * Mathf.Deg2Rad);
    public static float HypotenuseByBoHe(float bottom, float height) =>
        bottom / Mathf.Cos(Mathf.Atan2(height, bottom));
    public static float HypotenuseByHeAn(float height, float angle) =>
        height / Mathf.Sin(angle * Mathf.Deg2Rad);

    // 底辺
    public static float BottomByHeAn(float height, float angle) =>
        height / Mathf.Tan(angle * Mathf.Deg2Rad);
    public static float BottomByHyAn(float hypotenuse, float angle) =>
        hypotenuse * Mathf.Cos(angle * Mathf.Deg2Rad);
    public static float BottomByHeHy(float height, float hypotenuse) =>
        Mathf.Sqrt(Mathf.Pow(hypotenuse, 2) - Mathf.Pow(height, 2));

    // 高さ
    public static float HeightByBoAn(float bottom, float angle) =>
        bottom * Mathf.Tan(angle * Mathf.Deg2Rad);
    public static float HeightByHyAn(float hypotenuse, float angle) =>
        hypotenuse * Mathf.Sin(angle * Mathf.Deg2Rad);
    public static float HeightByBoHy(float bottom, float hypotenuse) =>
        Mathf.Sqrt(Mathf.Pow(hypotenuse, 2) - Mathf.Pow(bottom, 2));

    // 角度
    public static float AngleByBoHe(float bottom, float height) =>
        Mathf.Atan2(height, bottom) * Mathf.Rad2Deg;
    public static float AngleByBoHy(float bottom, float hypotenuse) =>
        Mathf.Acos(bottom / hypotenuse) * Mathf.Rad2Deg;
    public static float AngleByHeHy(float height, float hypotenuse) =>
        Mathf.Asin(height / hypotenuse) * Mathf.Rad2Deg;
}

浮動小数点の演算なので、許容誤差 0.00001 に設定、直角三角形を題材にしてテストを実施している。

[Test]
public void HypotenuseByBoAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByBoAn(route3, 30), delta);
    Assert.AreEqual(route2, Util.HypotenuseByBoAn(1, 45), delta);
    Assert.AreEqual(2, Util.HypotenuseByBoAn(1, 60), delta);
}

[Test]
public void HypotenuseByBoHe()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByBoHe(route3, 1), delta);
    Assert.AreEqual(route2, Util.HypotenuseByBoHe(1, 1), delta);
    Assert.AreEqual(2, Util.HypotenuseByBoHe(1, route3), delta);
}

[Test]
public void HypotenuseByHeAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByHeAn(1, 30), delta);
    Assert.AreEqual(route2, Util.HypotenuseByHeAn(1, 45), delta);
    Assert.AreEqual(2, Util.HypotenuseByHeAn(route3, 60), delta);
}

[Test]
public void BottomByHeAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHeAn(1, 30), delta);
    Assert.AreEqual(1, Util.BottomByHeAn(1, 45), delta);
    Assert.AreEqual(1, Util.BottomByHeAn(route3, 60), delta);
}

[Test]
public void BottomByHyAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHyAn(2, 30), delta);
    Assert.AreEqual(1, Util.BottomByHyAn(route2, 45), delta);
    Assert.AreEqual(1, Util.BottomByHyAn(2, 60), delta);
}

[Test]
public void BottomByHeHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHeHy(1, 2), delta);
    Assert.AreEqual(1, Util.BottomByHeHy(1, route2), delta);
    Assert.AreEqual(1, Util.BottomByHeHy(route3, 2), delta);
}

[Test]
public void HeightByBoAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByBoAn(route3, 30), delta);
    Assert.AreEqual(1, Util.HeightByBoAn(1, 45), delta);
    Assert.AreEqual(route3, Util.HeightByBoAn(1, 60), delta);
}

[Test]
public void HeightByHyAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByHyAn(2, 30), delta);
    Assert.AreEqual(1, Util.HeightByHyAn(route2, 45), delta);
    Assert.AreEqual(route3, Util.HeightByHyAn(2, 60), delta);
}

[Test]
public void HeightByBoHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByBoHy(route3, 2), delta);
    Assert.AreEqual(1, Util.HeightByBoHy(1, route2), delta);
    Assert.AreEqual(route3, Util.HeightByBoHy(1, 2), delta);
}

[Test]
public void AngleByBoHe()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByBoHe(route3, 1), delta);
    Assert.AreEqual(45f, Util.AngleByBoHe(1, 1), delta);
    Assert.AreEqual(60f, Util.AngleByBoHe(1, route3), delta);
}

[Test]
public void AngleByBoHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByBoHy(route3, 2), delta);
    Assert.AreEqual(45f, Util.AngleByBoHy(1, route2), delta);
    Assert.AreEqual(60f, Util.AngleByBoHy(1, 2), delta);
}

[Test]
public void AngleByHeHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByHeHy(1, 2), delta);
    Assert.AreEqual(45f, Util.AngleByHeHy(1, route2), delta);
    Assert.AreEqual(60f, Util.ngleByHeHy(route3, 2), delta);
}

在宅勤務14週間と経過の雑記とマイクケーブル等のDIY

kumak1.hatenablog.com

生活の変化

  • 運動は復活!あったかくなってきたので、汗を流す量が増えて気持ちいい

マイクケーブルのDIY

長い在宅勤務。勢いで ATEM Mini Pro とアナログミキサーをポチってしまったので、それらをつなぐためのXLRケーブルが必要に。 ついでに今使ってるマイクケーブルも10年たったし、PC周りで使うには長すぎる(60cm程度で十分)なので、自作しますぞ。

作り方はサウンドハウスさんが動画を公開しているので「やってみたい」となったらコチラを参考にしよう

www.youtube.com

f:id:kumak1:20200504214144j:plain

部品は前述のサウンドハウスから取り寄せ。電線の作業する際、ワイヤーストリッパーはほぼ必須なくらい便利(ハサミだと絶縁体を切るのに切れ味が物足りなかったり、ニッパーでは銅線まできっちゃったり)

f:id:kumak1:20200504214209j:plain

ハンダする際は手が何本も生えて欲しいくらい手が足りない。ので、東急ハンズとかで売ってるメモクリップを使う。安くて便利。

f:id:kumak1:20200504214230j:plain

クランプなどを組み合わせれば、位置調整はすべて任せてしまって、自分はハンダづけに集中できる。

f:id:kumak1:20200504214252j:plain

成果物は ミキサー to オーディオインターフェイスのLRケーブル、マイク用の短めのケーブル。 これくらいのハンダ作業であれば、小学校で習ったレベルでできるので大変おすすめ。 (安くすむし、自分が必要な長さに調整できるので)

嫁友とDiscord飲み会に参加

嫁友夫婦3組で Discord 通話つなげながら飲み会するなどした。 食卓に iPad をおいて、カメラOffで適当にワーキャーいいながら飲み、どうぶつの森スプラトゥーンをやるなどして am4:00 に解散。 金はかからないし、好き勝手に飲み食いできるし、介護はおのおのの家庭でできるので大変楽ちんでした。

あと、やっぱり関西人が飲みの場にいると盛り上がって楽しい。