NodeCronで二つのタスクを実行させたら一方のタスクしか実行されなかった

verison

    "forever": "^4.0.3",
    "node-cron": "^3.0.3"

起こったこと

NodeCronをForever上で実行しているUbuntuサーバーがある。

NodeCronでは以下のようなコードで実行している。

import cron from "node-cron";
import { execSync } from "child_process";

cron.schedule('*/5 * * * *', () => {
  console.log("alive monitoring");
  execSync("cd ../alive_monitoring; npm run ping");
});

cron.schedule("0 7 * * 1-5", () => {
  console.log("get hogehoge new data");
  execSync("cd ../get_hogehoge_new_data; npm run start");
});

しかし、ログではalive_moniteringしか表示されていなかった。

タスクの実行タイミングが重複している可能性

execSyncが悪いのか、それともNodeCronが悪いのかはわからないけど、cronの実行タイミングが全く同じの場合だと一つしか実行されないのでは、という仮説のもと、ソースコードを以下に変更した。

そしたら動いた。

import cron from "node-cron";
import { execSync } from "child_process";

// タイミングが重複したときに一方しか実行されなさそうなため、秒単位で変更
cron.schedule('0 */5 * * * *', () => {
  console.log("alive monitoring");
  execSync("cd ../alive_monitoring; npm run ping");
});

cron.schedule("1 0 7 * * 1-5", () => {
  console.log("get hogehoge new data");
  execSync("cd ../get_hogehoge_new_data; npm run start");
});

ブラウザゲームを作った話「TheWizardOfOverNight」

作ったもの

以下のリンクから遊ぶことが出来ます。 extab-az.itch.io

ライブラリなど

  • typescript
  • phaser3
  • parcel

ゲームのこと

コンセプト

「しゃがんで機をうかがって、狙って撃つシューティングゲーム

概要

このゲームはレトロな固定画面シューティングゲームです。主人公の魔法使い(フィンリー)は左右へ動かすことが出来ます。スペースキーやZキーで魔法攻撃を行います。攻撃を敵に当てて倒しましょう。また、下キーを押すと「しゃがむ」ことが出来ます。しゃがんているときは攻撃を受け付けません。10ステージをすべてクリアし、ハロウィンの夜を守りましょう。

どうして無敵になれるしゃがみシステムを作ったか

シューティングゲーム初心者の人でも簡単に遊べるシューティングゲームを作りたかったからです。

ここでいうシューティングゲームとは、STG(Shoot’emUpGameとかShumpとも)と呼ばれるジャンルの方で、FPSやTPSではありません。

私自身、怒首領蜂鋳薔薇と言ったゲームが大好きで、プログラマーになったらいつかSTGを作りたいと思っていました。

STGというと、最近では初心者向けではないイメージが大きいと思います。私もそういうイメージを持っています。

STGをやらない人にゲームを勧めたことがあるのですが、そのときSTGについて「難易度が高い」「弾幕に引く」「動きがしょぼい」「古い」というイメージがあるらしく、「だからやらない、やりたくない」と言われました。少なくとも僕が勧めた人たちはそう実際に言ってました。これらは事実だと思います。

初心者にプレイしてもらうには、これらのイメージを消さないといけなさそうです。これらのイメージのうち、少なくとも「難易度が高い」「弾幕に引く」という要素は消せそうだと考えました。

さて、STGにはしばしば、敵キャラのいる方向にキャラを移動させるとゲームを有利に進められる状況があります。縦シューだったら上方向に、横シューだったら右方向に移動するとよいことがある、ということです。例えば、「切り返し」だったり、「密着撃ち」が挙げられます。

これらのテクニックは初心者にとって大きな壁です。敵に近づくというデメリットの代わりに得られるメリットが理解しにくいからです。この辺りのテクニックがSTGの醍醐味でもあると思うんですが、初心者が理解して実行できるかどうかは微妙だと考えました。であれば、敵に近づいたり離れたりする機能そのものをオミットしてしまえ、ということから「左右移動のみ」に至ります。

一方で、移動できる範囲が狭まる = 回避できる幅が狭まることになるので、このままでは初心者向きのゲームにはなりません。そこで「しゃがみによる無敵」というコンセプトに行きつきました。*1

