作ったものまとめウェブページを新しくしつつGithub Pagesで公開しました

こんにちは。表題の通り、がらくたツールボックスのウェブページをあたらしくしました。

garakuta-toolbox.com

ちなみにGithub Pagesで公開しています。ソースコードはこちら。

github.com

Github PagesはリポジトリにPushしたら勝手にページが公開されるのでべんり。しかもオープンソースなら、独自ドメインも無料であてられます。

help.github.com

というわけで、上記の公式ドキュメントの通りに設定しました。今回は garakuta-toolbox.comドメインを当てたいので、ドキュメントに従ってDNSサーバの Aレコードを設定 しています。

なんであたらしくしたの?

以前の作品まとめページ は、凝った作りにはしたものの、このご時世になんとスマートフォン対応していなかったのでした。

この間 Picotachiで発表した ときに、「わたしはこういうものをつくっているのですよ~」とすぐに言える状態にしておくのは大事だなと思いつつ、そういう状況のときには大体スマホでページを見せながら紹介したいわけで、そうなると「スマホで見られない」という点がだいぶもったいないなという気持ちになりました。

というわけで、今回は凝りすぎず、いろいろなデバイスで表示できるようなページを作ろうということで新しくしました。 ページも更新しやすくなった分、なにかつくったらここにこまめに上げていきたいな~と思っています。

ではでは~

Vue.jsのコンポーネントをWebComponents化してみる

きょうはVue.jsのコンポーネントをWebComponents化してみたよという話をば。

どうやって

WebComponents化しておけば、カスタム要素とJSをひとつ置けば、かんたんに任意のVue.js製コンポーネントを配置できるので便利そう~ということでお試ししてみました。

コンポーネント化には「vue-web-component-wrapper」をつかいます。 github.com

まずはインストールをば。

npm install --save-dev @vue/web-component-wrapper

あとは、コンポーネント化したいものを wrap して、カスタム要素を登録すれば完了です。

import Vue from 'vue'
import Player from 'src/player/player.vue'
import wrap from '@vue/web-component-wrapper'

const CustomElement = wrap(Vue, Player)
window.customElements.define('map-player', CustomElement)

これで、HTMLを書いてゆけば、任意のVue.jsなコンポーネントを気軽に置くことができました。Props もちゃんとカスタム要素の属性として記述できます。べんり。

<map-player mapid="1"></map-player>
<!-- スクリプトはDOMが出来上がったら読まれるようにする -->
<!-- ref: https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_custom_elements -->
<script src="/player.bundle.js"></script>

とおもいきや、なんだか思ったようにスタイルが当たらない...。これは、 vue-style-loaderhead 要素の中にビルドしたスタイルを差し込むためのようなので(WebComponents配下はShadow DOMでスタイルが分離される)、以下のようにloaderを設定し、WebComponentsのShadowDOM配下にスタイルタグが差し込まれるようにします。(Webpackでビルドされる前提)

/* webpack.config.js */

{
  // ... 省略...
  {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
      shadowMode: true
    },
  }
 {
    test: /\.css$/,
    use: [
      {
        loader: 'vue-style-loader',
        options: {
          shadowMode: true
        }
      },
      'css-loader',
    ]
  }
}

めでたしめでたし。

(ちなみに、「vue-web-component-wrapper」をつかわずに、 HTMLElement を継承したクラスでVueインスタンスを作成し、window.customElements.define する方法も試してみましたが、どうもスタイルをShadowDOMに入れ込む周りで苦戦して、とりあえずあきらめたのでした。)

なんのために

わたしのポートフォリオサイトをつくりたい(正確にはつくりなおしたい)と思い、せっかくなので「みんなでつくるダンジョン」のマップをポートフォリオサイトに埋め込みたいなと思ったのでした。そこで、Vue.js製マップ表示+たんけんコンポーネントをWebComponents化すれば、気軽にマップを張り付けられるじゃん~と思いついてやってみたのでした。

ではでは~。

わくわくアバター作成日記リターンズ

ひさびさに開発っぽくない最近のあれこれをばをば。

今年の2~3月ごろに、わたしの3Dモデルが欲しいなとふと思い、アバター作成をしていた時期がありました。

garakuta-toolbox.hatenablog.com garakuta-toolbox.hatenablog.com

f:id:piyorinpa:20190204232600p:plain
こういうやつ

一応それっぽいアバターは出来たのですが、以下のような気に入らなさポイントがありました。

  • 等身が思っていたより小さい...
    • 画面上で見ると「おお~いいじゃん」感を(少なくとも私は)感じていたのですが、ヘッドマウントディスプレイをつけてVR空間で見ると、明らかに頭が大きく感じる
    • まるで着ぐるみを着ているかのよう...
  • 一部のディティールがふわふわしている
    • 手とか、足とか、あごまわりとか
  • 服を体が貫通しちゃう問題
    • 鬼の調整業によってだいぶマシにはなったものの、どうしてもポーズによっては皮膚がはみ出ちゃう

