趣味開発にタスク管理ツールを使ってみている

最近、趣味開発にタスク管理ツールを取り入れてみたりしています。とりあえず以前に使ったことのあるTrelloで、現在行っている趣味開発作業に関するタスクを2週間くらい管理してみました。

Trelloは一部機能を除き概ね無料でつかえます。

trello.com

趣味開発は締め切りなどがないことが多く、また開発のやる気維持も結構大変のため、エターナってしまう※1リスクが常に付きまといますよね。というわけで、少しでもそのようなリスクを減らすために、 普段している作業を細かく「タスク」として分割して、進捗どうなっているの?とか、今何やっているの?とか、これから何やればいいの?を見える化してみようと思ったわけです。

実際に趣味開発に使ってみて、たとえばフリーゲーム作者の方など、比較的開発に時間がかかるような作品を手掛けている方なら、趣味開発でもタスク管理するとよさそうだなという気持ちになりました。

でも、「タスク管理」というと大げさな響きもします。そこで、こんないいことがあったよ!とか、こんな感じに使っているよ!などを書いてみようと思います。

(Trelloについてはググればたくさん使い方が載っていると思うので、ここではツールの使い方には触れません)

(※1:作品が未完になってしまうの意)

どのようにつかっているの?

普段している作業を細かく分割し、「カード」に書き込み「リスト」に追加していきます。私の場合は、「Backlog(いつかやるタスク)」「Todo(次にやるかもしれないタスク)」「In Progress(作業中)」 「Done(作業完了)」という感じでリストを切って、カードを移動しながらタスクを管理してみています。

f:id:piyorinpa:20180621233556p:plain
作業を「カード」に書き出して「リスト」に登録する

どんな良いことがあるの?

タスクの見える化によって以下のような良いことがあります。

その日にできるタスクを適切に選ぶことができる

時間がない~とか、今日は疲れたけどちょっとだけ作業しようかな、とか、趣味開発の場合は日々の生活をこなしながら行っていくので、その日その日で進めることのできるタスク量には 限りがあると思います。そこで、予めタスクをそこそこ登録しておけば、時間や体力、気力と相談して、その日にこなせそうなタスクを処理することができるようになります。

エターならないようにするには「ちまちまとでも作業を進める」ことが大事かなと思っている私にとっては結構よい使い方だなと思っています。

やったことが自動的に記録されていく

「今週はあまり進まなかったな...」という気持ちが長く続くと、先が見えない感が出てきて開発が止まってしまうことがありますが、案外作業量としてはこなしていたりもするものです。

タスク管理ツールをうまく使えば、行った作業が記録されていくので「実は結構作業しているんだな~」とか、「あとはこれだけやればよさそう」という作業の実態が可視化され、先の見通しがしやすくなり、やる気の維持につながると思います。

(一方で、タスクの見える化によって、Todoにたまった膨大なタスクを目の前にし、「これいつ終わるんだ...」という気持ちになる副作用もあります。そういう気持ちとうまく付き合う必要もあります)

なんかやっているな~感がでる

「ちゃんと作業しているじゃん」という気持ちになることは、孤独な個人開発にとってはとても重要だと思います。 タスクを細かく切って、こまめにカードを移動すれば、適度な「やっている感」を得ることができます。

まとめ

割と長めの趣味開発をされている方々なら、作業計画を頭の中で見積もったり、紙に書き起こしたりすることがあると思いますが、ツールを使うと捗るよという話でした。 今回は利用経験のあるツールを使ってみましたが、様子を見ながら別のツールも使ってみてもいいかもなと思っています。

私もエターならないようにがんばらないとな。。。

ではでは~

開発報告(6/17)とだらりんぴっく

みんなでつくるダンジョンの開発報告

まずはいつもの開発報告をばをば。大体以下のようなことをやっていました。そういえば最近、Trello(タスク管理ツール)を趣味開発でも使ってみているのですが、そのおかげで 「先週何をやったか」が記録できているので、このような記事が書きやすくてよいなーと思ったのでした。

とにかくひたすらバグを取る

