📚 UI開発を10倍速くするための実戦ポイント

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに 多くの人は「UI開発=手の速さ」だと思っています。 でも実際のプロジェクトでは、進行を遅らせる原因はコードの速さではなく、データフローとUI構造設計の悪さ です。 Unity(UGUI)で時間を食う典型的なポイントを挙げると、以下のようになります。 データ前処理がバラバラ :ソートやフィルタが各UIに散らばっていると、UI層が処理待ちになる。 階層構造が深すぎる :1回の更新で大量のTransformを走査、処理も重くバグも出やすい。 アニメーション・状態管理が未抽象化 :新しい状態を追加するたびにコードを壊す。 同じランキングUIでも、ある人は半日で完成、ある人は3日かかる。 その差は、手の速さではなく設計の差 です。 データ層とUI層を明確に分離する ありがちなアンチパターンは、OnClick() の中で データ取得 → オブジェクト生成 → UI更新を全部やるケース。 短期的には動いても、UI改修のたびに全部作り直しになります。 もっと良い設計はこうです👇 DataService / Manager にデータロジックをまとめる UI は DTO(Data Transfer Object) を受け取って表示専念 データ前処理は Python や外部ツールで事前に済ませる こうすると要件変更が来ても、UI側はほとんど触らずに済みます。 データ層が変わってもUIは安定、これが真の開発効率化です。 リスト描画は「オブジェクトプール」が必須 ランキング、インベントリ、スキルリストなど、 毎回 Instantiate + Destroy していると、 処理が重くなるだけでなく、開発時間も無駄に増えます。 対策: オブジェクトプールを構築する : 再利用可能なUI要素をキャッシュして、再描画時はデータだけ差し替え。 シンプルな PoolManager でOK : 使い終わった要素は SetActive(false) して再利用。 ScrollRect の仮想化レンダリングと併用 : 数千件でも軽く動作。 あるプロジェクトでは、インベントリUIの描画時間が 500セルで200ms → プール導入後は30msに短縮。 体感で「カクつき」が消えました。 ...

2025年10月9日 · 1 分 · 133 文字 · Wuyukwi

