最近の開発の記録(2021/5/24)
こんにちは、最近の開発の記録をばをば
みんなでつくるダンジョンのマップエディタのあたり判定
先日の記事で、みんなでつくるダンジョンに「マップチップを並べてマップをつくれるモード」を作っていることに触れましたが、あたり判定編集・判定まわりもこれに合わせて作っていました。
このように、マップチップを塗りつぶすと、その箇所が通行不可になるようになります。あたり判定処理を久々に書いたので、なかなかうまくいかずに時間がかかってしまいました。(ソースコードはこちら)
- キャラクタの4隅のマップ座標(マップ座標 = マップチップの大きさ / ピクセル座標)を取得して、キャラクタにかかるマップチップを取得(画像1)
- キャラクタにかかっているマップ座標から、あたり判定情報を確認する(画像の赤い部分があたり判定するマップチップとする)(画像2)
- マップチップ(0, 0)とのめり込み量 を計算(画像3)
- マップチップ(0, 1) とのめり込み量 を計算(画像4)
- すべてのマップチップとの比較においてめり込み量が大きいほうの座標軸を確認
これで「マップチップの配置」と「当たり判定処理」がそろい、いよいよマップチップなマップを構成できるようになりかけていますが、まだまだなんだか動作が不安定です。引き続き調整をがんばらねば...という雰囲気です。
あともうちょっとなので、もうちょっとだけおまちください~!
おてがるに質問フォームを設置できる仕組みづくり
おてがるに質問フォームを作りたいけど、Googleフォームなどだとやや要件を満たせない...という需要があり、これを解決する仕組みをつくったりしていました。
https://github.com/piyoppi/report-box
以下の要件を満たすように開発中です。
- スキーマを記述するとそれっぽい質問フォームができて、回答をサーバに保存できる
- ウェブサイトに埋め込めるようにする
- 埋め込み元のウェブサイトが署名した任意のデータを回答に紐づけられる(たとえばどのログインユーザーが回答したか、などを記録できる)
- 埋め込みたいウェブサイトなどから独立したシステムとして提供できる
これを自作ウェブアプリに組み込めるようにしたく、ちまちまとつくっているのでした。
最近の活動はこんなかんじでした。はやくマップエディタまわりを完成させたい~。ではでは。
最近の開発の記録(2021/4/30)
最近の開発のようすをまとめてないじゃん、という気持ちになったのでまとめます。
みんなでつくるダンジョンにマップエディタを組み込んでいる
マップをつくりやすくするぞ企画の第一弾ということで、いわゆる「マップチップを並べてマップをつくる機能」をつくっています。こんなかんじ。(画像中のマップチップは「ドット絵世界 さんからお借りしています)
これまでは「マップにアイテムを設置して壁や床をつくる」ことでマップを構成していましたが、これに加えて以下のようにマップチップを並べてマップを構成できるようにします。
もともとマップチップを並べて構成するマップのようなデータ形式を想定していないつくりになっているので、埋め込みにとても苦戦中(はちゃめちゃにたいへん)ですが、なんとか動いている雰囲気を感じられる程度には動くようになってきました。
ウディタやRPGツクールなどがメジャーなゲーム制作ツールであるということもあり、マップチップ画像が素材として充実しているという背景があるので、この機能が完成したらたくさんの素材をつかってマップをつくることができるようになるのではと期待しています。
このマップエディタは「pico2map(読み方は「ぴこぴこマップ」)」として、以下のリポジトリで管理しています(絶賛開発中)。ウェブブラウザでつかえる汎用マップエディタです。みんなでつくるダンジョンは以下のマップエディタを埋め込むように使っている格好になります。なので、これをつかえばお好きなウェブサイトにマップエディタを埋め込めるようになります。
マップエディタUIやマップチップ選択UIなどで構成されるWebComponentモジュール(@piyoppi/pico2map-editor, @piyoppi/pico2map-ui-components)や、マップデータに関するモジュール(@piyoppi/pico2map-tiled)で構成されています。もうちょっと形になったら、そのうちぴこぴこマップについての解説記事でも書いてみようかなと思います。自由に使っていただけるようなライセンスで提供予定です。
残された課題はこんなかんじです。改めて書き出すとそれなりにあるなぁ。。。
- このマップエディタでつくったマップの当たり判定どうしよう
- 描画が安定しないので安定させる
- というか全体的に安定していないので安定させる
- オートタイルの取り扱いはどうしようか....(ぴこぴこマップ上には実装されているので、これをどうやって埋め込むか)
めげずに開発をがんばろうとおもいます。最近いろいろ新たに作りたいものを思いつくようになったので、早く終わらせてしまいたい~。ではでは。
WebFont(アイコンフォント)をつくってみた
自作アプリケーションのあれやこれやのために描きためたアイコンがお手元にそこそこたまってきました。たとえばこんなかんじのもの。
これらのアイコンは主にウェブページで使用され、SVGデータとして管理しているのですが、かねてからWebFontにしたいなと思っていました。理由は以下の通り。
- 色や大きさを簡単に変えられるから(CSSの
color
font-size
などで簡単に変えられる) - 画像として管理するよりも利用が楽だから(フォントファイルを読めばすぐ使える)
というわけで、WebFontにしてみたのでした。利用できるアイコン一覧はこちら
このフォントにはまだライセンスを付けていないのですが、基本的には自由に使っていただけるようにするつもりです。
使い方の雰囲気
こんな感じで使うことができます。フォントファイルたち を設置することで、たとえば以下の表記で、ピンアイコン( )を設置することができます。アイコン一覧に掲載されているアイコンは、同様に設置できます。
<!-- @font-face の設定などを行うCSSを読み込む --> <!-- (/fonts ディレクトリにフォントファイルなどの生成物を設置する) --> <link rel="stylesheet" href="fonts/garakuta-font.css"> <!-- アイコンフォントを利用する --> <span class="garakuta-font">pin</span>
どうやってつくったの?
webfont を利用させていただきました。指定したSVGからフォントファイルを作成してくれます。assets/svg
配下にフォントにしたいSVGファイルを入れておくことにした場合、以下のようなコマンドでフォントを作成できます。
npm i --save-dev webfont npx webfont assets/svg/**/*.svg
.webfontrc
ファイルを設置することで、設定を書くこともできます。今回はこんな感じに設定してみました。
{ "fontName": "garakuta-font", "dest": "fonts/", "template": "css" }
- フォント名は
garakuta-font
- 出力先は
./fonts/
- フォント読み込みの各種設定をCSSファイルとして書き出し
最近ligatureにも対応した ようで、HTML上のコーディングがしやすくなりました。ligatureは複数文字を合わせて1つの文字にすることですが、先ほどの例のように、 pin
と書けば、これを一文字にしたが表示されます。pin
という文字は、SVGのファイル名から決定されています。
<span class="garakuta-font">pin</span>
今後の予定
まだまだフォント化していないSVGがあるので、コツコツと調整しながら追加していくつもりです。また、そのうちライセンスをちゃんとつける予定です。ではでは~。
公開しているnpmパッケージをGitHub Packagesから取得できるようにしました
じぶんのプロジェクトでつかうために、いくつかのnpmパッケージ を公開しています。これらのパッケージをGitHub Packagesから取得できるようにしました。
以下の設定を .npmrc
に追加すると、 @piyoppi
スコープのパッケージについては GitHub Packages(npm.pkg.github.com) から取得します。
//npm.pkg.github.com/:_authToken=${NPM_TOKEN} @piyoppi:registry=https://npm.pkg.github.com
インストールするときは、NPM_TOKEN環境変数を設定してふつうに npm install
します。たとえばこんなかんじ。
export NPM_TOKEN=xxxxxxxx npm install --save @piypppi/pico2map-editor
パッケージの取得にはGitHubのアクセストークンが必要です(ref)。NPM_TOKENには、read:packages
スコープの付いたアクセストークンを発行し、設定します。
たとえばHerokuにデプロイする過程で npm install
をするときは、上記 .npmrc
をコミットしておき、別途 NPM_TOKEN
環境変数を設定することで同様にパッケージを参照できます。
https://devcenter.heroku.com/articles/nodejs-support#private-dependencies
最近は自作ライブラリの公開に GitHub Actions を活用していますが、GitHub Packagesなら別途アクセストークンを用意しなくていい(GITHUB_TOKEN
がつかえる)ので、
お手軽だな~というかんじです。
また、開発中のパッケージをちょっと公開して自分のプロジェクトで参照したいときに、なんとなくnpmに公開するよりはお手軽感を感じているのでした。(たとえば、目下開発中の @piyoppi/pico2map-editor はいまのところGitHub Packagesでのみパッケージを公開しています。それ以外のパッケージはnpmでも公開しています。)
ではでは
オートタイルを実装してみる
みんなでつくるダンジョンで使うために簡易的なマップエディタを構成するためのWebComponentを作っています。
この過程でオートタイルを実装してみたのでまとめてみます。デモはこちら(グリッド上をドラッグアンドドロップするとオートタイルを配置できます)
オートタイルって何ですか
あるフォーマットに則ったマップチップから複数のマップチップパターンを自動生成するとともに、適切なマップチップを自動選択しながら配置するというものです。こんなかんじ。
(上の動画に登場するマップチップは、ぴぽや様 https://pipoya.net/ からお借りしました)
今回は、上図のフォーマットのオートタイルから、上記を実現します。WOLF RPGエディタで採用されているフォーマットですね。縦に5つのパターンのマップチップが並んています。
先ほどのオートタイルのフォーマットから、このようなマップチップを生成することができます。
基本的には、一つのマップチップを4分割して、それぞれの場所にオートタイルから切り出した画像の欠片を当てはめて生成します。
今回の処理の仕様
以下のような仕様が前提条件です。
- 選択範囲に対してオートタイルから生成したマップチップを配置する(ドラッグアンドドロップで選択範囲を設定するイメージ)
- 選択範囲に配置済みマップチップが重なったときは接続する
- 選択範囲に配置済みマップチップが重ならないときは接続しない
隣り合うマップチップを確認する
配置されるマップチップの種類は、「これから配置される、あるいは既に配置されているマップチップ」に影響されます。まずは周囲のマップチップを確認する処理を考えます。
まず、バッファに既に配置されているマップチップを転写した後、さらにこれから配置する領域(選択領域)をバッファに転写します。このとき、配置済みマップチップとの接続確認のために周囲1マス分多めに転写しておきます。
次に、バッファを走査して一つずつ隣り合うマップチップの存在を確認していきます。周囲8チップを一つずつ確認し、選択範囲部分、あるいは配置済みのマップチップの部分をマークしていきます。このとき、上図のように周囲8チップに番号を振っておくと、マークした領域をその番号の和として、一つの整数で表現できます。(2進数に直すと、各ビットの有無で領域のマークの有無が表現されます。たとえば、1 + 2 + 8 = 11 を2進数にすると、 00001011 となり、1番目、3番目、4番目のビットが1ということは、領域番号1, 2, 8 がマークされていることを示しています)
ただし、配置済みマップチップに対しては、注目しているマップチップから見た向きと、配置されているマップチップの種類との組み合わせによってはマークしないようにします。たとえば、上図の選択領域3部分のように、隣り合う配置済みマップチップが「境界」に相当する場合はマップチップ同士を接続しないのでマークしません。
また、このままだと領域のマークの結果は同じなのに、期待する結果が異なる場合があります。
このことを考慮し、斜め方向に隣り合う要素が交差パーツだったときは領域をマークしないようにします。
よって、ここで説明しているような処理を実装するには、マップ上のマップチップに(何らかの方法で)「境界」と「交差」の情報を持たせる必要があります。
疑似ソースコードを書くと以下のような感じになります。(登場するクラスやメソッドは架空のものです)
const x1 = 10 // 選択領域の始点 const y1 = 10 const x2 = 20 // 選択領域の終点 const y2 = 20 const bufferWidth = x2 - x1 + 2 // 終点- 視点 + 周囲1マス分 const bufferHeight = x2 - x1 + 2 // 終点- 視点 + 周囲1マス分 // バッファをつくる const tiledBuffer = new tiledMapData(bufferWidth, bufferHeight) // マップ(mapData)から領域を切り出してバッファに転写する(選択領域+周囲1マス分をコピー) tiledBuffer.copy(mapData, x1 - 1, y1 - 1, bufferWidth, bufferHeight, 0, 0) // 選択領域をバッファに写す(このとき、配置済みマップチップの転写が上書きされることになる) const temporaryChip = new MapChip() // 選択領域を示す仮のマップチップ for(let x = x1; x < x2; x++) { for(let y = y1; y < y2: y++) { tiledBuffer.put(temporaryChip, x, y) } } // 配置結果を格納するバッファをつくる const width = x2 - x1 const height = y2 - y1 const result = new tiledMapData(width, height) for(let y = 1; y < height + 1; y++) { for(let x = 1; x < width + 1; x++) { const cursor = tiledBuffer.getMapDataFromChipPosition(x, y) /** * adjacent * 周囲のマップチップのマーク結果を格納する * * x : 注目点(x, y) * 周囲の数値: 領域番号 * *-----*-----*-----* * | 16 | 1 | 32 | * *-----*-----*-----* * | 2 | x | 4 | * *-----*-----*-----* * | 64 | 8 | 128 | * *-----*-----*-----* * 領域番号の和を2進数に直すと、各領域がマークされているかどうかがわかる * ex) * adjacent = 131 = 128 + 2 + 1 = 0b10000011 * adjacent = 34 = 32 + 2 = 0b00100010 */ let adjacent = 0 // 周囲のマップチップ情報を取得 const around = [ tiledBuffer.getMapData(x, y - 1), tiledBuffer.getMapData(x - 1, y), tiledBuffer.getMapData(x + 1, y), tiledBuffer.getMapData(x, y + 1), tiledBuffer.getMapData(x - 1, y - 1), tiledBuffer.getMapData(x + 1, y - 1), tiledBuffer.getMapData(x - 1, y + 1), tiledBuffer.getMapData(x + 1, y + 1) ] // 周囲のマップチップの状態を確認して領域をマークしていく // isAdjacent(chip) : 指定されたマップチップがオートタイル対象あるいは選択範囲内ならTrueを返却 // around[].boundary : 配置済みマップチップの境界の情報 // around[].cross : 配置済みマップチップの交差の情報 if (!around[0].boundary.bottom) adjacent += isAdjacent(aroundChips[0]) ? 1 : 0 if (!around[1].boundary.right) adjacent += isAdjacent(aroundChips[1]) ? 2 : 0 if (!around[2].boundary.left) adjacent += isAdjacent(aroundChips[2]) ? 4 : 0 if (!around[3].boundary.top) adjacent += isAdjacent(aroundChips[3]) ? 8 : 0 if (!around[4].boundary.bottom && !around[4].boundary.right && around[4].cross.bottomRight) adjacent += isAdjacent(aroundChips[4]) ? 16 : 0 if (!around[5].boundary.bottom && !around[5].boundary.left && !around[5].cross.bottomLeft) adjacent += isAdjacent(aroundChips[5]) ? 32 : 0 if (!around[6].boundary.top && !around[6].boundary.right && !around[6].cross.topRight) adjacent += isAdjacent(aroundChips[6]) ? 64 : 0 if (!around[7].boundary.top && !around[7].boundary.left && !around[7].cross.topLeft) adjacent += isAdjacent(aroundChips[7]) ? 128 : 0 // マークされた情報に基づきマップチップを決定 // getAutoTiledChip()の中身については後述のとおり const generatedMapChip = getAutoTiledChip(adjacent) // 結果バッファに格納する result.put(generatedMapChip, x, y) } } // 結果バッファをマップに転写する mapData.copy(result, 0, 0, width, height, x1, y1)
マークした領域情報からマップチップを生成する
あとは、マークされた情報に基づきマップチップを決定できればよさそうです。まずは、当てはめるマップチップを4分割して、それぞれにオートタイルの欠片を当てはめます。
たとえば上図のように4分割したときの左上側の欠片に着目します。このとき、欠片に隣り合う領域がどのようにマークされているかによって、5つの種類の欠片のうち一つを当てはめることができます。
これは、以下のような疑似コードで表現することができます(あくまで処理の雰囲気を伝えるためのコードなので、コピペしても動きません)。
欠片に接する領域は、以下のようにマークした領域番号の和である adjacent
にビットマスクを当てて確認できます。
境界や交差の情報をどのように持たせるかも工夫の余地がありそうですね。
function getAutoTiledChip(adjacent) { const mapChip = new MapChip() // 境界の情報(デフォルトではどこにも境界がない) const boundary = { top: false, bottom: false, left: false, right: false } // 交差の情報(デフォルトではどこにも交差していない) const cross = { topLeft: false, topRight: false, bottomLeft: false, bottomRight: false } // マップチップを4分割したときの左上側の欠片を決定する // (19 = 1 + 2 + 16) if ((adjacent & 19) === 0) { /* コーナー */ mapChip.renderingArea[0] = clipAutoTile[0] boundary.top = true // コーナーは上側と左側が境界になる boundary.left = true } else if ((adjacent & 19) === 1) { /* 縦方向の道 */ mapChip.renderingArea[0] = clipAutoTile[1] boundary.left = true // 縦方向の道は左側が境界になる } else if ((adjacent & 19) === 2) { /* 横方向の道 */ mapChip.renderingArea[0] = clipAutoTile[2] boundary.top = true // 横方向の道は上側が境界になる } else if ((adjacent & 19) === 3) { /* 交差 */ mapChip.renderingArea[0] = clipAutoTile[3] cross.topLeft = true // 左上側の領域で交差していることを示す } else if ((adjacent & 19) === 19) { /* 境界なし */ mapChip.renderingArea[0] = clipAutoTile[4] } // [TODO] そのほかの部分の欠片も同様のロジックで決定する // 境界情報・交差の情報をマップチップに設定する mapChip.setBoundary(boundary) mapChip.setCross(cross) return mapChip }
残りの3つの欠片も同様に処理すれば、オートタイルからマップチップを生成できます。
実装してみたようす
このようにして実装した結果、こんなかんじに動くようになりました。
動くとけっこうきもちいい~というきもちになりました。ではでは。
2020年の週末開発ふりかえり
今年ももう終わっちゃうんですね。毎年振り返りをしている のを思い出したので、今年もやっておきます。
あいこんくらぶの店じまい
ドット絵投稿・公開サービス「あいこんくらぶ」を2020/03/01に店じまいしたのでした。それなりに長いこと(3~4年くらい?)運営していたので愛着もあったのですが、管理コストなどを考えてこのようになりました。
Twitterのモーメントに、これまで頑張って宣伝してきた様子とかが残っています。今見ると結構機能もりだくさんだったんだな~となります。
これまでに投稿された作品の一部は、#あいこんくらぶ でも見ることができます。
最終的に、公開/非公開のドット絵を合わせてそれなりの数になっていました。みなさんのドット絵ライフの充実に役立てていたらうれしいです。
ウェブサービスをはじめて店じまいして一番困っているのは、ドメインいつまで持っていようかな問題です。当分は iconclub.jp を保有しているつもりですが、 お金もかかるので難しい。。。 ということで、これから生み出されるかもしれないコンテンツはすべて garakuta-toolbox.com ドメインのサブドメインという形で提供していこうと思います。
みんなでつくるダンジョンの開発をひきつづき
開発ペースは落とし気味ではありますが、ひきつづきみんなでつくるダンジョンの開発をしていました。
- 3000px四方の大きなマップをつくれるようになった
- 見下ろしビュー(RPGみたいな)マップをつくれるようになった
- そのほか細かいいろいろ
- ゲームパッド使えるようにしたとか
- マップ内にリンクを貼れるようにしたとか
ぜんぜんマップが増えない!という課題があります。一般的なマップエディタと操作性が異なることや、オートタイル系が活躍しそうなマップ作りが大変という事情も 要因の一つと思うので、来年は、マップをつくりやすくする方向のリリースをいくつかしていきたいなと思っています。みんなつかってくれよな!
ぴこぴよv2のリリース
もともとぴこぴよは「あいこんくらぶ」のシステムを流用して作られています。あいこんくらぶの店じまいにあたり、ぴこぴよの機能だけを切り出して 運用していましたが、そろそろシステムを運用しやすいように作り直したいなということで、ぴこぴよ2をリリースしました。 (あと、React触っておきたい~とか、サーバレスなアプリケーションをつくってみたいという技術的興味に対してお手頃な規模感のアプリだったという事情もあります。 フロントはReact、バックエンドはAWS Lambdaをつかって運用しています。)
初代ぴこぴよのリリース時は「Twitter投稿時に、透明ピクセルが存在するとPNG画像がJPEGに圧縮されずに表示される」という特性を利用して、 ドット絵がきれいに投稿できるというアプリでしたが、Twitterの画像圧縮まわりのアルゴリズムも変わり、いまとなってはぴこぴよの存在意義もかなり薄れたなと思っています。
ただ、手軽にドット絵をブラウザ上で加工して投稿できるという利便性はまあまあ有用かもという気持ち(いまのところ)なので、当面は維持運用していこうかなと思っています。
自分用ライブラリを切り出してオープンソースにする業
自分のプロダクトに使うための各種ライブラリは、なるべくオープンソースにしていこうという雰囲気で活動していました。これは、自分のプロダクトが万が一 おしまいになってしまっても、付随する開発を無駄にしない(=アウトプットが0にならない)という戦略に基づくものです。去年からこのようにふるまっていますが、 今年もなるべくこのようにしてきました。
そのほか
みんなでつくるダンジョンは、以下のウェブサービスの2D版です。
箱庭がつくれるウェブサービスをつくりたさがある~。都市開発シミュレーションではなく、単にたてものなどのアセットを配置するだけ。街を歩けて、他の人の街にも行けるといい。
— ぴよっぴ (@piyorinpa) December 30, 2020
もともと、つぶやきのようなウェブサービスをずっと作りたいなと思っていたのですが、やっぱり3Dも扱いたいな~と思い、WebGL触ってみたりなど していました。
あとがき
去年書いた振り返り の「来年はなにやるの?」に書いたことをあまりできてないじゃん!って思いましたが、そういうこともあります。ただ、去年の振り返りにも書いた「ここ最近の3Dの世界にはそこはかとないあこがれがあるので」 というのはずっと持っているお気持ちなので(そういう背景があってWebGL触り始めたというのもある)、ちまちまとそういう知識も入れていこうというのは引き続きの目標です。 一方で、3Dモデリングの世界は、モデリング以外の技術(キャラクターデザインとかとか)に関心が向かず、長続きしないので、あきらめかけている感じ。でも自分のアバターはほしいな~う~ん。
あと、自分の作品をなにかのイベントにだしたい~という気持ちもちょっとあります。これはずっとそういう気持ちを持っているのですが、中の人はとてもシャイなひとなので、 なかなか一歩が踏み出せない~みたいなところがあります。
今年は世情のあれこれもあって、わたしもずいぶんと翻弄されてしまいましたが、来年もひきつづき楽しく開発していきたいな~と思っています。
ではでは。
最近の開発記録(20201117)
こんにちは。最近の開発記録をまとめるなどしました。
見下ろしビューのマップをつくれるようになりました
みんなでつくるダンジョンで見下ろしビューのマップをつくれるようになりました。今まではサイドビューマップしか作れませんでしたが、 こんなかんじのRPGみたいなマップがつくれるようになりました。
(このマップの画像はぴぽやさん(https://pipoya.net/ )の素材をお借りしてつくりました)
見下ろしビューモードの設定 をすることで、重力が無効になり、十字キーで上下左右に移動できるマップとなります。マップ同士の接続も通常通り行えます。
公開したてなので仕様がちょくちょくかわるかもしれませんが、なにとぞよろしくお願いします。
ちなみに、この機能の公開に当たり、描画速度の最適化なども行っています。ちょっとだけパフォーマンスが向上していると思います。
WebGLをさわりはじめました
3Dっぽい何かを作りたくなり、three.js などを使って何かをつくってみるといいかなと思っていましたが、この際WebGLをちょっとちゃんと触ってみようかなということで最近なんとなく遊んでみています。
https://webglfundamentals.org/ を参考にしながらポチポチすすめています。ようやっとシェーダとかとかが分かってきた感じ。ちょっとくらいであれば 何とかコーディングできるようになったので、箱クラスを定義して、箱をくるくるさせることができるようになりました。
いまのところ、WebGLが難しいというよりかは、線形代数的な知識を思い出すのにちょっと時間がかかる...という感じでしょうか。
これを活用して何をつくるかは決めていませんが、何か新しいものが作れるようになればいいなと思っています。
ではでは。