feature image

2026年9月6日 | ブログ記事

藍ノ國、紅ノ國 【1-Monthon 2026 29班】

こんにちは。hiyokoといいます。
一か月で何かを作るイベント、1-Monthon 2026に一人で参加しました。
この記事では、制作した2Dの見下ろし型アクションゲーム『藍ノ國、紅ノ國』の軽い紹介と、こだわったしくみを紹介したいと思います。

一か月頑張った結果、優秀賞をいただくことができました。ありがとうございます!

制作したゲームについて

プレイリンク

unityroomにて絶賛公開中!
>>>ここからプレイ<<<

ゲーム概要

三柱の神々がいる世界。そこでは、藍ノ國と紅ノ國が長きにわたって戦争状態にあった。
しかし、その均衡はある時から崩れた。
主人公、レイメイは、藍ノ國の長であるタデアイから特別任務を任される。その任務中、レイメイは多くの苦難に見舞われる...
1v1で戦う見下ろし型スピードアクションゲームです。
重要なのはパリィをうまく使い、相手の攻撃をいなすこと。
迫りくる強敵を、あなたは倒すことができますか?

Unityroomではこう書かせてもらっています。なかなか端的に説明できているんじゃないでしょうか。

この一か月で様々な方向転換がありましたが、最終的に落ち着いたのは、「すべての攻撃をパリィすることができる爽快アクション」でした。

ゲームができるまで

班構成

準備

構想段階のmarkdownを残してあったのでそのままはってみましょう。

いろいろ書いてますが、この中で現実のものになったのはあんまりないですね。
このときは一人だけで開発するとはいえ、一か月あるからという余裕が感じられます。

実際にゲームとして取り込めたのはこれくらいですかね?
テーマ「あい」「まい」に絡ませるのを何にするかを非常に悩んだ記憶がありますが、結局「藍色」に落ち着きましたね。

開発

本格的に開発が始まりました。
正直あんまりなにしたか覚えていないので、ここでは雑にコミット履歴を見てみましょう。

8/8 ~ 8/12

この人mainで作業してる(笑)
やってますね。このころは若かったな(3週間前)。

playerとakane(ハクボの古い名前)の基礎実装と、塗りシステム作ってます。
この時点で某インク塗りゲームの土台っぽいのができてますね。

8/12 ~ 8/19

Branchを作ることを覚えました。ありがとう。

最初はakane(ハクボの古い名前)の基礎実装が終わってすぐに攻撃AIを作っていたのですが、あまりにも面白くなくて断念。
面白くなさがコミットメッセージに垣間見えます。

このころはなにも絵をかいていないので、見た目がシンプルなんですよね。
ただの三角形に複雑な動きをさせようと奮闘しても、あまりモチベーションが続かないわけで。

ということでボス以外の大まかなシステム実装に逃げましたね。

8/20 ~ 8/29

8/20にしてついにグラフィックを描いて導入してますね。
仮の見た目として導入して、余裕があるときにまた書き直そうと思ってました。
もちろんできませんでしたが...

この時期もずっとボスAIからは逃げ続け、ついにopening sceneが大方出来上がっていました。

8/29 ~ 8/31

この時期は細かいグラフィック面をいじるのが面白くていろいろやってますね。
夜とか夕方とか夜明け前とかを実装したのはこのころだったように感じます。
むっちゃ実装簡単でした。上からそれっぽい色をかぶせて合成するだけなので。

8/31 ~ 9/2

ついに逃げ続けてきたボスAIに立ち向かう時が来ました。
足を震ふるわせながらブランチ(boss-ai-2)を作ります。

ふと日付を見ると、八月が終わろうとしているではありませんか。
途端に泣き崩れる俺。

視界をぼやけさせながら、爆速でboss aiに向かってキーボードをたたきます。

9/2 ~ 9/5

「やべ、もう3日しかないやん!!!もう同じブランチですべてをいじったろ!!!」
そう思った私は、あるブランチを作りました

