0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(きっと挫折する)ひとりアドカレAdvent Calendar 2024

Day 8

FF14の地図の記録を残すためのツールを作りかけた話

Last updated at Posted at 2024-12-08

休日なので、オンラインゲームのプレイ記録を残すためのツールをつくります。

全部つくることはできないと思うため、今回はアイデア出し、設計、ツールのUI部分の作成となります。

その後の工程実施と記事にするかどうかは未定です。

アイデア

オンラインゲームFF14のコンテンツである「トレジャーハント」(通称、地図)の記録を残すツールをつくる

残したい記録とは?

  • 魔紋が開いたか(地上で宝箱を開けた後、ランダムで宝物庫への扉である魔紋が開く)
    →開いたか開いていないかのどちらか
  • ドロップしたアイテム(特に、パーティメンバーでダイス勝負して、いちばん大きな目を出した人が獲得するロット品を記録したい)
    →アイテム名(アイテムは限られている)
  • 宝箱の座標(マップエリアと詳細位置)
    →エリア名と詳細座標(座標の選択肢は限られている)
  • 地図の種類
    →1から17まで、もしくは、S1からS3まで
  • 実地日時
    →日時

要件は?

  • データベースに記録を残し、後で解析、図表化できるようにしたい
  • お手軽に入力フォームを生成したいので、Webページで入力したい
  • 各項目はできるだけ選択肢から選べるようにする
  • フォーム入力中は自動保存したい
  • ネット経由でどこからでも入力できるようにしたいが、アクセスは自分だけ

実現方法をあれこれ考える

  • ネット経由で入力したいけど、セキュリティを厳密に考えるのはめんどくさい
  • 自動保存はデータベースへ書きこむのではなく、Webブラウザのストレージでいいかな
  • Webページの作成は、ある程度知識があるVue.jsでいいか
  • Nuxtでもいいけど、複数ページがあるわけではないから、Vue.jsでお手軽にしよう
  • 選択肢を選ぶのと、新しい選択肢を追加するのはシームレスにしたい
  • アクセス制限の設定が簡単だった気がするし、Cloudflareにデプロイでいいかな
  • となると、データベースはD1でいいか?
  • でもそうなると、フロントエンドとサーバーサイドを両方とも開発する必要があるのか、めんどくさいな
  • めんどくさいから、開発を二段階にわけるか
  • 一段階目は、Webページから入力、テキストデータ(JSON)で出力する
  • 二段階目で、サーバーサイドを経由して、データベースに出力する
  • つまり、一段階目の開発では、ローカルでHTMLファイルをひとつつくればいいんだな、これは楽だ
  • 選択肢があるから、選択肢のJSONデータを入力できるようにしておけばいいか
  • ゼロ段階目が、選択肢のJSONデータを入力して、フォームを生成することだな
  • ファイルひとつなら、別のパソコンで入力したい際も、コードを管理しているGitHubからダウンロードすればいいから楽だろう
  • 一段階目の出力データはひとまずGitHubでソースコードと一緒に保存するようにしよう
  • 選択肢のJSONデータもツールから出力できればいいけど、まだデータ構造が固まってないから、手戻りもありそうだし、ひとまず妥協して、JSONデータは手で書こう

開発の方向性の結論

開発を三段階にわける

  • ゼロ段階目は、選択肢を記載したJSONデータを入力とし、ツールであるWebフォームを出力する仕組みの作成(フロントエンド)

  • 一段階目は、トレジャーハント実施時にその記録を随時入力し、JSONデータとして出力する仕組みの作成(フロントエンド)

  • 二段階目は、JSONデータを入力し、データベースへ出力する仕組みの作成(サーバーサイド)

フロントエンドはCDNのVue.jsで構築する

ゼロ段階目の仕組みを作成してから、一段階目の仕組みを作成する順番でコツコツと作成し、必要に応じて段階を戻って修正していく。

ファイル選択・保存ではなく、フォームへテキストデータをコピペする形式、フォームからテキストデータをコピーする形式でお手軽にする(ファイルそのものは扱わない。やるなら後で)。

サーバーサイドは未定

APIを簡単に構築できること、フロントエンドとの統合管理が簡単になる方法を採用する予定。

デプロイ先の有力候補がCloudflareだから(使用経験があるため)、Cloudflare Workersか何かが使えるのではないかと考えている(根拠のない妄想)。

ひとまずベースを準備する

Vue.jsの公式ドキュメント「CDN の Vue を使用する」より、ベースのHTMLファイルを作成する。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>トレジャーハント・ログ・ツール</title>
</head>

<body>
    <div id="app">{{ message }}</div>
