C++11とゲーム製作

1K Views

March 21, 14

スライド概要

C++11で追加された機能を利用してゲームを作ってみた

profile-image

サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

C++11でゲームを作ってみた SDLとC++11でダンジョン探索ゲームを作る mixC++勉強会@Tokyo @uchan_nos

2.

自己紹介 • @uchan_nos • OSやC++が大好き • 『30日でできる!OS自作入門』校正 • 『C++でできる!OS自作入門』執筆 (SlideShare)

3.

今日のテーマ と C++11 によるゲーム製作のお話 • SDL:ゲーム用ライブラリ • C++11:C++言語の最新規格 • 作ったゲーム:ダンジョン探索ゲーム

4.

SDLとは • Simple DirectMedia Layer • ゲームプログラミング用のライブラリ • • いろいろなOSで動く シンプル(低レイヤ)、C言語向けAPI • ウィンドウ生成、マウス・キーボード入力、グラフィック描 画など、OS固有の処理を担当 • シンプルなのでライブラリに振り回されない

5.

C++11 • C++の最新規格 • スマートポインタ、ラムダ式、範囲for文など面白い 機能がたくさん追加された • そうだ ゲーム、作ろう。

6.

作ったゲーム

7.

作ったゲーム ランダム生成マップ 主人公がぬるっと動く

8.

デモ

9.

こだわりポイント • ぬるっと動くメニュー選択画面 • 無駄なメニュー選択エフェクト • シーンスタックによるシーン管理

10.

ゲームプログラミングの基本 初期化 while (1) { 状態更新(アニメーション) 画面描画(状態に応じて描画) }

11.

アニメーション:固定フレーム • 1秒間のループ回数を固定する(例えば60FPS) • 各ループで、状態変数を更新 • if ((cnt % 10) == 0) { x += 2; y += 1; } • 1秒に(12, 6)の等速直線運動 • t秒の4乗に従って動かしたい→立式が面倒 • 高負荷な環境、60FPS以外の環境がある

12.

アニメーション:可変フレーム 分割加算法 • 1秒間のループ回数が可変 • 各ループで、状態変数を更新 • • double dt = current_time - previous_time; x += dt * 12; y += dt * 6; • 1秒に(12, 6)の等速直線運動 t秒の4乗に従って動かしたい→立式が面倒

13.

アニメーション:可変フレーム 直接法 • 1秒間のループ回数が可変 • 各ループで、状態変数を更新 • • // t0 はある基点時刻 t = current_time; x = (t - t0) * 12; y = (t - t0) * 6; • 1秒に(12, 6)の等速直線運動 t秒の4乗に従って動かしたい→立式が楽! x=pow(t-t0,4);

14.

直接法のデメリット • • 基点時刻の記憶が面倒 • 動かす物体の数だけメンバ変数が増える • double base_time; ポーズ機能をつけると面倒 • ポーズ解除後に基点時刻の修正 • base_time += pause_leave_time_ - pause_enter_time_;

15.

シーンスタック

16.

シーン • シーン:表示要素のカタマリ • メニュー画面、メニュー選択エフェクト画面、ゲー ム本編それぞれが1つのシーン

17.

シーンスタック • シーンスタック:シーン遷移を管理するやり方 • PUSH:新たなシーンを一番上に重ねる POP:一番上のシーンを取り去る GOTO:POPしてPUSH • スタックの下から順に状態更新と画面描画をする

18.

シーンスタック PauseScene GameScene

19.

シーンスタック シーンスタック PauseScene GameScene シーン

20.

シーンスタックの更新と描画 SceneManager Push(shared̲ptr<Scene>) Pop() GoTo(shared̲ptr<Scene>) Update() Draw(SDL̲Surface*) Scene scenes̲ * Update() Draw(SDL̲Surface*)

21.

シーンスタックの更新と描画 SceneManager Push(shared̲ptr<Scene>) Pop() GoTo(shared̲ptr<Scene>) Update() Scene scenes̲ * Update() Draw(SDL̲Surface*) Draw(SDL̲Surface*) 各SceneのUpdate/Drawをscenes_の先頭から順に呼び出す

22.

シーンスタック Update GameScene

23.

シーンスタック GameScene Update

24.

シーンスタック Draw GameScene

25.

シーンスタック GameScene Draw

26.

シーンスタック Update GameScene

27.

シーンスタック GameScene Update

28.

シーンスタック GameScene キー入力 Update

29.

シーンスタック SceneManagerさん、 これをPushしてね GameScene キー入力 Update PauseScene

30.

シーンスタック PauseScene GameScene

31.

シーンスタック Update PauseScene GameScene

32.

シーンスタック PauseScene GameScene Update

33.

シーンスタック Update PauseScene GameScene

34.

シーンスタック Draw PauseScene GameScene

35.

シーンスタック PauseScene GameScene Draw

36.

シーンスタック Draw PauseScene GameScene

37.

シーンスタック Update PauseScene GameScene

38.

シーンスタック PauseScene GameScene Update

39.

シーンスタック Update PauseScene GameScene

40.

シーンスタック Update PauseScene GameScene キー入力

41.

シーンスタック Update PauseScene SceneManagerさん、 一番上をPopしてね GameScene キー入力

42.

シーンスタック GameScene

