Category: Siv3D

  • Siv3Dゲームジャム(2025)に参加した話

    作ったもの

    達磨落としシンフォニー

    右側からコード(和音)の指示が流れてくるので、それに合うようにだるま落としをするゲーム。

    ダウンロードはこちら

    作品紹介ページはこちら

    作りかけでもう少し作り込みたいと思いながらも、
    他にやりたいことが出てくると放置してしまいがちなんだよなぁ。

    収穫、良かったこと

    新しい分野に挑戦してみたこと

    CESA賞のお題が発表されたので、少し意識して音楽系のゲームをなにか作ろうと思い、その流れでリアルタイムの音声データ書き込みを手掛けることに。

    音ゲーなので画面と音の同期を取るべしと公式サイトを探し、IAudioStreamの派生クラスを作ってデータを書き込むという方法にたどり着き、実際に作ったことで作れるアプリの幅が広がったのではないか。※公式のサンプルだけだと書き方は分かる一方で使い方が良くわからず、結果試行錯誤しながら作り進めることに

    また、自分は作曲趣味はあるものの勘だけで作ってきたので、自分がコードや音楽理論の勉強もできるようなものができればいいということで、コードの種類を当てるようなゲームシステムにしてみたのも今まで作ったゲームの中では珍しい部類だった。

    自分用のSiv3Dゲーム用テンプレートを作ったこと

    これはお題の発表前に作ったもので、いくつかのシーン実装済みのシーンマネージャー、Iniファイルからのウィンドウサイズ設定読み込みとオプション画面での切り替え、Escキーでアプリ終了させずにタイトルに戻るなどの挙動にする、といった「ある程度ゲームとしての土台ができているもの」を作ったのだが、これは作った甲斐があったしスタートダッシュは非常に良好だった。多少作り慣れた人にはおすすめしたい。

    Siv3Dの素のテンプレートのファイルはダウンロードしたての入門として、「これを書けば文字が描けるのか」とかすぐに分かって非常に良いと思う一方、「さあゲーム作ろう!」となった時にはもっとスラスラ作り進められるテンプレートがあるべきなような気がしている。でもこれはすごく汎用性のあるものは難しいので、自分で作るのが一番だろう。

    ついにコードの勉強を始めた

    「絶対わかる! コード理論」買いました。
    作曲スキルも多分上がるから一石二鳥だったな!

    参加してみて思ったこと

    モチベーションが爆上がりする

    正直、昨年はアドベントカレンダーを書く以外にSiv3Dを触らなかったよう気がする程度にC++プログラミングから遠ざかっていたので今回のゲームジャムには参加しようかどうか迷ったのだが、結果的には参加することでプログラミングへのモチベーション自体が高まり、いい影響のあるイベントだった。

    他の人の作品が刺激になる

    昔参加していた1週間で作るSiv3Dゲームジャムのときとは違ってZoomでの中間発表があり(時代だなぁ)、他の人の進捗や苦戦の数々を見ることができたり、アイデアの説明を聞くことができたりして、これも自分のモチベーションになったように思う。

    GitHub Copilotがとんでもなく強力

    見出しの通り。htmlなどで多少使っていて勝手は分かっていたのだが、いざしっかりとコードを打つという時にコード補完機能の尋常じゃない強さには恐れ入った。

    かなりの数のコード(和音)とその構成音を事前に登録する処理なのだが、第1引数のコード名を打てば後は勝手に補完してくれたり、省力化にはだいぶ貢献している。合ってるかどうか疑ったほうが良かったとは思ったものの、最終的にはノーチェックで通してしまった。

    サラリーマンしている中では、一言でAI活用に目を向けてると、ぶっちゃけ「そこまでの費用対効果あるか?」と思う場面も多々あるのだが、正直なところコード生成AIは趣味であっても必須レベルに近いと思う。

    なんというか、コンパイラや開発環境が無料ではなかった時代に(上達にはお金が必要だという意味で)近づいたような気がしている。今回のゲームジャムでは無料版でなんだかんだ間に合ったが、基本的には有償版が必要な印象だった。

    GItHub Copilotは無料版で1000回くらいコード補完でき、VisualStudioも対応するようになっているので使ったことがない人は是非試したほうがいい。カルチャーショックを受けます本当に。

    反省点

    反省点というか次回の心がけというか。

    反省点① ゲーム内に操作法の説明は入れておいたほうがいい

    最終発表の後、遊べる作品は殆どの人のをDLして片っ端から遊んだのだが、痛感したのは

    「ゲームの遊び方はゲーム内に記そう!」

    ということだった。

    これは審査された方もそうなんだろうなと思ったのだが、片っ端から遊ぼうとするとそもそもマウスで動かすのかキーボードで動かすのか、それすらも未知なので、アプリの画面に操作法がなければ、ウィンドウを一旦押しのけて作品紹介ページを再度開き、そのテキストを舐め回すように読んで操作法を調べることになる。

    で、この作品ページを見るという行為がある種負担のようにも思えてしまったのだ。

    タイパが求められる時代なんて寂しい世の中になったねえ、などとか言ったりする自分が、同じ口で作品紹介ページに戻るのはしんどいとか言いやがるのである。

    こんな理不尽な事があっていいのだろうか。

    ……と、まあよくわからないテンションになってしまったが、要するにゲームへのモチベーションを下げさせたくなければ、極力ゲーム内に操作方法を出したりとか、「Hit Enter Key」とか次のアクションを記したりしてあげたほうがいい。PCは特に。

    達磨落としシンフォニーはこれさえ気を付けていれば大賞間違いなしだっただろう。うん。

    嘘です。

    反省点② ミーハーを気取ってC++のmoduleに手を出すのはやめたほうがいい

    C++23から、まあ数字が23だったかは今勘で書いているので間違っていても不思議はないのだが、#include に代わって import文が使えるようになっているので、じゃあ物は試しとimportを今回使ってみたりした。

    しかし、ただ何となく時代の最先端を気取りたかった二シャスゆえに作品を提出し終わる頃にはmoduleを使うのはやめたほうが良いぞとなった。

    これには理由がふたつある。

    1. Visual StudioのIntelliSenseの挙動がまだ怪しい
    2. #includeと混ぜて使うと怒られたりする

    1.は字の通りである。なんかねぇ、他のファイルの警告表示が消えるのにmoduleだと時間がかかったりするし、IntelliSenseくんがファイルの中身をゆっくりと解釈して消化している間にCtrl+Sでモジュールファイルを上書き保存しようとすると、何故か名前をつけて保存のダイアログが出てきて保存させてくれなかったりしたんですよぉ。

    まあこのへんは時代が進めば使いやすくなるんじゃないかと思う。今までみたいに宣言や定義と実装を別々のファイルに分けずに書けるのは嬉しいので将来の発展に期待したい。

    2.については、自分が作ったものではないファイル、例えば標準ライブラリ関係は基本的にmodule化されているようで、#includeでもimportでも使えるのだが、#includeとimportはそれぞれ別のヘッダライブラリのように扱われているらしく、混在させると多重定義になってしまい、エラーの嵐になってしまうことがあるらしい、という話だ。

    それを考えると、気を使わないといけないのが既存のライブラリとかを組み込んで使うときで、よっぽど新しいライブラリとかじゃないとガッツリmoduleを使っていく構成は無理があるのでは……と思ってしまった。

    実際のところ達磨落としシンフォニーはエラーは酷くなくてmodule使った形で提出までやれたのだが、最近やったSiv3Dでない別のプログラムではmoduleをすべてhppとcppファイルに書き直すところまで追い詰められてしまったので、便利な面もあるがもう安易に手出しはしないと思う。

    反省点③ 「駄目」の「駄」の字を間違えて覚えてた

    ゲームに実際に登場した画像。もう駄目だ。

    楽をしようと筆で描いたのを実写取り込みしてたらこのザマだよ!

    リアルで散々話のネタに擦りました(Ryoさんのご指摘に感謝である)。

    結論

    素晴らしいイベントでした。皆さんありがとうございました!

    また参加したいね。

  • 切り出したドーナツ型の描画

    Siv3D Advent Calender 2024 21日目の記事です。

    ※本ページはHPリニューアルに伴い元記事を移植したものです。

    まずこの形をなんと呼ぶべきか

    本日の図形

    この形だ。なんと呼べばいいのか、正式名称がないのだ。

    ドーナツ型を切り出した図形と呼べるが、そう思っている人ばかりではあるまい。

    ただの円を切り出したものであれば「扇形」という立派な名前があるのだが、これは数学の定義上の扇形ではない。

    「でも扇ってこういう形してるしドーナツ型の切り出しも扇形呼んでもよくない?」とは思う。

    ドローソフトを使って作った扇。下手か?

    Siv3Dでの切り出したドーナツ型の描画

    Siv3D切り出したドーナツ型を描画するのはCircle.drawPieメソッドでは実現できない。空隙を指定することはできないからだ。

    しかし実際のところは分厚い弧を描く形を取ることで、そう苦労せず描画できる。
    これにはCircle.drawArcメソッドを使う。

    図形の塗りを描くのならそれで済むのだが、境界線を描きたい場合、rawPieやdrawArcの返り値はCircleなので、これにdrawFrameを使うと基準となる円の外周を描くだけになってしまう。

    circle.drawArc(0_deg, 90_deg, 20, 0, Palette::White).drawFrame(8, Palette::Red);

    境界線を描きたい場合は以下に2通りになりそうだ。

    • (A) Circle.arcAsPolygonメソッドで一旦Polygon型に変換し、drawFrameする。
    • (B) 4本の線をそれぞれ直線と弧で描画する。

    普通は(A)のやり方を取るだろう。線を図形にするのがポイントだ。

    
            double angle1 = 0_deg;
            double angle2 = 90_deg;
            double line_thickness = 10;
    
            circle.arcAsPolygon(angle1, angle2, 150, 0)..drawFrame(line_thickness, Palette::Orange);
          

    (B)は地道に描画するのでそれぞれの線の色を変えることができる。
    OffsetCircularで角度と距離を指定すれば特定位置からそれだけ離れた座標を取得できるので、意外とそこまで面倒ではない。
    ちなみに弧の描画で内側と外側を同じ太さにしないときれいにならない。

    
            double angle1 = 0_deg;
            double angle2 = 90_deg;
            double line_thickness = 10;
    
            circle.drawArc(angle1, angle2, line_thickness / 2, line_thickness / 2, Palette::Green);
            circle2.drawArc(angle1, angle2, line_thickness / 2, line_thickness / 2, Palette::Orange);
            Line(OffsetCircular(circle.center, circle.r, angle1), OffsetCircular(circle.center, circle2.r, angle1)).draw(line_thickness,Palette::Orange);
            Line(OffsetCircular(circle.center, circle.r, angle2), OffsetCircular(circle.center, circle2.r, angle2)).draw(line_thickness, Palette::Orange);
            
    かぼちゃの煮つけにピッタリ!

    この図形の用途

    かぼちゃの煮つけにも使えないことはないが、ゲーム等での用途はほぼパイメニュー(円状のメニュー)に絞られると思われる。

    パイメニューについては公式でコードを出しているのでそちらも役に立つだろう。

    手作りで実装する場合はきれいに見せるのにコツがある。

    例えば4分割のメニューを作るとして、「87度+隙間3度で作ろう」とやると以下のようなものが出来上がって、メニューUIとしてはちょっと違うなという感じになる。

    模様としてはいいけど

    そのため一旦隙間のないように切り出しドーナツを敷き詰めてから、それぞれを少し散開させるようにするときれいに見えるだろう。

    あまり離しすぎると中心の円が歪に見えるので注意

    描線に影をつけたい場合、一旦レンダーテクスチャに完成形を作っておいて、そのテクスチャをdrawする方法が無難だと思うが、
    場合によっては予め画像ファイルを作ったほうが楽かもしれない。

  • Siv3D(2016)のゲームをOpenSiv3D(0.6.6)に移植する

    はじめに

    ※この記事は多分Siv3D Advent Calender2022の記事で、HPリニューアルに伴い移植したものです。

    今年も12月がやってまいりました。
    毎年この季節になるとVisualStudioの新しいバージョンとOpenSiv3Dの新しいバージョンをインストールします。
    あと冬至なのでかぼちゃの煮つけを作ったり年賀状を書こうとしたりします。師走というだけあります。こんにちはニシャスです。

    私はOpenSiv3Dがリリースされる前の旧いSiv3Dでちょこちょことミニゲームなどを作っておりました。(最近あんまり作ってません)
    そこで今回はこの「昔のSiv3Dを使ったプロジェクトデータ」をOpenSiv3Dに移植できるか、
    できるとしてその負担感はどのくらいかを調べるべく、実際に過去に自分が制作したゲームのコードを
    OpenSiv3D向けに書き直してみることにしました。

    結論から言うと移植はそんなに苦労しませんでした。

    移植したもの

    「10秒シューティング」を移植に使用しました。

    「冬至用のアプリケーションいくつか作って投稿日冬至選んでるのにそれかよ!」というツッコミはナシでお願いします。

    ゲーム「10秒シューティング」の画像

    遊びたい方はこちら

    修正するポイント

    文字列

    多分一番変更箇所が多いやつです。
    旧Siv3Dは文字列に L プレフィックス(ワイド文字)が使われていましたが、OpenSiv3Dは U プレフィックス(UTF-32)に変わっています。なので文字列を使ったメソッドなどはすべてエラーになり変換が必要です。

    L” を U” に置換すると良いでしょう。Siv3Dの他にライブラリなど使っていなければズギャッと全置換して問題ないと思います。

    ウィンドウの幅・高さ

    OpenSiv3Dのプロジェクトは、特に指定がなければ800×600のウィンドウが表示されます。

    旧SIv3Dは640×480だったので大きくなっています。もしウィンドウサイズを書き換えているのであれば心配いらないかもしれませんが、これも要修正です。

    また、ウィンドウの幅や高さを取得するのはWindow::Width()ではなくScene::Width()となっています。Windowは表示のモードやウィンドウ位置などのデータ提供役になっていますが描画画面の幅などは受け持たなくなったようです。

    イージング関係

    以前イージングはEasingControllerというのが使えましたが、これはないと言われます。

    Siv3D 0.6.6リファレンスには、EaseInBack(t)といった「そのまんま」の名前で、tに0~1を入れると0から始まって1で終わる値を得られる「イージング関数」を使用している例が、代わりに挙げられています。

    しかしこれは「n秒かけて値Aから値Bの遷移を特定のイージングの種類を使って行う」というEasingControllerとは挙動が異なるのでちょっと困ります。

    ただ、実はEasingControllerは、0.6.6にEasingABと名前を変えて生き続けています。

    なのでコードをなるべく変えずに再利用を行いたいのであればEasingControllerの宣言をEasingABに変えてやれば良いと思われます。

    アセット関係

    アセットの利用は大きくは変わっていませんが、音声関係のアセットはSoundAssetからAudioAssetに名前が変わっているので、置換してしまいましょう。特に苦労はしないでしょう。

    ちなみに画像ではなくテクスチャのデータや生成したAudioのデータからアセットを生成するような場合、TextureAssetDataなどのAssetDataを使うことになると思います。この使い方の作法が従来と変わっています。

    ゲームで使われる爆発の画像。横にアニメーションのそれぞれのコマの画像が連結されている
    上記は1枚の画像なのですが、これを分割して複数枚数の画像アセットにしたい場合などには使うんじゃないかな

    TextureAssetDataについて説明すると、以前はtextureメンバにTextureを生成してそのオブジェクト自体をTextureAsset::Register()してやれば良かったですが、現在はTextureAssetDataはstd::unique_pointer<TextureAssetData>で宣言してやって、登録の際はstd::move()の形で渡してやる必要があります。書いていて分かりづらいのでRyoさんが書いてくれたこちらご覧いただければと思います。多分「一旦TextureAsset管轄にすると以降は他から触れなくなる」というのを実現しているのだと思います。

    キー、マウス関係

    キーが押されていることを判定するのはInput::KeySpace.pressed プロパティから KeySpace.down() へとメソッド化しました。Input要らなくなったよ!

    マウスがクリックされたかどうかを以前はInput::MouseL.clicked プロパティで見ていましたが、現在はキーボードと同じInput型で扱われるためMouseL.down()をクリックした瞬間の検知に用います。
    clickedは廃止されたようです。

    数学定数

    円周率などの数学の定数に関しては、以前はHalfPiと書けば1/2πが使えましたが、今はMath::HalfPiと名前空間が必要になりました。

    スクリーンキャプチャ

    画像を使いたい前のフレームでリクエストをしてから取得をするという操作の基本は同じですが

    リクエストはScreenCapture::Request() から ScreenCapture::RequestCurrentFrame() に名前が変わりました。

    画像取得はScreenCapture::GetFrame()で変更ありません。

    描画関係の関数

    Font.drawCenter() が廃止されました。drawCenter(x, y)とdrawCenter(y)がいますが、それぞれfont.drawAt(x, y)、font.draw(arg::topCenter = Point(x, y))で代用です。基準位置を文字の上側かつ左右中央で揃えます。

    ちなみにfontの文字サイズ指定は旧Siv3Dの1.5倍くらいにしないと小さくなります。ただ、これはどこか他にオプションがあるかもしれません。

    その他

    playMulti() は playOneShot()へ名前を変えました。

    そんなこんなで、割とVisualStudioの赤線を見て修正すればそれで大体できちゃうと言った感じでした。
    名前の変更などは結構ありましたが本質的な部分は踏襲されている物が多く移行しやすい印象です。
    まあ長らく更新をするようなアプリケーションというのはゲームよりもツールのほうが多そうなので本記事がどれくらい役に立つかは少々疑問ではありますが……。
    Siv3Dは他にも機能がたくさんあるのでこれ以外にも移植で修正しなければならない点は出るかとは思いますが、気づいたり指摘されたりしたら追記していきます。

  • Siv3Dを使ってシリアル通信

    Siv3D Advent Calender 2021 8日目の記事です
    ※HPのリニューアルに伴い当時の記事を移植したものです

    昔ほど見なくなった気がするシリアル通信をSiv3Dで行います

    シリアル通信とは

    PCと他のデバイスとの間で行う通信のひとつです
    実際にはPC以外のもの同士で通信することもあります

    ここでのシリアル通信とは、一般的にRS-232C通信と呼ばれる通信を指します
    PCと何らかの機器をシリアルポートで接続して通信するのですが、シリアルポートは最近のPCにはあまり付いていません

    D-Sub9ピンポート

    ですがシリアル通信が絶滅したのかというとそうではなくて、物理的なシリアルポートを使わずにUSBの通信をシリアルポートでの通信に見立てるICが出回っており、そのICを媒介してシリアル通信を行う機器が生き残っています

    Arduino(アルデュイーノ)と呼ばれる、電子回路やマイコンのプログラミングの勉強などに便利なデバイスがあり、比較的馴染み深く有名でしょう

    また、分析測定を行う機器についてもシリアル通信を行う機能を持った機器は多く存在します
    例えば科学実験などに使用する電子天秤は単体で使用できますが、PCと繋いでPC側から測定や風袋引きの命令を送ることもできるようになっているものがあります
    複雑で大量なデータをやり取りするような機器では純粋なUSBと専用のドライバが使われる場合も多いですが、電子天秤のように簡単なもので、なおかつ通信が必須ではない場面においてはシリアル通信が比較的採用されていると思われます
    いわゆる実験室で過ごされている方はこれを利用することで測定などの効率化が図れるかもしれません

    ただし、機器の内部設定値を書き換えるコマンドを搭載しているような機器もあるかもしれないので、説明書はよく読んで取り掛かるほうが良いでしょう

    通信する

    では通信を行っていきましょう
    本例では通信先のデバイスとして「Writer509CDC」を使います
    これはPICと呼ばれるマイコン(IC)にプログラムを書き込むための機器で10年くらい前に重宝された電子工作物です

    Writer509CDC

    ピンポイントで「このWriter509CDCこそ自分が通信させたかった機器だ」と思っている人は一人もいないと思います
    他の機器で置き換えてお考えください、そのための記事です

    仕様を確認する

    さて、通信するにあたり、どのようなコードを送ればよいかは通信先の機器に完全に依存します
    つまりIntellisenseなどのコード補完でどうにかなる部分ではないということです
    正直に仕様書や説明書を読みましょう
    下記は「Writer509CDC」のベースになっている「Writer509」の仕様です

    仕様

    最初に見るべきは太枠の部分、通信の形式です
    シリアル通信ではいくらか通信仕様にバリエーションがあります

    • 通信速度(ビットレート/ボーレート)
    • データ長
    • パリティ(エラー検知用のデータ)
    • ストップビット

    これを通信先に合わせたプログラムを作らなければなりません

    Writer509CDCの場合、設定がプロトコルの説明用ファイルに書いてあるため、これに従います
    他の機器でも説明書のシリアル通信をさせるための節に必ず書いてあるはずです

    仕様に合わせたプログラムを書く

    シリアル通信はUSBと同じように複数のデバイスがPCに接続される場合があり得ます
    しかし「このデバイスと通信して!」というプログラムを書くわけではありません
    そういう仕組みはないからです

    方法としてはこちらから特定のポートに所望のデバイス用のコマンドを送ってみて、うまく結果が返ってくるかどうかで判断をします
    間違ったデバイスにコマンドを送ると結果が返ってきません
    まずないでしょうが、最悪の場合はデバイスA用のコマンドが同時に繋いでいるデバイスBの全設定消去コマンドと共通で、うっかり消去してしまうという事故が起こるかもしれません
    こうした経緯から大抵の場合は人が一覧から接続するポートを選ぶようになっています

    プログラムの流れは次のようなものになります

    1. このPCが扱えるシリアルポートを列挙する
    2. 通信するポートを選択する(これは人間に選択してもらう必要があります)
    3. ポートを開く(通信を開始する)
    4. コマンドやデータを送受信する
    5. 通信を終了する

    プログラム

    ではプログラムを書いていきます

    C++とSiv3Dで記述していきます
    シリアル通信もサポートしているのでこうした簡単な通信プログラムが作れるのは重宝します
    測定機器との通信を一部自動化させるようなソフト用途には便利なのではないでしょうか

    なお、ここではフロー制御と呼ばれる操作は行いません
    フロー制御とは、データ通信用の他にも信号線を使って、接続した相手がデータを受信できるかどうか様子をうかがいながら通信し、もし準備ができていない場合には通信を一時中断するものです
    フロー制御を行うべきかどうかはデバイスに依存します
    Writer509(CDC)ではフロー制御用の信号線を全く使っていない(常に通信OKとして扱う)仕様になっており
    特に制御を必要としないため無視します

    ここからはデバイスに応じたデータを送受信します
    試しにWriter509(CDC)のVersionコマンドを扱ってみます

    Versionコマンド

    アプリケーションのコードを以下に示します(Siv3D 0.6.3)
    長いのでクリックまたはタップで全て見られます

    
    # include <Siv3D.hpp> // OpenSiv3D v0.6.3
    
    const int BAUD_RATE = 38400;
    
    void Main()
    {
      Scene::SetBackground(Palette::Gray);
    
      //シリアルポートの列挙 (ラジオボタンの選択で使うnone付き)
      const Array array_serial_info = System::EnumerateSerialPorts();
      const Array array_serial_names = array_serial_info.map([](const SerialPortInfo& info)
      {
        return U"{} ({})"_fmt(info.port, info.description);
      }) << U"none";
    
    
      Serial serial;
      size_t radio_index = (array_serial_names.size() - 1);
      char c_buf[256];
      size_t sz;
    
      while (System::Update())
      {
        // シリアルポートを列挙するラジオボタンが押されたときの処理
        if (SimpleGUI::RadioButtons(radio_index, array_serial_names, Vec2(200, 300)))
        {
          ClearPrint();
    
          //noneを選択したらシリアルオブジェクトをクリア
          if (radio_index == (array_serial_names.size() - 1))
          {
            serial = Serial();
          }
          else
          {
            Print << U"Open {}"_fmt(array_serial_info[radio_index].port);
    
            // シリアルポートをオープン
            if (serial.open(array_serial_info[radio_index].port,
                    BAUD_RATE,
                    s3d::Serial::ByteSize::EightBits,
                    s3d::Serial::Parity::None_,
                    s3d::Serial::StopBits::One))
            {
              Print << U"Succeeded";
            }
            else
            {
              Print << U"Failed";
            }
          }
        }
    
    
        // 通信を実行するボタン
        if (SimpleGUI::Button(U"Get Version Info", Vec2{ 440, 40 }))
        {
          Array bytes;
          if (serial.isOpen())
          {
            String err;
            try {
              serial.clear();
    
              err = U"コマンド00 00の1バイト目の送信で問題が発生しました: ";
              if (not serial.writeByte(0x00))
              {
                throw(err + U"writeByte");
              }
              System::Sleep(10);
              bytes = serial.readBytes();
              if (bytes.size() != 1)
              {
                throw(err + U"read length is not 1");
              }
              if (bytes[0] != 0)
              {
                throw(err + U"read data is not 0x00");
              }
    
              err = U"コマンド00 00の2バイト目の送信で問題が発生しました: ";
              if (not serial.writeByte(0x00))
              {
                throw(err + U"writeByte");
              }
              System::Sleep(200);
              sz = serial.read((void *)c_buf, 1);
              if (sz != 1)
              {
                throw(err + U"response is not 1 byte");
              }
              sz = serial.read((void*)c_buf, 13);
              if (sz != 13)
              {
                throw(err + U"version string is not 13 byte");
              }
              c_buf[13] = '\0';
    
              Print << Unicode::WidenAscii(c_buf);
            }
            catch (String e)
            {
              Print << U"エラー:" << e;
            }
          }
        }
    
      }
    }
          

    ポートの取得はSystem::EnumerateSerialPorts()で行い、これでSerialPortInfoのArrayを取得できます
    この情報さえあれば通信はできますが、ポートが複数ある場合は少々厄介です
    先述したとおり極力間違ったデバイスにコマンドを送りたくはないのですが、デバイスは名乗ってくれません
    それゆえ正しいポートを人間様に選んでもらうことになります
    多少なりとも見分けをつけやすくするためにそれぞれのポートに関する名称も知っておきたいですね
    これはSerialPortInfoのportとdescriptionにそれぞれ格納されています

    ポートを開くにはs3d::Serial::open()を使います
    プログラム上ではラジオボタンをクリックした時にポートを開いています
    列挙したSerialPortInfoの中から接続したいポートの情報が入っているものを引数に指定し、同時に通信速度等の条件も引数に入れてやります
    ボーレート(通信速度)以外はs3d::Serial名前空間で指定します
    本例では示されていませんがフロー制御の利用もここで指定します

    ポートを開くのはあくまで今後の通信のための準備であり、この時点でポートにデバイスが接続されている必要はありません
    また、デバイスの通信条件とSerial.open()で指定する条件が食い違ってもポートを開くことについては支障が出ません
    なのでここは大抵の場合は成功します
    他のアプリケーションでポートを開いているときは失敗するので、Teratermなどのソフトと挙動を比べながら開発する場合は留意してください

    データを送信する場合はs3d::Serial::write()またはSerial::writeByte()を使います
    データを受信する場合はs3d::Serial::read()またはSerial::readBytes()です
    readやwriteのやり取りはデバイスに依存しますが、本記事では先に挙げたWrite509(CDC)のバージョンを取得するため以下のような流れになります

    1. 0x00を送信
    2. 1バイト受信、0x00が返されることを確認
    3. 0x00を送信
    4. 1バイト受信、0x00が返されることを確認
    5. 13バイト受信、“W509 Ver. X.XX”形式の文字列かどうかを確認

    Serial::readBytes()はデバイスから送信されてきたデータをすべて取得してきてくれます
    しかし今回の例では3.で0x00を送信するとデバイスは4.と5.の結果を間髪入れず送ってくるため、Serial::readBytesを使う場合は一気に14バイト受信する形になります
    本例では14バイト一括で取得してもあまり不都合はありませんが、Serial::readを使って1バイトと13バイトの2回に分けて受信しています

    なお、Writer509の仕様を見ると5.の際に200ms程度かけてすべて読んでやることが推奨されています
    これはつまりコマンド送信後に待たずにすぐデータを受信しようとするとデータが欠落する可能性があるということです
    s3d::Sleep()を使って待ってやりましょう
    このあたりの処理は別スレッドにやらせたほうが良いかもしれません

    コードを実行するとデバイスへのコマンド送信が正常に行われ、デバイスのバージョン文字列が取得できました

    機器から返ってきたバージョン文字列を表示している(左上の文字)

  • イージングを使おう

    ※ この記事は2020年の記事をリニューアルサイトに移転したものです。

    イージングの模式図

    お久しぶりの方はお久しぶり、はじめましての方ははじめまして。肩書は考え中、ニシャスでございます。
    ゲーム作ってますか? 私は作ろうと思いながら多忙で2019年を越してしまい、10月頃に労組から開放されたものの、この2、3ヶ月は大して作らずに遊んでしまっていたことを白状します。

    この記事はSiv3D Advent Calender2020の21日目の記事です。

    12/21は冬至なんですよね。かぼちゃ食べるのを忘れないように。間違えても冬至のかぼちゃを忘れてハロウィンを祝っちゃあいけない。
    まあアップしたの12/22になっちゃいましたけど

    さて、ゲームを作るとき、例えばタイトルやスコアなんかを登場させるときなどにアニメーションを使うことありますよね!
    一番簡単なのは、フレームごとに

            if(pos.x < 400)
            {
              pos.x += 4;
            }
        

    とかやる方法です。こうすると止まってほしい位置までは同じ速度で動き続けて所望の位置でピタッと止まる動作になるわけなのですが、
    この方法だとどうしてもなんかこう、チープな感じになってしまいます。

    もちろんチープな感じを出すのもゲームの味の一つだと思います(これは本気で思っています)が、少しかっこよく見せようと思っているあなたには、「イージング」をお勧めします。

    イージング

    実世界においては、「フレームごとに3ピクセル進む」ような単純な等速直線運動はあんまりないらしくて、何かしら緩急がついているのが「自然」みたいです。

    ただ個人的な感覚としては「自然か不自然か」というよりは「かっこいいかダサいか」の2通りの方が表現としてはより近いです。

    こういう「心地よく感じる条件」って突き詰めようとすると楽しいですよね。

    イージングではありませんが、自然界にあるものを数学的な法則にあてはめたものの一例に「フィボナッチ数列」というのがあるのをご存知でしょうか。

    「1,1」から始まって、2,3,5,8……と、直前2項の和が連なっている数列、というやつなのですが、自然界によく見られるのだそうです。デザインを美しくするための比率などと言われう「黄金比」もこの数列と関係があるそうです。

    若干話がそれましたが緩急のある動きというのも似たようなものなのではないかと思います。小気味よいとか美しいとか、そんな印象を持つようにできているんじゃないでしょうかね。人間って生き物は。

    Siv3Dでイージングを使う

    さて、動画編集ソフトやWebページのアニメーションでも使われるイージング、Siv3Dで使ってみましょうじゃありませんか。

    Siv3Dではイージング用の関数が用意されています。

    簡単に書いてしまうとEasehogehoge(t)という形で、時間t(0.0~1.0)における値(始点0.0、終点1.0)を返してくれます。 実際にEasehogehogeという関数があるわけではなくて、hogehogeの部分が七変化します。イージングにもかなり色々種類がありますので詳しくはeasings.netでも見ていただければと思います。

    実際には”EaseInOutCubic”みたいな名前で打ち込むことになります。

    ちなみにSiv3Dのイージング関数は引数tを0.0~1.0の間で使うのですが、1.0を超えた数を与えても1.0として解釈されるようなので多少乱暴に扱えるのも嬉しいです。

    実例

    とはいっても、実際イージングでどんな事ができるのかわからないと使いたい気持ちにもならないと思いますので、実例をご紹介。

    1. 矢印のエンドレスアニメーション

    エレベーターの液晶で見たやつ。2個の矢印をEaseInOutCubicで動かしています。

    止まっているように見えるのはEaseInOutCubicの最初と最後の部分は変化量が非常に少ないからです。

    Shape2D::Arrow(Vec2(400, 300), Vec2(400, 100), 90, Vec2(120, 90)).asPolygon().moveBy(Vec2(0, EaseInOutCubic((double)(Scene::FrameCount() % 120) / 120.0) * -600 + 82)).draw(Palette::Black);
    Shape2D::Arrow(Vec2(400, 300), Vec2(400, 100), 90, Vec2(120, 90)).asPolygon().moveBy(Vec2(0, EaseInOutCubic((double)(Scene::FrameCount() % 120) / 120.0) * -600 + 600 + 82)).draw(Palette::Black);
        

    2.なんとなくかっこいいゲームのメニュー表示

    メニューがすすすすっと現れるやつです。

    前述したとおりイージング関数のtは1より大きくても1として扱ってくれるので、Stopwatchの経過秒数をそのまま食わせることができます。

    Stopwatch sw[5];
    for (int i = 0; i < 5; i++)
    {
      if(t == 0)sw[i].reset();
      else if(t > 0.03 * i && !sw[i].isStarted())sw[i].start();
    }
    
    font(U"EASY MODE").drawAt(EaseOutExpo(sw[0].sF()) * 800 - 400, 100, Palette::Black);
    font(U"HARD MODE").drawAt(EaseOutExpo(sw[1].sF()) * -800 + 1200, 180, Palette::Black);
    font(U"BAKE A BREAD").drawAt(EaseOutExpo(sw[2].sF()) * 800 - 400, 260, Palette::Black);
    font(U"OPTIONS").drawAt(EaseOutExpo(sw[3].sF()) * -800 + 1200, 340, Palette::Black);
    font(U"EXIT").drawAt(EaseOutExpo(sw[4].sF()) * 800 - 400, 420, Palette::Black);
        

    ゲームオーバー

    イージング関数にバウンドがあるんですね。EaseInBounce、EaseOutBounce、EaseInOutBounceがありますがEaseOutBounce以外はまず出番ないと思います。

    tは0.0~2.0で変化するようにさせて、下記コードで「前半でバウンド、後半で静止」を実現しています。

    tex_gameover.drawAt(Vec2(400, EaseOutBounce(t) * 400 - 100));
    

    ハイスコア更新

    EaseOutBackを使うと「勢い余ってちょっと行き過ぎちゃったけど戻るぜ!」ってエフェクトも簡単に実装できます。もうちょっとうまい表現がありそうな気もしてます。

    tは0.0~4.0で動かしてます。

    newrecord.drawAt(Vec2(EaseOutBack(t)*800-400, 300), ColorF(1.0, (t>=1.0) ? 1.0 : t));
    

    結論

    みんなもイージングで気持ちのいいアニメーションを作ろう!

    あと日頃からなにか作っていたほうが書く側としては気持ちよく書けただろうなあと思いました反省

  • OpenSiv3D用のチートシートを作った

    ※この記事は2019年の末くらいの記事なのでだいぶ古いです。サイトリニューアルしましたが公開日は当時の日付を想像で付けています。

    経緯

    今年もSiv3D Advent Calenderにご招待いただいてありがたいと思いつつ、「お主は年賀状との両立で地獄を見るであろう」と心の奥の預言者に叫ばれていた二シャスですが。

    何も作っていないまず今年は何も作っていない

    テーーーーーットゥトゥットゥットゥトゥピラピラ~(適当な鼻歌を歌いながら暗転)

    まあそういうわけで何で記事を作ろうかなと思った矢先にですね、
    今までニシャス居住区を置かしていただいたレンタルサーバーさんが

    「あっ、12月でサービス終了するからね。ごめんね!」などと言ってきたわけでございます。

    こうして私はかねてから気になっていたドメイン取得をやってのけて”nicious.net”なんて自分以外誰も取らなさそうなやつを買って、サーバーも有料のやつを契約したのでございます。

    http://nicious.toypark.inはもうダメなんですね。

    待てよ、ってことは昨年までの私の記事全部飛んでるってことだね。なんということだ

    そんなわけで色々と怪しい状態でSiv3Dアドベントカレンダーを書くことになって色々とうにょうにょしていたのですが要するに、あと1時間すると自分の担当日が終わってしまうんですね。
    本題からしてダウンロードボタンをポンッと置いて終了でいいよねと思っていたのですが

    Siv3D に関する 400字~程度の記事を書いていきます。Siv3D Advent Calender 2019

    あ゛あ゛ーーッ!!

    400字書かねばならないのだ。大人の事情ッッ!

    いい加減にしなさい

    はい乱文乱筆大変失礼いたしました。400字程度がちょうど稼げたのではないでしょうか。

    今回ご用意いたしましたのはOpenSiv3Dを時々使う人にありがちな

    「これどんなコード書けば動くんだっけ!? 忘れた!」

    ってのを補助するべく私が作った早見表的なやつです。

    正直な話、ネットで調べれば済むのですが、「Alt+Tabでウィンドウ切り替えてAlt+Dで検索ワード打ち込んで上の方のやつクリックしてスクロールする」って時間かかるよねって思います。あとブラウザの誘惑ステータスが強すぎる。

    ニシャスさんは(情報量の豊富さはともかく)開発環境でF1を押すだけ(またはそこから打ち込み)というウルトラ爆速のWin32ヘルプの速さを知ってしまっていて、ブラウザを使う時点でもうアレに勝てないと思っているんですね。

    で、そんな中でこういう早見表の類は引き出しから召喚することができるわけで、それすら凌ぐ強さを持っているのです。引き出しをガラガラ引くと出てくるんですよ!

    ということで自分が一番使いそうだなぁって思って作っています。まあ机は綺麗にしようぜ。

    で、ご注意としては

    • めちゃくちゃ基本的なのは多少省いている(忘れないから)
    • 個人的にあまり使わなそうなのは省いている(紙面の都合上)
    • そもそもまず完成していない(風邪で寝込んでしまったんだ許してくれ)

    というものがありますので用法用量を守って正しくお使いください。ダウンロード

    2020/01/06更新版
    Mouse::Pos()が間違いなのと、その他誤字の修正

    あ……あと5分じゃないか

    たまにネットプリントを使って全国のセブンイレブンでも印刷できるようにしたりします。

    本当は完成させて胸を張りたかったけど空白が多いし間違ったコードとか入っているかもしれないので印刷は自己責任で。