特に、等身が気に入らないのは致命的です。第一印象が完全に「着ぐるみ」になってしまうので...。

敗因はおそらく、キャラクターの設定不足なのだろうなという反省をしました。当時はとにかくさっさと3Dモデルづくりを試してみたかったということで、半ば見切り発車的にモデルを作っていましたが、これが失敗の原因だと感じています。

最近「やっぱり自身がウェブ上に存在するなら3Dモデルはほしいよな~」という気持ちに再びなったので、今度はキャラクターをある程度詰めてモデリングに挑もう、ということで、わくわくアバター作成日記を再開したいなと思っています。

f:id:piyorinpa:20191026181951p:plain
靴はまだ履いていただいていません...

蛇足ですが、いままでは一般的なお絵かきソフトと慣れないペンタブをつかって設定画を描いていましたが、私の絵心ではどうもうまくいかないようなので、ドロー系ソフト(ベクタ画像をつくるソフト)でお絵かきしてみた結果が上のものです。少なくとも今のところは、「はじめからこれをつかっておけばよかった...」という程度には作業しやすいです。線の微修正なども、ベジェ曲線のハンドルをちょっといじるだけで簡単にできたりします。

(ちなみにわたしは Inkscape というドローソフトを好んで使っています)

inkscape.org

ではでは~。ほかのことをしながらゆっくり進めるので、3Dアバターになるのは年末ごろになるかも....。

「みんなでつくるダンジョン」開発中のチャット機能をおためししてみたよ

チャットをお試ししてみたよ!

先日、開発中の「みんなでつくるダンジョン」のチャット機能を時間限定で公開してみました。(チャット機能の概要については 前回の記事 をご覧ください)

以下のようなツイートをして、わたしがマップに待機していたところ、数名の方に来ていただいたのでした。(既にチャット機能は無効になっているので悪しからず。)

チャットな様子はこんなかんじ。ふきだしは5秒ほどで消えてしまうために、他の方とおしゃべりしている様子なスクリーンショットを撮るのが大変で、わたしがしゃべっている様子しか撮れなかった...。

f:id:piyorinpa:20191014223724p:plain
おはなししている様子

数名の方に来ていただいて、以下のような課題が発見できました。

  • ふきだしが数秒で消えてしまうので、ちょっと目をそらすと話の内容が分からなくなる(ログが必要そう)
  • アバターの大きさが人それぞれなので、マップの作りによっては他の人が通れない場所が出そう
    • 今回のマップは高さ64pxなアバターを想定していたのですが、それ以上の大きさなアバターの方が部屋から出られなくて、なるほど~という気持ちになったのでした。

ちなみに、スマートフォンでもおしゃべりできるように、以下のようなUIを調整中です。(スマホは画面の大きさが限られるので、毎度悩まされる..。)

f:id:piyorinpa:20191014225319g:plain
スマホ版のチャットはこんなかんじ

今後の予定

今後は以下のような調整をしていこうかな~と思っています。

絵文字をかんたんにつぶやけるようにしたい

なるべく「会話しなければならない」みたいなプレッシャーを感じてほしくないので、絵文字をかんたんにつぶやけるといいのかな?と思っています。私自身、最近でこそ慣れてきたものの、インターネット越しのコミュニケーションに苦手意識がある時期もあったので、そういう方にも気軽に入っていただけるような仕組みを取り入れたいのでした。(あと、絵文字はかわいいので🐤🌳🍣...というのもある。)

チャット機能をどのように提供するか

いつでも、どのマップでもチャットっぽいことができるのが理想かもしれませんが、サーバのリソースや管理まわり的になかなか難しそうです。現状はとりあえずつくってみた、という感じですが、どのように提供していくかを考えねば~という状況なのです。また、マップの作者が「チャット機能を提供したくない」という状況も考えられそうなので、その点も考慮したいなと思っています。

今後も不定期にテスト公開をしたい

今後もツイートを通じた告知で、こんなかんじのテスト公開をしたいなと考えています。事前告知するかどうかはわかりませんが、開催の際にはTwitterにてつぶやくので、興味がある方はアカウント登録をしてツイートをたまにちらちらとチェックしていただけるとです。

ではでは~!

マップにいるほかのユーザーとおはなしできるようにしてみている

きょうもきょうとて作業報告をばをば。前回記事 にひきつづき、マップでわいわいできる機能を引き続き作っています。

