レベルアップ!

備忘録として綴ります。

ココフォリアがすげー!

最近TRPGにハマってます。

技術録を書くはずのブログでしたが諸々ありまして更新せず放置...

縛らず趣味も載せてこうと思います。さて序文はここまでにして…



始まりはクトゥルフ神話TRPGの動画を見たことが始まり。

CoC*1って何?肉声セッション*2?何それ?から始まった自分も、今では

「とふ*3やっと使えるようになった―!」

「INT*4たけぇwww」

と、にわかのくせに知ったかぶって卓*5に混ざることも増えてきました。

自分はクトゥルフ以外のTRPGにはまだ手を付けていないのですが、余裕が出来たらいつか触ってみたいなぁなんて思うこともあります。今はクトゥルフが楽しいのでそちらを中心に遊んでます。ルルブ*6が高くて沢山は買えない…

さて、TRPGに触れ日も経験も遊んだ回数も少ない自分ですが、数日前TRPGを通じて仲良くさせていただいている方からココフォリアと言うサイトを教えて頂きました。

既にTRPGで遊んでいる人の中には、先に記述した「とふ」即ち「どどんとふ」を知る人もいるでしょう。ココフォリアはどどんとふみたいなTRPGで遊ぶ時に便利なツールが入ったサイトです。

そのココフォリアが自分の中で大ブーム。

どどんとふに比べると、やはり開発段階と言うだけあり「ああこの機能ないのかぁ」と思うこともありますが、それでも自分の中で大ブームなんです。


ココフォリアのここが推し

①音楽(同時に2曲まで)やSEを流せる。しかもループ設定ができる。

②シーンにパネル画像・背景・音楽(SEも含む)を設定でき、ワンクリックで遷移できる。

③見た目がシンプルでかっこいい。え、かっこよくない?かっこいいよ。

④共有メモを予め用意しておける。セッション中にワンクリックで公開・非公開できる。

⑤共有メモの背景画像を変えられる。

⑤予め用意した「セッションの途中で出したいキャラクター」を秘匿設定でき、出したい時に公開できる。

⑥複数人に同時に秘匿チャットを送れる。

ザっと思いつくのはこのくらいですかね。もっといいところ沢山あると思います。


ココフォリアのここが惜しい

①シークレットダイスがまだ利用できない。
見直したら普通にできました

②チャットのチャンネル名を変更できない。

③システムによっては使いづらいらしい。他のシステムを知らないのでちょっと何とも言えない。

④盤上にいるキャラクターを回転できない。

⑤一度設定したシーンとかキャラクターは一つ一つ消さないといけない。(全消しの方法あったら教えてください)

どどんとふと比べるとまだまだ痒いところに手が届かない、欲しい機能が今一つ足らない、という印象は確かにあります。

特にシークレットダイスは重要だと思うので、実装してほしいなぁと思います。シークレットダイスできます!

一長一短ありますが自分的にココフォリアはめちゃくちゃアリでめっちゃ良いサイトだと思います。

どのくらいかと言うと、思わず放置していたこのブログを再開して更に「使い方でも記事にしようかなー」と準備しちゃうくらいには使いやすいサイトです。

とはいえ自分も勉強中と言うか、TRPG自体初心者で一つ一つ学びながら遊んでるので、ココフォリアだけでなく沢山あるTRPGに便利なサイトを熟知しているわけではないことだけは予めご了承頂ければと思います。

ということで次回はココフォリアの使い方を自己満足で記事にしていきまーす。

*1:CoC・・・CALL of CTHULHU の略。つまりクトゥルフ神話TRPGのことらしい

*2:肉声セッション・・・TRPGで遊んでいる実際の音声を動画化したものだと捉えてる

*3:とふ・・・「どどんとふ」というオンラインでTRPGを行う時に便利なサイト

*4:INT・・・Intelligenceの略で、CoCの能力値の一つ。データ型のことじゃないよ。

*5:卓・・・TRPGで遊ぶことを”セッション”とも言うんだって。そのセッションを行ったメンバー全員を総称して卓と呼ぶことがある、ってことのはず

