Coupling Monsterを使ってSimutransアドオンを作ろう【Simutrans Advent Calendar 2023】

雑記

この記事はSimutrans Advent Calendar 2023 12/01の記事です。

今年もこの季節がやってまいりました。半分寝ながらアドカレの枠を抑えたんですが、後で見たらうっかりトップバッターになってました、M_Kasumiと申します。

さて、突然ですが、Simutransの鉄道車両アドオンを作る際に書かなければならない、「datファイル」。その中でも特に書くのが面倒な項目に、「連結設定」があります。

車両同士の整合性が取れなくなってエラーになったり、意図した編成が組めないアドオンが出来上がってしまったり……。時には、検証が不十分だったのか、そのような状態のままリリースされているアドオンに出くわすことさえあります。

とはいえ、そんなアドオンをリリースしてしまった作者を責め立てることは、私にはできません。同じくアドオンを作る者として、連結設定のやりにくさ、わかりにくさは痛いほどよくわかるからです。

ということで、「連結設定」に潜む問題や面倒事を解消し、視覚的にわかりやすく編集できるWebアプリ「Coupling Monster」を製作いたしましたので、ここでこのツールの製作に至る過程、つまり要件定義から設計・実装、そして完成まで、徹底的にレポートしてみたいと思います。

アプリ製作過程のことなんか興味ない!アプリが使えればそれでいいんだ!という方は、「付録. Coupling Monsterの使い方」(次頁) まで飛ばしていただければと思います。

なお、私自身、比較的有用なものからしょうもないものまで含めて、様々なアプリを世に送り出しては来ましたが、このような文書を書くのは初めてなので、お見苦しい点が多々あるかと思われます。どうか寛大な目で読んでいただけると幸いです。また、なるべく技術的な話は避け、専門外の方が読んでもなんとなく理解はできる程度の文章を心がけますので、何卒最後までお付き合いいただければと思います。

なお、本稿の章立ては、業務等で開発を行う際の工程に一部倣ったものとなっていますが、本アプリは個人で適当に作ったものですので、実際にはここまで厳密にはやっていません。(なんなら要件定義のあたりから実装も同時並行でちょいちょいやっていましたし……。)

まあ、この辺りは、実際にこのようにしっかり工程を分けて作業したというよりも、記事として分かりやすくするために情報整理した結果こういう章立てになった、と思っていただければ幸いです。

また、アプリケーション制作においては、さまざまな手法や考え方が存在します。本稿で紹介するのはあくまでも一例で、他の手法を否定する意図はまったくありません。

  1. Coupling Monster へのリンク
  2. 前提
    1. 連結設定とは
    2. なぜ連結設定は面倒なのか
  3. 事前準備
    1. 用語定義
    2. アプリの名前
    3. テーマカラー
  4. 要件定義
    1. 目的
    2. 構成
    3. 機能要件 (メイン機能のみ)
      1. datファイル読み込み機能
      2. datファイル書き出し機能
      3. 画像ファイル読み込み機能
      4. 日本語化ファイル読み込み機能
      5. 日本語化ファイル書き出し機能
      6. 総合読み込み機能
      7. 連結設定機能
      8. 連結プレビュー機能
    4. その他
  5. 設計
    1. 画面構成
    2. オブジェクト設計
  6. 実装
    1. 心得
    2. 使用するライブラリ等
      1. Ligature Symbols
      2. natsort
    3. 機能
      1. 機能実装概要
        1. datの読み込み (オブジェクト化)
        2. 画像の読み込み
        3. 日本語化ファイルの読み込み
        4. 連結設定
      2. 追加実装したもの
        1. 編成画像撮影機能
        2. プロパティ編集機能
        3. コスト計算機能
    4. UI
    5. 個人的に初めて触れた技術
      1. Promiseを用いた非同期処理
      2. Canvasを用いた画像の透過処理
      3. CSSネスティング
  7. その他の作業
    1. UI用画像の作成
    2. アプリアイコンの作成
    3. マニュアルの作成
  8. リリース
  9. 課題と今後
    1. 画面サイズの問題
      1. 結論・展望
    2. フォルダ読み込みの問題
      1. 結論・展望
    3. 他Pakセット対応
      1. 結論・展望
    4. そもそも:「Webアプリ」という形態について
      1. File System Access APIを使う
      2. ネイティブアプリとする (Webアプリは諦める)
      3. ユーザ側でローカルサーバを立ててもらい、そのローカルサーバにファイル操作の役割を負わせる
      4. 結論・展望
  10. 総括
  11. 参考:実装に利用したソフトウェア・ツール・サービス等

Coupling Monster へのリンク

現時点ではPak128系統のPakセットのみ対応しています。

コスト計算はPak128Japan公式準拠の計算式のみ対応しています。

前提

連結設定とは

序文でも少し触れましたが、本章ではSimutransの連結設定の仕様について、詳しく見ていきましょう。

まず、そもそも連結設定は、車両単位に指定されます。連結設定に関連するプロパティとして、1つの車両に対して、「ConstraintPrev」(前の車両) および「ConstraintNext」(次の車両) という2つを指定することが可能です。

さて、「Constraint」を辞書で引くと、ざっくり「制約」とかそんな感じの意味が載っていますね。つまり、連結設定とは、連結できる車両の制約を設けるためのパラメータであると言い換えることができます。