他方で、デメリットの作成のため制限時間を設けています。一定時間内に敵を全滅できないと自機を失ってしまう仕組みになっています。

このメリット、デメリットの駆け引きはSTG初心者でも理解し易いと考えました。

大変だったところ

システムを作る

このゲームは敵データとステージデータをJSONで持っています。ステージデータは敵編隊データを複数持つことで構成されています。敵編隊データは出現する敵、出現順番、数、座標などがあります。ゲームはこのデータを読み取って敵を出現させたりするのですが、このシステムを作ることが少し大変でした。どちらかというと、この形に落ち着くまでに時間がとられました。

また、敵の出現順番についてもいくつか考えなければならない点がありました。このゲームは早くクリアできればできるほど高得点にしようと考えていたため、敵編隊の「早回し」ができないといけません。この早回しを作るのにも微妙に大変でした。

さらに、ある程度ランダム性を持たせるため、設定されたスポーン座標からランダムで少しずらす仕組みも必要でした。他方で演出の観点からこれを無効化するオプションも必要です。

これも最初からわかっていたら軽く作れたんですが、後からその必要性に気がついたために面倒なことになりました。設計は先に行えってことです。

JSONでデータを作りましたが、後半ではJSONでなくてもよかったかもしれないと考えました。JSONは人が直に書くには向いていないと思います。複数プロパティが存在するとなるとよりそうだと思います。先の通り、結構なプロパティ数になったので、これを型チェック無しで描き切るには少し難だと思いました。もしJSONが良いならエディタも作ったほうがよいと思います。エディタを作ればプログラマー以外の人が編集できるようになります。

同じ仕組みを持つ別のゲームを作ろうと考えていて、その時はこのJSONエディタを作成しようと思います。そして、そのエディタも公開して「ステージエディタ機能」としてゲームの一部に盛り込もうかなと思っています。

絵を描く大変さ

また、キャラクターの絵を描くという点も大変でした。万年美術1の私にキャラ絵を描く必要がでてきたのです。最初はAIにやらせるか、アセットをダウンロードをする予定だったのですが、以下の理由で使えなくなりました。

まずAIについては、「倫理的観点」「権利観点」「品質的観点」の三つの観点をクリアしている必要があったのですが、残念ながらこれらを全てクリアできるものがなかったです。 「倫理的観点」とは、「ネット上にある画像データを断りなく学習データとして収集していない」という点です。ここの辺りの良し悪しの観点はまだ人の中で定まっていないと思います。私は、この学習データの勝手な収集は良くないと考えていたため、この観点に沿ったAIを探したかったわけです。 次に「権利的観点」とはそのままで、AIが出力した画像データの使用権に「商用利用可能」と書いてあるところを探したかったですが、これが見つかりませんでした。もっとも、今回作成したゲームが商用利用に該当するのかどうかはわからないんですが、このゲームは投げ銭が可能なので、ワンチャン商用利用に該当するのでは、と勝手に解釈しています。いまだにこの考えがあっているかはわかりません。 3つ目の「品質的観点」ですが、AIの画像データがゲームで使えるぐらいレベルが高いかどうかという部分です。いくつか試してみたのですが、どうもやりたかったドット風の画像で、ゲームに耐えうる(アニメーションできる)ものが見つかりませんでした。これは2023年の8,9月ぐらいのことなので、今現在(2023/11/21)がどうなのかはわかりません。ということがあって、AIを使うということに踏ん切りがつきませんでした。

アセットについてですが、これはアセットが全般的にアセットすぎると思いました。汎用性が高すぎるというか、「アセットで作りました」感がどうしても出てしまうと思ったのです。これはこれでよかったのですが、アニメーションまで制作されているアセットがそもそも少ないように感じました。というか、私の欲しかった「魔法使いが上向き(奥向き)に杖を振っているアニメーションのあるアセット」が見つからなかったです。

以上の観点から、自分で絵を作る必要が出てきました。楽しかったし、大変でした。とても勉強になったので良かったです。次にゲームを作るとしたとしても、自分の絵を使って表現しようと思えました。楽しみが増えて本当に良かったです。

