アズマオオズアリの頭部とキーボードを模したアイコンとjonnityという文字

Sketch Matchの紹介: 作成中のゲームの現状

個人開発 ゲーム SketchMatch

2024/3/17


このサイトを作った主目的の一つ、個人開発で作っているものの進捗報告をします。 ブログでブログの話しかしていない状態だったので、ようやく意義が生じますね。

今回のテーマは、お絵描き + 神経衰弱のパーティーゲーム、Sketch Matchについてです。 一応、一通り遊べるようにはなってるので、動画を撮ってみました (一人でやってます)。現状はこんな感じ!

コンセプト

Gartic Phoneとかピクトセンスみたいな、みんなで絵を書くゲームはなんだかんだ盛り上がるので、その辺の枠に滑り込もう! というのが最初の取っ掛かりだったと思います。

その上で、なんやかんやあって神経衰弱を組み合わせました。 (ほぼ同じコンセプトのゲームがWeb上に存在していたようなのですが、現状遊べない状態にあるようなので気にしないことにしています。)

基本的な流れは↓のようになっています。

  1. 各プレイヤーは表示されたお題の絵を描く
    • このとき、各お題はそれぞれ、2人のユーザーに出されてる (同じお題で描かれた絵が2つある状態になる)
  2. みんなが描いた絵で神経衰弱をする
  3. 正しいペア (同じお題で描かれた絵) を選べたらそのプレイヤーは1点獲得
  4. ペアのうちどちらが上手かを投票を決め、絵が選ばれたプレイヤーは1点獲得

神経衰弱なので、全部の絵が表になるまで繰り返します。 同じお題でも全然違う絵になったりして、神経衰弱パートで「絵は覚えてるのにわかんない」みたいになったら面白いだろ!!なあ!!!という気持ちです。

技術要素

技術スタック

フロントエンドの構成要素は、↓のような感じです。

  • Next.js (Page Router)
    • SSG機能 (next export) を利用
    • Github ActionsでビルドしてFirebase Hostingにデプロイできるようにしてある
  • Typescript
  • CSS Modules

バックエンドは全部Firebaseに頼っていて、↓のサービスを利用しています。

  • Firebase Realtime Database
  • Cloud Functions for Firebase
  • Cloud Storage for Firebase
  • Firebase Hosting

DB部分は、RealtimeDBの他にCloud Firestoreも選択肢にありましたが、リアルタイムに各プレイヤーの接続状況 (プレゼンス) を把握したいがためにRealtimeDBを使っています。 FirestoreにはOnDisconnectに相当するものがないんですよね。 ただ、データ更新の監視はどちらでも可能、かつ、OnDisconnectが不発になるときがあるので、プレゼンスのみRealtimeDBで管理するというのもありなのかもしれないです (OnDisconnectの設定後に他のReference (DBの要素) いじるとそのハンドラが消える?とかだった気がする。ともかくそんなに使いやすくはない)。 ドキュメントにある通りFirestoreのほうが新しくて、クエリや、そもそものDBとしてのパフォーマンスはいいらしいので。

基本的な仕組み

基本的には、各プレイヤーでRealtimeDBを監視しておいて、「誰かの操作によってDB更新→それをトリガーに画面更新」を繰り返すことでゲームを実現しています。

ただ、特定の誰かの操作をトリガーにできない処理、例えば「全員が絵を書き終えたときに神経衰弱の盤面を作る処理」とかがあるので、そういうところでCloud Functionが必要になります。 その場合でも、Cloud Functionからクライアントが変更を監視しているRealtimeDBを操作することでバックエンド→フロントエンドで情報伝達をしており、 フロント側でDBを監視する部分がキモになっています。

「firebase SDK→zodでバリデーション→jotaiで状態管理」というのを基本にしています。 例えば、一緒に遊ぶプレイヤーたちは、ゲームの進行状況 (今が「絵を描いている」ところなのか、「神経衰弱をしている」ところなのか) を共有する必要がありますが、その情報は以下のようなカスタムフックでDBの情報を取得しています。

1import { useCallback, useEffect, useState } from "react";
2import { onValue, ref, update } from "firebase/database";
3
4import { dbPaths, RoomState, RoomStateSchema } from "@domain/RoomInfo";
5import { useDatabase } from "@hooks/atoms/useDatabase";
6import { useJoinedRoomId } from "@hooks/atoms/useRoomId";
7
8export function useRoomState() {
9  const db = useDatabase();
10  const roomId = useJoinedRoomId();
11  const [state, setState] = useState<RoomState>("loading");
12  useEffect(() => {
13    const stateRef = ref(db, `${roomId}/${dbPaths.state}`);
14    const unsubscribe = onValue(stateRef, (data) => {
15      try {
16        const fetchedState = RoomStateSchema.parse(data.val());
17        setState(fetchedState);
18      } catch (e) {
19        console.error(e);
20        setState("error");
21      }
22      return () => {
23        unsubscribe();
24      };
25    });
26  }, [roomId]);
27  return state;
28}
29
30export function useUpdateRoomState() {
31  const db = useDatabase();
32  const roomId = useJoinedRoomId();
33  return useCallback(
34    async (state: RoomState) => {
35      const roomRef = ref(db, roomId);
36      update(roomRef, { state });
37    },
38    [roomId]
39  );
40}

他に管理している情報としては、一緒に遊ぶプレイヤー (ルーム内のプレイヤー) の情報 (名前などもですが、神経衰弱をする中で今誰のターンか、なども) はもちろん、 ルーム内のカードの情報や、(神経衰弱でペアが揃ったあとの) 投票状況など色々あります。

ここが色々ありすぎてわけ分からなくなりつつあることも、モチベ低下に繋がっているので、うまい実装ができるといいんですが、よくわからない…。

現状と今後について (まとめ)

「一応遊べるようになっているものの、悪意に無防備すぎるのでもう少しちゃんとしなければと思ったままやる気が消えている」というのが現状です。 セキュリティ的にはFirebaseに乗っかってるので概ね大丈夫なのですが、悪意が私の財布に向いた瞬間にピンチになるなと思っています。 公開すればある程度しょうがないのですが、今はルームに無制限にプレイヤーが参加できてしまったりするので…。

あとはルール説明など、細かい部分がいくつか作れてません (いったん雑なテキストを置いてたりする)。 この辺のやる気ってどうしたら出るんですか?クスリ?? 広告が出せて、人気が出たらお金がもらえるかもと思えればまた違うんですかね。

お金で言うと、なんか毎月Googleに1円を払っています (Hosting/Functionでちょっとストレージが必要なので、そのお金なはず)。

計画と1円にムカついてきたので、取り急ぎ公開できるようなものにできるよう、早急に作業を進めようと思います。 公開できたときには、ぜひ友達と遊んでみて、感想を教えてください。友達がいなければ私にお声がけください。