💡 C++ の「奇妙な再帰テンプレートパターン(CRTP)」を理解する

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに C++ のテンプレートには数多くの応用テクニックがありますが、その中でも「CRTP(Curiously Recurring Template Pattern:奇妙な再帰テンプレートパターン) 」は特にユニークで強力な手法です。 Boost をはじめとする有名ライブラリでも多用されており、C++ の表現力を大きく広げてくれます。 この記事では、CRTP の基本から実用例、さらには応用パターンまでを整理して解説します。 CRTP とは? CRTP の基本形は以下のようになります。 template <typename T> class Base { public: T& getDerived() { return static_cast<T&>(*this); } }; class Derived : public Base<Derived> { // Derived 自身を Base のテンプレート引数として渡す }; 一見すると「派生クラス Derived が、まだ完成していない自分自身を基底クラス Base に渡している」ように見えます。この“自己参照”こそが CRTP の特徴です。 コンパイラはこれを問題なく処理でき、基底クラスはコンパイル時に派生クラスの型情報を知ることができます。 実用例:比較演算子の自動生成 C++ では == や < などの比較演算子を定義するのはよくある作業ですが、すべてを個別に書くと冗長になりがちです。 CRTP を用いると、基底クラスに「共通の比較演算子」をまとめ、派生クラスは最小限の実装だけで済ませられます。 基底クラス template <typename Derived> class Comparable { public: bool operator==(const Derived& other) const { return static_cast<const Derived*>(this)->equalsImpl(other); } bool operator!=(const Derived& other) const { return !(*this == other); } bool operator<(const Derived& other) const { return static_cast<const Derived*>(this)->lessImpl(other); } bool operator>(const Derived& other) const { return other < *this; } bool operator<=(const Derived& other) const { return !(*this > other); } bool operator>=(const Derived& other) const { return !(*this < other); } }; 派生クラス例 class Point : public Comparable<Point> { public: Point(int x_val, int y_val) : x(x_val), y(y_val) {} bool equalsImpl(const Point& other) const { return x == other.x && y == other.y; } bool lessImpl(const Point& other) const { return (x != other.x) ? (x < other.x) : (y < other.y); } private: int x, y; }; これで ==, !=, <, >, <=, >= がすべて利用可能になります。 ...

2025年9月30日 · 2 分 · 235 文字 · Wuyukwi

🔥 ゲームプログラマーとして成長するために大切な3つの力

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに 「どうすればプログラミングが上手くなるのか?」 これは、ゲーム業界を目指すエンジニアにとって、常につきまとうテーマです。 Unity や Unreal Engine などの便利なエンジンやライブラリが整っている時代でも、「本当にゲームを作れる」プログラマーになるためには、言語やフレームワークの知識以上に重要な根本的スキル があります。 本記事では、ゲームプログラマーとして実務を経験する中で特に大事だと感じる3つの力 を紹介します。 ① 抽象化の力 —— 巨大なコードを「分けて」考える ゲーム開発は、数千〜数百万行にもなるコードの集合です。 その全てを頭に入れることは不可能なので、必要な部分だけをブラックボックスとして扱う 考え方が必須になります。 例えば、「プレイヤーが持つ全ての武器を取得する」処理を作る場合を考えます。 // Repositoryパターンによる例 class WeaponRepository { public List<Weapon> GetAllWeapons(PlayerId playerId); } この WeaponRepository クラスを作るときは、 DBやセーブデータへのアクセス ログやエラーハンドリング フォーマット変換 など、たくさんの下位レイヤのライブラリを呼び出しますが、上のレイヤからは「GetAllWeapons」だけ見えればよい のです。 これが抽象化であり、モジュールを積み重ねて大規模ゲームを成立させる基本 です。 実戦ポイント オブジェクト指向(OOP)、関数型(FP)、手続き型といった 複数のパラダイムを状況に応じて使い分ける トップダウン(設計から実装)とボトムアップ(共通処理の抽出)を行き来しながら開発する 抽象化しすぎると開発が重くなり、抽象化しなさすぎるとコピペ地獄になるため、バランスを取る ② デバッグの力 —— ブラックボックスを「開ける」技術 抽象化したコードは便利ですが、バグが起きたときには中を開ける必要 があります。 たとえば「インベントリ画面に武器一覧が表示されない」と報告されたとき、どこから原因を追うべきでしょうか? F12(DevTools)や curl でクライアント→サーバ間通信を確認 サーバで WeaponRepository.GetAllWeapons() の戻り値をログ出力 DBにクエリを直打ちして期待通りの結果か確認 こうして層を一つずつ下って調査 するのがデバッグの基本です。 このスキルはバグ修正だけでなく、新しいプロジェクトやチームに参加したときにコードを理解する力 にもなります。 実戦ポイント ログやアサートを常に入れておく デバッガでステップ実行できるように設計する バグ報告を「再現可能な条件」に落とし込む ...

2025年9月18日 · 1 分 · 102 文字 · Wuyukwi

😯 C++で高性能計算を書くときに意識すべき最適化ポイントまとめ

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに 本記事では、私自身が経験したことや、信頼できる実践的な知見を元に、C++で高速な数値処理や行列演算を行う際に意識すべき最適化ポイント をわかりやすく整理しました。 HPCやリアルタイム演算に関心のある方に参考になれば幸いです。 1. メモリのアライメントとキャッシュ効率 アライメント(__attribute__((aligned(n))) CPUはキャッシュライン(通常64バイト)単位 でメモリを読み込む。 メモリが正しく整列されていないと、キャッシュミスやfalse sharing が発生し、性能が大幅に低下。 GCC/Clangなら __attribute__((aligned(32))) など、MSVCでは __declspec(align(32)) を使う。 連続したメモリを使用する(std::vector<T>など) 行列は行優先(row-major)の順番で連続配置することで、CPUの事前読み込み(prefetch)が効きやすくなる。 std::vector<std::vector<float>> のような非連続構造は避ける。 2. メモリ確保の最適化:高速メモリアロケータ 標準の malloc や new は汎用的で安全ですが、性能面ではボトルネックになることがあります。 おすすめのアロケータ tcmalloc (Google製、スレッドセーフ) jemalloc (Facebook製、ゲームやサーバーで人気) Hoard (スケーラブルなスレッド対応) これらを使うと、多スレッド環境でのアロケーション性能が大幅に向上 します。 3. 行列演算の高速化手法 Strassenアルゴリズム(O(n^2.81)) 通常のO(n³)よりも理論上速い。 再帰的に分割しながら計算することで、掛け算回数を削減。 ブロック分割(Block-based Multiplication) 大きな行列を小さなサブブロックに分けて計算 すると、キャッシュが有効活用できる。 代表的なライブラリ(後述するBLASなど)ではこの最適化が組み込まれている。 4. 並列計算(マルチスレッド化) C++11の標準スレッド std::thread, std::async を使って並列化可能。 シンプルな制御だが、スレッド数の管理や同期は自前で対応が必要。 OpenMP #pragma omp parallel for のように、簡潔にfor文を並列化。 コンパイラ依存(GCC/Clang/MSVC全て対応)。 Intel TBB(Thread Building Blocks) 動的なタスクスケジューリング・負荷分散・ワークスティーリングが可能。 不規則な処理を高速並列化したいときに強力。 ...

2025年8月4日 · 1 分 · 204 文字 · Wuyukwi

💡 C++ の emplace_back は push_back を完全に置き換えられるか?

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに C++11 から導入された emplace_back は、コンテナに要素をその場で構築できる便利なメソッドです。 一方、それ以前から使われてきた push_back とはどんな違いがあり、emplace_back は完全に push_back を代替できるのでしょうか? 本記事では、 emplace_back が本当に push_back より常に速いのか 両者がどんなケースに使えるのか 実装の違いと注意点 について、自分なりに整理した考えをまとめます。 push_back と emplace_back の違い push_back // C++20以降 constexpr void push_back(const T& value); // コピー constexpr void push_back(T&& value); // ムーブ push_back は、すでに生成されたオブジェクトをコピーまたはムーブして格納する関数です。 emplace_back // C++20以降 template<class... Args> constexpr reference emplace_back(Args&&... args); // その場で構築 emplace_back は、コンストラクタに渡す引数をそのまま受け取り、コンテナ内部で直接オブジェクトを構築します。そのため、中間オブジェクトなし に要素を追加できる点が特徴です。 これらの大きな違いは、push_backが既に生成されたオブジェクト(T型)をコピーまたはムーブする一方で、emplace_backは任意のコンストラクタ引数からその場でT型のインスタンスを構築できる点です。 LLVMにおける内部実装 以下はLLVM(libc++)におけるpush_back内部の実装です。 if (this->__end_ != this->__end_cap()) { __construct_one_at_end(__x); // 残り容量がある場合、__xをコピーまたはムーブ構築 } else { __push_back_slow_path(__x); // 足りない場合、新規領域を確保して再配置 } __construct_one_at_end の中では、allocatorが提供する construct() を使って、指定されたメモリにオブジェクトを構築しています。 また、allocatorが construct() を持たない場合には単純に in-place new を呼び出す仕組みです。 ...

2025年6月24日 · 1 分 · 193 文字 · Wuyukwi

🎉 std::function がヒープメモリを確保する条件とは?— コピーキャプチャされた変数の型がカギ

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに C++ の std::function は「小さいオブジェクトならヒープ確保しない(Small Object Optimization, SOO)」という知識は一般的です。しかし、以下のようなコードでは意外な挙動が観察されます: struct A { int value; A(int v) : value(v) {} A(const A &other) : value(other.value) {} }; struct B { int value; B(int v) : value(v) {} }; A a{0}; B b{0}; std::function<void()> funcA = [a] {}; // ヒープ確保あり std::function<void()> funcB = [b] {}; // ヒープ確保なし このように、コピーコンストラクタの有無だけ で挙動が変わるのはなぜでしょうか? std::function がローカル格納を行う条件(GCC/libstdc++ 編) libstdc++ の実装(GCC 標準ライブラリ)では、以下の条件を満たすとオブジェクトはヒープではなくローカルに保存されます: static const bool __stored_locally = (__is_location_invariant<_Functor>::value && sizeof(_Functor) <= _M_max_size && __alignof__(_Functor) <= _M_max_align && (_M_max_align % __alignof__(_Functor) == 0)); この中で特に重要なのが: ...

2025年6月5日 · 2 分 · 215 文字 · Wuyukwi

💨 Fusionの[Networked]属性はどう動いている?IL変換と自動同期の仕組みを解剖する

📌 この記事は Zenn に投稿した内容のアーカイブです。 はじめに Photon Fusionは、高性能・高柔軟性なネットワーク同期システムを提供する次世代ネットワークライブラリです。 その中でも特に重要な役割を担っているのが、[Networked] 属性です。 この属性を使うことで、プレイヤーの位置やアニメーション状態、ゲーム内オブジェクトの変化 などを自動的に同期でき、煩雑な手動のデータ送受信処理を省くことができます。 本シリーズでは、Fusionの内部同期処理を段階的に掘り下げて解説していきますが、今回は、[Networked] 属性の裏側で何が起きているのか、どのようにして自動同期が実現されているのかを詳しく見ていきます。 Fusionにおける[Networked]属性の同期処理概要 [Networked] 属性は? Fusionでは、[Networked] 属性が付与されたプロパティはネットワーク状態の同期対象 として扱われますが、ユーザーが手動で同期コードを書く必要はありません。 この仕組みは、Unityが提供する ILPostProcessor API を活用しており、C# で書かれたコードをビルド後にIL(中間言語)レベルで自動変換 しています。 IL ILは、.NET における中間言語です。 Unity では、C# で書いたスクリプトは必ずこの IL を経由してからネイティブコードに変換されます。 具体的には、以下のような処理が行われます: NetworkedWeaved という属性を追加して、該当プロパティがネットワーク状態バッファに割り当てられることを明示 getter/setter が NetworkBehaviour の内部バッファ(StateBuffer)へのメモリアクセスに差し替え られる 実行時に Spawned() されていない場合のアクセス制限も挿入される 変換前のC#コード: [Networked] private Vector2 _position { get; set; } IL変換後(ILSpyで逆コンパイルしたコード): [Networked] [NetworkedWeaved(0, 2)] private unsafe Vector2 _position { get { if (base.Ptr == null) { throw new InvalidOperationException("Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called."); } return (Vector2)base.Ptr[0]; } set { if (base.Ptr == null) { throw new InvalidOperationException("Error when accessing PhotonCapybara._position. Networked properties can only be accessed when Spawned() has been called."); } Unsafe.Write(base.Ptr + 0, value); } } 補足解説: base.Ptr は NetworkBehaviour に割り当てられたネットワーク状態メモリの先頭ポインタ です。 ...

2025年6月4日 · 1 分 · 187 文字 · Wuyukwi