一方で労力がとても大きかったです。絵のノウハウがないのですから、書いても書いてもまともなものにならないのです。絵だけで1ヶ月ぐらいかかったような気がします。

妻にドット絵を見せたら「かわいい」と言ってくれたのでとても良かったと思います。気を遣ってくれてるだけかもしれないですが。

絵は描けないけどドットキャラを描きたいと思っている人に私からアドバイスすると「まずはキャラの正面ドットを描きましょう」です。正面が描けないキャラは横向きも無理です。そして、恐らくですが、もっとも簡単な絵は「真正面の絵」です。私は基本的にどのキャラも正面のドットを描いてから横向き、アニメーションを描くようにしました。自分の書いた真正面の絵のドットを参考にしたというわけです。 

本来のフローは、キャラデザ絵があって、それを参考にドット絵に落とし込むというのが通常なのだと思うのですが、絵が描けない私にはキャラデザ絵を作ることが出来ないです。なので、キャラデザ絵の代わりに真正面の絵を使うということをしていました。(画像の一番上3つが正面の絵)

正直、自分で絵を描くのは無理だと思っていました。意外といけるもんだな、と思ったのが一番の収穫だったと思います。

時間が足らなくなった。

とにかく時間がありませんでした。このゲームのモチーフがハロウィンのため、ハロウィンである10/31までに作成する必要があったのです。ただ、10月の半ばにきてステージ数はたったの2つ。ステージ構成だけで1か月かけようと思ったのですが、思った通りいかないものです。とくに絵の周りの関係で遅れたと思いました。絵に関しては完全に事前に調べておけば予想できた部分だと思います。これは本当に良くなかった。

また、これはすべてのステージデータを1つのJSONで持っていたというのもよくなかったと思います。スコープが広くなりすぎてどこのステージデータをいじっているのかがわからなくなりました。ステージごとにJSONを分割させる仕組み*2を作るというのも手かもしれないですが、JSONを直で書くのが大変なので「エディタ」の方が正しいような気がします。

結局10ステージ分までしか作れませんでした。この10ステージもきちんと精査して作れたわけじゃないので粗削りになっています。特に後半は慣れればクリアまで15秒前後でクリアできてしまいます。バグ修正などでテストプレイするたびに尻切れトンボになっているなぁと、つくづく思います。

Phaser3について

Phaser3というゲームエンジンを使ったのですが、やりたいことを日本語で調べてもあまり情報が出てきませんでした。特にver3になるとより出てこないです。どういうわけか、ver2にあった機能がver3でなくなっていたりと、結構な改変があったらしいです。コピペして動かそうとしたらvscodeでエラー出されたりしました。ChatGPTに聞いても嘘つかれるし、この辺りも取られた時間だと思います。

この辺りは自分の力で何とかする必要がありました。ドキュメントとかとにらめっこしたり、寝ながら実装方法を考えました。

じゃあ、Phaser3がダメなライブラリかというと、全くそうではないです。Phaser3はJSなのでWebでサクッと動かせるのがとても良いと思っています。構造もシンプルでわかりやすい。updateメソッドがあるのですが、この中に動かしたい処理を書いていけば動くというのがわかりやすいです。また、Spriteクラスの中にupdateメソッドがあるので、そのスプライトの処理をそのupdateメソッド内で完結できるのです。スコープ管理がしやすくてうれしいです。

  // 自機のupdateメソッドで実行しているソースコードの一文
  private pcInputUpdate = (time: number, delta: number) => {
    // しゃがみ状態
    if (this.isCrouchInputing()) {
      this.crouch();
      return;
    }
    this.standUp();
    const leftDown = this.isLeftDown();
    const rightDown = this.isRightDown();
    if (leftDown && rightDown) {
      this.play("fighter_standing", true);
      this.setVelocityX(0);
    } else if (leftDown) {
      this.moveLeft();
    } else if (rightDown) {
      this.moveRight();
    } else {
      this.play("fighter_standing", true);
      this.setVelocityX(0);
    }
    if (this.isShotKeyDown() && time - this.lastShotTime > this.shotInterval) {
      this.fire();
    }
  };