*6:ルルブ・・・ルールブックの略。TRPGをコンピュータのゲームに例えるとルルブは説明書にあたるんだって

一番効率のいいループ文はどれ?(std::vector)

C++11からauto型という型推論が使えるようになります(何年前の話だよって話ではありますが)
「auto型は便利だけど型が分かってるならそっち使った方がいいんじゃ?」という疑問から、検証しようと思いました。
しかしその検証にて、いろんなループ文を利用してみたところ結果に差が出たのでそちらを記事にします。
尚、効率の良い=処理速度が速いです。

検証したループ文はこちら

std::vector<int> intvector;
①for (int i = 0; i < intvector.size(); i++)

②for (std::vector<int>::iterator i = intvector.begin(); i != intvector.end(); i++)

③for (auto i = intvector.begin(); i != intvector.end(); i++)

④for (int i : intvector)

⑤for (auto i : intvector)

⑥std::for_each(intvector.begin(), intvector.end(),Hoge);

私は⑤ばかりつかってます。求めている内容によっては①の時もありますが基本は⑤ですね。
したい処理によってどれを使うかはかなり変わってくると思いますが、とりあえず処理速度の観点のみで見ていきたいと思います。

用意したコードはこちら

#include <iostream>
#include <vector>
#include <algorithm>
#include <Windows.h>

//for_each用関数
void Hoge(int x)
{
	int a = x;
}

int main()
{
	std::vector<int> intvector;
	const int MAX = 1000*1000;

	//値を代入
	for (int i = 0; i < MAX; i++)
	{
		intvector.push_back(i);
	}

	{//初期化int型、サイズ分回す、増分処理
		std::cout << "int ; size ; i++___";
		DWORD start =  GetTickCount();
		int count = 0;
		for (int i = 0; i < intvector.size(); i++)
		{
			int a = intvector[count++];
		}
		DWORD end =  GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}

	{//初期化イテレータ、終わりまで回す、増分処理
		std::cout << "iterator ; end ; i++___";
		DWORD start =  GetTickCount();
		for (std::vector<int>::iterator i = intvector.begin(); i != intvector.end(); i++)
		{
			int a = *i;
		}
		DWORD end =  GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}

	{//初期化auto型、終わりまで回す、増分処理
		std::cout << "auto ; end ; i++___";
		DWORD start =  GetTickCount();
		for (auto i = intvector.begin(); i != intvector.end(); i++)
		{
			int a = *i;
		}
		DWORD end =  GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}

	{//初期化int型、サイズ分まわす(終わりまで回す)
		std::cout << "int : vector___";
		DWORD start =  GetTickCount();
		for (int i : intvector)
		{
			int a = i;
		}
		DWORD end =  GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}

	{//初期化auto型、サイズ分まわす(終わりまで回す)
		std::cout << "auto : vector___";
		DWORD start =  GetTickCount();
		for (auto i : intvector)
		{
			int  a = i;
		}
		DWORD end =  GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}

	{//for_each
		std::cout << "for_each___";
		DWORD start = GetTickCount();
		std::for_each(intvector.begin(), intvector.end(),Hoge);
		DWORD end = GetTickCount();
		std::cout << (double)(end - start) / 1000 << "\n" << std::endl;
	}
	std::getchar();
	return 0;
}

前回記事同様、秒で処理結果を出します。
結果を顕著に出すため100万回回してみました。
その結果がこちらっ!
f:id:jagabeeInitialize:20180123235143p:plain
④と⑤の0秒は驚きを隠せないです。表示される数字から0.001未満秒なのかもしれません。
④と⑤は

範囲ベースのfor文(range-based for statement)または、範囲ベースのforループ(range-based for loop)は、C++11で新たに取り入れられた言語機能です。for (要素 : コンテナ)という形式で利用します。

C++のforeach文(範囲ベースfor)【range-based for, for_each】 | MaryCore
とのことです。

初期化式 ; 終了条件 ; 増分処理 と一々書くよりかは④や⑤の書き方の方がすっきりすると思います。
ループ文の中の処理があまりに長くなりそうであればstd::for_eachの方が可読性が高そうですね。