Prev, Nextともに、複数の車両を指定することが可能ですので、クハの後ろにクハが連結されるのを防止する、モハユニットを分割不可能にする、など、車両同士の関係に「制約」を設けることが可能です。

ただし、これは記述が非常に面倒くさいのです。

なぜ連結設定は面倒なのか

連結設定は車両名を記述することで指定しますが、まずは単純に、車両を文字で指定するという作業そのものが面倒です。連結設定は車両名を文字で記述することで指定するのですが、連結先の車両名をコピーして正しい位置にペーストして……という作業を繰り返していると、途中でわけがわからなくなってきます。

実際の連結設定の記述。この程度の量ならまだよいが、実際にはより複雑なことも多い

また、連結設定には前後それぞれ0番から順番に番号を振る必要もあります。つまり、連結設定の数が違う車両だと、同じような連結設定を追加したくても単純なコピーアンドペーストでは正しく追加することができず、作業ミスを発生させる原因となっています。

このほか、関係する車両同士で記述の整合性を取らなければならないというルールも、面倒さに拍車をかけています。

例えば、Aという車両のNext欄にBという車両が書かれたとすると、Bという車両のPrev欄にはAが書かれている必要があります。PrevとNextで相互に対応がとれていないといけないわけです。どちらか片方が欠けていると、Simutransゲーム上で連結ができなくなります。これはPrevとNextを逆にしても同じことです。

PrevとNextの対応をとらなければならないということは、1個の連結設定を追加するたびに2ヶ所の修正が発生するということです。

これが1両や2両ならよいのですが、たとえば実車の形態差を網羅する目的で大量の車両が存在する場合などは、連結設定も複雑になります。これをいちいち手で書いていたのでは、ミスをするなという方が酷というものです。

しかし、これらは全て、「量が多いだけの単純作業」です。このような「量が多いだけの単純作業」は、人間にとってはミスを誘発する種でしかありませんが、コンピュータにとっては逆に得意分野です。

これはもうアプリを作らない手はないですね。ということで、次章からは実際にアプリを作っていきます。

事前準備

それでは実際にアプリを作っていきましょう。とはいっても、いきなりコードを書くわけではありません。まずは基礎の基礎の基礎、形から入ります。

用語定義

用語を厳密に定義することは非常に重要です。

人間の使う言語、いわゆる自然言語は非常に曖昧で、同じ言葉でも受け取り方によって意味が変化してしまうことは往々にしてあります。

一方でアプリ開発においては (本当はどんな仕事でもそうですが)、単語に対して一意の解釈または値が割り当てられていることが求められます。これを数学用語でWell-Definedと言ったりします。

そのため、作業の前には必ず用語の意味をしっかりと定義し、作業にかかわる全員で認識を共有しておくのです。もちろん、多人数での開発になればなるほど用語定義が重要になりますが、本アプリのように完全個人開発の場合でも厳密に定義しておいて損はありません。意外と人間 (私) は適当な生き物なので、放っておくとすぐに適当な言葉を使ってしまいます。

実際には登場する単語ほとんど全ての定義を決定する必要がありますが、本章では簡単な一例として、本稿を読む上で必要となりそうな部分だけを紹介します。本稿および本アプリで以下の用語が出てきた場合、以下の定義の通りの意味となります。

用語定義
アドオン読み込まれたdatファイルに記述されている、本アプリで編集対象とするデータのこと。
車両datファイルに記述された内容のうち、「obj=vehicle」である部分のこと。datファイル上では「—」(3つ以上の半角ハイフン)で区切られ、区切られた1区画ごとに1両の車両である。
プロパティ車両の特徴などを定める項目。速度や定員などの値がある。連結設定もプロパティの一つである。
連結設定datファイル上で「ConstraintPrev」または「ConstraintNext」で表されるプロパティ。車両ごとに指定される。
読み込みPC上に存在するファイルを、アプリ内に取り込んで利用できる状態にすること。
日本語化ファイルSimutransの車両名は基本的に英語でのみ記述されるが、それをローカライズするために使用されるファイルのこと。「車両名」と「日本語名」をセットで記述することで指定する。なお、日本語化ファイルは一般的に「ja.tab」とも呼称されるため、スペース上の問題などで「ja.tab」表記を用いることも許容する。

上記の表のように定義したのなら、たとえば「アプリにファイルを取り込む」という操作について言及するときは、「ファイルを追加」といった表現ではなく「ファイルを読み込み」と言わなければなりません。このように厳密な言葉遣いをすることが、後々の作業で混乱を避けることに繋がります。

この他、ここでは取り上げませんが、プログラムに実際に記述する変数名の命名規則なども決定します。

アプリの名前

本アプリの名前を決めていきましょう。

名前なんて後でいいだろ、と思われるかもしれませんが、世の中、どんなアプリやシステムでも、必ず名前が付いています。私も初めてシステムエンジニアとして現場に出た時、小さな機能だけの単純なシステムにすらしっかりと名前が付けられていて驚いたことを覚えています。

このアプリにも、親しみやすく覚えやすく、他と被らず、なにより簡潔に特徴を表現した名前を付けたいと考えました。

というわけで、「連結」を意味する「Coupling」と、「カップリング」という単語から連想した以下のツイートを元に、「Coupling Monster」という名称に決めました。

機械が適当な文章を生成する有名なTwitter bot「しゅうまい君」によるツイートです。

かなりどうでもいい余談

アプリ製作当時、私の周りで早朝からTwitterでカプ語りをするやつが発生しており、2年前のこのツイートが掘り起こされて俄かに話題になっていたのです。掘り起こしたの私なんですけどね。

