MISW.github.io

プログラミング超初心者のためのOpenSiv3Dでゲームを作ってみる入門

OpenSiv3Dとは

OpenSiv3Dはプログラミング言語C++のゲーム制作やヴィジュアルプログラミングのためのフレームワークです。早稲田大学表現工学科博士課程のRyo Suzukiさんが中心となって開発されていいます。

コンソールに文字を表示するのと同じような感覚で、図形,Emojiや様々な形式の画像を表示したり、効果音、BGMを鳴らしたりできます。その他、物理エンジン、QRコード読み取り、NintendoSwitchのJoyConの使用など様々な機能が用意されています。

2019年3月時点ではVersion 0.3.1がリリースされていて、現在も開発が続いているフレームワークです。

導入しよう

Windowsの方はこちら Macの方はこちら

プログラムを実行してみよう

OpenSiv3Dのプロジェクトを作成すると、デフォルトでMain.cppというサンプルコードが用意されています。各環境に対応した実行方法で実行してみましょう!!

プログラムの基礎

OpenSiv3Dについて知るためにはC++についての知識がある程度必要です。公式チュートリアルを見る前に、基礎的な知識を確認しましょう。

最小のOpenSiv3Dプログラム

サンプルコードを見た後は、とりあえず最小の何もしないプログラムを作りましょう。 サンプルコードの一部を消して、次のようなコードを書いてみましょう。

# include <Siv3D.hpp>

void Main()
{
}

これで実行できる最小のSiv3Dのプログラムができました。もちろん何もしないプログラムです。

#include文

最初の#includeの文はOpenSiv3Dの機能を導入するために必要な機能です。 具体的に言うと、というファイルをinclude文が書かれた場所に展開しています。

Main()

OpenSiv3Dは「void Main(){}」の中括弧{}の中にある処理が(基本的に)上から順番に呼ばれていきます。 このMainは後述する関数と呼ばれるものです。

コメント

プログラミングのソースコードだけではわからない部分を注釈として、コード内に残すようにします。

これをコメントといい、C++では //(スラッシュ2個)以降の同じ行の文字がコメントとなります。コメントはプログラムとして認識されません。

/*(文章)*/とすれば複数行のコメントも書けます。


# include <Siv3D.hpp>

void Main()
{
    //この文字は認識されません...
    //これらは一行のコメントです。
    /*
      この文字も認識されません
      複数行コメントです
    */
}

OpenSiv3DでHello World

まず以下のコードを書いてみてください。


# include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
	Print << U"Hello,world!";
	while (System::Update())
	{
	}
}

プログラミングでは入れ子構造が見やすいように中括弧{}が来るたびtabやスペースによって字下げをしています。この字下げをインデントといいます。Visual Studioでは改行すると勝手にやってくれます。

こうすると画面上に”Hello,world!”と表示されます。

上記コードについて解説します。

while(System::Update())

このwhile(System::Update()){}の中括弧「{}」の中の処理が1フレーム(ここでは1/60秒)ごとに呼ばれます。

この文がないと、実行した後に一瞬で画面が消えてしまうので処理の一番最後に書いてます。

Print命令

Siv3Dでは 

Print << (文、数字等);

とすることで画面に文字を表示できます。

この文に限らずC++では文の最後にセミコロン「;」をつける必要があります。

//Main()の中括弧の中のwhile(System::Update())の前に書いてください

//数値は何もつけずに表示する。
Print << 54;
/* 
 * 文字列はダブルクオーテーション""の中に書く。
 * Siv3Dでは文字コードの関係上、文字列のダブルクオーテーションの前にUをつけて「U"文字列"」をとする。
 */
Print << U"何もしてないのにPCこわれた";

// <<をつないで複数つないで表示できる。
Print << U"円周率は" << 3.14 << U"くらいです"; 

新元号は#VALUEです -変数を使おう-

ここでは変数について学びます。 とりあえず変数を使ってコードを書いてみましょう。


//Main()の中括弧の中のwhile(System::Update())の前に書いてください

//これはint(整数)型の変数yearを宣言している。
int year;   

//変数yearに2019を代入している。
year = 2019;  

//変数は普通の数値や文字列と同じように使える。
Print << U"今年は" << year << U"年です"; 
Print << U"来年は" << year + 1 << U"年です";

変数

変数はデータを入れる箱と思ってよいです。

上のコードでは「year」が変数となっており、yearの中には2019という値が入っています。

データを入れる箱の大きさや入れるデータの種類の違いを「」というもので区別しています。

上のコードでは「int」が型を表しており、int型の変数は整数を格納できます。(整数は英語でInteger -> int型)