また、Phaser3は物理エンジンを持っています。物理エンジンの適応は、mainのゲームエンジンのコンストラクタに一文、spriteのコンストラクタに一文書けば動きます。手間なく物理エンジンを動かしたいならこれでいいような気がします。*3今作ではPumpkinの跳ねる動作が物理エンジンで動いています。ちょっとオーバーなことをやらせている気もしますが、せっかく存在しているものを使わないのも損ですから。

さらに、HTMLを生成、表示させることもできます。ゲーム内のUIのほとんどはHTMLで書きました。Webやっている私にとってはメリットでしかなかったです。以下の画像のコンフィグメニューはHTMLで書きました。

そしてプレイされない現実

そうして、このゲームは、ver1.0.0が10/23にアップロードされます。itch.ioというプラットフォームです。

itch.io

このサイトに登録したのは「全世界からのアクセスが多い」「投げ銭システムを導入している」「インディーゲームが多い」という三つの理由がありました。

それでもって、「アップロードされるゲーム」も多いです。同じ時期にアップロードしたゲームが7つぐらいありました。そうして、それらに埋もれたのか何なのか、特にこのゲームが遊ばれる様子もありませんでした。現在(2023/11/21)でプレイされた回数は17回です。約1か月でこの回数でした。これが現実かと思いました。当然、投げ銭もありませんでした。ショックでした。もう少しプレイしてくれる人がいると思ってました。浅はかでした。

色々と頭の中を巡り巡って「完成させて公開したから良し」という考えにいたりました。完成させただけえらいと自分をほめています。

表現できるものを作るって楽しい

結果は惨敗だったわけですが、それは置いといてゲームを作るのって楽しいなと思いました。自分の思った通りキャラが動いて遊べるって、もうそれだけでもうれしいです。今回、怪我の功名で絵が少しだけ描けるようになったのもいい結果でした。絵が描けるって、頭の中のキャラを表現できるわけですよ。これってすごいことだと思いませんか?その架空のキャラが表現できる、動かせる、それって嬉しいことだと思います。お金より大切なことが出来たような気がします。それだけでもいいことだと実感します。

次回作があるとしたら、やっぱり自分で描いて自分で動かしたいです。愛情を込めて、せっせと作ってあげたいです。

今回の個人的成功体験に味を占めた私が、次にやってみたいこととして「音楽、効果音の作成」があります。今回ドット制作がうまくいった自分なら、大変だけど頑張ったらいけるかも、というちょっとした万能感があります。

もういい大人なのに子供っぽくてすみません。でも、「描ける」「表現できる」って嬉しいことです。美術1だってドットを打ってもいい。私と同じぐらい絵が苦手な人にも伝えたかった。*4

次はどうしようか

さて、次はどうするか、悩んでいます。というのも私は適応障害不眠症で医者と相談した上で会社をやめているのです。会社の休職期間中にこのゲームを作ったのですが、経緯としては、エンジニアとして伸び悩んでいるときに上司から「ゲームを1から作ってみろ」と言われたことにあります。設計とか上位レイヤーの部分の構成が苦手な私に、上司がくれたアドバイスだったわけです。結果、やはり設計レイヤーでずっこけているわけですが、最終的には動いたのでよかったです。上司は私を客観的に見ていたわけですから、このアドバイスは適格だったのだと思います。

残念ながら、この上司とはもう連絡が取ることが出来ないです。この記事が上司に届くとよいなと思っています。少し頑張りましたので、良かったらダメ出ししてほしいです。体調回復が優先事項だったから、無茶はしなかったですけどね。

もう前の会社には戻れないですが、上司の言ったことは忘れないようにしようと思います。まだ未定な私ですが、次の場でもほどほどに頑張ろうと思います。

*1:防御出来るSTGにはR-TYPEなどがありますが、ほとんどがそれを無効化する攻撃も存在していました。完全に無敵というシステムはほとんどないと思います。

*2:正しくは分割したJSONをマージする仕組み

*3:まぁ私はUnityとUE5を知らないんですけどね

*4:下手すぎて画伯と呼ばれた人。描いた絵を笑われた苦い経験のある人なんかがそうだと思います。

phaser3でスプライトの出現が重くて焦った話

起こったこと