元のツイートの文言が「妖怪」なので、内輪では本アプリのことも「妖怪」などと呼ぶことがあります。

由来は酷いですが、「Coupling Monster」、なかなかいい名前だとは思いませんか?

テーマカラー

本アプリのテーマカラーは、紫としました。これは、私の主催するNSにおいて、私が会社のカラーとして伝統的に紫を使ってきたことが由来です。

また、紫は冠位十二階でもっとも高い位「大徳」に与えられた色であるなど、古来から特別な扱いを受けてきた色でもあります。(後付け)

Monsterという名称と相まって、結果的に非常にマッチした色だったのではないでしょうか。

要件定義

さて、名前とテーマカラーも決まったことですし、いよいよ本アプリに期待される要件を明確にします。とはいえ、仕事でやるような本格的な要件定義ではなく、ざっくりした適当なものですので、皆さんも適当に読んでください。(本来はもっとしっかり定義する必要があります)

目的

Simutransの鉄道車両アドオン製作におけるdatの編集作業、特に連結設定について、視覚的にわかりやすいUIを与え、アドオン製作者の生産性に寄与することを目的とします。

構成

本アプリの形態は、ブラウザ上で動作するWebアプリとします。Webアプリは、ユーザであるアドオン製作者が、インストールなどの操作を行う必要がなく、すぐにアプリを使用できることが利点です。(この選択に至った思考過程の一部は課題の章でも後述します。)

動作環境はPCのGoogle Chromeとし、最新版のGoogle Chromeに本格的に採用されている機能はすべて使用可能とします。画面はFHD (1920×1080) 以上、ブラウザは最大化した状態を想定します。アドオン製作など、画像のかかわる作業を多く行う人は、最低限FHDの画面を使っているだろうと勝手に推定しました。(ただし、これに関しては、思ったより小さい画面を使っている方も多かったようで、ちょっと失敗したなあと思っています。課題の章で後述します。)

アプリは1ページ内で完結するもの、今風の言い方で言えばSPA (シングルページアプリケーション) とし、アプリ内でのダイアログ表示などは行いますが、別のページに遷移するような動作は基本的に行わないものとします。

また、サーバとの通信は行わず、クライアント側のみで全てが完結することとします。

機能要件 (メイン機能のみ)

datファイル読み込み機能

まずはともかく、アドオンの情報が記述されたdatファイルを読み込むことができ、ブラウザ上で編集可能である必要があります。

なお、datファイルの読み込み方法は、指定領域へのドラッグアンドドロップとします。

datファイル書き出し機能

ブラウザ上で編集したアドオン情報を、再度Simutransのアドオン向けのdatファイルとして書き出し、コンピュータ上に保存することができる必要があります。

画像ファイル読み込み機能

datファイルから指定されている画像ファイルを読み込むことで、車両ごとにどのような画像が指定されているかを視覚的に確認しやすくします。

なお、画像ファイルの拡張子はpngを扱うものとし、読み込み方法は、指定領域へのドラッグアンドドロップとします。

また、本アプリでは画像の編集は行わないため、画像の書き出し機能は設けません。

日本語化ファイル読み込み機能

datファイルに記述されている車両の日本語化ファイルを読み込むことで、車両名を日本語でも確認できるようにします。

なお、日本語化ファイルの拡張子はtabファイルを扱うものとし、読み込み方法は、指定領域へのドラッグアンドドロップとします。

また、読み込んだ日本語化ファイルは編集可能であるものとし、編集された名称は即座に画面上に反映されることとします。

なお、日本語化ファイルは一般的に「ja.tab」とも呼称されるため、スペース上の問題などで「ja.tab」表記を用いる場合があります。

日本語化ファイル書き出し機能

ブラウザ上で編集した日本語化情報を、再度Simutrans向けの日本語化ファイルとして書き出し、コンピュータ上に保存することができる必要があります。

総合読み込み機能

利用者の便宜のため、前述のdatファイル・画像ファイル・日本語化ファイルの3種類を含む複数のファイルをまとめてドラッグアンドドロップすることで、それぞれの種類のファイルをまとめて読み込むことができるものとします。

連結設定機能

本アプリのメインの機能です。

編集したい車両を選択すると、その車両に設定されているPrevおよびNextの連結設定を読み込み、車両名とその車両に指定されている画像を一覧表示できる必要があります。

また、編集中の車両の連結設定に追加または削除の操作をする際には、編集中の車両側のNextプロパティと記述されている車両側のPrevプロパティを同時に、あるいはその逆を同時に、追加または削除できる必要があります。

また、それぞれの車両のそれぞれの方向の連結設定プロパティ内には、同じ車両は1度のみ登場するものとします。

連結プレビュー機能

本アプリで指定した連結設定が正しく動作するかをアプリ上で確認できる必要があります。本アプリ上でSimutransの車庫での動作を可能な限り再現するものとします。

その他

  • 少なくともメイン機能はマニュアルなしでもなんとなく使えること
  • クリックできる部分とクリックできない部分の違いを明確にすること

設計

要件を満たすアプリを実際に作っていきます。

画面構成

この段階で、大まかな画面の構成は決めてしまいました。

まず、基本となるメイン画面。

実際、ほぼこの通りの形で実装しています。

メイン機能以外の要素として、各操作ボタンを画面上部に固定表示することとしたほか、必要に応じて下段の車両一覧は折り畳みができるようにします。