ほかにも様々な型(もしくはクラス)が存在し、たとえば

… などなど様々な型があります。 (型を自分で作ることができます -> C++ 構造体,クラスでググって)

変数の使い方

変数を使うときは、まずその名前の変数を用意することを示す「宣言」をします。

変数を宣言するときは

(型名) (変数名);

とする。 変数名は分かりやすい名前を自分でつけます。

次にその変数の箱に値を入れる「代入」の操作が必要となります。 代入するときは

(変数名) = ();

とします。 (ここでのイコールには等しいという意味はない!!「=」はあくまで代入するための記号です。)


//Main()の中括弧の中のwhile(System::Update())の前に書いてください

//int型(整数型)の変数 yearがあることを宣言
int year; 

//yearに2019という値を代入する
year = 2019;

//yearの中の値を出力。
Print << year; 

//宣言と代入を逆にしたら動かないことも確認してみてください。

//Stirng型(文字列型)の変数new_gengoがあることを宣言
String new_gengo;

//new_gengoに"令和"という文字列を代入する(文字コードの性質上U"文字列"とする必要がある)
new_gengo = U"令和";

Print << U"新元号は" << new_gengo << U"であります";

また、(型名) (変数名) = (値);とすることで変数の宣言代入同時にできます

//Main()の中括弧の中のwhile(System::Update())の前に書いてください

//int型の変数scoreを宣言して334という値を代入
int score = 334;

//double型(浮動小数点型,つまり小数を扱える型)のpiを宣言して3.141593という値を代入;
double pi = 3.141592;

//String型の変数sを宣言して文字列を代入
String str = U"何もしてないのにPC壊れた";

Print << score << U" " << pi << U" " << str;

//変数には代入しなおすことができる
pi = 3;
Print << U"円周率は" << pi << U"とする";

演算子

整数int型,浮動小数点float型などの一部の型同士は特定の演算子を用いて計算できます。

他にも様々な演算子が存在しています。

演算子を確認してみましょう。

//Main()の中括弧の中のwhile(System::Update())の前に書いてください

double pi = 3.141;

Print << U"1.618 + 変数pi = " << 1.618 + pi;
Print << U"2 かける 3  = " << 2 * 3;
Print << U"5 わる 2 = " << 5 / 2;
Print << U"5 を 2 で割ったあまり = " << 5 % 2;

int a = 5;
Print <<U"a = "<< a;
a ++ ;
Print << U"a++" ;
Print << U"a = " << a;

数値以外でも演算子が用意されていることがあります。 例えば、String型同士に”+”演算子を適用することで文字列を連結できる

//Main()の中括弧の中のwhile(System::Update())の前に書いてください
String uenoku = U"嵐吹く 三室の山の もみぢ葉は";
String shitanoku = U"龍田の川の 錦なりけり";
//連結できる
String tanka = uenoku +U" "+ shitanoku;

Print << tanka;

関数を使おう

プログラムにおける関数とは、ある値(引数)を入力すると、それに対応した処理をして、値を返すものです。返される値を返値(かえりち)、戻り値などといいます。

例えばsiv3Dの関数 Pow は 引数をa,bとして Pow(a,b)すると、aのb乗が返ってきます(返値)。

//Main()の中括弧の中のwhile(System::Update())の前に書いてください

double num = Pow(2, 10); 
//Pow関数の返値(2の10条)を変数numに代入している
//このPow関数は int型二つを引数にとり、double型(浮動小数点型)を返す関数になっている

Print << num;

Print << Pow(1.5, 0.5);
//関数の返値をそのまま値として用いることができる
//この関数は上のように引数にint型のみならず、double型の値等を引数にしてもよい

関数の引数たちの型返値の型関数ごとに決まっているので、どの関数がどんな引数をとって、どの型の返値が返るかということを調べておく必要があります。

上のPow関数のように、関数の引数にとりうる値の型の組複数ある関数も多く存在します (関数のオーバーロードと言ったりする)

また、関数によっては引数をとらないものや、値を返さないものも存在します。

例えばSiv3Dの関数Randomは 引数をとらず、Random()とすることでランダムな0以上1未満の数を返します。

Print << U"ランダムな値を表示します";

Print << Random() * 100;
//0以上100未満の数を表示する。
//引数はなし、返値はdouble(浮動小数)型の関数である

様々な便利な関数がSiv3Dには用意されており、必要であれば自分で関数を作ることもできます。

やっとSiv3Dらしいこと: 関数を使って円を描画してみよう!

ではSiv3Dの関数を使って円を書いてみようということです。

円を書く時にはSiv3Dで定義された備え付けの型(~正確には構造体というもの~)であるCircle型を使います。

