Gaming Life

一日24時間、ゲームは10時間

(遅すぎる)今年の目標 2021年版

遅すぎますが今年の目標を簡単に書いておこうと思う。

ブログ毎月更新継続

こんな急ぎ書いたようなのが丸見えな記事でも、3年以上続いているブログの毎月更新は、今年も継続したい。その中で、1、2記事は、10数時間かけた大作の記事が書きたい

今年こそ一人Advent Calender完走

すっかりなかったことになっている去年の一人アドベントカレンダー。あれは直前に思いつきでカレンダーを作って、即興で記事を書いていたところがあるので仕方ないところがある。

今年は、まだまだ時間があるので、記事のストックをためて、12月に一人アドベントカレンダーが完走できるようにしたい。(そしてその最終日に、この記事を踏まえた今年の振り返り記事を書きたい)

就活記事のリメイク

昔書いた就活記事のリメイクを書く。これは今絶賛書いているところで、来月中に公開する目処が経っているので、乞うご期待ということでお待ち下さい。

月2冊は技術書を読む

去年は本を4冊買ったら1冊買うみたいなスタイルで、完読したという本があまり多くなかった。最近本棚を買い替えたこともあり読書のモチベが高いので、まずは積んでいる本の消化から優先して、最低月2冊ペースで本を読みたい。

二ヶ月に1個はGithubに何かしらの小さいプロジェクトを公開する

最近OpenSiv3Dを使って2048ゲームのクローンを作成した(これについては後でまた詳しくブログに詳細を書くと思います)。この規模でいいので、小さいプロジェクトを最低でも二ヶ月に一個は開発して、それをGithub及びブログで公開していきたい。

新しい言語を一つ覚える

今の所考えているのはjavascript + HTML5。web系の知識が空っぽなので、Github pagesにポートフォリオとなるサイトが作れる程度にはなにか作りたいなーと。

まとめ

以上。今年もフルリモート勤務が続くと思われるので、この目標が達成できるだけの時間は取れると思う。今年は無理に大きいことをやろうとせず、将来の肥やしになるような、小さい事をコツコツと、をテーマにエンジニアライフを送りたいと思います。

windowsのリモートデスクトップで複数画面を使う&画面上部の接続バーを消す方法

本記事はai_9684_dctソロ Advent Calendar 2020 10日目の記事です。

Windowsリモートデスクトップ接続」に関する機能の紹介。

コロナ禍の中、4月の入社以来、数回の出社を除いてリモートワークが続いている。弊社の場合、自宅にある会社から貸与されたPCから会社のネットワークにVPN接続し、windows標準の「リモートデスクトップ接続」か、「Chromeリモートデスクトップ」で会社のPCにつないでリモートで作業するというスタイルを取っている。

私は学生時代もたまに使っていて、使い慣れていたということもあり、半年以上「Chromeリモートデスクトップ」を使用していた。しかしながら「Chromeリモートデスクトップ」は1画面でしか作業できない事が非常に不満だった。

そんなことをMTG中にぼやいたら、先輩から「windowsリモートデスクトップなら複数画面使えるよ!」というアドバイスを貰った。マジか、と調べてみたら確かにあった。

f:id:ai_gaminglife:20201210215105p:plain

リモートデスクトップ接続の接続前の設定画面で、「リモートセッションで全てのモニターを使用する」というオプションを有効にすると、例えばリモート接続元のPCが2画面使っていれば、2画面まるごと使ってリモートデスクトップの画面を使う事が出来た。なぜこの機能にもっと早く気づかなかったんだ……

更に、windows標準のリモートデスクトップ接続の大きな不満の一つだった、画面上部に常に表示される↓この接続バーも消せることが分かった。

f:id:ai_gaminglife:20201210215138p:plain

同じ設定画面で、「全画面表示の使用時に接続バーを表示する」という設定のチェックを外せば、接続バーが最初以外一切表示されなくなる。なお、接続バーを使わなくても、「[Ctrl] + [Alt] + [Pause]」キーを同時押しすれば、全画面表示が解除される。

というわけで、リモートワークの不満の一つだった「画面の狭さ」という課題がクリアされ、リモート作業中にかかるストレスを大きく減らすことができましたとさ。まあ、夕方6時頃になると回線が重くなって、入力の遅延が我慢できないほど目立つようになるのは変わらないのだが。10Gビット回線引こうかなあ……