</body>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
    const { createApp, ref } = Vue

    createApp({
        setup() {
            const message = ref('Hello vue!')
            return {
                message
            }
        }
    }).mount('#app')
</script>

</html>

ここから始めます。

今日はここまで

半日かかって、ここまでできました。

image.png

見た目を整えるのは、後で悩むことにしました。
まだ、手戻りがあるでしょうし。

内容を簡単に説明します。

機能

自動保存

Webブラウザのローカルストレージに入力データをJSON形式で定期的に自動保存するようにしました。
ブラウザをリロードした際は、ローカルストレージからデータをロードして表示します。

このローカルストレージを明示的に消去するボタンを設置しました。

フォームの一括消去

一から新しく入力したい時用に、消去ボタンを設置しました。

データ入力欄

トレジャーハントのパーティでは複数枚の地図を開くため、各地図情報を入力できるようにしました。
「地図追加」ボタンを押すと、入力欄が追加されます。

「Open/Close」ボタンを押すと、入力欄を閉じたり開いたりできるようにし、入力しやすいようにしました。

また、「Delete」ボタンはそれぞれの地図データセットを消すボタンです。
ですが、完全にデータを消去するのではなく、消去フラグを立てるだけにして、後で復活させられるようにしました。

出力欄

さしあたっては、データをJSON形式でコピーできるようにしました。
「Generate」ボタンを押すと、現時点での入力データが右側の欄へ表示され、コピーできます。

技術的な話

自動保存のローカルストレージ

ローカルストレージを使用したため、(プライベートモードでの表示でなければ)データは期限なしに保持されます。
そのため、ローカルストレージのデータを明示的に消去する機能を設けました。

保存するときの形式は、単純にアプリで扱っているデータをごそっとJSON.stringify()しました。
巨大なデータではないため、そのままJSON文字列化して、データのロード/セーブを簡単にしています。

内部的なデータ構造

選択肢などフォームを構築するための情報を以下のように保持しています。

const maps = ref([17, 15, 14, 12, 10])
const areas = ref({
    1700: "オルコ・パチャ",
    1701: "コザマル・カ",
    1702: "ヤクテル樹海",
    1703: "シャーローニ荒野",
    1704: "ヘリテージファウンド",
    1500: "エルピス",
    1400: "ラヴィリンソス",
    1401: "サベネア島",
    1402: "ガレマルド",
    1403: "嘆きの海",
    1404: "ウルティマ・トゥーレ",
});

さらに、入力データは以下の配列にオブジェクトを追加していきます。

const logs = ref([
    {
        open: true,
        delete: false,
        number: 1,
        map: 17,
        area: 1700,
        position: 1,
        portal: false,
        itemsAbove: [],
    }
]);

これらを組み合わせて、入力フォームを生成します。

<div v-for="perMap in logs" :key="perMap.number" class="perMap">
    <h2>{{perMap.number}}枚目の地図 </h2>
    /* 略 */
</div>

これが一番外側のfor文です。

この内部では、mapsareasを表示しながら、v-modelperMapのデータをバインディングしていきます。

入力欄の開閉

入力欄の開閉はv-showを使用しています。

フォームのname

inputタグにはnameを記載していますが、このnameには何番目の地図であるかを付与し、地図毎に一意のデータとして扱われるようにしています。
これを忘れると、すべての地図を通してデータがひとつとして扱われてしまいます。
最初、ここに気づかず、少し悩みました。

別記事書いていて思ったのですが、v-modelを書いたなら、name属性は不要かもしれません。後ほど検証。

<div class="positions">
    <h3>Position</h3>
    <template v-for="area in Object.keys(areas)">
        <div v-if="String(area) === String(perMap.area)">
            <label v-for="position in positions[area] ">
                <input type="radio" :name="'position'+perMap.number" :value="position"
                    v-model="perMap.position">
                <img style="width:150px; height:120px; background-color:red;">
            </label>
        </div>
    </template>
</div>

終わりに

目標としていたUI部分までは大体できましたが、結局ゼロ段階目は作成していません。
ひとまず目に見える部分である一段階目を構築しました。

最初から計画倒れですが、ひとまず適切なデータ構造は思いつけたような気がします。

ここから、

  • ゼロ段階目に必要なデータを収集する
  • ゼロ段階目のデータを入力し、フォームを生成する仕組みを作成する

と、ゼロ段階目を実行するのがよさそうです。

JSONテキスト形式で出力はできるため、二段階目は後回しでもよいと思っています。

どちらかというと、今回は考慮していない、宝物庫(ダンジョン)に入ってからの記録もできるようにしたいところです。

完成するといいですね。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?