また、メイン以外の機能や画面は、メイン画面の上にダイアログ形式で表示するものとします。

オブジェクト設計

「オブジェクト指向プログラミング」という言葉があります。これは、ざっくり言うと、「プログラムによって処理されるデータは究極的には全て『もの』(=オブジェクト) である」という考え方に基づいてプログラミングを行う手法です。現代で主流のプログラミング言語の多くはこの考え方を使って開発を行うことができます。

今回、本アプリの開発でもこの考え方を用います。

以下、本アプリでどのようにこの考え方を利用したのかを説明しますが、あくまでも本アプリの実装に使用する言語であるJavaScriptのオブジェクトについての説明になります。JavaScriptでは「すべてがオブジェクト」であり、他の言語と比較するとオブジェクトに対しての考え方が非常に柔軟になっています。今回の解説は「オブジェクト指向」についての一般論ではなく、あくまで本アプリを実装する上での話として読んでいただければと思います。

また、本来はオブジェクト指向において重要な要素となる「カプセル化」などの要素は完全にスルーし、本アプリの設計だけを理解できるような説明のみにとどめます。ご容赦ください。

さて、「オブジェクト」はいくつもの「プロパティ」を持ちます。鉄道車両を例とすると、「最高速度」が「110km/h」、「定員」が「136名」……このような「キー」と「値」のセットが「プロパティ」です。

分からない方は、「オブジェクト」はすなわち「2列の表」だと思ってください。表の1行1行が「プロパティ」で、表全体が「オブジェクト」になります。また、表の中に表があるといった、入れ子になっているオブジェクトを作ることもできます。

オブジェクト 概念図

オブジェクト指向を取り入れた開発を行うためにはまず、どのようなプロパティを持ったオブジェクトを作るのか、つまりどんな「表」を作るのか、最初に定義しておく必要があるのです。

本アプリでは、datファイルから、車両を1両ごとに1つの「車両オブジェクト」を作って読み込むことにします。

「車両オブジェクト」のプロパティですが、datファイルに記述された「name」「payload」などのパラメータの1つ1つが、基本的にはそのまま「車両オブジェクト」のプロパティとなります。パラメータの種類は日本語化wikiの記事が詳しいです。本アプリの開発でもこちらを参照しながら設計し、最終的に以下のようなオブジェクトを定義しました。

基本的には全ての値を「文字列」として格納するシンプルなオブジェクトとしていますが、例外的にいくつかの値に特殊なオブジェクトを格納しています。

まず、本機能のメインとなる連結設定のプロパティ「ConstraintNext」および「ConstraintPrev」については、「Set」という特殊な値とします。詳細は割愛しますが、簡単に言うと「何個でも値を持つことができるが同じ値は1つしか持てない」というデータです。

ちなみに、Setの中身は「車両オブジェクトへの参照」とします。

「オブジェクトへの参照」とは、簡単に言うと「オブジェクトそのもの」ではなく「オブジェクトの住所」を格納するということです。こうすることで、車両の中身が変わっても、連結設定として指定されている車両は変わりません。たとえば従来のdat編集では、車両名を変更したら連結設定で記載されている車両名も変更しなければなりませんが、本アプリ内では連結設定に「オブジェクトの住所」を指定していますので、書き直す必要はありません。名前が変わっても「オブジェクトの住所」は変わらないためです。

datで連結設定を指定する際には車両名を書きますが、本質的には「車両そのもの」を指定しているわけですから、「参照」を格納することができるのであれば、そちらの方が本質的な実装であると言えます。

もう一つの例外として、画像指定のプロパティは「EmptyImage[方角]」で表されますが、これは「方角」をキーにした新しいオブジェクトにしてしまいます。車両オブジェクトの中に画像指定のオブジェクトが入るような構造になります。

このほか、人間のわかる言語で「オブジェクト」を指定するための方法として、「name」は必須のプロパティとします。また、車両のアドオンを扱う本アプリの特性上、「obj」は固定値「vehicle」に限るものとします。

以上が、今回扱うオブジェクトの、めちゃくちゃざっくりとした設計となります。(何度も言うようですが、本来はもっとしっかり設計する必要があります)

実装

いよいよ設計をもとにプログラムを書いていきます。

なお、本来ならこの後にテスト工程が入りますが、テストは作りながら同時並行でやったので、章には立てていません。まあこの辺は個人製作アプリなのでだいぶガバガバです。

心得

実装時には、自分の過去の経験から、いくつか意識的に気を付けたことがあります。

まず、後から修正する場合の事を考慮した書き方をすることです。

これは一般的に言われることですが、私の場合は拙作の別アプリ「Pak128Japan Train Dat Maker」(こちらはコスト計算にのみ特化したアプリです) を作った際の反省も大きいです。こちらのアプリではあまりにも適当な書き方をしていたため、後から修正を行うたびにどんどんメチャクチャなコードになってしまいました。建物で喩えるなら、後先考えず増築を繰り返した結果とんでもない違法建築ができちゃったイメージですね。

今回はそのようなことにならないよう意識しながらコードを書いていきます。具体的には、各コンポーネント同士が「疎結合」という状態であるように心がけることになります。「疎結合」をざっくり説明すると、「ある機能があったとして、その機能を他のソフトウェアに移植したくなったら、コピペするだけでOK」な状態です。ちなみに対義語は「密結合」です。先ほどの建物の喩えを続けると、中銀カプセルタワービルとかがこれに近いですかね?