"Everything is going to be better."

ありがとう、git。フォーエバー、git。
そう思いながら、私はあらゆる変更をこのブランチに食わせました。

楽しかったです。

ボスAIのついて詳しく書いてみる

大枠

このゲームで一番複雑なものはボスAIでしょう。
このボスAIについて、私なりに実装したものを紹介してみたいと思います。

まず、前提として、ボスには様々な条件から適切な攻撃を選択してほしいですよね。
ですが、今回私が作るゲームはそれだけでは足りません。

まず、このゲームにおけるボスには、以下の特徴があります。

これらと大体のゲームの敵との違いは、「playerからのダメージを受け続けることができるか」ということです。
多くのボス敵は、たくさんのHPをplayerがボスの攻撃の間を縫って削っていくことが多いですが、私が作りたいゲームはそうはいきません。
だってボスがplayerの攻撃何発か食らうだけで死んでしまうのですから。

つまり、playerに対して攻撃を選択するほかに、緊急時にplayerの攻撃をよける動きを強制的にさせることが必要です。
これがめんどくさい。

ということで、この二つを頑張ってなんとか実装しようぜとなったわけです。

通常時 : 攻撃システム

緊急回避システムがめんどくさいとか書きましたが、こっちも別にめんどくさかったです。

ここでの目標は、いろいろな条件から適切に攻撃を選択することです。

考えた結果、まずは大別にAIにAttackStanceというものを持たせることにしました。

この状態は、おもに攻撃の激しさ、つまり逃げの姿勢か攻めの姿勢か、塗りの姿勢かなどを持っています。
この状態をもとに、具体的な攻撃技を選んでもらうわけですね。

AttackStanceには、以下の状態を持っています。

こうみるとシンプルですね。例えばOFFENSIVEが選ばれている時は、playerに向かって突撃して剣戟を浴びせる、のような攻撃的な技が選ばれるわけです。
そして具体的に最適なAttackStanceを戦闘中に選んでもらうわけですが、実装するやり方のイメージはユーティリティベースAIと呼ばれるものです。
各行動に「今どれくらい良いか」の効用値(utility)を付けて最大のものを選ぶってかんじですね(by Claude)。
つまり、AttackStanceに対して個別にいくつかの要素の評価値を計算し、その合計和をとります。
AttackStanceの場合は、以下の要素の合計値ですね。

これの最大値が、はれてその状況でのボスの攻撃スタンスとなるわけです。

これでやっとメンバーが出そろいました。
今度は具体的に攻撃を適切に選択してもらいます。
ここでも、評価値を計算し、その和をとってもらう形です。
このゲームでは、以下の要素の加重和です。

そしてこの出てきた評価値に対して、以下の要素で乗算していきます

こんな感じの評価システムを、すべての攻撃を引数として入力すれば勝手に計算してくれるようにすれば完成です。
おめでとうございます。

異常時 : 緊急回避システム

このゲームのボスは「ダッシュ」というものを頻繁に行ってきます。
playerに急接近してくる攻めのダッシュと、playerから逃げる撤退のダッシュですね。
これらの動作は、普通に登録されている攻撃と全く別の扱いをされているわけではなく、全く同じ"攻撃"としてダッシュが実装されています。
つまり、このダッシュも普通の攻撃とまったく同じように評価値を計算して、その結果最大のものであったために選択されているわけですね。

ここに先ほど言った「直前に実行した攻撃。コンボボーナス」を組み合わせれば、ダッシュした直後に近接攻撃技が繰り出されやすくなったりするわけです。

さて、ここまで実装したAIに何が足りていないかというと、playerがこちらに攻撃してくる瞬間を見て、逃げたりしてくれないということです。
大問題です。このゲームのボスのHPは全然ないので、そう易々と攻撃を与えられたくはありません。

ということで、playerの攻撃をよける動作を作りたいのですが、ここで問題が起きました。
このゲームのplayerの攻撃は、攻撃ボタンを押した瞬間に当たり判定が生成されるので、よける猶予フレームが0ということです。
なのでここはしょうがないと、「playerが攻撃しようとしてくる」ことを読み取って回避させることにしました。