現時点で、「マップを読み込んで保存する」「マップ間を移動する」「アバターを作る」「マップ間を移動する」など、あらかたの動作が(雑ではあるけど)できてきつつあるので、 動かしながらバグを取る作業をしていました。まずは安定性を一定部分まで上げて、人に見せられるような状態にしたいなと思ったのでした。

(こんなの作っているんですよー。シュッ(~o~)っみたいな感じにできたらよいなーという気持ち)

バグを洗い出すには、「ひたすら動かす」と「テストコードを書く」な手段があると思いますが、重要な部分はテストを書き、そうでもない部分やリアルタイム性が重要な部分は ひたすら動かす、みたいな感じで進めていきたいなーと思っています。テストプレイを自分以外の人に一度やってもらったほうがよさそうだな...。

テストコードについては、以前の記事でちょっとお試ししたJestを様子を見ながら取り入れていきたいなと思いながらつくっています。

マップサイズが変更できるようになった

マップサイズが変更できるようになりました。今までは1500px四方固定だったのですが、面積が1500×1500px2となることを条件として可変できるようにしてみました。

これで縦長や横長のマップが作れるので、よりダンジョンっぽくできるかなーと思っています。

マップ上のアイテムの位置や大きさを固定できるようにした

実際に使ってみてつらかったのが、マップ上のアイテム(壁床などなど)を意図せず触れてしまい、移動してしまうことでした。いまのところアンドゥ機能を付ける予定がないので、 このままではちと辛すぎる~ということで、マップ上のアイテム位置や大きさを固定できる編集オプションを作りました。

f:id:piyorinpa:20180617190957p:plain
鍵マークを押すと位置や大きさを動かさないようにできる
(マップチップはbunaguchiさんからの借り物です)

こんなかんじで、実際に触ってみて追加すべき機能を検討しながら作っています。

そのほか

そのほかには以下のような機能を実装・調整していました。

  • 背景設定機能(まだまだ調整が必要そう...)
  • コネクタ間移動のときのアバター(プレイヤーキャラクター)の移動位置の調整
  • 各種編集インタフェースの調整などなど

そういえば、マップ編集やテストプレイの実装ばかりすすめているけど、実際にマップを移動してあそぶ機能(というか導線)をちゃんと作っていないなーと思ったのでした。その辺にそろそろ手を付けないとな...。

素材募集のおしらせ

このようなブログ記事に載せるためのゲームっぽい画像素材を探しています。私の使っていいよ!という方はぜひぜひお気軽に Twitter もしくはブログのコメントにてお声かけください! (お声掛けいただけると開発のモチベーションがとっても上がります。)素材を利用させていただく条件はこちらに記載しています。

雑記(だらりんぴっくについて)

家でも会社でもPCを触っていると電池切れしちゃう~という話を以前しましたが、最近は「この日はまったくPCを触らないぞ」という日を設けて、ひたすらだらだらしたりしながら バランスをとったりしています。(私は勝手に「だらりんぴっく」と称しています)

というわけで、だらりんぴっくのお供にしたまんがをぺたり。(特に感想とかは書いていないですが...)

tonarinoyj.jp

中世ファンタジー風の世界のおしごとまんが。中世欧州こじつけコラム「行末ファンタジア」が各話の間に掲載されているのですが、これが個人的に好きです。

sonorama.asahi.com

先ほどのまんがと同じ作者のまんが。世界中の雑兵がとあるアパートに集結するお話。

afternoon.moae.jp

ひさびさに本屋で表紙買いしました。(この作者の他の作品を月刊誌で読んでいたことがあったのですが、絵がきれいだなーと思っていたのでした。お話もおもしろかったので早く続きが読みたい...)

ではではー。

ちょっっとだけJestをつかってみた

こんにちはこんにちは。きょうはちょっとだけJavaScriptのテスティングフレームワークJestをお試しでつかってみました話をば。

趣味開発なので、がっつりテストプログラムを書くつもりはないのですが、それでも重要な部分(データを更新したり、読み込んだりなど)はテストがあったほうが安心できていいなーと思ったのでした。 サーバ側ではRSpecを使ってテストを書いているのですが、JavaScript はどうしようかなーと思っていたので、今回はJestを素振りしてみることにしました。