WindowsのC++でウィンドウアプリケーションを書く際にWinMain()を隠匿する方法の一例

序文

OpenSiv3Dなど、ユーザーが使いやすいように考えて作られたC++Windowsアプリケーション開発用フレームワークは、複雑なシグネチャを持つ WinMain() を隠してくれる。

# include <Siv3D.hpp>

// WinMain()を書く必要がない
// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
//                    LPSTR lpCmdLine, int nCmdShow)
void Main()
{
    Print << U"Hello, Siv3D!";

    while (System::Update())
    {
    }
}

私もこうしたWinMainの隠匿をしてみたいなーと思い、実際に実装してみた。

最も簡単な実装

FrameworkMain.cpp

#include <Windows.h>

void Main();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    Main();
    return 0;
}

UserMain.cpp

#include <Windows.h>

void Main() {
    MessageBoxA(nullptr, "Demo", "Demo Message", MB_OK);
}

上記のコードのように、フレームワーク側(.lib)で、宣言だけ用意して未実装な void Main() 関数を用意し、それをフレームワーク側に記述した WinMain() で呼び出す。これにより、ユーザーは WinMain() の存在に気を払うことなく、Main() 関数さえ実装することができる。

以上で目的は達成できたのだが、これではあまりに薄いので、もう少し凝った実装をしてみた。

クラスのメンバ変数をエントリポイントのように扱う

ここでは以下の要件を満たすものを実装する。

  1. フレームワークはAppBaseという名のクラスを用意する
  2. AppBaseは virtual int Init() という純粋仮想関数をもつ。
  3. ユーザーはAppBaseをpublic継承したアプリケーション用クラスを作成できる
  4. ユーザーは継承先で virtual int Init() 関数をエントリポイントのように扱って実装できる
  5. ユーザーは、これらの機能を最小限の労力で使用できる。

AppBase.h(フレームワーク実装)

#pragma once
#include <windows.h>

class AppBase* GetWinPtr();

class AppBase {
private:
    friend AppBase* GetWinPtr();
    static AppBase* m_App;

public:
    AppBase();
    virtual int Init() = 0;
};

AppBase.cpp(フレームワーク実装)

#include "AppBase.h"

AppBase::AppBase() { m_App = this; }

AppBase* AppBase::m_App = nullptr;

AppBase* GetWinPtr() { return AppBase::m_App; }

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    AppBase* g_App = nullptr;
    g_App = GetWinPtr();
    if (g_App == nullptr) {
        return -1;
    }
    return g_App->Init();
}

MyApp.cpp(ユーザー実装)

#include "AppBase.h"

class MyApp : public AppBase {
public:
    virtual int Init() {
        MessageBoxA(nullptr, "Demo", "Demo Message", MB_OK);
        return 0;
    }
};

MyApp app;

解説

ユーザーは、グローバルに自身がAppBaseクラスを継承して作成したクラスのインスタンスを作成するだけで、 Init() 関数をエントリポイントのように扱って実装できる。仕組みとしては以下の通り。

AppBaseはstatic変数として、自身の型のポインタ変数を持つ。このポインタは、AppBaseのfriend関数である GetWinPtr() から取得できる。

フレームワーク上では、 WinMain() において GetWinPtr() 経由でAppBase型のポインタから Init() メンバ関数を呼び出す。しかし、この時点ではそのポインタの実体がどの型を表しているか分からない。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    AppBase* g_App = nullptr;
    g_App = GetWinPtr();
    if (g_App == nullptr) {
        return -1;
    }
    return g_App->Init();
}

そこでユーザーは、AppBase型を継承したクラスであるMyApp型のインスタンスを、グローバルで作成する。グローバルで定義した変数は、エントリポイント、つまり WinMain() より早く用意される。

これにより、ユーザーは、AppBase継承クラスの Init() 関数を、エントリポイントのように扱って実装することができる。

補足

この実装は、WindowsGUIフレームワークとしてMicrosoftが提供する、MFCMicrosoft Foundation Class)の実装とほとんど同じらしい。

参考