現実問題、本当の意味で完全な疎結合を実現する方法は存在しませんが、この状態を理想とすることには意味があります。

また、機能とUIを分離するよう心掛けます。引き続き建物で喩えると、装飾品と断熱材の部材をきっちり区別する、といったところでしょうか。装飾品に断熱効果を持たせてしまうと、断熱性能を上げたくなった時に装飾を変える羽目になりますよね。断熱材だけ交換できる構造の方がメンテナンス性が高いと言えます。

これはGUIのアプリケーションを製作する上での鉄則ですが、うっかりしているとついUI側に機能を持たせるような書き方をしてしまいます。しかも今回のような個人のプロジェクトでは指摘してくれる人もいませんしね。

使用するライブラリ等

アイコンはLigature Symbols、ソートにはnatsortを使用しました。

これ以外のライブラリは使っていません。あとはUI部分も含めて自作です。これは、作る過程を楽しみたかったという理由もありますが、ライブラリの使い方や仕様に慣れるまでの時間で1から書けてしまうものがほとんどだからです。

Ligature Symbols

Ligature、つまり「合字」を利用してアイコンを表示するフォントセットです。

「合字」とは、たとえば、「ff」のお互いの「f」の横棒同士を連結させて一本の棒のようにしたり、「ae」を「æ」のように表示したりするものです。

本来はこのような目的で使用される「合字」の機能を利用して、たとえば「file」という文字列だったら「ファイルのアイコン」が表示されるように作られたフォントが「Ligature Symbols」です。2013年に更新が止まってしまっているのですが、本アプリ程度の規模の開発であれば今でも十分使用可能で使いやすいため、本アプリではこちらのフォントを採用しました。

natsort

自然順ソートを実現するライブラリです。

例えば、「資料1」「資料2」「資料3」…「資料10」という10個のファイルがあるとして、これらを名前順に並び替えた時、ユーザが期待するのは当然、1から10までの数字の順に並んでいることだと思います。しかし、機械的にソートをしてしまうと、「資料1」「資料10」「資料2」…という不自然極まりない並び順になってしまいます。これを解消し、「資料1」「資料2」「資料3」…「資料10」の順に並べるためのライブラリです。

これを一から実装するのはかなり面倒なアルゴリズム (詳細は省きます) を書く必要があったため、既存のライブラリを利用させていただきました。

機能

機能実装概要

基本的には要件通りのものを粛々と実装していきます。ですが、いちいち全ての実装を説明していたら本当にキリがないことになるので、ここでは主要機能の大まかな処理の流れと仕様を説明します。

datの読み込み (オブジェクト化)

ユーザ操作によって読み込まれたdatファイルの内容は「文字列」ですが、これを「車両オブジェクト」として解釈し直さなければなりません。

基本的には、datファイルの内容をそのまま上から順番に「車両オブジェクト」として解釈していきます。datファイルの記述ルールとして、「3つ以上のハイフン」で車両同士を区切ることになっていますから、その通りのルールでファイルを分割し、その分割された一部分一部分を1つの「車両オブジェクト」とします。

オブジェクト設計の章で述べた通り、nameプロパティは一意である必要があることから、オブジェクト化時にはこのチェックも実施します。

また、こちらもオブジェクト設計の章で述べた通りですが、いくつか特殊な値として格納するプロパティもあります。そちらもその通りの形式に変換します。

注意すべき点として、ConstraintプロパティのSetには「車両オブジェクト」を入れることになっていますが、今はdatを読み込んで「車両オブジェクト」を作る作業の最中ですから、datファイル側の連結設定で車両名が指定されていても、アプリ側で対応する「車両オブジェクト」がまだ作られていない可能性もあります。

そのため、一旦「仮のConstraint」を作って車両の名前を格納しておき、datファイル内の情報を全て「車両オブジェクト」として解釈し終わったら、「仮のConstraint」の車両名情報をもとに、改めて「本当のConstraint」に「車両オブジェクト」を割り当てる、という一手間を加えています。

画像の読み込み

「画像ファイル一覧のオブジェクト」を作り、読み込んだpngファイルを1枚ずつ、「ファイル名」をキー・画像そのものを値として格納します。オブジェクトの値には、文字列だけでなく、画像も入れることができるのです。

こうすることで、アプリ上でファイル名から画像を取得できるようになりますし、「画像ファイル一覧のオブジェクト」を見れば、どんな画像が読み込まれているのかを確認することができます。

日本語化ファイルの読み込み

こちらも、「日本語化辞書オブジェクト」を作りますが、ここで作るオブジェクトは少々特殊で、オブジェクトをキーにすることができるMapというものを使用します。オブジェクトをキーにすることができるというのはわかりにくいかもしれませんが、生体認証で開く金庫みたいなのをイメージしてください。人間オブジェクトがキーで金庫の中身が値ですね。

「日本語化辞書オブジェクト」は、「車両オブジェクト」をキーにして、日本語名を値として格納します。こうすることで、車両オブジェクトから日本語名を取得できるようになります。

連結設定

基本的には、Simutransの仕様をそのままJavaScriptで記述するような形になります。Setのおかげで重複を許容しない処理を書く手間が省けて助かります。

追加実装したもの

基本的には要件通りのものを実装していったのですが、中には途中で思い立って追加実装した機能もありますので紹介します。

編成画像撮影機能