インストール

インストールは基本的に公式のチュートリアル通りにやればよさそうなので、以下のページを参考にインストールしてみます。

facebook.github.io

まずはインストール。プロジェクトディレクトリで以下を実行します。

npm install --save-dev jest

適当にテストを書き(書いたテストについては後述)、テストを npm test で実行しようとしたら怒られてしまいました。

 This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
 By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

ES201xな記述だと、どうやらそのままではテストできないみたい(import で構文エラーになってしまった)なので、babelをインストールします。

 npm install  --save-dev babel-jest babel-core regenerator-runtime
 npm install babel-preset-env --save-dev

あとは、.babelrcを以下のように設定します

// .babelrc
{
  "presets": ["env"]
}

package.jsonに以下を追加します

"scripts": {
    "test": "jest"
}

あとは npm test を実行してあげれば、テストを実行することができました。インストールはとても簡単でした。

簡単なテストを書いてみる

いまつくっているプログラムのテストを部分的に書いてみます。以下のプログラムは、与えられた文字列が意図した数値であるかを確認するためのものです。 (開発中なので、このプログラムの良し悪しについては目をつぶっていただきたく...)

具体的には、次のような確認をします。

  • 文字列が数値かどうか
  • 整数値であることが条件の場合は整数値であるかどうか
  • 最大値が設定されている場合は最大値以下か
  • 最小値が設定されている場合は最小値以上か

関数は、意図した数値であれば数値自体を戻り値として返し、意図せぬ数値であれば例外をスローします。

// validateHelper.js

import * as Errors from './errors.js'