In a typical MFC application we never write WinMain() function so how is it possible to compile and link a windows program with out WinMain()?(http://www.equestionanswers.com/vcpp/mfc-application-winmain().php)

winmainの隠蔽工作

PIXでシェーダデバッグする際はwindowsの開発者モードを有効にしなければならない

この記事は、ai_9684_dctソロ Advent Calendar 2020 7日目 の記事です。

PIX on Windowsとは

PIXとは、Microsoftが開発したDirectX12製ゲームのパフォーマンス測定及びシェーダデバッグのための、プロファイラである。DirectX12を使った開発をする際は、ぜひ使いたいソフトの一つ。

遭遇したエラー

PIXを使って、シェーダデバッグをしようとしたところ、以下のエラーでシェーダデバッグが起動出来なかった。

「An Error has occured. This feature requres developer mode to be enabled on the analysis device. Type "developer mode" into the start menu to find the correct setting」

f:id:ai_gaminglife:20201207200737p:plain

エラーの回避方法

エラーメッセージの指示どおり、windows10の開発者モードを有効にすれば、エラーを回避できる。

windows 10の [設定] → [更新とセキュリティ] → [開発者向け]の画面で、[開発者用モード]をオフからオンに切り替える。(この時PCの再起動は必要ない)

f:id:ai_gaminglife:20201207200748p:plain

これにより、PIXでDirectX12のシェーダデバッグが可能となる。

UE4のコンソール変数の命名規則とsgコマンドの役割

この記事は、ai_9684_dctソロ Advent Calendar 2020 5日目 の記事です。

UE4には r.ScreenPercentage や、 r.Streaming.PoolSize だったり、多くの便利なコンソール変数が用意されている。こういったコンソール変数には、命名規則がある。

ConsoleManager.cpp に、コンソール変数の命名規則が書かれている。

// r.      Renderer / 3D Engine / graphical feature
// RHI.    Low level RHI (rendering platform) specific
// a.     Animation
// s.     Sound / Music
// n.      Network
// ai.     Artificial intelligence
// i.      Input e.g. mouse/keyboard
// p.      Physics
// t.      Timer
// log.       Logging system
// con.       Console (in game  or editor) 
// g.      Game specific
// Compat.
// FX.     Particle effects
// sg.     scalability group (used by scalability system, ini load/save or using SCALABILITY console command)

この中で、 sg. から始まるコマンドは少々他と違う扱いになっている。

sg.コマンドについて

sgとは、Scalability Groupの略で、sgコマンド類は、ある設定ファイルに基づいて、他のパラメータをまとめて制御する役割を持つ。例えば自分の環境だと、 sg.PostProcessQuality を3から1に変更すると、

LogConfig: Applying CVar settings from Section [PostProcessQuality@1] File [C:/Users/detec/Documents/Unreal Projects/ActionRPG/Saved/Config/Windows/Scalability.ini]
LogConfig: Setting CVar [[r.MotionBlurQuality:3]]
LogConfig: Setting CVar [[r.AmbientOcclusionMipLevelFactor:1.0]]
LogConfig: Setting CVar [[r.AmbientOcclusionMaxQuality:60]]
LogConfig: Setting CVar [[r.AmbientOcclusionLevels:-1]]
LogConfig: Setting CVar [[r.AmbientOcclusionRadiusScale:1.5]]
LogConfig: Setting CVar [[r.DepthOfFieldQuality:1]]
LogConfig: Setting CVar [[r.RenderTargetPoolMin:350]]
LogConfig: Setting CVar [[r.LensFlareQuality:0]]
LogConfig: Setting CVar [[r.SceneColorFringeQuality:0]]
LogConfig: Setting CVar [[r.EyeAdaptationQuality:0]]
LogConfig: Setting CVar [[r.BloomQuality:4]]
LogConfig: Setting CVar [[r.FastBlurThreshold:2]]
LogConfig: Setting CVar [[r.Upscale.Quality:2]]
LogConfig: Setting CVar [[r.Tonemapper.GrainQuantization:0]]
LogConfig: Setting CVar [[r.LightShaftQuality:0]]
LogConfig: Setting CVar [[r.Filter.SizeScale:0.7]]
LogConfig: Setting CVar [[r.Tonemapper.Quality:2]]
LogConfig: Setting CVar [[r.DOF.Gather.AccumulatorQuality:0        ; lower gathering accumulator quality]]
LogConfig: Setting CVar [[r.DOF.Gather.PostfilterMethod:2          ; Max3x3 postfilering method]]
LogConfig: Setting CVar [[r.DOF.Gather.EnableBokehSettings:0       ; no bokeh simulation when gathering]]
LogConfig: Setting CVar [[r.DOF.Gather.RingCount:3                 ; low number of samples when gathering]]
LogConfig: Setting CVar [[r.DOF.Scatter.ForegroundCompositing:0    ; no foreground scattering]]
LogConfig: Setting CVar [[r.DOF.Scatter.BackgroundCompositing:0    ; no foreground scattering]]
LogConfig: Setting CVar [[r.DOF.Recombine.Quality:0                ; no slight out of focus]]
LogConfig: Setting CVar [[r.DOF.TemporalAAQuality:0                ; faster temporal accumulation]]
LogConfig: Setting CVar [[r.DOF.Kernel.MaxForegroundRadius:0.006   ; required because low gathering and no scattering and not looking great at 1080p]]
LogConfig: Setting CVar [[r.DOF.Kernel.MaxBackgroundRadius:0.006   ; required because low gathering and no scattering and not looking great at 1080p]]
sg.PostProcessQuality = "1"

この様にポストプロセス関連のコンソール変数がまとめて変更される。

Scalability Groupの設定は、Engine/Configフォルダに格納されている。BaseScalability.iniに記述されている。ここに記載されている値を変更すれば、例えば、 sg.PostProcessQuality を1にさげても、 r.BloomQuality は下げてほしくない……みたいな調整ができる。

参考サイト

docs.unrealengine.com

Pythonのデコレータ(風)をC++で実装してみる

本記事はai_9684_dctソロ Advent Calendar 2020 3日目の記事です。

Pythonには「デコレータ」という、関数に追加の機能を修飾するシンタックスシュガーが存在する。

def decorator_sample(func):
    def wrapper(*args, **kwargs):
        print(f'Arguments: {args}')
        print(f'Keyword arguments: {kwargs}')
        result = func(*args, **kwargs)
        print(f'Result: {result}')
        return result
    return wrapper

@decorator_sample
@decorator_sample
def add_int2(a: int, b: int):
    return a + b

add_int2(12, 41)

# 出力結果
Arguments: (12, 41)
Keyword arguments: {}
Arguments: (12, 41)
Keyword arguments: {}
Result: 53
Result: 53

デコレータは、親切なライブラリだとサポートしていることが多い印象。

今回はPythonのデコレータっぽいものをC++で実装してみた。

C++17によるデコレータの実装

といってもPythonのように @decorator とつけるだけで修飾するには、言語機能レベルでサポートしなければならない。そこまでのことは出来ないので、デコレートしたい関数オブジェクトを受け取る関数を作って、そこでデコレートするという実装にした。

今回の実装にはこちらのサイトを参考にした。

C++のPython関数デコレータに相当するものは何ですか?

#include <iostream>

template <class... Ts>
void print_all(std::ostream& os, const std::string& separator, Ts const&... args) {
    ((os << args << separator), ... );
}

template <class T>
auto decorator(T&& func) {
    auto wrapper = [func = std::forward<T>(func)](auto&&... args){
        std::cout << "arguments: ";
        print_all(std::cout, ", " , args...);
        std::cout << "\n";
        auto result = func(std::forward<decltype(args)>(args)...);
        std::cout << "Result: " << result << '\n';
        return result;
    };
    return wrapper;
}

int add_int2(int a, int b) { return a + b; }

int main() {
    auto decorated = decorator(decorator(add_int2));
    decorated(12, 29);
}

// 出力結果
// arguments: 12, 29, 
// arguments: 12, 29, 
// Result: 41
// Result: 41

[C++] gcc HEAD 11.0.0 20201106 (experimental) - Wandbox

(ちょっと)文法解説

デコレータへの関数及びその引数の受け渡しには完全転送を利用している。

template <class T>
auto decorator(T&& func) { // ユニバーサル参照で仮引数を宣言して
    auto wrapper = [func = std::forward<T>(func)](auto&&... args){ // std::forwardで完全転送
// 以下省略
}

完全転送についてはこの辺りの資料が参考になる。

marycore.jp

デコレートする関数に渡される実引数を列挙するための print_all 関数には、C++17以降でサポートされている畳み込み式(fold expression)を利用している。そのため、C++14以前のコンパイラでは動作しない。

cpprefjp.github.io

私的ゲーム開発技術情報集め ver. 2020年12月

本記事はai_9684_dctソロ Advent Calendar 2020 1日目の記事です。

まだ大学生だった1年前、下記の記事のような、私流の技術情報収集の手段をまとめた記事を書いた。今年、ゲームエンジニアとして就職して以降、当時の情報収集手段とは少し変わってきたので、今年もこの内容で記事を書くことにした。

ai-gaminglife.hatenablog.com

Google検索

疑問に思うことがあればまずはGoogleで検索。プログラムの深い部分を調べたい時は、検索言語を英語に変えると良い。

余談だが、「英語の記事は理解できないから絶対読まない」なんてプログラマは、自身の価値を大きく毀損している。今の時代、たとえ自分で英語が読めなくても、DeepL翻訳やGoogle翻訳があるので何とかなる。特にアプリ版DeepL翻訳は、Ctrl + Cを2回押すだけでそれなりの精度で自動翻訳してくれるのでオススメ。

書籍

信頼と安心の情報源。書籍の情報が全て正しい、というわけではないが、インターネット上で無料で手に入る情報よりは確実に精度が高い。体系立った情報を手に入れたいなら、値は張るが、まず最初に書籍を当たったほうが良いと思う。

これまで、私は技術書は本屋かAmazonで買って読むだけのものだったが、最近もう一つ技術書の付き合い方が増えた。図書館だ。今年、私は横浜に引っ越したのだが、夏頃から最低でも月に1度は横浜市立中央図書館に足を運ぶようになった。中央図書館には、最新の技術書や古典的な名著など沢山の技術書が揃っていることに気づいたからだ。あなたがもし都会に住んでいるのなら、是非一度は近所の大きな図書館に足を運んでみてほしい。沢山の技術書にタダで触れることができる。

Twitter

自分がよく使うフレームワークやアプリなどの第一人者、今風にいうとエヴァチェリストを数人フォローしておけば、最新の情報がすぐ手に入る。一年前はTweetDeckでそういった人のツイートを全て追っかける……ということをよくやっていたが、最近は公式TwitterクライアントでTLを見ることが多くなったので、そうしたストーカーまがいの行為はやってない。

作業中、困ったことがあればGoogle検索に加え、Twitterでも検索するというのは今も続けている。それで業務中助けられた事が何度かあった。

Inoreader(RSSリーダー

今年に入って、Inoreaderを使った情報収集を始めた。これがなかなか良い。これまで取りこぼしていた情報を集めることができるようになった。最近RSSを使う人は減っていると聞くが、私には性に合った。

以下に、今購読しているサイトで、特に有用だと思うサイトを挙げる。

プログラミング関連

IT News

はてなブックマーク

はてなブログを使っておきながら言うのもなんだが、今からはてブを見るというのはオススメできない。はてブの技術カテゴリは、有用な情報を集める事ができるが、Web系の技術記事ばかりがトレンドに上がるし、雑音(ノイズ)が多すぎる。はてなブックマークという場所がこの一年で更に政治臭が強くなったので、その辺り耐性のない人は、使わないほうがいい。

なお私は今更はてブやめれなくなったので今後も使います。

NotionのWebクリッピング

最強のAll in Oneメモツールには、Webクリッピング機能が備わっている。ChromeFirefox拡張機能ですぐにNotion上にWebサイトをクリップできる。私は、上記のサービス・ニュースサイトで集めた情報は、ほぼすべてNotionのWeb クリッピングで収集している。

まとめ

今年はQiitaの対抗サイトとしてZennが登場するなど、技術トレンドを追う手段は強烈な速さで移り変わっている。情報過多のこの時代、どれだけノイズを排してほしい情報を得るかが大切だと思っている。今後も、トレンドに合わせて、情報収集の手段を適宜アップデートしていくことになるだろう。