ひとつずつ解説していきます。

まずCircle型の変数(インスタンスともいう)を宣言します。ここでは変数circle1を宣言します。

Circle circle1;

次に変数circle1にCircle型の具体的なデータを代入したいです。

型名(型に応じた引数たち....)

とすると、その型の具体的なデータを返す関数として働くので、これを使って円の情報を代入します。このような関数をコンストラクタといったりします。

Circle型では

Circle((x座標),(y座標),(半径の値))

などとすると引数に対応したデータを持つCircle型の具体的なデータが返ってきます。これがCircle型のコンストラクタ(Circle型の値を構成する関数)の一つです。 (Siv3Dでは原点は画面の左上、x座標は画面右向き、y座標は画面下向きがせいになっています)

つまり先ほどの宣言と合わせるとこうなります。

//Main()の中括弧の中のwhile(System::Update())の前に書いてください

//Circle型の変数circle1を宣言して、中心(100,200),半径80の円の情報を代入。
Circle circle1 = Circle(100,200,80);

//さらに簡単な書き方として Circle circle1(100,200,80);ともできる(というか、こちらのほうが正統派)

円の情報を持つ変数が作れたので円を描画してみましょう!!

描画する際はCircle::draw()という名前の関数を使います。この関数の使い方は少し特殊で、「Circle::draw()」とそのまま書かずに

circle型の値.draw();     //(プログラム内ではCircle::draw()っていう関数として定義されている

とすることで、その円を描けます。基本的に 型(クラス,構造体)::関数名という名前の関数はその型の値.関数名()という形で使うことができます。(System::Update()などは例外です(なぜか知りたい人は名前空間,static関数でググって…))

しかし、この関数は呼ばれたフレームでしか円を描画してくれないので、毎フレーム関数を呼ぶ必要があります

よって、この関数は毎フレーム呼ばれるwhile(System::Update()){}の中括弧’{}’の中に書きます

とりあえず円を表示させるコードは次のようになります。


# include <Siv3D.hpp> // OpenSiv3D v0.3.1


void Main()
{
    //先ほどの宣言と代入
    Circle circle1 = Circle(100, 200, 80);
    
    //毎フレーム繰り返し呼ばれる
    while (System::Update())
    {
        //Circle型の値.draw()で描画
        circle1.draw();
    }
}

うまくいくとこうなります。

非常に殺風景な感じなので、色を付けてみましょう。 先ほどのcircle型の値.draw()の関数は引数なし、返値なしの関数ですが、引数に色を表すColor型の値を取れるものもあります。

先ほどのコードの中でcircle1.draw()の代わりに

circle1.draw(Palette::Yellow);

としてみましょう。Palette::(色)の値はもともと用意されたColor型の値なのでdraw(Color型…)の関数の引数にとれます。

また、Circle型のコンストラクタ(Circle型の値を構成して返す関数)を用いて、Circle型の変数を宣言せずに円を書くこともできます。

//while(System::Update())の中に書いてください

//Circle(300,400,100)によって、その円の情報を持つCircle型の値を構成し、その値に対して(Circle型の値).draw()関数をよんでいる
Circle(300, 400, 100).draw(Palette::Red);

これが一番簡単に円を書く方法になっています。

さらにもう一つ、マウスカーソルの位置に円を表示させてみましょう!

//while(System::Update())の中に書いてください
Circle(Cursor::Pos(),80).draw(Palette::Green);

こうするとマウスカーソルを負う緑色の円が出現します。

Cursor::Pos()はPoint型の値を返す関数です。CircleのコンストラクタにCircle(円の中心となるPoint型の値,半径の値)とできるものも存在していて、今回はそれを使っています。

if文を使ってモグラたたきゲームを作ってみよう!!

さて、今まで描画しかしてきませんでしたが、条件分岐の構文if文を使えば、入力を受け付けてゲームが作れます!

if文は次のようなものです

if(条件式)
{
    条件を満たしたときの処理....
}

条件式が真であったとき、if文内の処理が行われ、条件式が偽であったとき処理は行われません。

例えば次のようなコードを書いてみましょう。


 # include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
	while (System::Update())
	{
		if (MouseL.pressed())
		{
			Circle(Cursor::Pos(), 80).draw(Palette::Gold);
		}
	}
}

このコードを実行すると、マウスが左クリックが押されている間、マウスの位置に金色の円が表示されます。これが条件分岐です。

MouseL.pressed()という関数は(true)であるか(false)であるかを示す真偽値型bool型を返す関数です。