「連結プレビュー機能」で連結のプレビューができるようになった状態で、私の主催するNetSimutrans (AhozuraNS) のメンバーに軽く共有したところ、せっかくなら編成を組んでいる状態の画像を出力できるようにしてみてはどうか、という提案をいただきました。確かに、編成画像を撮れるようになれば、アドオンを公開する際のサムネイル作成などに役立つ場合もあるかもしれません。

連結プレビュー機能

Webアプリ上で画像をいじる場合、「Canvas」というものを使用します。しかし、私はこのCanvasを使い慣れておらず、思いのほか苦戦することとなりました。

編成画像撮影機能

最終的に、ちゃんと撮影できるようになりました。背景透過画像としてダウンロードまたはコピーすることが可能です。

プロパティ編集機能

datファイルを読み込んでオブジェクト化できるようになったので、せっかくならとプロパティを追加・編集できるようにしました。

プロパティ編集機能

このほか、定型句が存在するプロパティについては、入力候補を表示する機能も実装しました。

入力候補表示機能

ちなみに、入力候補を無視して手動で適当な値を入れることも可能です。その結果makeobjに通るかどうかは別ですが……。本アプリではそこまでのバリデーションチェックは行いません。

コスト計算機能

Simutransもゲームですから、鉄道車両のコストについては各Pakセットごとに計算式が用意されていると思います。

本アプリでは、「連結プレビュー機能」で編成が組めるようになりましたし、コスト計算に必要となる各種プロパティも「車両オブジェクト」のプロパティとして読み込んでいますから、計算することが可能なはずです。

本アプリでは、Pak128Japanの計算式に基づいた計算をできるようにしました。

コスト計算画面

追加情報として実車の起動加速度を入力すると、これらの数値に基づいて、Cost (購入費), RunningCost (運行費), gear (ギア比) の各値を計算・設定します。

他Pakセットへの対応については、課題の章にて後述します。

UI

UIの実装では、とにかく自分で使ってみては修正する、という作業を繰り返し、利便性を高めていきました。また、自分で何度も使っているとどうしても慣れが生じてしまうことから、身内の人間にも実際に使用してもらって使用感のフィードバックを受けるなどもしました。

ダイアログなどのUI部品は、以前から趣味で製作していた「編成表マネージャ」のものをそっくりそのまま流用しました。こちらも自分で実装したものです。

このほかUIの実装では、今年かなり話題になった生成AIのChatGPTを、私の人生で初めて開発に取り入れてみました。

ChatGPTにCSSを書くのをお手伝いしてもらう様子。実際には、さらに追加で指示を出したり、自分で手直ししたりしてから使用する。

こんな感じで、CSS記述の一部をお手伝いしてもらいました。無課金なのでGPT3しか使えませんから、実のところそんなに精度は良くないですし、ChatGPTが書いてきたコードを書き直す必要も生じますが、それでも、全部を1から書くよりはだいぶ楽にCSSを書くことができました。

個人的に初めて触れた技術

本アプリを製作するにあたって、個人的に初めて触れた技術がいくつかありますので簡単に紹介します。

Promiseを用いた非同期処理

本アプリではファイルの入出力や画像の生成など、非同期で処理することが望ましい場面がそれなりに存在します。

他にも書き方はあるのですが、今回はまとまった量の非同期処理があるということで、Promiseを利用しました。複数の非同期処理を同時に行う場合など、他の書き方だと読みにくいコードになってしまったり、面倒だったりすることも多いのですが、Promiseを使うと書きやすくていいですね。

Canvasを用いた画像の透過処理

「指定した色を消しといて」と自然言語で言えば一言なのですが、Canvasは意外と融通が利かないので、どうやらデフォルト状態ではこのような処理は用意されていないようです。なので、「与えられた画像を左上から右下まで1pxごとに見ていって、指定された色が見つかったら透明度を0にする」……みたいな処理をひたすら自分で書いていく必要があるんですね。普段全然Canvasを使わないので初めて知りました。

CSSネスティング

素のCSSも、最近ではネスト表記に対応していたんですね。初めて知りました。

2023年8月頃から全てのブラウザで対応しているということなので、さっそく使って書いてみました。書きやすくて大変良かったです。また、メンテナンス性の面でも優れていそうです。今後も積極的に取り入れていこうと思います。

その他の作業

HTMLやJavaScriptやCSSを書く以外に発生した作業も少量ながらありますので、ご紹介します。

UI用画像の作成

本アプリでは、主要部には既存のアイコンセットを利用したため、そこまで多くの画像を作る必要はなかったのですが、どうしても一部足りない画像が出てきたため、その部分に関しては自作しました。

具体的には「画像なし」状態を示すアイコンなどが該当します。

「画像がないですよ」を表す画像

アプリアイコンの作成

ブラウザのタブなどに表示されるアイコンを作ります。必須ではありませんが、たくさんタブを表示している場合など、本アプリが目立つ方がよいため、本アプリでは専用のアイコンを製作しました。

まず、適当に落書きしながら方向性を決めていきました。

実際に描いて検討していた殴り書き。iPadで適当に描いていた

最終的に、「Coupling」の「C」「P」、連結を表す「チェーンのマーク」、前後それぞれの連結設定を表す「2つの矢印」などの要素を盛り込んだアイコンに決定しました。

草案をもとにイラレで清書し、以下のようなアイコンが完成しました。

結構気に入っています。ちなみにこのアイコンのこと、私は密かにミトコンドリアって呼んでます。(ミトコンドリアみたいなので)