参考
auto型
auto - cpprefjp C++日本語リファレンス
怖いものなんてない!!: C++の「auto型」について
C++11 auto の使い方 - プログラミングの教科書を置いておくところ

for文
C++のforeach文(範囲ベースfor)【range-based for, for_each】 | MaryCore
C++11 範囲ベース for ループ 入門

C++ メンバイニシャライザの効率を調べてみた

C++にてクラスを作り、コンストラクタ内で初期化をするときに

Hoge()
{
	m_integer = 0;
	m_float = 0.0f;
	m_char = 'a';
	m_string = "abc";
};

という書き方と

Huga()
	:m_integer(0),
	m_float(0.0f),
	m_char('a'),
	m_string("abc")
{};

という書き方があります。 
Huga()の書き方ををメンバイニシャライザと言います。
コンストラクタで初期化するときはこのメンバイニシャライザの方が効率がいいとのことです。

オブジェクトがインスタンス化される際の流れは、次のような感じになっています。

1.オブジェクトをインスタンス化する。
2.クラスが持つメンバ変数のコンストラクタが起動し、中に書かれている処理が実行される。
3.コンストラクタの内容が実行される。
2の部分で、メンバ変数のコンストラクタが呼び出されていて、それぞれ初期化済みになっています。 メンバイニシャライザを使わずに代入で初期化しようとすると、それは3のタイミングになりますから、 2と3とで、処理が重複してしまいます

コンストラクタとデストラクタ | Programming Place Plus C++編【言語解説】 第13章


でも効率ってどのくらい良いんだろう?と疑問に思い、調べてみました。
以下のソースを用意し処理時間を計測してみました。

#include <iostream>
#include <string>
#include <Windows.h>
class Hoge
{
public:
        //コンストラクタ内で初期化
	Hoge()
	{
		m_integer = 0;
		m_float = 0.0f;
		m_char = 'a';
		m_string = "abc";
	};
	~Hoge() {};
private:
	int m_integer;
	float m_float;
	char m_char;
	std::string m_string;
};

class Huga
{
public:
        //メンバイニシャライザ
	Huga()
		:m_integer(0),
		m_float(0.0f),
		m_char('a'),
		m_string("abc")
	{};
	~Huga() {};
private:
	int m_integer;
	float m_float;
	char m_char;
	std::string m_string;
};

int main()
{
	const int MAX = 1000 * 1000;	//100万回
        //コンストラクタ内で初期化
	std::cout << "Hoge"<<std::endl;
	DWORD hogeStart = GetTickCount();
	for (int i = 0; i < MAX; i++)
	{
		Hoge hoge;
	}
	DWORD hogeEnd = GetTickCount();
	std::cout << "Hoge_duration: " << (double)(hogeEnd - hogeStart) / 1000 << std::endl;

        //メンバイニシャライザ
	std::cout << "Huga" << std::endl;
	DWORD hugaStart = GetTickCount();
	for (int i = 0; i < MAX; i++)
	{
		Huga hoge;
	}
	DWORD hugaEnd = GetTickCount();
        //秒で表示
	std::cout << "Huga_duration: " << (double)(hugaEnd - hugaStart) / 1000 << std::endl;
	std::getchar();
	return 0;
}

10万回ループを回してもこの程度のクラスだと差分が出なかったので、100万回回してみました。時間は秒です。
Hoge=コンストラクタ内で初期化
Huga=メンバイニシャライザ
1回目 差分0.392
f:id:jagabeeInitialize:20180121185947p:plain

2回目 差分0.171
f:id:jagabeeInitialize:20180121190028p:plain

3回目 差分0.313
f:id:jagabeeInitialize:20180121190054p:plain

4回目 差分0.188
f:id:jagabeeInitialize:20180121190724p:plain

5回目 差分0.297
f:id:jagabeeInitialize:20180121190841p:plain

だいたい0.2~0.4秒ほど早いですね。
メンバイニシャライズの方が効率がいい、というのがこれで分かりました。
記述も何となくそっちの方がかっこいいから、と言う理由でメンバイニシャライズで初期化していましたがコンストラクタの仕組みを一つ理解できた気がします。