ただいま「おなじマップにいる他のユーザーとおはなしできる」機能をつくってみています。文章で説明するよりも動画をご覧いただいたほうが説明がはやいので、以下の動画をみてみてください。

いわゆるチャット機能ですね。チャット機能は実装するか否かだいぶなやみました。みんなかならずしも平和にチャットしてくれるとは限らないので、そういう点での管理コストとかどうなんだろう~とか、そもそもチャット機能を嫌う人もいるかな~とか、いろいろ思うところがあったのです。

ただ、技術的に機能の実装を試したかったことや、いろいろなマップを渡り歩けるというサービスの特性から、ほかのひととコミュニケーションがとれてもおもしろいかも、と思ったことなどなどから、ひとまず実装してみることにしました。

はじめはとあるマップ限定で、かつ時間も限定して私がマップで待機しながら様子をみて動かしてみる...ということを本番環境下でお試しできるようにすることを目標に開発を続けています。将来的には、マップの作者が自身のマップ上でのチャット機能のON/OFFを設定できるようにしたいな~と思っています。

ではでは~

ActionCableをつかって複数のひとが集まれるマップをつくっている

こんにちは。みんなでつくるダンジョンに複数のひとが集まれる仕組みがほしいな~とずっと思っていました。というわけで、前回記事の下準備 を経て実装してみています。

こんなかんじのことがやりたい

やりたいことはこんなかんじ。ほかのユーザーをじぶんの画面にも表示できるようにします。おおよそ0.5秒ごとに座標を送るようにしてみたのですが、予想どおり結構カクカクになる...。しかも、たまにデータが詰まったみたいな挙動をしていて、しばらくアバターの位置が反映されないことがありました。

(どうでもいいけど、連動しているよ感がある動画を撮るにはどうしたらいいのだろう~という気持ちになり、結果としてカメラ片手に動画を撮ったのですが、三脚とか持っていないのでなかなか大変だったのでした...)

ちょっとよくなった

動いているアバターは、別ブラウザから操作しています。座標を送る間隔を1.5秒間隔にしつつ、0.2秒間隔でサンプリングしたアバターの位置リストを渡すようにし、かつアバターをばねっぽく移動させるようになどしてみた結果、このくらいまではカクつきが改善されました。

ActionCableはどんなかんじでつかったの?

基本的にはRailsガイドにぜんぶかいてある!というかんじです。

railsguides.jp

上記記事では、JavaScriptファイルをアセットパイプラインに乗せるようにしていますが、みんなでつくるダンジョンのフロントエンドまわりはRails管理下にはなく、Webpackをつかってビルドしています。なので、 こんなかんじにクライアント側ライブラリを別途インストールをします。

npm install --save @rails/actioncable

あとは記事の通りにJS側、Ruby側のソースコードを用意すればOKです。(記事ではRailsapp/javascripts 配下にJavaScriptを置いていますが、ここには置かずに別途Webpackでビルドするかんじ)

基本的に、同じマップにいるひとたち間でのやりとりができればよいので、以下を満たすようにします。

  • stage_?? というチャンネルを用意する(??はマップID)
  • 同じマップにいるひとたちは stage_?? というチャンネルでアバターの位置やアニメーション情報を共有しあう
  • 誰かが送信した情報はマップにいるひとたちすべてに送られる

サーバ側のソースコードは概ねこんなかんじ。 (ここには書いていませんが、 app/channels/application_cable/connection.rbRailsガイドを参考に別途実装します)

# /path/to/app/channels/avatar_channel.rb

class AvatarChannel < ApplicationCable::Channel
  def subscribed
    stream_from "stage_#{params[:stage_id]}"
  end

  def avatar_state(data)
    ActionCable.server.broadcast("stage_#{params[:stage_id]}", data)
  end
end

クライアント側がチャンネルを購読し始めたときに呼ばれる subscribed で 「stage_?? でデータのやり取りをしますよ~」という宣言をします。 また、クライアント側から avatar_state を呼び出したとき、 stage_?? を購読しているみんなにデータをばらまきます( ActionCable.server.broadcast をつかって )。

avatar_state とは私が勝手に定義したメソッドですが、特に難しいことを気にせずに、クライアント側から任意のメソッドを呼び出すように通信できるというのが便利ですね。

リクエストヘッダを送れなさそうですが、Cookieは送信されるので、認証情報などを送りたいときはCookieをつかうとよさそうです。

クライアント側のソースコードはだいたいこんなかんじ。先ほどサーバ側で定義した avatar_state へデータを送りつけたり、 broadcast されたデータを受け取ったりします。

// /path/to/javascript/avatar-socket.js

import { createConsumer } from "@rails/actioncable";