「playerが攻撃しようとしてくる」ってなんやねんって感じですが、私が実装したやり方はこんな感じです。

float型emergency_dash_scoreに対して、playerが「playerが攻撃しようとしてくる」ほどこの変数に値を加算し、EMERGENCY_DASH_THRESHOLDを超えたら緊急回避のダッシュをおこなう。
emergency_dash_scoreは以下の条件で加減を行う。

(具体的には、許容誤差範囲内からの距離0で0減算/許容誤差範囲内からの距離150度(MAX)でMAX_SCORE_MODIFIER * weight減算)

func _evaluate_emergency_dash_score(weight: float = 1.0) -> void:

    var direct_direction = (hakubo.global_position - player.global_position).normalized()

    var player_direction = hakubo.get_player_vector()

    var diff_angle = direct_direction.angle_to(player_direction) if player_direction.length() > 0 else (-PI)

    var distance_to_player = hakubo.get_player_distance()

    var processed_distance_score = remap(_range_score(distance_to_player, 0.0, 400.0, true), 0.0, 1.0, 0.8, 1.2)  # 近いほどスコアが高くなる

    if abs(diff_angle) < deg_to_rad(60):

        emergency_dash_score = clamp((emergency_dash_score + MAX_SCORE_MODIFIER * weight) * processed_distance_score, 0.0, EMERGENCY_DASH_THRESHOLD)

    else:

        var remaped_score = remap(abs(diff_angle), deg_to_rad(60), deg_to_rad(180), 0.0, MAX_SCORE_MODIFIER) * (-1)

        emergency_dash_score = clamp((emergency_dash_score + remaped_score) * processed_distance_score, 0.0, EMERGENCY_DASH_THRESHOLD)

いかかでしたか?

ということで、おめでとうございます。これでいい感じのボスAIができました。
あとは1 monthonで発表して賞をもらうだけです(ここに強欲の壺の画像)。

余談 : 移動方向

別のボスAI実装の話で、移動方向のことについてちょっと話そうと思います。
このゲームのボス戦の戦闘場は、やった方はわかると思うのですが、きれいな円形をしています。

このマップを描いている時は特に何も考えていなかったのですが、今となってはこの円形には実装上の意味ができています。

何のことかというと、ボスがどの方向に歩いたり、ダッシュで逃げたりするかを決めるのに使用しています。

このゲームのボスは普通に歩いたり、もちろん先ほどから言っているようにダッシュをします。
しかし、普通に実装すると、どうしても壁に向かって歩き続けたり、逃げたくてplayerから遠ざかるようにダッシュしたのに、壁に阻まれでそのままお陀仏、みたいなアホAIができてしまうんですね。
これはいかんと。私はクールな激しい1v1がしたいのです。

ということで、ダッシュのシステムと、歩くシステムに対して、その適切な(壁にぶつからない)方向を計算するシステムを作ることにしました。
そしてここにきてステージが円形であることに価値が生まれるわけです。

まず、どんな方向が適切かを考えます。
AttackStanceOFFENSIVEである場合は簡単です。攻撃をしに行きたいので、歩きもダッシュもplayerに向かっていけばよい。
じゃあほかの時は?逃げたいときは、少々複雑になります。

上の画像の場合を考えます。この時、ステージ壁に近いボスは逃げたいと思っているとします。
playerはステージ壁に対して若干左上にいるとすると、理想の逃げる方向は以下の条件を満たすものです。

あとは簡単ですね。この二つのそれぞれを表す単位ベクトルを合成すればよい。
playerから離れることができる方向はかんたんです。playerとボスの座標ベクトルの差で出せます。

ステージ壁から離れることができる方向は少し難しかったですね。