マニュアルの作成

本来であればリリースと同時にマニュアルを作るべきではあるのですが、今の今まで作っていませんでした。よって、本アプリのマニュアルは本稿です。マニュアルは2ページ目をご覧ください。

リリース

GitHubのコミット履歴によると、製作開始が2023年9月18日で、完成が同年10月21日でした。まあなかなかの突貫工事ですね。

ここまで早く完成したのには理由がありまして、それは単純に、自分がこのアプリを必要としていたからです。連結設定が複雑になることが見込まれるアドオンの製作 (正確にはリメイク) を予定していたのです。

とにもかくにも、アプリの製作は一旦一段落して、一般公開といたしました。

完成したアプリの起動画面
アプリを使用して実際に編集を行っている様子

課題と今後

さて、アプリを一般公開し、皆さんにも使っていただいたり、私も実際に利用したりしていく中で、様々な課題も浮き彫りになってきましたので、今後の展望も交えていくつか取り上げたいと思います。

画面サイズの問題

先述の通り、本アプリの推奨ディスプレイ解像度はFHD以上です。これは特に根拠があって設定した値ではなく、私の個人的かつ勝手な推定、というより思い込みに基づいたものでした。

しかし、いざ一般公開してみると、自分が思っていたよりも多くの人が、FHD未満のディスプレイを利用しているようなのです。このような小さなディスプレイでは、表示が崩れてしまうことがあります。

ユーザ側でブラウザの拡大縮小率をいじっていただくことで、一応は表示崩れを起こすことなく利用可能になるのですが、確かにWebアプリとして由々しき問題ではあります。

ただ、各所をFHD前提で作ってしまったということもあり、今更作り直すのもかなり難しくなってしまっているのが実情です。なので、FHD未満のディスプレイをご利用の方には大変申し訳ないのですが、当分はこのような運用で対処していただくことになると思います。

結論・展望

  • 見通しが甘くてごめんなさい。
  • 対応できる見込みはない。
  • 当分はブラウザ側の縮小機能で頑張って対処してほしい。

フォルダ読み込みの問題

現時点でのファイルの読み込み方法は、ファイルをまとめて選択してドラッグアンドドロップするという方法のみです。同じフォルダに入っているファイルなら、いっそフォルダごとドロップできたほうが操作性は向上しますよね。

私もその方が便利だということは重々承知しているので、一応対応しようとはしているのですが、なぜか動かないのです。シンプルに私がJavaScriptを用いたファイル操作周りに明るくないのが原因です。

結論・展望

  • 目下対応中。なんで動かないのかわからない
  • なくても動くためしばらくはこのまま

他Pakセット対応

現在、本アプリは「Pak128」系統のみの対応となっており、さらにコスト計算については128系統の中でも「Pak128Japan」のみの対応となっています。しかし、64系や192系など他のPakセットをご利用の方の中にも、本アプリを使用したいという方はいるかもしれません。

私は最初から他Pakセットに容易に対応できるようなプログラムを書いていますので、対応すること自体は簡単です。

ですが、「その人がどのPakセットを使っているかという情報をどこに保存してどのタイミングで読み込むか」「Pakセットの切り替えはどのように行うか」「関連するUIの実装」などの課題や検討事項があり、それぞれの問題の方針決定ができておらず、実装に踏み込めていないのが実情です。

また、コスト計算については、実装は簡単なのですが (要はコスト計算式を書き換えるだけでよい)、単純に私が他Pakセットのコスト計算式についての知識を持っていないのです。仮に計算式をアプリに組み込んでしまうのではなく、ユーザ側で計算式を書いてもらうような仕組みにするとしても、今度はその計算式を指定するためのUIについて検討する必要があり、こちらについてもなかなか実装に踏み切れていません。

結論・展望

  • 技術的には可能。
  • 実現方式についてさらなる検討を要する。

そもそも:「Webアプリ」という形態について

今までさんざん述べてきた通り、本アプリはブラウザ上で動作するWebアプリです。以下、「Webアプリ」という形態の抱える問題や課題、解決策について考えたいと思います。もちろん、この点に関しては、アプリの製作前から考えていた問題になります。

まずWebアプリの長所としては、要件定義の章でも述べた通り、ユーザがインストールなどの操作を行うことなくアプリを使用可能であるというものがあります。

しかし一方で、Webアプリケーション特有の問題もあります。それは、ファイル入出力に関する制約が非常に大きいということです。とはいえ、何も知らずにアクセスしたWebサイトが勝手にPC上のファイルにアクセスできてしまうと、もちろんセキュリティ上の問題がありますから、ある程度仕方のない部分でもあります。

ただ、本アプリのように、PC上のファイルを扱うアプリでは、この制約が非常に大きな障壁となります。

例えば本アプリでは、PC上のdatやpngを読み込むことを前提としていますが、PC上のファイルに変更があった場合、その変更がアプリ側に自動で読み込まれることはなく、手動でもう一度読み込みの操作を行う必要があります。逆に、アプリで変更を加えたファイルを保存する場合も、同様に都度手動で操作を行う必要があります。PC上のファイルの変更を自動で検知して読み込んだり、Webアプリ上で変更したファイルを自動でPCに保存したりはできないのです。

では、どうするのがよいでしょうか?いろいろ検討してみました。

File System Access APIを使う

利点:PC上のファイルをWebアプリから直接扱うことができる。