つまりMouseL.pressedという関数は、マウスの左クリックが押されている間はtrueの値が、押されていないときはfalseの値が返ってきます。関数から返ってきた値によってif文で処理を変えています。

条件式にはbool型を返す関数のほかにも、等号不等号などが扱えます。 プログラミングの世界では、等号や不等号はこのような形で表します。

意味 表記
aがbと等しい a == b (単なる「=」は代入なので注意)
aがbよりも大きい  a > b
aがbよりも小さい a < b
aはb以上である a >= b
aがb以下である a <= b
aがbと等しくない a != b

それぞれ真であるか偽であるかを示すbool型を返す演算です。

このような条件式を使えば、n回マウスがクリックされたら~とか、x座標が〇〇以上になったら~とかっていう処理をすることができます。

例えばマウスがクリックされた回数を表示して、20回以上クリックされたらメッセージを表示するプログラムを書いてみましょう。

必要な関数はマウスが押されたその瞬間だけtrueの値を返すMouseL.down()関数です。


 # include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
	//クリックされた回数を保存しておく変数を宣言
	int click_count = 0;

	//中身の処理が毎フレーム実行される
	while (System::Update())
	{

		if (MouseL.down())
		{
			click_count++;
			Print << U"アリ";

			if (click_count >= 20)
			{
				Print << U"アリーヴェデルチ!(さよならだ)";
			}
		}
	}
}

マウスがクリックされるたび click_count++;の処理で値が更新され、その値が20より大きいときは追加でメッセージを表示します。

if文ほかの使い方

if文には他にも使い方があります。まずelse句を用いた形です。 これを使うと条件を満たしたときの処理と、条件を満たさなかったときの処理をこのように書くことができます。

if(条件式)
{
    条件を満たしたときの処理....
} 
else {
    条件を満たさなかったときの処理....
}

if文を使ってゲームを作る

さて、前置きがかなり長くなりましたが、今までの事柄を使って簡単なモグラたたきゲームを作ってみましょう。

仕様

このような簡単なゲームを作ってみましょう!

簡単に使用する関数,変数の紹介です。

関数,変数 機能
Window::Center() Point型でウインドウの中心座標を返します。
RandomPoint(a,b) x座標が0からaまで、y座標が0からbまでのランダムな座標を返します。
Window::Width(),Window::Height() それぞれウインドウの横幅、高さを返します。
Circle型の値.leftClicked() bool型でその円の内部が押されているかどうかを返します。
Circle型の値.center Point型の変数です。その円の中心座標を表します。

自信がある方は上記の関数を用いて、組んでみてください!



まず骨組みのコードはこのような感じになっています。


 # include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
	Circle circle1 = Circle(Window::Center(),80);

	//中身の処理が毎フレーム実行される
	while (System::Update())
	{
		if (circle1.leftClicked())
		{
			circle1.center = RandomPoint(Window::Width(), Window::Height());
		}
		circle1.draw(Palette::Orange);
	}
}

クリックしたら、ランダムな位置に円が動きます。

ではint型の変数を用意してスコアを表示しましょう。とりあえずPrint文によって表示します。

while(System::Update()){}の中にPrint文を入れてしまうと無限に文字を表示してしまうので、Printで表示した内容を消すClearPrint()関数を使用します。また、殺風景なのでGraphics::SetBackground(Color型の値)の関数で背景色を付けます。


 # include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
//↓==ここから追加==
    Graphics::SetBackground(Palette::Green);
//↑==ここまで追加==

	Circle circle1 = Circle(Window::Center(),80);

//↓==ここから追加==
	int score = 0;
//↑==ここまで追加==


	//中身の処理が毎フレーム実行される
	while (System::Update())
	{
//↓==ここから追加==
        //Printした内容を毎回消す。
		ClearPrint();

		Print << score;
//↑==ここまで追加==

		if (circle1.leftClicked())
		{
//↓==ここから追加==
			score++;
//↑==ここまで追加==
			circle1.center = RandomPoint(Window::Width(), Window::Height());
		}
		circle1.draw(Palette::Orange);
	}
}

これでスコアが右上に小さめですが表示できました。

ではタイマーを導入しましょう。自分で変数を用意して毎フレーム足しあげてもできますがOpenSiv3DにはTimer型を使うことでTimerを簡単に作れます。

Timerの関数の使い方はこちら


 # include <Siv3D.hpp> // OpenSiv3D v0.3.1