export default class AvatarSocket {
  connect(mapId) {
    const cookieVal = '認証情報のクッキー’;
    document.cookie = cookieVal;
    this._consumer = createConsumer('wss://example.com/cable');
    this._channel = this._consumer.subscriptions.create({ channel: 'AvatarChannel', map_id: mapId }, {
      received(data) {
        // broadcastされたデータを受け取る
        console.log(data);
      },
      sendAvatarState(data) {
        this.perform("avatar_state", data);
      }
    });
  }

  send(params) {
    this._channel.sendAvatarState({...params});
  }
}

アバターの情報はこんなかんじに送ります。

import AvatarSocket from "/path/to/javascript/avatar-socket.js";

// アバターの情報を送る
// positions はアバターの0.2秒毎の位置情報(時系列順)
this._avatarSocket.send({
  avatar_id: 1,
  positions: [
    [10, 10],
    [10, 20]
  ]
});

ハマりどころ

なぜかbroadcastがうまくいかない~となっていました。原因は以下の通りでした。

  • config.reload_classes_only_on_change = false が設定されており、変更が読み込まれずうまく動かなかった(っぽい?)
  • わたしの環境では config/cable.yml で設定する async adapter だとbroadcastしたデータをうけとれず
    • (いろいろガチャガチャといじっていたので、もしかしたら async adapter でもうまくいくのかもですが...)
    • asyncではプロセス間のpub/subができないようなので、ワーカーの数が2以上だとうまくいかないのかも?
    • 本番環境相当では async adapter は非推奨なので、 redis adapter を利用するようにしました

心配ごと

本番環境で動かしたときに負荷はどうなるんだろう...というのが結構気になっています。みんなでつくるダンジョンのアクセス数的には全然大丈夫だろう、という気持ちでつくってはいるけど、動かしてみないとわからないので心配...。

ActionCableサーバはRailsアプリケーションサーバと同一でなくても良いらしいですが、趣味開発なのでほどほどのお金しかかけられないことを考えると、はじめのほうは様子を見つつ、機能を制限しながらお試し運用する感じになりそうです。

まとめ

ひきつづきこんな感じの機能を実装しています。お楽しみに~

ではでは。

みんなでつくるダンジョンをRails5 -> Rails6にアップグレードしました

みんなでつくるダンジョンのアップグレード業をして、Rails5 -> Rails6 になりました。 使っている人からすると「なんのこっちゃ~」と思われるかもしれませんが、いろいろ開発に必要な足回りの環境が最新のものになったよ!ということです。

なんでアップグレードしたの?

みんなでつくるダンジョンにリアルタイムっぽい機能を付けたくなったのでした。たとえば、ログイン中のユーザーをマップに登場させたり、会話できるようにしたり...などなど。 みんなでつくるダンジョンで使わせていただいているRuby on Railsでは、バージョン5から「ActionCable」という、WebSocketを扱えるフルスタックなフレームワークをサポートしています。 しかしながら、デフォルトJavaScriptクライアントがCoffeeScriptなため、既にES201xなソースコードで開発している部分にどうやって組み込もうかな~などと考える必要がありました。

Rails6ではJavaScriptクライアントがnpmで取得できるようになり、かつES2015なソースコードになったということで、これをつかいたい~ということになりました。

railsguides.jp

アップグレードはどのように行ったの?

Railsガイド(日本語版)のアップグレードガイドの手順に従ってアップグレードしました。

railsguides.jp

まずは既に導入済みのGemがRails6対応しているかどうかを確認しつつ、古いパッケージを一つずつアップデートし、都度テストコードを走らせます。 (趣味開発ながら、一応きちんとテストコードを書いていたのでよかった~...)

つづいてGemfileを書き換えてRails6のバージョンを上げます。いくつかのテストが落ちるので、適切に修正していきます。 ちなみに、みんなでつくるダンジョンのRailsアプリケーションはほぼ単純なAPIサーバなこともあり、なにもせずともテストもだいぶ通過し、修正もさほど大変ではありませんでした。

あとは、テストを走らせるとDeprecatedなメソッドやモジュールの警告が出るので、適宜修正していきます(どのように修正すればよいかは警告文に表示されているのでありがたや~)。

はまったところ

ActiveStorage::Downloading がDeprecatedになったからなのか、明示的に require 'active_storage/downloading' しないと動かない箇所があったくらいだったと思います

まとめ

テストコードの存在や、こまめなGemのバージョンアップのおかげでさほど苦労せずにアップグレード業が完了しました。(ゴールデンパスはそれなりに手元で動かして確認しましたが、趣味開発プロダクトなので、わりとえいやっと本番環境に出してしまったので、不具合を見つけた方はこっそりおしえてください。)

維持管理しながらいろいろ機能を追加しようとしているんだよ!という話でした。

ではでは~