ゲームとメディアアートのための C++ ライブラリ「Siv3D」
ゲームとメディアアートのための C++ ライブラリ「Siv3D」
C++ Advent Calendar 2015, 25 日目の記事です。
年末年始は C++ Advent Calendar を振り返って、テンプレートメタプログラミングの腕を磨いたり、GPGPU に挑戦したり、Boost の知らなかった機能を試すのも楽しいでしょう。そしてまた、Siv3D を使ってコンピューターを遊びつくすのも面白い選択肢の1つです。
45 秒で始められる
豊富な機能と優れた性能にもかかわらず、Siv3D の導入はとても簡単です。このサイトから 50MB の SDK をダウンロードし、ワンクリックのインストーラを実行するだけで、グラフィックスも、音楽再生も、画像処理も、カメラやマイクといった入力デバイスも、数行の C++ コードでコントロールできる贅沢な開発環境が手に入ります。
▲ ダウンロードから 45 秒で最初のサンプルをビルド・実行
活用事例
2015 年夏にリリースされた現バージョン Siv3D June 2015 v2 は 2,100 ダウンロード、専門学校での授業、ゲームコンテストへの応募作品、大学の研究のビジュアライゼーション、ロボットの操作など、幅広い分野で活用されています。
東京ゲームショウ 2015
SIGGRAPH 2015
2015 国際ロボット展
サンプルプログラム
Siv3D でどんなことができるのでしょうか。簡単なサンプルをいくつか紹介します。
驚く方もいるかもしれませんが、ここに載っている数行~数十行のコードだけでアプリケーションが動きます。
[NEW] は、2016 年 1 月 11 日に登場する最新版 Siv3D January 2016 の新機能です。
図形の表示とあたり判定
# include <Siv3D.hpp> void Main() { const Rect rect(20, 20, 200, 100); const Circle circle(150, 300, 100); const Polygon star { { 430, 100 }, { 470, 240 }, { 610, 240 }, { 505, 325 }, { 545, 460 }, { 430, 380 }, { 315, 460 }, { 355, 325 }, { 250, 240 }, { 390, 240 } }; while (System::Update()) { const Circle player(Mouse::Pos(), 30); rect.draw(player.intersects(rect) ? Palette::Red : Palette::Yellow); circle.draw(player.intersects(circle) ? Palette::Red : Palette::Yellow); star.draw(player.intersects(star) ? Palette::Red : Palette::Yellow); player.draw(); } }
リアルタイム電卓 [NEW]
# include <Siv3D.hpp> void Main() { Graphics::SetBackground(Color(240)); const Font font(40); String expression; while (System::Update()) { Input::GetCharsHelper(expression); font(expression).draw(10, 10, Palette::Black); if (const auto result = EvaluateOpt(expression)) { font(result.value()).draw(10, 250, Palette::Black); } } }
マイク入力音声の解析
# include <Siv3D.hpp> void Main() { const Font font(30); Recorder recorder(0, 5s, RecordingFormat::S44100, true); recorder.start(); while (System::Update()) { const auto fft = FFT::Analyze(recorder); for (auto i : step(640)) { RectF(i, Window::Height(), 1, -Pow(fft.buffer[i], 0.5) * 500).draw(HSV(i)); } const int mouseX = Mouse::Pos().x; Rect(mouseX, 0, 1, Window::Height()).draw(); font(L"{:.1f}Hz"_fmt, fft.resolution() * mouseX).draw(Mouse::Pos().moveBy(0, -50)); } }
ブロック崩し
# include <Siv3D.hpp> void Main() { const Size blockSize(40, 20); const double speed = 8.0; Rect wall(60, 10); Circle ball(320, 400, 8); Vec2 ballSpeed(0, -speed); Array<Rect> blocks; for (auto p : step({ Window::Width() / blockSize.x , 5})) { blocks.emplace_back((p*blockSize).moveBy(0, 60), blockSize); } while (System::Update()) { ball.moveBy(ballSpeed); wall.setCenter(Mouse::Pos().x, 420); for (auto it = blocks.begin(); it != blocks.end(); ++it) { if (it->intersects(ball)) { (it->bottom.intersects(ball) || it->top.intersects(ball) ? ballSpeed.y : ballSpeed.x) *= -1; blocks.erase(it); break; } } for (auto const& block : blocks) { block.stretched(-1).draw(HSV(block.y - 40)); } if (ball.y<0 && ballSpeed.y<0) { ballSpeed.y *= -1; } if ((ball.x < 0 && ballSpeed.x < 0) || (Window::Width() < ball.x && ballSpeed.x > 0)) { ballSpeed.x *= -1; } if (ballSpeed.y > 0 && wall.intersects(ball)) { ballSpeed = Vec2((ball.x - wall.center.x) / 8, -ballSpeed.y).normalized()*speed; } ball.draw(); wall.draw(); } }
Twitter クライアント
# include <Siv3D.hpp> void Main() { Graphics::SetBackground(Color(160, 200, 100)); // Consumer Key (API Key) const String API_key = L"XXXXXXXXXXXXXXXXXXXXXXXXXXXX"; // Consumer Secret (API Secret) const String API_secret = L"XXXXXXXXXXXXXXXXXXXXXXXXXXXX"; experimental::TwitterClient twitter(API_key, API_secret); twitter.openTokenPage(); GUI guiAuth(GUIStyle::Default); guiAuth.setTitle(L"PIN の入力"); guiAuth.addln(L"PIN", GUITextField::Create(7)); guiAuth.add(L"auth", GUIButton::Create(L"認証")); guiAuth.setPos(40, 40); GUI guiTweet(GUIStyle::Default); guiTweet.setTitle(L"ツイート"); guiTweet.addln(L"text", GUITextArea::Create(6, 24, 140, false)); guiTweet.add(L"tweet", GUIButton::Create(L"ツイート")); guiTweet.setPos(40, 200); while (System::Update()) { guiAuth.button(L"auth").enabled = (guiAuth.textField(L"PIN").text.length == 7); guiTweet.button(L"tweet").enabled = !guiTweet.textArea(L"text").text.isEmpty; if (guiAuth.button(L"auth").pushed && twitter.verifyPIN(guiAuth.textField(L"PIN").text)) { guiAuth.release(); guiTweet.textArea(L"text").enabled = true; } if (guiTweet.button(L"tweet").pushed) { twitter.tweet(guiTweet.textArea(L"text").text); guiTweet.textArea(L"text").setText(L""); } } }
対称定規
# include <Siv3D.hpp> void Main() { const int N = 12; Window::Resize(1280, 720); Image image(Window::Size(), Color(20, 40, 60)); DynamicTexture texture(image); Mouse::SetTransform(Mat3x2::Translate(Window::Center())); while (System::Update()) { if (Input::MouseL.pressed) { for (auto i : step(N)) { Circular cs[2] = { Input::MouseL.clicked ? Mouse::Pos() : Mouse::PreviousPos(), Mouse::Pos() }; for (auto& c : cs) { c.theta = i % 2 ? -c.theta - TwoPi / N * (i - 1) : c.theta + TwoPi / N * i; } Line(cs[0], cs[1]).moveBy(Window::Center()).overwrite(image, 2, HSV(System::FrameCount(), 0.5, 1.0)); } texture.tryFill(image); } else if (Input::MouseR.clicked) { image.fill(Color(20, 40, 60)); texture.fill(image); } texture.draw(); } }
Leap Motion
# include <Siv3D.hpp> # include <Siv3DAddon/LeapMotion.hpp> void Main() { Window::Resize(1024, 640); LeapMotion::RegisterAddon(); const Font font(36), font2(18); while (System::Update()) { Graphics3D::FreeCamera(); for (const auto& hand : LeapMotion::Hands()) { const Vec3 scaledPos = hand.pos*0.05; Sphere(scaledPos, 0.5).draw(HSV(hand.id * 30).toColor()); const Vec2 screenBase = Graphics3D::ToScreenPos(scaledPos).xy; font(hand.isLeft ? L"Left" : L"Right").drawCenter(screenBase.movedBy(20, -160)); const Circle pinchCircle(screenBase.movedBy(-60, -260), 60); const Circle grabCircle(screenBase.movedBy(100, -260), 60); pinchCircle.draw(HSV(hand.id * 30).toColor(60)); pinchCircle.drawPie(0.0, hand.pinchStrength * TwoPi, HSV(hand.id * 30)); grabCircle.draw(HSV(hand.id * 30).toColor(60)); grabCircle.drawPie(0.0, hand.grabStrength * TwoPi, HSV(hand.id * 30)); font2(L"Pinch").drawCenter(pinchCircle.center); font2(L"Grab").drawCenter(grabCircle.center); for (const auto& finger : hand.fingers) { for (const auto& joint : finger.joints) { Sphere(joint*0.05, 0.2).draw(HSV(finger.id * 10).toColor()); } } } Box(1).draw(); } }
MIDI ビジュアライザー [NEW]
文字や記号の認識 [NEW]
エレガントな C++
Siv3D は C++ の最新規格やテクニックを駆使して、コードを読み書きしやすくする仕組みを提供しています。
ループのユーティリティ
# include <Siv3D.hpp> void Main() { // 0 ~ 9 を表示 for (auto i : step(10)) { Println(i); } // 10 ~ 0 を表示 for (auto i : step_to(10, 0, -1)) { Println(i); } // 横 3 個、縦 4 個、計 12 個の四角形を表示(本来なら二重ループ) for (auto p : step({ 3, 4 })) { Rect(p.x * 10, p.y * 10, 8, 8).draw(); } WaitKey(); }
文字列フォーマット
# include <Siv3D.hpp> void Main() { Println(L"{}+{}={}"_fmt, 1, 1, 1+1); // 1+1=2 Println(L"{2}/{1}/{0}"_fmt, 2015, 12, 25); // 25/12/2015 Println(L"{:.2f}, {:.5f}"_fmt, Pi, Pi); // 3.14, 3.14159 Println(L"This is {}"_fmt, Window::GetTitle()); // This is Siv3D App WaitKey(); }
Optional
# include <Siv3D.hpp> void Main() { const CSVReader csv(L"test.csv"); // CSV ファイルの 2 行, 3 列目の文字列を double 型に変換 if (const auto cell = csv.getOpt<double>(2, 3)) { // 変換できたら値を取得 const double value = cell.value(); } }
ユーザ定義リテラル [NEW]
# include <Siv3D.hpp> void Main() { Stopwatch s(true); while (System::Update()) { // 開始から 3 秒未満なら if (s.elapsed() < 3s) { // 四角形を 30° 傾けて描画 Rect(100, 100, 50).rotated(30_deg).draw(); } } }