Instantly share code, notes, and snippets.
Last active
December 27, 2025 12:37
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save sthairno/764bc519672ffafbf71da290d62f19b6 to your computer and use it in GitHub Desktop.
Siv3D_MessageBusの活用例:マルチプレイヤー対応クッキークリッカー
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <Siv3D.hpp> | |
| #include <MessageBus/MessageBus.hpp> | |
| // クリック地点に簡単な波紋を出すエフェクト | |
| struct ClickEffect : IEffect | |
| { | |
| Vec2 m_pos; | |
| double m_duration; | |
| explicit ClickEffect(const Vec2& pos) | |
| : m_pos{ pos } | |
| , m_duration{ 0.5 } { | |
| } | |
| bool update(double t) override | |
| { | |
| const double scale = EaseOutCubic(t / m_duration) * 50; | |
| const double alpha = 1.0 - EaseOutSine(t / m_duration); | |
| Circle{ m_pos, scale }.draw(ColorF{ 1, alpha }); | |
| // 継続するなら true(Effect が保持する) | |
| return (t < m_duration); | |
| } | |
| }; | |
| // IDから識別用の色を作る | |
| ColorF ClientIdToColorF(int64 clientId) | |
| { | |
| return HSV{ clientId * 360 / Largest<int32>, 0.5, 1 }; | |
| } | |
| void Main() | |
| { | |
| // このクライアントのランダムなID | |
| const int clientId = RandomClosed(0, Largest<int32>); | |
| // メッセージ送受信 | |
| MessageBus::MessageBus bus; | |
| bus.subscribe(U"cursor:move"); | |
| bus.subscribe(U"cursor:click"); | |
| auto point = bus.variable(U"point", 0); | |
| // 接続UI | |
| bool initialized = false; | |
| TextEditState hostInputState{ U"127.0.0.1" }; | |
| // 他クライアントのカーソル位置 | |
| HashTable<int, Point> otherCursorPositions; | |
| // 最後に送ったカーソル座標 | |
| Point lastSentCursorPos{ -1000, -1000 }; | |
| // エフェクト管理 | |
| Effect effect; | |
| // 他クライアント表示用のカーソル画像 | |
| Image cursorImage{ Icon{Icon::Type::MaterialDesign, 0xF01C0}, 48 }; | |
| Texture cursorTexture{ cursorImage }; | |
| // 色付きカーソル設定 | |
| Image myCursorImage{ cursorImage.size() }; | |
| cursorImage.stamp(myCursorImage, 0, 0, ClientIdToColorF(clientId)); | |
| Cursor::RegisterCustomCursorStyle(U"my_cursor", myCursorImage); | |
| // クッキークリッカー | |
| Font font{ 30 }; | |
| Texture cookieTexture{ U"🍪"_emoji }; | |
| while (System::Update()) | |
| { | |
| // 事前に作成したカーソルを反映 | |
| Cursor::RequestStyle(U"my_cursor"); | |
| // MessageBusの更新処理 | |
| bus.update(); | |
| // 接続 UI の表示 | |
| if (not initialized) | |
| { | |
| SimpleGUI::TextBox(hostInputState, Vec2{ 10, 10 }, 180, unspecified); | |
| if (SimpleGUI::Button(U"Connect", Vec2{ 195, 10 }, 100)) | |
| { | |
| bus.connect(hostInputState.text, 6379); | |
| initialized = true; | |
| } | |
| } | |
| // 受信:届いたイベントを反映 | |
| for (const auto& event : bus.events()) | |
| { | |
| if (event.channel == U"cursor:move") | |
| { | |
| const int id = event.value[U"id"].get<int>(); | |
| const int32 x = event.value[U"x"].get<int>(); | |
| const int32 y = event.value[U"y"].get<int>(); | |
| // 自分のイベントは無視 | |
| if (id == clientId) | |
| { | |
| continue; | |
| } | |
| Point pos{ x, y }; | |
| otherCursorPositions[id] = pos; | |
| Print << U"イベント受信: cursor:move\nid={}, pos={}"_fmt(id, pos); | |
| } | |
| else if (event.channel == U"cursor:click") | |
| { | |
| const int id = event.value[U"id"].get<int>(); | |
| const int32 x = event.value[U"x"].get<int>(); | |
| const int32 y = event.value[U"y"].get<int>(); | |
| // 自分のイベントは無視 | |
| if (id == clientId) | |
| { | |
| continue; | |
| } | |
| Point pos{ x, y }; | |
| effect.add<ClickEffect>(pos); | |
| Print << U"イベント受信: cursor:click\nid={}, pos={}"_fmt(id, pos); | |
| } | |
| } | |
| // 送信:自分の操作を配信 | |
| const Point myCursorPos = Cursor::Pos(); | |
| // 送信頻度を下げるため、ウィンドウ内でカーソルが動いたときだけcursor:moveを送る | |
| if (initialized && | |
| myCursorPos.distanceFrom(lastSentCursorPos) > 20 && | |
| Scene::Rect().contains(myCursorPos)) | |
| { | |
| bus.emit(U"cursor:move", JSON{ | |
| {U"id", clientId}, | |
| {U"x", myCursorPos.x}, | |
| {U"y", myCursorPos.y} | |
| }); | |
| Print << U"イベント送信: cursor:move\nid={}, pos={}"_fmt(clientId, myCursorPos); | |
| lastSentCursorPos = myCursorPos; | |
| } | |
| // 左クリック | |
| Circle cookieHitArea{ Scene::CenterF(), 60 }; | |
| if (initialized && MouseL.down()) | |
| { | |
| // クリックのエフェクト表示 | |
| effect.add<ClickEffect>(myCursorPos); | |
| // cursor:clickイベントを送る | |
| bus.emit(U"cursor:click", JSON{ | |
| {U"id", clientId}, | |
| {U"x", myCursorPos.x}, | |
| {U"y", myCursorPos.y} | |
| }); | |
| Print << U"イベント送信: cursor:click\nid={}, pos={}"_fmt(clientId, myCursorPos); | |
| // クッキーの当たり判定 | |
| if (cookieHitArea.mouseOver()) | |
| { | |
| point.set(point.get() + 1); | |
| } | |
| } | |
| // 描画 | |
| // クッキー | |
| cookieTexture.drawAt(Scene::CenterF()); | |
| // ポイント | |
| font(U"{}"_fmt(point.get())) | |
| .draw(Arg::bottomCenter = (Scene::CenterF() + Vec2{ 0, -60 })); | |
| // クリックエフェクト | |
| effect.update(); | |
| // 他クライアントのカーソル | |
| for (const auto& [id, pos] : otherCursorPositions) | |
| { | |
| cursorTexture.draw(pos, ClientIdToColorF(id)); | |
| } | |
| } | |
| bus.shutdown(); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Recording 📹
video.mp4