また、上記リンク先にある通り

メンバイニシャライザでのメンバ変数の記述順については、 クラス定義の中でメンバ変数を書いた順番通りにするのが基本です。 これは、メンバ変数それぞれのコンストラクタが呼び出される順番は、 クラス定義の中でメンバ変数を書いた順番と同じだからです。 余計な混乱を招かないように、順番を合わせておきましょう。

とのことです。初期化の順番も大事なんですね。

UE4の配列が思ってたのと違った

ここ最近UE4を触っているのですが、配列で躓いたので備忘録として綴ります。

UE4に限らず知識も浅く勉強がてら記事にしていますので、おかしいところあればご指摘頂けると欣喜雀躍です。

 

DirectXやUnityでゲームを作ったことはあったのと、UE4C++ベースなので配列も同じような感じでいけるっしょと思ってたけど意外と違いました。思考が違ったので変なところに躓きました。

分かりやすいよう整数型配列を用意するとします。

 

C++の場合

ぱっと思い浮かんだものは

int hoge;

int* hoge;

std::vector<int> hoge;

std::list<int> hoge;

また調べていて見つけたものは

std::array<int> hoge;

std::deque<int> hoge;

arrayやdequeはこの記事を書かなければ知らなかったです。何て便利な配列なんだ…。それぞれの機能やら意味やらは今回の記事と趣旨が異なるので書きません。

 

C#の場合

C#と言ってもUnityで触ったことのある配列です。

int hoge;

Array<int> hoge;

List<int> hoge;

Arrayは知ってはいましたが基本Listばかり使っていました。もっと色々考えて適切な宣言しないといけないんですよね、ゴメンナサイ。

プログラム初めて一年も経っていない時にXNAでゲームを作ったことがありますが、その時はListなどは知らなかったので只管にint[] hoge;のやり方でやってました。

 

UnrealC++

UEで使うC++のことをUnrealC++と呼ぶと解釈してます。これで合ってるのかな。

TArray<int> hoge;

何とこれだけらしい。一種類しかないのは、他の配列と比較しなくていいとポジティブにとらえるべきなのかどうなのか…。

 

UnrealC++の配列、ここが違った!

ここ数日無駄に悩んだ部分だけ載せました。

尚、TArray(UnrealC++)とList(C#)はstd::vectorと近いということなので、この三つで比較していきます。

 

素数の取得

C++             std::vector.size();

C#               List.Count;

UnrealC++ TArray.Num();

何が分からなかったって、TArray.Max()の存在ですよ。最初これかと思いました。Num()は現在の要素数、Max()は確保された要素数という違いがあるんですね。どうりで数が合わないと思ったんですよ!

Max()はstd::vectorだとcapacity()、C#だとCapacity(実は存在知らなかったです…)

 

配列が空かどうか(bool)

C++             std::vector.empty();

C#               

UnrealC++

書いていないんじゃないんです。C#もUnrealC++も空かどうかの取得関数が無いみたいです。

CountやNum()で返って来た値が0かどうかで判断するしかなさそうです。

 

全ての要素を削除する

C++            std::vector.clear();

C#              List.Clear();

UnrealC++ TArray.Empty();

Emptyまさかのここで登場。C++C#もclearなのに…UnrealC++…ここでEmptyとは…。

 

 

 

UnrealC++の配列TArray。リファレンス見た感じだとC++C#の両方を兼ね備えた便利配列っぽいです。

TArrayの存在を知って三日目でこの記事を書いていますが、関数を理解できれば効率の良い処理ができるんだろうなと思います。

「UEはC++ベースだしC++調べれば作れる」と思ってましたが、UnrealC++を理解しないといけないのですね。

 

 

参考

C++ std::vector

vector - cpprefjp C++日本語リファレンス

C++ 動的配列クラス std::vector 入門

C# List

List(T) クラス (System.Collections.Generic)

C# 動的配列 List 入門

UnrealC++ TArray

TArray | Unreal Engine

[UE4] TArrayについて|株式会社ヒストリア