export default class ValidationHelper {

...

static isValidNumber(num, param = {}) {
  if( (typeof param.defaultValue !== 'undefined') && (typeof num === 'undefined') ) {
    return param.defaultValue;
  }

  if( param.isInt ) {
    if( !(/(^-?\d+$)/g.test(String(num))) ) throw new Errors.WrongIntValidationError('the string is including characters');
  } else {
    if( !(/(^-?\d+(\.\d+)?$)/g.test(String(num))) ) throw new Errors.WrongNumberValidationError('the string is including characters');
  }

  if( typeof param.maxValue !== "undefined" ) {
    if( num > param.maxValue ) throw new Errors.WrongNumberValidationError('given number is over than maximum');
  }

  if( typeof param.minValue !== "undefined" ) {
    if( num < param.minValue ) throw new Errors.WrongNumberValidationError('given number is less than minimum');
  }

  return Number(num);
}

こんな感じに使うことを想定しています。

// 与えられた文字列が数値かどうか
let hoge = Validate.isValidNumber(fuga);
// 与えられた数値が整数値でかつ、10以下か
let hoge = Validate.isValidNumber(fuga, {isInt: true, maxValue: 10});

とりあえず、まずは「文字列が数値である」という判定が正しく行われるかを確認するテストを書いてみます。describeでテスト条件によって適切にグループ化し、test内で確認をします。 (後で知ったのですが、ittestエイリアスとして使えるみたいです(参考)。個人的にはitのほうが好きだなーと思ったのでした。)

今回テストしたい関数は「意図した数値であれば数値自体を戻り値として返す」仕様になっているので、数値が与えられたときに正しく数値が返されるかどうかを確認します。

テストのMatcher(判定器という感じかな?)は公式ドキュメントを見るのがよさそうなので、都度確認します。値が同一かどうかを確認するには toEqualが使えそうなので、 これを使います。書いたテストは以下の通り。

import ValidateHelper from './validateHelper.js' 

describe('when number is given', () => {
  test('return number', () => {
    // 数値を渡したら数値が返される
    expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
  });

  test('return number when type of string', () => {
    // 文字列型でも数値が返される
    expect(ValidateHelper.isValidNumber('1234')).toEqual(1234);
  });

  test('return number when the given number is less than zero', () => {
    // 負号が付いていても数値が返される
    expect(ValidateHelper.isValidNumber('-1234')).toEqual(-1234);
  });

  test('return number when the given number includes a decimal point', () => {
    // 小数点があっても数値が返される
    expect(ValidateHelper.isValidNumber('12.34')).toEqual(12.34);
  });
});

つぎに、「最大値が設定されている場合は最大値を超えたら例外をスロー」が正しく動くかを確認してみます。書いたテストは以下の通り。

import ValidateHelper from './validateHelper.js' 

describe('maximum value is given', () => {
  test('throw error when given number is over than maximum value', () => {
    expect(() => ValidateHelper.isValidNumber('12', {maxValue: 10})).toThrow(/maximum/);
  });

  test('return number when given number is less than maximum value', () => {
    expect(ValidateHelper.isValidNumber(1, {maxValue: 2})).toEqual(1);
  });
});

「最大値が与えられた場合、最大値より大きな値が与えられた場合は例外を発生する」「最大値以下の場合は数値を返す」が仕様なので、それらを確認できるように書きます。 例外が発生することを確認するには toThrow をつかいます。expect で例外を捕捉するには、functionでラップする必要があることに注意です。

ちなみに、toThrow には例外クラスを引数に取ることができるとドキュメントには記載されているのですが、なぜかうまくいかず。。。 どうやら以下のリンク先と同様の事象なのかもという感じですが、具体的な解決策はもうちょっと調べないとわからないかなという感じです。

toThrowError not working with custom errors and Babel · Issue #2123 · facebook/jest · GitHub

ひとまずエラーメッセージを引数に取ることもできるようなので、今回はエラーメッセージを確認するテストとしました。

あとは同じようにテストを書いていきます。最終的に書いたテストはこちら。

// validateHelper.test.js

import ValidateHelper from './validateHelper.js' 

describe('#isNumber', () => {
  describe('optional parameter is not given', () => {
    describe('when number is given', () => {
      test('return number', () => {
        expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
      });

      test('return number when type of string', () => {
        expect(ValidateHelper.isValidNumber('1234')).toEqual(1234);
      });

      test('return number when the given number is less than zero', () => {
        expect(ValidateHelper.isValidNumber('-1234')).toEqual(-1234);
      });

      test('return number when the given number includes a decimal point', () => {
        expect(ValidateHelper.isValidNumber('12.34')).toEqual(12.34);
      });
    });

    describe('when the string includes invalid characters', () => {
      test('throw error', () => {
        expect(() => ValidateHelper.isValidNumber('123hoge')).toThrow(/including characters/);
      });
    });
  });

  describe('allow only integer', () => {
    test('throw error when given number includes a decimal point', () => {
      expect(() => ValidateHelper.isValidNumber('123.45', {isInt: true})).toThrow(/including characters/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(1234)).toEqual(1234);
    });
  });

  describe('maximum value is given', () => {
    test('throw error when given number is over than maximum value', () => {
      expect(() => ValidateHelper.isValidNumber('12', {maxValue: 10})).toThrow(/maximum/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(1, {maxValue: 2})).toEqual(1);
    });
  });

  describe('minimum value is given', () => {
    test('throw error when given number is over than maximum value', () => {
      expect(() => ValidateHelper.isValidNumber('0', {minValue: 1})).toThrow(/minimum/);
    });

    test('return number', () => {
      expect(ValidateHelper.isValidNumber(10, {minValue: 1})).toEqual(10);
    });
  });
});

つかってみて

インストールが簡単でよいなと思いました。とりあえず今作っているものでテストしやすそうなものを引っ張ってきて書いてみたので、 今度はモックとかスタブとかを使いそうなものを使って書いてみようかなという気持ちです。 また、Webpackとの組み合わせとか、Vue.jsのテストを書くには?みたいなところをクリアできればよさそうなので、引き続きお試ししてみようかなと思っています。

github.com

なにか進捗があれば書いていこうと思っております。ではでは。

(開発報告)マップ接続UIつくっているよ!なはなし

いつもの進捗報告

恒例のみんなでつくるダンジョンの進捗報告をばをば。 いまは「マップ同士の接続をするためのユーザーインタフェース」をつくっています。 (以前にも軽く紹介したかもですが、ただいまちゃんと使えるように作りこんでいる段階です)

マップ同士をつなくパーツを「コネクタ」と呼んでいますが、こんな感じに「どのコネクタと接続するか?」を画面上で選んでいきます。

f:id:piyorinpa:20180607225732p:plain
コネクタをみつける

まずは読み込むマップを選択し、表示されるマップからコネクタを選択します。マップが広いと画面に収まりきらないので、コネクタの存在場所を示す矢印が現れます。 矢印のほうに向かってスクロールするとコネクタが現れるので、そいつを選択してやれば接続完了といった具合です。

とりあえずこんなかんじに作っておいて、あとは様子をみながら改良するとかしないとかをしていけたらなーと思っています。

f:id:piyorinpa:20180607232823p:plain
矢印の向き計算と画面とコネクタのあたり判定

ちょっとだけ実装の話をば。矢印の向きはCSStransform: rotate で指定してあげています。コネクタの中心座標と矢印画像の中心座標を結ぶ直線のなす角が回転角度になるので、 距離 (x, y) を計算してJavaScriptatan2 関数を使い、 回転角度 = Math.atan2(y, x) * 180 / Math.PI としてあげれば簡単に矢印回転角度を求められます。 あとは transform: rotate(回転角度deg) を矢印画像のスタイルとしてあててあげれば、コネクタの位置を向くように矢印の角度を決定することができます。 (CSSの画像変形は実装が楽でよいですね。)

ここで、ディスプレイ座標系はY軸が下を向くので、CSSで指定する回転角は時計回りが正の方向になるのですが、それをすっかり忘れていて1時間くらいつぶしてしまったのでした。また、 右向き矢印の画像を用意しておくと、計算した角度をそのまま回転角度と出来て楽でよいです。

コネクタが画面内に表示されているときは矢印を出したくないので、画面とコネクタのあたり判定もします。 Ps1(x) > Pc2(x) Ps1(y) > Pc2(y) Ps2(x) > Pc1(x) Ps2(y) > Pc1(y) の条件をすべて満たせばコネクタ矩形が画面内に入っているということになるので、このときは 矢印画像を表示しません。

これらの処理をマップをスクロールするごとに行えば、矢印がいい感じに動いたり表示されたりされなかったりするようになります。

雑記

昼間はおしごとでプログラミングをし、夜はこつこつと趣味開発を続けていますが、たまにパソコンから離れないと電池切れになってしまい、PC作業系についてはなにもしたくなくなってしまうことがあります。 ということで、それを防止するために今週末はまんが大会と称して積みマンガを消化していたりしました。気が付いたころに電池切れになって開発がエターなってしまう、みたいなことにならないように気を付けたいなと思ったのでした。

ではでは。

開発報告(6/1版)

前回の開発報告から日が開いてきたのでひさびさの報告をばをば。とはいいつつも、サーバ側で処理するあれやこれやを実装していたりしたので、派手な成果はないのでした。

とりあえず現状のマップエディタを使ってみた

作ってばっかりいると、ついつい使うのを忘れてしまうのですよね。というわけで、ちょっとマップを作ってみました。画像は墨cm様からお借りしたものを使います。 マップチップを置くとそれっぽくなったなぁとうれしくなりつつ、マップに使えそうな画像一式を送っていただきありがたいなぁと思いました。

(このようなサンプル画像に使っていい画像を募集しているので、「つかっていいよ!」という方はお声かけください!利用条件などはこちらです。ただし、作者は気まぐれなのでいつ利用させていただくかはわかりません...)

f:id:piyorinpa:20180531225118p:plain
マップを描いてみたよ!

やっぱり実際に使ってみるの大事ですね。アイテムなどの配置のしづらさや微妙なあたり判定バグなどがいろいろ出てきて、まだまだいろいろ作りこまないとなぁと思ったのでした。道のり険し...

レイヤー機能を実装した

先ほどの画像で樽や看板が草に隠れている場面が映し出されていますが、このような表現を実現するために「レイヤー機能」を実装していたりもしました。画像エディタとか、ツクールとかウディタとか使っている人にはおなじみのやつですね。 マップチップに前後関係を持たせることができるようになるので、ちょっとだけ表現の幅が広がりました。ちょっとだけ実装のお話をすると、(描画にはPixiJSを利用していますが、)PixiJS単体ではレイヤー機能はありませんが、拡張ライブラリを入れることでレイヤー機能を簡単に利用できます。公式のサンプルはこちら。 https://pixijs.io/examples/#/layers/zorder.js

f:id:piyorinpa:20180531225740p:plain
レイヤー番号を指定して所属レイヤーを決めます

PixiJSはV5が開発中のようですが、楽しみである一方、アップデートがスムーズにいくといいなぁとちょっとだけ恐々としています。色々見こしてレンダラまわりはある程度融通がきくように作ってはいるつもりですが...

そのほか

あとは概ね以下のようなことをしていました。(主に開発環境の構築まわりとかのお話なので、「へぇー」程度に眺めてください)

