Unityの新入力システム
Unityの入力システムは汎用性が低い
Unityはマルチプラットホームという利点がありますが、入力システムの汎用性は低いです。
当然同一で処理したいタッチ処理とマウスクリック処理を別々に書かなければいけなかったり、マルチプレイヤーゲームで1プレイヤーはキーボード操作、1プレイヤーはコントローラーといったことをするのに複雑な機構が必要になってしまいます。
また、プレイヤーの状態に合わせて操作を変更したいというのは自然な要望ですが、それにも対応していませんでした。
そこでUnityでは新しい入力システムとしてPlayerInputという機能をβ版で発表しています。
新しい入力システム
新しいインプットシステム、一緒に開発しませんか? – Unity Blog
https://github.com/Unity-Technologies/input-prototype
どういった機能なのかは動画を見てもらうのが一番早いかと思います。
今までとは何が違うのか
今まではProjectSettings/InputManager.asset等に保存していたため、プロジェクト全体でグローバルに入力システムを運用する必要がありスコープが広い上に指定も文字列でやらなければいけない等、とても積極的に使いたいシステムではありませんでした。
この新しいシステムではプレイヤーのある状態でのキーマップをActionMapとして作成し、そのアクションマップを状態に合わせて切り替えることでキーコンフィグを動的に切り替えることができるようになります。
これによってコード上では
if(firstPersonControls.fire.isHeld){ ... }
という風に入力システムを意識しない統一的なアクションでコーディングできるようになります。
未来と生きるために
Unityでは難しいことを簡単にするという明確なミッションがあります。なので、VR等の最先端の技術や新しいデバイスの対応をどんどんしていくことになるでしょう。
例えば、このゲームをViveコントローラとOculus Touchに対応して欲しいと言われた際にそれぞれがインハウスで開発するのはいかにも時間の無駄です。
この機能が入れば、そのDeviceProfile機能で入力とアクションをマッピングできるようになるかもしれません。
汎用的な機能が入ればAssetStoreに各デバイスのプロファイラーやマッパーがリリース会社から提供されることが期待できるので信頼性もあがります。
良きデベロッパーのために
Unityはスコープがゆるかったりパブリックにするように設計されていたりで悪いデベロッパーを育てる側面が強いです。
しかし、Unityは新しい機能で確かに問題を切り分けて良い設計ができるようにしています。
この思想にのって、ステートマシンという概念、ステート毎の入力(アクションマップ)、どのスクリプトで入力を取得すべきか。を学習することは良いデベロッパーへの近道に思えてなりません。
Unityのフォルダ構成ベストプラクティス
モチベーション
Unityでは多岐にわたるプロジェクトを考慮してフォルダ関係のサポートは薄く、プロジェクト毎に自由に設定しなければならない。
自由は素晴らしいことだが統一規格がないことは他の人のコードを読む時にもんにゃりする。
そこで、私が良いと思っているフォルダ構成を紹介しようかと思う。
オススメする方法
とりあえず結論から紹介する。
- Assets直下をAsset Storeのインポート先とする。
- Assets以下に自身のグループや会社名のフォルダを作り、ここを作業フォルダとする。
- Assets/MyGroup以下にScenesやScripts、Prefabs等のフォルダを作る。ここは複数のシーンで利用するアセットを格納する。
- Assets/MyGroup/Scenes以下にシーン名フォルダを作る
- Assets/MyGroup/Scenes/SceneName以下にScripts、Prefabs等のフォルダを作る。ここはこのシーン専用のアセットを格納する。
- Assets/MyGroup/Scenes/SceneName/SceneName.unityにシーンファイルを作る。
タグを有効活用すれば、もっと良い方法もありそう。
けど、いちいちアセットを作る度にタグ打つのが私には無理だった。
ダメだった方法
Assets/Pluginsフォルダに全てのAsset Store等の外部アセットを入れる。
上記をすると誤作動するアセットが度々存在したためダメだった。
スクリプトに自身のパスを指定しており、デフォルトインポートパス以外では動作しないアセットが存在する以上デフォルトインポート以外は信用し辛い。
そして多くのデフォルトインポート先はAssets/GroupNameである。これはもう諦めてそれに従った方が良い。
たまにAssets直下に展開しているアセットもあるが、その場合は適当なフォルダを作って詰める。
それで動かなかった場合はスクリプト修正か利用しないかを選択した方が良いと思う。プラグインを簡単に外せる環境構築は大事。
というか、有料アセットが間違ってgithubに上がってたりするのでプラグインマネージャ欲しい。いろんな人が困っているはず。
ダメだった方法2
Assets/Scenesに全てのシーンファイルを詰める。
Assets/Scriptsに全てのスクリプトファイルを詰める。
Assets/Prefabsに全てのPrefabファイルを詰める。
シーンが増えてくるとアセットのスコープが見えなくなってくる。
スコープが見えないということは変更に対する影響範囲が見えなくなるということだ。
つまり、ちょっとシェーダを書き換えたり、ちょっとPrefabを更新したりするだけで他のシーンでは動作しなくなっている可能性がある。
スクリプトのnamespaceもシーン毎に変える。
ちょっと横にそれるが、Unityではスコープの概念がゆるゆるなので色んな所で自ら制限をかけた方が良いかと思う。
例えば私はtransform.parentは使わないようにしている。こうすることで、末端のgameObjectはアクセスできる範囲が狭まり必要以上に多機能になることはないはず。
そもそもがそもそもだった
概要
何故またこっちで書こうと思ったのか。
そもそもの動機
将来性を考えると独自ドメインでブログは運用すべき。
なぜなら資産であるブログ記事を検索サイトに模倣と思われず引っ越す方法がないからだ。
ブログを書くモチベーション
結局は多くの人に見てもらいからだろう。
そうなった時に新しいブログではこのサイトの数%のPVしか付かない。
やる気が湧かないから記事の数も増えないのでPVが増えない。
うーん、バニティメトリクス。
敷居の高さ
記事のクオリティを一定水準まで上げようと思うと、どうしても下書きが増えてしまう。
Qiitaの下書きが10記事までしか保存できないシステムはぐだぐだ言ってねーでさっさと投稿しろ!ってことなんだろうけど、どうも苦手で常に下書きが埋まってしまう。
これは良くないなー、せめて書き切らないとブログを書くモチベーションが保てない。
ということで、こんな感じの頭の中整理みたいな記事も投稿するようにしてみようというチャレンジ。
問題
せっかくなので、現状抱えている問題を列挙しておこう。
下記独自ドメインに移行したい tcat.space
下記アプリを改善したい play.google.com
こっちは改善案がコメントに色々出ているので対応したいところだが、いかんせんアップデートしようがしまいが改善しようがしまいがアクティブやプレイ、インストール数が変わらないので改善する意欲がわかない。
- Ghostに付けたい機能が付けられない
twitterのカードサムネに対応したかったけど、やり方が分からなかった。頻繁にアップデートが来るのでもう対応しているのかもしれないが、リリースノートをチェックするのも手間。オープンソースなのでコードを読めば自力で実装もできたかもしれないが、私がブログフレームワークに望んでいることではなかった。
無難にWordPressにした方が良いと思い直したのは良いもののデータ移行のことを考えると面倒になって後回しにしてしまっている。
機能的にははてなブログで十分なんだけど独自ドメイン使おうと思うと月600円。サーバが月500円で借りれることを考えると中々使う気にはなれない。
- Unityは記事書くの面倒
私は分かりやすく記事を書くのをモットーにしている。何故なら未来の自分へのリファレンスとして書いているからだ。
人は昔のことは忘れるものだ。3年前にやったことを平気で忘れる。そんな時に見返せば当時と同じことが可能になったら素晴らしい。
しかし、Unityの記事を分かりやすく書くのは難しくないが面倒だ。分かりやすくするためには必然的にスクリーンショットを多用する必要があるからだ。
今後の方針
C++で無理矢理クロージャを使ってみる
クロージャ
現在クロージャという表現を使う場合状態を持ったラムダを指す場合が多い気がします。 つまり、ワンライナーで書けるクラスといった感じです。これはC++では関数オブジェクト的な手法で似たようなことができますが、javascript程普通なものではありません。
// javascript const twice = (action) => { action(); action(); }; const counter = () => { let count = 0; return () => { count++; console.log(i); return i; }; }; const a = counter(); twice(a); // 1, 2 const b = counter(); twice(b); // 1, 2
C++だと外部ストレージを用いなければできないと思っていましたが、内部ローカル変数を参照でバインドし延命させることでできました。
// C++ auto twice = [](auto action) { action(); action(); }; auto counter = [] { int count = 0; auto increment = [](int& count) { count++; std::cout << count; }; return std::bind(increment, count); }; auto a = counter(); twice(a); // 1, 2 auto b = counter(); twice(b); // 1, 2
応用
これを利用するとfor_each
中にコンテキストを保存することができます。
今回は前回のループの値を利用してみました。
素直にローカル変数をキャプチャした方が良いような気もしますが、内部で完結しているのも1つのメリットではないでしょうか。
#include <algorithm> #include <vector> #include <functional> #include <iostream> namespace { auto context = [](auto function) { auto context = std::make_pair(false, 0); auto action = [](decltype(context)& context, auto& function, auto& value) { function(value, context); }; return std::bind(action, context, function, std::placeholders::_1); }; } int main() { std::vector<int> vec = { 5, 10, 15, 20, 25 }; std::for_each(vec.cbegin(), vec.cend(), context([](auto& value, auto& context) { if(context.first) { std::cout << value - context.second << std::endl; } context = std::make_pair(true, value); })); }
参考
C++の標準描画ライブラリに入るかもしれないCairoのサンプルをwindows向けにビルドしてみた
cairoとC++
本の虫: Herb SutterがCairoのMLにC++標準規格にCairoを入れられないか打診中
環境
- windows
- msys2
依存関係
$ pacman -S mingw-w64-x86_64-zlib $ pacman -S mingw-w64-x86_64-libpng $ pacman -S mingw-w64-x86_64-pixman $ pacman -S mingw-w64-x86_64-cairo
サンプル
ビルド
$ x86_64-w64-mingw32-g++ -I/mingw64/include main.cpp -L /mingw64/lib -lcairo -lgdi32
上記ソースとはパスが違うのでcairo-win32.h
をcairo/cairo-win32.h
としました。
基本クラスの継承を考えたデストラクタの属性設定
概要
この記事は間違った情報が多分に含まれるため
と
の翻訳記事に書き換える予定です。
以下元の記事。
実行時の型とポインタの型が違う場合、ただnewするだけでは実行時の型情報がロストするので、ちゃんとtype erasure
するかvirtualを付けましょう。
型情報がロストして困る例
#include <iostream> struct Foo{ ~Foo(){ std::cout << "Foo" << std::endl; } }; struct Bar: public Foo{ ~Bar(){ std::cout << "Bar" << std::endl; } }; int main(){ Foo *bar = new Bar(); delete bar; }
このコードではBarのデストラクタが実行されません。何故ならBarの型情報をどこにも保持していないからです。
virtualで解決する
struct Foo{ virtual ~Foo(){ std::cout << "Foo" << std::endl; } };
そこでデストラクタにvirtualを付けてやるとBarの型情報が(正確にはオーバーライドされた関数へのポインタ)が保持されるのでBarのデストラクタが実行されるようになります。
type erasureで解決する
int main(){ std::shared_ptr<Foo> bar(new Bar); }
std::shared_ptr
はtype erasure
というテクニックを使い型を内部で保持します。なのでBarのデストラクタにvirtualが付いていなくてもBarのデストラクタが実行されます。
どっちを使うべきか
Foo
がインターフェイスの場合継承される意図を明確にする為にvirtualを付けるのが良いと思います。反対にFoo
がただの一般クラスで同じ機能を流用したくて継承する場合はvirtualを付ける事を推奨しません。こういう場合は使いたい機能をインターフェイスに分離すべきです。
インターフェイスが何らかの事情で用意できない場合ですらstd::shared_ptr
で解放するのは意図が明白にならないのでオススメしません。継承先解放用のクラスを作成するのが本来なら良いのですが、このような複雑なテンプレートはしっかりとテストできる環境でのみ作成すべきです。なので、妥協案として継承先解放用のクラスをshared_ptrのエイリアスとして定義すると良いと思います。
余談
std::unique_ptr
は型情報を内部で保存していません。なので、継承先のデストラクタは実行されません。
シングルトンを使うのはやめよう
概要
この記事はC++の作者Bjarne Stroustrup
等によって書かれたコーディングガイドラインのAvoid singletons
の箇所の翻訳です。
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ri-singleton
シングルトンを使うのはやめよう
理由
シングルトンは基本的に変装したグローバルオブジェクトに過ぎないため。
例
class Singleton { // シングルトンオブジェクトが確実に1つだけ作られて // プロパティの初期化が行われるようにする };
シングルトンという発想には多くの亜種がある。 それが問題の1つである。
メモ
あなたがグローバルオブジェクトを変更したくない場合、const
またはconstexpr
を宣言する。
例外
最初の使用での初期化を行うための純粋なシングルトン(設計を考えなくて良いような)の場合は使用できる。
X& myX() { static X my_x {3}; return my_x; }
これは初期化順を制御する最も効果的な解決策の1つだ。 マルチスレッド環境において静的オブジェクトはレースコンディションを引き起こさない。(うっかりそのコンストラクタ内から共有オブジェクトにアクセスしない限り)
もし、あなたが他の大勢のようにシングルトンをオブジェクトを1つだけ作成するためのクラスと定義するのなら、myX
のような関数はシングルトンではない。そして、この便利なテクニックはシングルトンを無くすルールの例外ではない。
実施
一般化することは非常に難しい。
singleton
を含む名前を持つクラスを探す。- 作成された単一オブジェクトを探す。(オブジェクトを数えたりコンストラクタを調査する)
- クラスXの持つpublic static関数が内部に静的なクラスXを持ち、それのポインタや参照を返している場合、それを禁止する。