43.
[beta]
PauseSceneの追加
GameScene::Update
if (pressed_in_current_frame(SDLK_q))
{
scene_manager()->Push(make_shared<PauseScene>(
ticker_,
key_checker_,
[&](){
is_pausing_ = false;
}));
is_pausing_ = true;
return;
}

44.
[beta]
PauseSceneの追加
GameScene::Update
if (pressed_in_current_frame(SDLK_q))
{
scene_manager()->Push(make_shared<PauseScene>(
ticker_,
key_checker_,
[&](){
is_pausing_ = false;
}));
is_pausing_ = true;
return;
}

45.
[beta]
PauseSceneの削除
PauseScene::PauseScene
PauseScene(const Ticker& ticker,
const KeyChecker& key_checker,
std::function<void ()> on_pause_breaked)

PauseScene::Update
if (pressed_in_current_frame(SDLK_q))
{
scene_manager()->Pop();
on_pause_breaked_();
}

46.
[beta]
if (pressed_in_current_frame(SDLK_q))
{
scene_manager()->Push(make_shared<PauseScene>(
ticker_,
key_checker_,
[&](){
is_pausing_ = false;
}));
is_pausing_ = true;
return;
JavaScriptっぽい!
}

if (pressed_in_current_frame(SDLK_q))
{
scene_manager()->Pop();
on_pause_breaked_();
}

47.

シーンスタックの考察 • ○ シーンスタックを使うと、シーン管理が楽になる(気がす る) • • 状態番号とswitchで分岐するより楽 × シーンスタック中にHogeSceneがなければPush、のような 処理がしたくなる • スタックのシーンを全て取得するメソッドを付ける? • シーン自身に、2重生成禁止属性とか付ける?

48.
[beta]
コラム:ラムダ式
•

ラムダ関数、無名関数などとも呼ぶ

•

std::vector<std::function<void ()>> callbacks;
void KeyPressed() {
for (auto& callback : callbacks) callback();
}
void RegisterCallback() {
callbacks.push_back([](){
std::cout<<"called!"<<std::endl;
});
}

49.
[beta]
ラムダ式の文法
std::vector<int> items;
!

[&](int x) -> void
{
items.push_back(x);
}

キャプチャ

引数

ラムダ式本体

戻り値型

50.

ラムダ式の文法 [&](int x) -> void { items.push_back(x); } • キャプチャ:ローカル変数をラムダ式内部で使う • 引数:通常の関数引数と同じ用法 • 戻り値型:ラムダ式呼び出しの戻り値型 • ラムダ式本体:ラムダ式を呼び出すと実行される

51.
[beta]
ラムダ式の用法
#include <iostream>
#include <functional>
!

void test(const std::function<void ()>& func)
{
func();
}
!

int main()
{
test([] {
std::cout << "called!" << std::endl;
});
}

52.
[beta]
ラムダ式の用法
#include <iostream>
#include <functional>
!

void test(const std::function<void ()>& func)
{
func();
引数、戻り値型
}
!

は省略可

int main()
{
test([] {
std::cout << "called!" << std::endl;
});
}

53.
[beta]
ラムダ式の用法
#include <iostream>
#include <functional>
!

void test(const std::function<void ()>& func)
{
func();
}
!

int main()
{
test([] {
std::cout << "called!" << std::endl;
});
}

54.
[beta]
ラムダ式の用法
#include <iostream>
#include <functional>
!

void test(const std::function<void ()>& func)
{
func();
}
!

int main()
{
test([] {
std::cout << "called!" << std::endl;
});
}

55.
[beta]
ラムダ式を受け取る
•

•

std::function<戻り値型 (引数)>で受け取る
•

std::function<int (double f)> to_int =
[](double f){ return static_cast<int>(f); };

•

他の関数にラムダ式を渡したいときに活躍

autoで受け取る
•

auto add = [](int a, int b){ return a + b; };

•

addがローカル変数として生成される

56.

キャプチャの用法 • 例えばこんなAPIがあったとする • • いざ使おう • • DrawText(Surface& surface, Font& font, Color& color, int x, int y, const char* text); DrawText(window_surface, mincho_font, black, 0, 0, "hello"); DrawText(window_surface, mincho_font, black, 0, 16, "world"); ラムダ式! • auto draw_text = [&](int y, const char* text){ DrawText(window_surface, mincho_font, black, 0, y, text); }; draw_text(0, "hello"); draw_text(16, "world");

57.

関数オブジェクトとの比較 int main() { struct { public: int operator()(int a, int b){ return a + b; } } add0; cout << add0(10, 31) << endl; } int main() { cout << [](int a, int b){ return a + b; }(10, 31) << endl; } • 名前 add0 が必要 • • 戻り値型が必須 キャプチャできない • • • 名前不要 戻り値型を省略可 キャプチャ簡単

58.
[beta]
関数オブジェクトでキャプチャ
int main() {
int c = 10;
!

struct Adder {
int c_;
public:
Adder(int c) : c_(c) {}
int operator()(int a, int b){
return a + b + c;
}
} add0(c);
!

cout << add0(10, 31) << endl;
}

59.

ラムダ式でキャプチャ int main() { int c = 10; ! cout << [&](int a, int b){ return a + b + c; }(10, 31) << endl; }

60.

while ([]{}) ; キャプチャ無し、引数void、戻り値voidのラムダ式 キャプチャ無しの場合、グローバル関数へのポインタと同じ扱い