  • Webpackのアップデートなど、開発環境まわりの整備
  • コネクタまわりの公開設定などまわりの実装(このあいだのブログ記事に書いたこいつを実装している最中)
  • サーバ側のアプリケーションのテストコード書く準備など
    • サーバ側アプリケーションにはRuby on Railsを利用しています
    • とりあえずRSpecとfactory_botを入れてテストコードが書けるようになりました
    • 今回はサーバ側もちょっと複雑になりそうと思ったので、テストは書かないとなぁとおもいまして...
    • フロントエンド側のテストどうするかなぁ...

雑記

作っているとやっぱり見た目は大事だよねと思うわけです。この間、開発環境一式をつめこんで4KディスプレイなPCに接続してみたら、「あれ?なんか違うなぁ...」となってしまい、ちょっとだけ落ち込んだのでした。 ディスプレイによって色味や解像度が違うので、いつも使っているPC以外に接続してみた目を確認するのは必要かもなという気持ちになりました。

また、見た目がよろしくなると「うぉーつくっているぞー」感が出てやる気が上がったりするので、もうちょいとその辺頑張りたいなと思ったのでした。

さいきんはウェブ上にすてきなアニメーション効果などをふんだんに使ったサイトをたくさん見られるので、その辺を参考にしながらいい感じにしていければよいですね。(最近は、そういうサイトを見ていると「いいなぁーこういうのをつくりたいのよなー」という気持ちになったりしています)

ではではー。

Vue.js+VuexでFlashメッセージコンポーネントのサンプルを書いてみました

今回はVuexのお勉強もかねて、FlashメッセージをVue.js+Vuexで書いてみました。 というのも、今開発をすすめている「みんなでつくるダンジョン」はVue.jsを使って作っているのですが、「保存しました!」とか「通信中です」などのメッセージを出したくなったのでした。

つくったもののデモは以下にあります。リンク先に「Show Flash Message」ボタンが3つあるので押してみてください。

https://piyoppi.github.io/samples/vue-flash-message-sample/

このようなメッセージはいろいろなコンポーネントから呼び出したくなったりするのですが、各コンポーネントから$emitして値のやり取りをするのは大変です。 ということで、Vuexに慣れる意味も込めてVuexで作ってみることにしました。

Vuexについては本家日本語版ドキュメントに詳しくあるので、これを読んで理解しました。(日本語ドキュメントありがたや~)。 Vue.jsはコンポーネントに状態を持てますが、ものによっては状態を共有したい場合も出てきます。propsやemitをつかっての状態共有もできないことはないですが、規模が大きくなると状態の紐づきを管理するのが大変になるので、共有すべき状態について管理するためにつかうライブラリがVuex(という理解)です。

vuex.vuejs.org

Vuexには「State」「Getter」「Mutation」「Action」の4つの要素があり、それぞれ以下のような役割があります(という理解をしています)。