Phaser3でゲーム制作をしているときのこと、Phaser.Physics.Arcade.Groupを使って敵データ(Sprite)をたくさん出現させていた。 ステージクリア型のゲームで、ステージをクリアしたタイミングで現在の敵データをdestroyし、現在のGroupを持つ変数を次のステージのGroupに上書きする処理をしていた。

しかし、ステージがある程度進むと重くなっていった。

ステージをデバッグする機能を用いて敵を100ほど出現させても処理落ちしなかったが、次ステージに移ると処理落ちが激しい状態になった。

groupのclearメソッド

以下のリンクが答えだった。敵データをdestroyしてもgroupに存在しつづける仕様らしい。groupからも明示的にSpriteとのリンクを解除するメソッド(clear)を用いる必要があった。

www.html5gamedevs.com

作ったゲームの紹介

所謂固定シューティングというもので、キャラは左右にだけ動くことができるシューティングです。撃って避けてしゃがんでください。しゃがみ中は相手と重なっても弾に当たっても全くダメージを受けません。シューティングでは結構難易度低めだと思います。

extab-az.itch.io

またいつかしっかりとブログに書きたいところ。

休職することになった

起こったこと

最近、ちょっと精神的にかなり追い詰められていたらしく、休職をいただいた。

経緯

2年くらい前から不眠症だったのだが、ここ最近はかなり激しい不眠が続いていた。

半年前に昇格という形で業務内容が少し上のレイヤーに移った。

特段に難しいことはやってなかった認識なのだが、ずっと気分が落ち込んでいた。

胸の中にある何かがこぼれそうな気持ちがずっと続いていた。

そんな中、いくつかの業務上で失敗したことで、不眠が悪化してしまった。

気分はすぐれないし、眠れないしのダブルパンチでメンタルクリニックにいくことになった。

メンタルクリニックで「適応障害」の診断をもらった。診断書を書いてもらい、会社に提出して休職することになった。

筆記テスト

二回目のメンタルクリニックでの診察で、前回行った筆記テストの結果を見たところ、症状はかなり悪いらしく、「適応障害」ではなく「うつ病」ということになった。

まだこの結果については会社に報告をしていないが、月末にそれを伝える予定だ。

現在

現在は薬を飲んでぐったりしてたり、ゲーム制作をしている。

休んでいるのにゲーム制作とは何事かと思うかもしれないが、ゲーム制作はものづくりの勉強になるらしい。上司からは「ものづくりの視点を持ちなさい。現実的な設計を出来るようになりなさい。」という指摘を受けていた。その延長で「ゲームでも作ってみたらどうか」という話が出ていた。自分のことを客観的に見てきた人がいうのなら間違いないだろう。

だから、このゲーム制作は自分にとっていつかプラスになると思う。そういう修行としてやらなきゃいけない部分でもあると思っている。もちろん休むのが優先なんだけど。

CodeMirror V6でline numberを使いたい場合は

version

    "@codemirror/commands": "^6.2.4",
    "@codemirror/lang-json": "^6.0.1",
    "@codemirror/state": "^6.2.1",
    "@codemirror/view": "^6.16.0",
    "codemirror": "^6.0.1",

コード

import { EditorState, Compartment } from "@codemirror/state";
import { EditorView, lineNumbers } from "@codemirror/view"; // lineNumbersをインポート

declare var editor;

editor = new EditorView({
  state: EditorState.create({
    doc: `{}`,
    extensions: [
      new Compartment().of(lineNumbers()), // Compartmentする
    ],
  }),
  parent: document.querySelector("#id"),
});

AWS CloudFrontでS3データを配信しているときに、S3のデータを更新してもすぐに適応されないときが合った

起こったこと

S3バケットとCloudFrontを連携し、静的なコンテンツを特定のドメインから提供するパターンがある。

dev.classmethod.jp

このS3バケットのコンテンツの更新をしても、CloudFrontから見るデータが更新されていないときがあった

CloudFrontのキャッシュを削除する

CloudFrontのページに飛び以下の設定を行う

  1. ディストリビューションを選択
  2. キャッシュ削除を作成
  3. 対応するファイルのパスを指定
  4. キャッシュ削除を作成
  5. ステータスが完了になったら完了