欠点:発展途上の技術である。私がこの技術について何も知らない。修正すべき箇所も多い。

はい、今まで述べてきたのは何だったんだよ、という感じですが、実は2022年頃から、WebアプリからPC上のファイルに直接アクセスすることができるようになっているそうです。

だったらこれを使っちゃえばいいじゃん、と思われるかもしれないですが、恥ずかしながら私、実はこの技術の存在をかなり最近まで知りませんでした。そして、そんな状態のままで実装を進めてしまいました。完全に私の無知や勉強不足に起因することではあるのですが、今まで違う方法での実装を進めてしまった以上、どうしてもこの技術を当てはめられそうにない部分も多く、対応工事をするとしてもかなりの難工事になりそうです。例えば、File System Access APIを使うと、ファイルのドラッグアンドドロップ周りは、すべて作り直しになるのではないかと思われます。

そもそもこの技術、現在進行形で発展途上の技術であり、機能も少なめなようです。また、アプリを起動しなおすたびにファイルアクセスの許可を求められるようになるため、ちょっと鬱陶しいかもしれません。

ネイティブアプリとする (Webアプリは諦める)

利点:PC上のファイルをアプリから直接扱うことができる。

欠点:ユーザ側でインストールの作業が必要になる。手法にもよるが、多かれ少なかれ個別の問題がある。最悪の場合、ゼロから作り直しになることもある。

それでは、もうWebアプリはやめちゃえ、という場合のことを考えます。

まず、利点は何と言っても、ファイル操作周りがかなり楽になることです。以上。

欠点は、何度か述べている通り、ユーザがインストール作業を行う必要があることです。そして、インストールするということは、ユーザのPCのストレージを占有します。

ただでさえ、ユーザであるアドオン製作者は、pngを作るための「画像編集ソフト」、datを書くための「テキストエディタ」、そして「makeobj」という、最低でも3種類のソフトウェアを既にインストールして使っているはずです。そこにもってきて、「追加でさらにもう1つのアプリをインストールしてくださいね。但しこのアプリでできることは連結設定だけです」というのは、あまり受け入れられるものではない気がします。

また、一口でネイティブアプリを作ると言っても、様々な方法が考えられます。

まず一つ考えつくのは、「electron」などを用いて、Webアプリとして作ったものをそのままネイティブアプリ化すること。これを使えば、プログラムはほぼそのまま流用してネイティブアプリを作ることができるはずです。(試していないので「はず」としておきます)

ただ、electronを使って作ったアプリって、やたらファイルサイズが大きいうえに、動作もまあまあ重たいんですよね。何度も言う通り、本アプリはそこまでして動かすほどのものでもないと思います。

かといって、他の言語で実装しなおすとなると、当然ゼロからの作り直しとなります。ここまで作っちゃってますから、ゼロから作り直すのは精神的に厳しいです。

ユーザ側でローカルサーバを立ててもらい、そのローカルサーバにファイル操作の役割を負わせる

利点:現行アプリ側の改造が最小限で済む。ユーザ側でインストール作業は発生するが、ファイルサイズも最小限で済む。

欠点:クソめんどくさい操作をユーザに理解してもらい、強制することになる。

ローカルサーバとは、超ざっくり言うと、自分だけがアクセスできるサーバのことです。この案を採用した場合、本アプリは以下のような構成になります。

なんのことはない、「Webアプリがファイルをいじれないのであれば、ファイルをいじることのできる別な存在を立てて、Webアプリはその存在と通信すればいいのだ!」という発想です。ローカルサーバはファイルをWebアプリに渡すだけの役割ですから、先述のネイティブアプリよりも、ファイルサイズが小さく済むはずです。

……ボツですね。この仕組みを作ったとして、いったいどれだけの人が理解して、ちゃんとセットアップしてくれるんでしょう。

結論・展望

  • Webアプリで良い気がする。
  • Webアプリであることに由来する不便な点はしょうがない。
  • 将来的には、File System Access APIを使うことも視野に入れておく。

総括

いかがでしたか?

本アプリは、主要機能の完成をもって一応の正式リリースとしていますが、当然、まだまだ手直しする余地が多分に残されていると思います。

皆さんの目線からも、こうしたほうが使いやすい、とか、こんな機能が欲しい、といったアイデアがありましたら、ぜひ遠慮なく私までお伝えいただければと思います。

ここまでの解説を全てお読みいただいた方には多少お分かりかもしれませんが、ツールを製作する上では面倒なことが非常に多いです。一方で、一度ツールを完成させてしまえば、次回以降は使うだけです。

皆さんも、(Simutransに限らず) 少しでも不便だと思うことがあったら、抱え込んでしまうのではなく、ツールを作って解決してみてはいかがでしょうか?本稿は、そんな行動を起こす方が一人でも増えることを願って執筆いたしました。

2023年もありがとうございました。来年も引き続き、M_KasumiならびにM_Kasumi製ツール・アドオンを、よろしくお願いいたします。

以上でアドカレの内容は一旦終わりとしますが、引き続きアプリの使用方法などを解説していきます。

参考:実装に利用したソフトウェア・ツール・サービス等

用途ソフトウェア名
エディタ
  • Visual Studio Code
画像作成
  • Adobe Photoshop
  • Adobe Illustrator
  • AzPainter2
バージョン管理
  • GitHub
その他

次ページでは、本アプリの使い方を解説します。

コメント

タイトルとURLをコピーしました