  • State: 共有したい状態(変数)を保持するところ
  • Getter: Stateを加工して返すことができるもの(Stateを足したり割ったり文字列連結したりした値を欲しい時に使う)
    • 特に加工が必要ない場合は直接Stateから値をとることもできる
    • 今回は使っていません
  • Mutation: Stateを更新することができるメソッドたち
    • こいつが唯一Stateを更新できる
    • 更新したいときは this.$store.commit('Mutation名', 値); みたいにする
  • Action: Mutationを呼び出して何らかの処理をするためのメソッドたち
    • Action内でMutationを呼び出して状態を更新したりする
    • Actionを呼び出すときは this.$store.dispatch('Action名', 値); みたいにする
    • Mutationでは非同期処理ができないが、Actionではできる(ここがポイントみたい)
    • 単純なStateの更新ならMutation経由でもできるし、複雑な(?)処理を経てStateを更新したいときはMutationを呼び出す命令を書いたActionを実行する

基本的にVuexのこれらの要素にはどのコンポーネントからも簡単にアクセスできるようにすることができます。 また、Stateが変更されれば各コンポーネントでStateを参照している箇所も更新されるので、State(状態)の共有ができます。

今回のサンプルソースはこちら。 github.com

VuexのActionをつかって表示メッセージを受け取る

FlashメッセージにつかうState,Mutation,Actionはこのように定義しました。showFlashMessage をディスパッチすることで、必要な値がmutation経由でstateに反映されます。

# src/components/noticeMessageBox/noticeMessageBoxStore.js

export default {
  state: {
    text: '',
    mode: 'processing',
    visible: false,
    timeoutId: -1,
  },
  mutations: {
    setMessage: (state, payload) => {
      state.text = payload.text;
      state.mode = payload.mode;
      state.visible = true;
    },
    setMessageVisible: (state, value) => state.visible = value,
    setMessageTimeoutId: (state, value) => state.timeoutId = value,
    clearMessageTimeoutId: (state) => state.timeoutId = -1,
  },
  actions: {
    showFlashMessage: ({state, commit}, message) => new Promise((resolve, reject) => {
      //timeoutId !== 1 のときはVisibleを変更するsetTimeoutが生きているのでキャンセルする
      if( state.timeoutId !== -1 ) {
        clearTimeout(state.timeoutId);
        commit('clearMessageTimeoutId');
      }

      commit('setMessage', message);

      if( message.duration > 0 ) {
        //durationだけ時間が経ったらVisible=falseとする(メッセージを隠す)
        const timeoutId = setTimeout( () => {
          commit('clearMessageTimeoutId');
          commit('setMessageVisible', false);
          return resolve();
        }, message.duration);
        commit('setMessageTimeoutId', timeoutId);
      } else {
        return resolve();
      }
    })
  }
}

上記定義をVuexオブジェクトをインスタンス化する際に読み込んであげます。

# src/main.js

import Vue from 'vue'
import Vuex from "vuex"
Vue.use(Vuex);
import noticeMessageBoxStore from "./components/noticeMessageBox/noticeMessageBoxStore.js"
import appOptions from './main.vue'
let store = new Vuex.Store({
modules: {
    noticeMessageBoxStore
  }
});

// src/main.vueをdist/index.htmlのapp-entrypointにマウントします
window.addEventListener("load", ()=>{
  const app = Object.assign(appOptions, {
    el: '#app-entrypoint',
    store: store  //storeを注入する(後述)
  });
  new Vue(app);
}, false);

使うときのイメージはこんな感じ。textにメッセージを設定し、Durationに表示時間(ミリ秒)を設定します。modeは見た目を変更するための文字列(後述)です。

this.$store.dispatch('showFlashMessage', {text: 'Message!', duration: 4000, mode: 'done'});

Flashメッセージコンポーネントの定義はこんな感じ。先ほどのstoreを見て、変更がリアルタイムにコンポーネントに反映されるようにしています。表示・非表示切り替えはcomputedに記したスタイルで定義します。 (this.$storeをつかうには、ルートコンポーネントでstoreを注入する必要があります。)

storeのmodeに応じて適用するスタイルシートのクラスを変えることで、Notice、Warning、Errorなどの見た目を変えることができます。

# src/components/noticeMessageBox/noticeMessageBox.vue

<template>
<div class="msgbox-outer" :class="outerClass">
    {{ $store.state.noticeMessageBoxStore.text }}
  </div>
</template>

<script>
export default {
  computed: {
    mode: function() {
      return this.$store.state.noticeMessageBoxStore.mode;
    },
    visible: function() {
      return this.$store.state.noticeMessageBoxStore.visible;
    },
    outerClass: function() {
      return {
        darkblue: this.mode === "processing",
        lightblue: this.mode === "done",
        red: this.mode === "error",
        hidden_bottomside: !this.visible && !this.isTopside,
        hidden_topside: !this.visible && this.isTopside,
        show_bottomside: this.visible && !this.isTopside, 
        show_topside: this.visible && this.isTopside,
        topside: this.isTopside
    }
  },
  //(以下略)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             </script>                   

あとは、コンポーネントのテンプレートに以下のように書き、componentsにコンポーネントを登録してあげればどのコンポーネントからもFlashメッセージを表示させることができるようになります。

# src/main.vue

<notice-messagebox></notice-messagebox>

こんなかんじにVuexとVue.jsをくみあわせて作ることができました。もうちょいとVuexと戯れて、開発の効率が良くなると良いなぁとおもっています。 ではではー。

「ここちがうよ!」とか「ここはこうじゃないの?」みたいな建設的な技術的ツッコミは歓迎しますので、ぜひー。(わたしも自信があるわけではないので。。。)

マップ同士の接続をどうするか問題を考えてみた

みんなでつくるダンジョン(仮称)はマップ同士の接続によって無限に広がるマップを楽しめるよ!!というのがコンセプトなわけですが、ここで じぶんのマップと他の人のマップをどのように結べばよいかという点が難しいなと思った次第です。

ここでいう難しさとは、プログラミング的に難しいなどの実装面の話ではなく、どのようにユーザー同士で合意をとってマップを接続してもらうかというところ。

基本的に、マップAとマップBは以下のように相互に行き来できるようにするので、マップAにマップBの接続設定をすると、それはマップBからマップAに接続設定をするのと 同じことになります。このとき、マップAとマップBの持ち主が異なるとき、どのように合意を取ってもらい相互に接続してもらうかが問題になります。

f:id:piyorinpa:20180520004039p:plain
マップ間は相互に行き来できるようにします

たとえば以下のような案が考えられそうです。

マップの持ち主に許可をもらって接続する(承認制)

マップAの所有者がマップBの所有者に「接続したいぞー」とリクエストを出し、マップBの所有者が許可を出した場合に接続を許可するというもの。 これでもよさそうなのですが、マップAの所有者が断られてしまうと面白くないだろうし、マップBの所有者も断りづらくなって、いろいろと 上手くいかなくなるのではないかなという気持ちもします。

先着順で自由に接続できるようにする

マップAの所有者は、マップBが空いていたら許可などを経なくても接続できてしまうというもの。ただ、マップBの所有者が他のマップと 繋ぎたいと思っていた場合に、マップAの持ち主に勝手に繋がれてしまうと面白くないかもしれません。

みんなでつくるダンジョンのコンセプト的には、基本的にはマップはどんどん相互接続されて広くなっていってほしいので、これくらいの接続の気軽さや自由さが欲しいところですが、 マップはいわば制作者の作品でもあるので、それらを勝手に接続されてしまうというのはイマイチなのかもしれません。

んー、むずかしいですね。。。

「接続用のマップ」を用意して自由に接続できるようにする

じぶんの作ったマップが他のマップと気軽につなげられるように、「接続用のマップ」を用意してあげる方法です。接続用のマップはいくつかの「コネクタ」を持っていて、 空いているコネクタとじぶんのマップを自由に接続できるようにするというものです。

f:id:piyorinpa:20180520004643p:plain
マップ同士の接続

このようにすることで、「気軽に接続したい!」という要望と、「勝手に接続されては困る」という点をなんとか解決できるのではないかなぁと思っています。 はじめのうちは接続用マップは運営側で用意してあげる方向でよいかなと思っていますが、ゆくゆくはユーザーのみなさんで「接続用マップ」も作られるようになればいいなと 考えています。

もちろん、マップAの所有者とマップBの所有者が合意して相互に接続できる、みたいな機能も欲しいところなので、その辺は別途考えるとします。 (コネクタにパスワードをかけられるようにして、接続の際にパスワードを要求するなどを考えています)