void Main()
{
    Graphics::SetBackground(Palette::Green);
    
	Circle circle1 = Circle(Window::Center(),80);

//↓==ここから追加==
    //20秒のタイマーを設定してスタート
	Timer timer = Timer(20);
	timer.start();
//↑==ここまで追加==

	int score = 0;

	//中身の処理が毎フレーム実行される
	while (System::Update())
	{
		ClearPrint();
//↓==ここから追加==
        //タイマーがゼロになったら行う処理
		if (timer.reachedZero())
		{
			Print << U"STOP";
			Print << U"YOUR SCORE:" << score;
                       //while(System::Update())内の以降処理を飛ばして終了させる
			continue;
		}
//↑==ここまで追加==

		Print << score;
        
//↓==ここから追加==
		Print << timer.s();
//↑==ここまで追加==

		if (circle1.leftClicked())
		{
			score++;
			circle1.center = RandomPoint(Window::Width(), Window::Height());
		}
		circle1.draw(Palette::Orange);
		
	}
}

これでやりたいことはだいたい完成しました!!!!お疲れ様です!!!

しかし、なかなか殺風景ですよね…もっとましなフォント使ってスコアを表示したいし、クリックする対象や背景を画像にしたり他の図形にしたり、クリックしたときの効果音やBGMも入れたいでしょう…

そう思ったらこちら!!

https://scrapbox.io/Siv3D/クイック・チュートリアル

ここを一通り眺めれば、ほかの基本的な使い方が分かると思います!

この記事で紹介した事柄と上の公式のチュートリアルとを合わせて、ゲームを改良してみてください!!

次のステップ

OpenSiv3Dの機能についてもっと知りたい!

マニュアルの記事を見ながら何か簡単なゲームやアプリケーションを作ってみましょう!!

https://scrapbox.io/Siv3D/OpenSiv3Dリファレンス

公式のリファレンスマニュアルには他にも様々なサンプルプログラムや役に立つ機能が紹介されているので見てみてください!

C,C++についてもっと知りたい!!

また、ゲームやアプリケーションなどを作る際にはプログラミング言語CやC++等の知識が多く求められます。その際には以下のようなサイトを参照してみてください。

プログラミング研究会資料置き場

まず、弊サークルのプログラミング研究会の資料置き場です。C言語の解説記事がいくらかおいてあります。

苦しんで覚えるC言語

C++の元になっているC言語を学ぶための記事です。筆者はこれでプログラミング入門しました。

ロベールのC++

少し古いC++ですがC++の入門にはいい記事になっていると思います。WEB上では割と有名どころです。

さくさく理解する C言語/C++ プログラミング 入門

こちらはC++標準ライブラリ(STL(Standard Library))についての解説が豊富に載っています。

ネットを駆け巡ってもいいですが、本を買って/借りて読んでもいいでしょう。(本についてはどれがいいかというのはあまり詳しくないので、図書館等に赴いてみてください)

C++は様々な機能が用意されていて、筆者もその全容を把握していません…分からないことは積極的にググって見てください…

そのうち必要になりそうなキーワード: const修飾子,enum,ラムダ式,ポインタ,スマートポインタ,オブジェクト指向,クラス,継承,抽象クラス,演算子オーバーロード,テンプレート(メタプログラミング),型推論auto,参照&,const参照

ゲーム制作について学びたい

Siv3DとC++の標準ライブラリを駆使して本格的なゲームを作るためのプログラミング技法について知りたい場合は下記のサイトが役に立つかもしれません。(なかなか記事が少ないですが見つかりました) 内容はシューティングゲームを作ろうという記事になってます。(OpenSiv3Dではない昔のSiv3Dの記事なので、注意してください)

https://qiita.com/yagiri000/items/d07afd84934d5c62bc7c

Siv3Dではありませんが、同じ日本製のC++のゲームライブラリとしてDxLibというものがあります。こちらはwindowsでしか動きませんが、多くのリファレンスやゲーム制作について解説する記事が存在します。

DxLibはここから導入できます。

DxLib置き場

このようなWebサイトがゲーム制作の参考になります(DxLib)。

ゲームプログラミングの館

また、ゲーム制作全般について、弊サークルの52代プロ研会長たこわさびさんお勧めの本はこちらです。

ゲームプログラマのためのコーディング技術

簡単なゲーム制作に慣れたあと、大きめのゲームプロジェクトを始める人におすすめです。「タイトルに反してすごく汎用的なプログラミングの再利用性やオブジェクト指向の思想などがコンパクトにまとめられており、大きめのコードを書き始める人に是非読んでもらいたい一冊」とのことです。

まとめ

昨今Unity等のゲームエンジンが弊サークルの中でもゲーム制作主流になっていますが、このように1からプログラムしていくゲーム制作もなかなかいい経験なるかもしれません。

なかなかない機会ですので、興味があれば是非ゲームプログラミング始めてみてください!