最初はこの円形ステージのボスから見た原点中心の方向ベクトルでいいんじゃないかと思いましたが、これだと問題があります。
ボス、player、ステージの原点中心がだいたい一直線上にある場合です。
これだとplayerから離れることができる方向とステージ壁から離れることができる方向が正反対の向きになり、打ち消しあってしまいます。
結果として逃げるまたはダッシュする方向は、どっちに行くかわからない不安定な挙動を示してしまいそうです。

ということで、ステージ壁から離れることができる方向には、原点中心の方向ベクトルに加えて、「ステージ壁の接戦方向ベクトル」を足すことにしました。
もうこれで安心ですね。

これは製作中にちゃんと機能しているか見たかったので作った視覚化機能です。(ボスから右上に伸びている赤色の扇型は、ボスの攻撃エフェクトなので関係ありません。)

青棒が "playerから離れることができる方向"
赤棒が "ステージ壁の接戦方向ベクトル"
黄棒が "原点中心の方向ベクトル"
青棒が "上記3つの合成ベクトル、つまり結果逃げることになる方向"

この写真を見ると、青い棒は短いのがわかりますか?
実は今まで喋った内容とは別に、ステージ中心と壁の距離によってもそのベクトルをどれだけ重視するかを変えています。
まあこれはおまけ程度です。

func calc_retreat_direction(invert: bool) -> float:

    if not hakubo:

        return 0.0

  

    var inv_mult = -1.0 if invert else 1.0

  

    var direct_retreat_dir = (hakubo.global_position - hakubo.get_player_position()).normalized() * inv_mult

    var circle_tangent_dir = hakubo.battle_field_center_marker.global_position.direction_to(hakubo.global_position).orthogonal().normalized()

    circle_tangent_dir = _get_similar_direction_vector_from_opposite(circle_tangent_dir, direct_retreat_dir)

    var center_direction_dir = (hakubo.battle_field_center_marker.global_position - hakubo.global_position).normalized()

  

    var battle_field_radius = 268

    var distance_to_center = (hakubo.battle_field_center_marker.global_position - hakubo.global_position).length()

    var center_bias_strength = clampf((battle_field_radius - distance_to_center) / battle_field_radius, 0.0, 1.0)

  

    var processed_direct_retreat_dir = direct_retreat_dir * remap(clampf(center_bias_strength, 0.0, 0.5), 0.0, 0.5, 0.0, 1.0)

    var processed_circle_tangent_dir = circle_tangent_dir * remap(clampf(1.0 - center_bias_strength, 0.0, 0.5), 0.0, 0.5, 0.0, 1.0)

    var processed_center_direction_dir = center_direction_dir * remap(clampf(1.0 - center_bias_strength, 0.5, 1.0), 0.5, 1.0, 0.0, 1.0)


    var retreat_direction = (processed_direct_retreat_dir + processed_circle_tangent_dir + processed_center_direction_dir).normalized()
  

    return retreat_direction.angle()

おめでとうございます。これであなたのボスAIは壁にキスをすることが趣味の変態にならずに済みました。

感想

じつはこのゲームが人生初めてちゃんと完成したゲームなのですが、結構面白いゲームが作れてとてもうれしいです。
今までは、飽きたり、何か別のことに熱中してしまったりで、未完成お蔵入りさよならバイバイがすべてでした。
今回のこの1monthonで手に入れた技術は計り知れないですね。楽しかったです~

hiyoko icon
この記事を書いた人
hiyoko

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2025年4月25日
1週間でゲームを作った #Charon
Komichi icon Komichi
2024年9月20日
2024年 1-Monthonを開催しました!!
Synori icon Synori
2025年9月30日
2025年 1-Monthonを開催しました!!!
YMAC icon YMAC
2025年9月15日
traPでの一年半を振り返る〜全班所属の体験記(?)〜
gurukun41 icon gurukun41
2024年9月17日
1か月でゲームを作った #BlueLINE
Komichi icon Komichi
2023年12月11日
DIGI-CON HACKATHON 2023『Mikage』
toshi00 icon toshi00
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記