Unity公式チュートリアルSurvival Shooter日本語実践Chapter.10

10.GameOver

とうとう最終章。今回はプレイヤーの体力が0になった時のゲームオーバー処理を実装してする。
画面UIをアニメーションさせる方法が今回のキモ。

ゲームオーバー画面の下準備

ゲームオーバー画面背景

HUDCanvas下にImageオブジェクトを作成、名前をScreenFaderとする。
Anchor:Altをクリックしながら一番右下を選択する。これによってCanvas領域全体に画像を引き延ばす。
Color;いい感じの水色に設定する。これがゲームオーバー画面の背景となる。

ゲームオーバー画面テキスト

HUDCanvas下にTextオブジェクトを作成、名前をGameOverTextとする。設定は下記の通り。
Anchor:中央
Width,Height:300,50
Text:Game Over!
Font:LuckiestGuy
FontSize:50
Allignment:中央真ん中
Color:白
Shadowコンポーネントを追加し、EffectDistance(2,-2)

HUDCanvas内の配置順整理

Scene内のオブジェクトは、Hierarchy画面で上にあるものから順に重ねて描画される。一番上のものが一番奥に、一番下のものが一番手前に描画されることになる。オブジェクト内の子オブジェクトも同様だ。HUDCanvas内には、ゲームオーバー画面に表示したいものとそうでないものがあるため、配置順を下記の通りに整理する。 
(上から)
HealthUI
DamageImange
-ここから下がゲームオーバー画面に表示したいもの-
ScreenFader
GameOverText
ScoreText

ゲームオーバー画面のアルファ値をゼロに

ゲームオーバー画面にかかわるパーツ(ScreenFaderとGameOverText)のColorからアルファ値をゼロにして透明にしておく。

ゲームオーバー画面のアニメーション作成

Animation作成

なんと!UnityのAnimationはキャラクターのモーションだけでなくUIその他なんでもアニメにできるのだ!今回はUIのアニメを作成するぞ。
HUDCanvasを選択した状態でメニューバーからWindow>Animation>Animationを選択する。このAnimationはGameビューの表示位置にドッキングさせておくと便利らしい。Unity2018ではAddCurveのボタンがAddPropertyになっているので注意。RecordModeをオンにしておくとSceneビューで直接動きをチェックしながら作れて便利っぽい、が今回は特に利用しないっぽい。

AddPropertyからアニメさせる要素を追加していく。今回は下記の4つ。
GameOverText.Text.Color:30フレーム目でアルファ値を1に設定
GameOverText.RectTransform.Scale:0フレーム目で(0,0,0)、20フレーム目で(1.2,1.2,1.2)、30フレーム目で(1,1,1)に設定
ScreenFader.Image.Color:30フレーム目でアルファ値を1に設定
ScoreText.RectTransform.Scale:30フレーム目で(0.8,0.8,0.8)に設定

ゲームオーバー画面表示処理に移行してから全体で30フレームですべてのアニメが終了するのは早すぎるので、アニメの開始タイミングを1秒と30フレーム後に変更。

Animationウィンドウの要素の意味は大体こんな感じ

なんてこった……!たったこれだけで、「90フレームかけて透明から水色の背景に遷移しつつ、白のゲームオーバーテキストを色・大きさ共にフェードインさせ、ついでにScoreTextの大きさもちょっぴり小さくして動きのメリハリをつける」アニメーションが完成しちまったぜ!あとはこのアニメを再生するタイミングを制御するだけだな!
オッといけない、このAnimationはLoop設定がOnになっているから、GameOverClipからLoopTimeのチェックを外すのを忘れないようにしないといけない。

AnimatorControlleのHUDCanvasをダブルクリックで開き、空のStateを作成したらそれがデフォルトStateとなるよう設定。そこからGameOverClipへのTransitionをつなぎ、遷移条件のParameterとしてTriggerのGameOverを作成、設定しておく。

GameOverManagerスクリプト

HUDCanvasオブジェクトに、GameOverManagerスクリプトをアタッチしたら実装はほぼおしまいだ!GameOverスクリプトの中身を確認するぜ!あ、あとBGMのPlayOnAwakeをオンに戻しておくのを忘れずにな!

実装確認

無事に動けばおめでとう!これでこのチュートリアルはおしまいだ!
ここでやめてしまってもいいんだが、少しいじってこのゲームをもっと面白くしたいと思う!のでもうちょっとだけ続くんじゃ!

第11回はコチラ

Unity公式チュートリアルSurvival Shooter応用Chapter.11「敵の出現をWaveで管理する」

第9回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.9

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.9

9.Spawning Enemies

Prefab化したEnemyを、プレイ中にスポーンさせる処理を実装する。チュートリアルや教本でよく見るオーソドックスな方法がベースになっているものの、スポーンする種類の増やし方や複数のスポーン地点の設定など、今後のために押さえておきたいポイントも含まれている。

なお筆者の環境(Unity2018 2.1f1)では、PrefabにZomBearとHellephantが存在しなかったため、Complete-Gameフォルダからその2つのPrefabをコピーしてきた。その際、ScriptはComplete-Gameフォルダ内のものを参照していたため一度リムーブし、MyProject内のScriptに付け替えた。

ZomBear,HellephantにAnimatorを設定

ZomBearの場合

ZomBunnyと同じアニメーション、ステート、パラメータを流用できるため、ZomBunnyと同じEnemyACをそのままAnimatorのControllerとして設定する。

Hellphantの場合

モデルの大きさや骨格、関節が異なるためEnemyACをそのまま流用することができない。このような、異なる骨格、アニメーションを持つモデルでも、同じステート、トランジションを流用したいときに登場するのがAnimation Override Controllerである。
Projectパネル内で新たにHellephantAOCを作成したら、InspectorのControllerに基となるAnimationControllerをD&Dで設定する。するとその下に、ステートの一覧と上書きするアニメーションを設定するカラムが表示される。今回は、Hellephantのモデル内にあるMove,Idle,Deathのアニメをそれぞれ上書きする。
最後にHellephantのAnimatorコンポーネントのControllerにHellephantAOCを設定して準備完了。

何が同じアニメーションで、何がそうでないのか、については一度3Dのモデリングを学んだほうが理解が深まるような気がする。今の時点では、BunnyとBearは大きさも骨格も同じだから表面のテクスチャだけ張り替えてアニメを流用してるんだろうな、というくらいの理解で流しておく。というかそうしないとドツボにはまりそう……。

EnemyManagerの準備

空のGameObjectを作成し、EnemyManagerとリネームする。スクリプトEnemyManagerが用意されているのでそれをアタッチし中身を確認しておく。

変数

Startメソッド

Spawnメソッド

EnemySpawnPointの準備

上記のpublic Transform[] spawnPointsに代入するため、EnemyごとにSpawnPointを準備する必要がある。まずはZomBunnyのSpawnPointを設定するため、空のGameObjectとしてZombunnySpawnPointを作成する。空のGameObjectを視覚化するため、Inspectorの名前の横から色を設定することができる。ZonbearSpawnPoint,HellephantSpawnPointも同様にコピーペーストして作成し、それぞれ既定のPositionへ置く。

EnemyManagerの設定

EnemyManagerのPublicな変数にInspectorから値を設定して行く。
PlayerHealthは、前述したようにPlayerオブジェクトをそのままD&D。
EnemyはまずZomBunnyのPrefabをD&D。(つまりEnemyの数だけEnemyManagerスクリプトをアタッチし設定する必要がある)
SpawnTimeは3のままでOK。
SpawnPointsは一見D&Dする場所が無いように見えるが、先ほど作成したEnemySpawnPointをD&Dすれば配列の1要素として設定することができる。二個目をD&Dすれば配列の二個目として、どんどん追加していける。

ZomBear,Hellephantの分もEnemyManagerスクリプトをアタッチ、設定したら今回の作業は完了だ。
おっと、HellephantのSpawnTimeをちょっと短くするのを忘れずに。

実装確認

いろんなところからいろんな敵が出てくるようになった。だんだんゲームの様相を呈してきたぞ。
ゾンビゲーらしくラッシュとかあってもいいなあ。

第10回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.10

第8回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.8

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.8

8.Scoring points

今回は、Enemyを倒したときにスコアを獲得する処理を実装する。大きく分けて下記の段取りだ。

1.スコアを表示するUIを準備
2.Enemyを倒したとき、そのUIを書き換えるスクリプトを実装

スコアを表示するUIを準備

HUDCanvasの子オブジェクトとしてUIのTextをオブジェクトを作成。アンカーを中央上部に設置したら、Positionを(0、-55,0)に設定。Widthを300、Heightを50、textのColorを白、FontをLuckiestGuy、FontSizeを50に設定。Alignmentも真ん中中央に揃え、テキストの内容を”Score:0″に変更しておく。
文字に影をつけるためShadowComponentをアタッチしたら、EffectDistanceを(2,-2)に設定しておく。これは、文字の影を本体からどのくらいの場所に置くか、の設定であり、数字を大きくすればその分だけ影が文字から離れていく。

Enemyを倒したとき、そのUIを書き換えるスクリプトを実装

ScoreManagerをScoreTextにアタッチ。ScoreManagerの中身を確認しよう。

ScoreManager スクリプト

変数

public static int score:獲得したScoreを格納する。public staticの説明はちょっと適切にできる自信がないが、下記のように解釈している。

staticでない変数は、このスクリプト≒クラスがアタッチされたオブジェクト毎に生成されている(この生成すること、生成されたものがInstance)。オブジェクトAでは100という値をとっていても、オブジェクトBでは200という値をとっている、ということがあり得るわけだ。
一方staticな変数はクラスに紐づいて生成されている。複数のオブジェクトにスクリプトがアタッチされていようと、オブジェクトAで100という値を記録すれば、オブジェクトBでも100という値に書き換えられる。
また、そのため他クラスから参照する際にFindでオブジェクトを指定する必要がなく、クラスから直接呼び出すことができる。
あと、シーンが変わっても破棄されない。これも、オブジェクトでなくクラスに紐づいているから。

いままでStaticを使ってきた感じと、この動画の中での解説を統合して以上のように解釈してる。もし間違ってたらゴメンナサイ。

Text text:ScoreTextのtextコンポーネントを格納。

Awakeメソッド

text = GetComponent():ScoreTextのtextコンポーネントを格納。
score = 0:Scoreをリセット

Updateメソッド

text.text = “Score: ” + score:Textコンポーネントに文字列”Score: “と整数scoreを代入

EnemyHealthメソッドの修正

Enemy死亡時に実行されるメソッドStartSinkingの中にコメントアウトされていた下記一文をコメント解除。
ScoreManager.score += scoreValue:scoreに、scoreValueの値を代入

実装確認

Enemyを倒したときにScoreに10点加点されることを確認

ZonBunnyをPrefab化

次回、ZonBunnyのスポーンを実装する。それに備えてZonBunnyをPrefab化し、Hierarchyから削除しておく。

以上!前回に比べたらあっという間でしたね~

第9回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.9

第7回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.7

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.7

7.Harming Enemies

今回は、エネミーを倒す処理:エネミー側のHPや死亡判定、アニメ、プレイヤーの射撃などを実装していく。動画の時間が長めなので必定、今回の記事も長くなりました。

元動画はコチラ。
https://unity3d.com/jp/learn/tutorials/projects/survival-shooter/harming-enemies?playlist=17144

EnemyHealthの実装

いつものようにEnemyHealthスクリプトをZonBunnyにアタッチしたら中身を確認していく。

変数

startingHealth:Enemyの初期HP
currentHealth:Enemyの現在Hp
sinkSpeed:Enemy死亡時、床に沈んでいくスピード
scoreValue:Enemyを倒したときに獲得できるスコア
deathClip:Enemy死亡時に再生されるSE

anim:アニメーターを格納
enemyAudio:deathClipを格納するためのオーディオリソース
hitParticle:Enemyに攻撃が当たった時に生成するパーティクル(=ヒットエフェクト)
capsuleCollider:死亡時にEnemyにアタッチしているColliderのTriggerをOnにするため、Colliderを格納する
isDead:Enemyの死亡判定に使う
isSinking:Enemyの沈んでる判定に使う

Awakeメソッド

もはやお約束。各変数にComponentを格納していく。新しく登場するGetComponentInChildren<>()は、子オブジェクトからComponentを探して、Inspector上で一番上にあるものを返す。と、言うことは子オブジェクトに同じComponentが複数アタッチされているときは要注意である。
あとは、currentHealthに初期HPの値をぶち込んでおく。

Updateメソッド

isSinkingがTrueであれば、Enemyオブジェクトを床下のほうへ移動させる(沈んでいるように見せる)。なお、もはや物理は関係ないのでFixedUpdateに書く必要はない。
なお、isSinkingメソッドは、後述のStartSinkingメソッドの中でTrueにされる。

TakeDamageメソッド

Enemyがダメージを受けたとき、に実行するメソッド。
ここで真似したいのは、ド頭でisDeadフラグをチェックし、すでにフラグがたっていればメソッドをそこで終えているところ。これによってif文の構造をシンプルにできているんやね、スマート。
さて、もしまだisDeadが立っていない=Enemyが生きているのであれば、やられSEを再生し、Healthを減らす。減らす量は実行元であるPlayerShootingクラスのShootメソッド(後述)からintの引数amountとして渡す。さらにヒットエフェクトのパーティクルを再生する。パーティクルを生成するPositionは、これも実行元からVector3の引数hitPointを渡してある。
そして最後に、Healthが0を割っていればDeathメソッドを実行する。

Deathメソッド

前述のTakeDamageメソッドから、ヘルスが0を割った時に実行するメソッド。
まずisDeadをTrueにする。
アタッチされているCapsuleColliderのTriggerをTrueにしているのは、死亡したEnemyには当たり判定を残さないようにするため。特にTriggerを利用するわけではない。
アニメーターのDeadパラメーターをオンにして、EnemyAudioを死亡SEに差し替えたらそれを再生。複数の音源を適宜代入することで、コンポーネントを一つだけで賄っている。ちなみにコンポーネントが複数になった場合は、配列で指定する方法がある。

https://increment-log.com/unity-sound-se-play/

StartSinkingメソッド

Enemy死亡アニメーション終了後に実行されるメソッド。
StartSinkingメソッドが実行されるタイミングは後述。
NavMeshをfalseにする。これは、~|~~
RigidbodyのisKinematicをTrueにする。isKinematicによってこのオブジェクトはほかの物理の干渉を受けることがなくなる。死亡したオブジェクトに計算のリソースを割く無駄を省くためだ。
isSinkingフラグも立てておき、二秒後にこのオブジェクトを破壊する=沈み始めてから約2秒後にこのオブジェクトは消滅する。

StartSinkingメソッドはどこから実行する?

いままで見てきたコードの中に、StartSinkingメソッドを実行する記述はなかった。ではどこから実行するのか。ここで新しい概念Animation Eventが登場する。
ざっくり言えば、「アニメーション中の特定のタイミングで、特定のメソッドを呼び出す仕組み」である(たぶん)。ここでは、死亡アニメーションの途中で、このStartSinkingメソッドを呼び出している。ZonBunnyモデルのAnimationの中にすでに設定されているので、ここでは確認だけ行う。


ZonBunnyモデルのAnimationタグ、Deathアニメをクリックしてみると……


0:33のあたりにEventが設定されているのを確認、このタイミングでStartSinkingメソッドが実行されているのだ。

EnemyAttackスクリプトの修正

EnemyAttackスクリプトで、コメント化されていた部分、これはEnemyHealthにまつわる部分だ。今回改めてここのコメント化を解除していき、中身を確認していく。

EnemyHealthクラスを取得、格納する

EnemyHealth enemyHealth;
enemyHealth = GetComponent();

Enemyのアタック条件に、「EnemyのHealthが0より大きいなら」を追加する

if(time >= timeBetweenAttacks && palyerInRange && enemyHealth.currentHealth > 0){
Attack();
}
currentHealthはここで呼び出されるため、Publicな変数になっていたことに注意。

PlayerAttackの実装

大まかに言って三つに分かれる
・射撃時のマズルフラッシュ(パーティクル)
・射撃時のサウンド
・射撃時の弾道表示

GunParticlesの実装

マズルフラッシュの実装。PrefabにあるGunParticlesから、ParticleSystemをCopyしてPlayer以下にあるGunBarrelEndオブジェクトにPasteする。PrefabをD&Dでアタッチしないこと。

LineRendererの実装

GunBarrelEndオブジェクトにLineRendererコンポーネントを追加。LineRendererコンポーネントは、語義の通り直線を描画するものであり、今回は弾道表示に使う。アタッチ直後はマゼンタ色の四角い箱が表示されている。

直線の色を変える

LineRendererで描画する直線の色を変更するために、Material以下のElement 0に、用意されているLineRenderMaterialを設定する。これでLineの色が黄色になる。

直線の形を変える

この箱を戦に変えるために、Parametersを調整する。StartWidth/EndWidthともに0.05に設定する。

Unity2018ではUIが変わっているので注意。

射撃時にスクリプトから適宜呼び出すため、LineRendererコンポーネント自体は非アクティブにしておき設定は終わり。

Lightの実装

マズルフラッシュが周囲を照らす表現の実装。同じくGunBarrelEndオブジェクトにLightコンポーネントを追加。

色を変える

Colorを黄色に設定。

こちらも射撃時にスクリプトから適宜呼び出すため、コンポーネント自体は非アクティブにして設定完了。

射撃時のサウンド実装

AudioSourceを追加し、PlayerGunShotを設定、PlayOnAwakeからチェックを外して設定完了。

PlayerShootingスクリプトの実装

GunBarrelEndにPlayerShootingスクリプトをアタッチ。中身を確認していく。

変数

public int damagePerShot:ショット一発のダメージ値
public float timeBetweenBullets:ショット後、次のショットを打てるまでの時間
public float range:ショットの届く範囲

float timer:各種同期のために時間を格納
Ray shootRay:ショットの当たり判定をとるためのRay
RaycastHit shootHit:上記Rayのぶつかった情報を格納
int shootableMask:ショットがぶつかるオブジェクトが存在するレイヤーを格納
ParticleSystem gunParticles:マズルフラッシュのパーティクルを格納
LineRenderer gunLine:弾道のLineRenderを格納
AudioSource gunAudio:銃声のAudioSourceを格納
Light gunLight:マズルフラッシュのlightを格納
float effectsDisplayTime:エフェクトの表示時間

Awakeメソッド

shootableMaskにShootableレイヤーをLayerMaskで取得。LayerMaskのおさらいをしておくと、

//layerMaskに存在するオブジェクトにだけ、Rayが衝突する
Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask);

つまりShootableレイヤーが設定されているオブジェクト(今回のプロジェクトの場合はZonBunnyとEnvironment)にだけrayが衝突するようにするための下準備である。
それ以外は各変数にコンポーネントを格納。

Updateメソッド

Fire1に設定されたボタンがおされている間(デフォルトでは左クリック)かつ、ショット後、次のショットが打てるようになるまでの時間が経過していれば、Shootメソッドを実行する。
さらに、ショット後、十分な時間が経過していれば、DisableEffectsメソッドを実行する。(timeBetweenBulletsにeffectDisplayTimeを乗算しているのは、射撃間隔とエフェクトとの整合性をとるためで、それ以上の意味はないっぽい)

DisableEffectsメソッド

弾道表示と、マズルフラッシュによる周囲を照らすLightをfalseにする。オブジェクトでなくコンポーネントなので、setActiveではなくenabledをfalseにすることに注意。

Shootメソッド

まずtimerをリセットする。これをしないと射撃間隔の条件付けが機能しないので要注意。
銃声を再生し、マズルフラッシュのライトをTrueに。
また、gunParticlesを止めているが、これは前のショットの名残が残っていたときにそれを強制的に消すためのもの。直後にgunParticlesを再生。
弾道表示をtrueにし、さらにLineの始点PositionをGunBarrelEndと同じ位置に代入。

//indexは、0なら始点、1なら二個目の点、2なら三個目の点の位置を、Vector3に設定する。
//つまりまっすぐな一本の線だけでなく、途中で折れる線も描けるということ
LineRenderer.SetPosition(int index, Vector3 position)

rayの始点をGunBarrelEndのPosition、方向をforwardに設定する。forwardってどっちやねん!と思うが、forwardとはオブジェクトのZ軸に対してPositiveな方向、を意味するもの、みたいだ。3Dのゲームを作るときは、Z軸のプラス方向が正面になるように作る。覚えておこう。

次にRayを、先ほど設定した場所・方向から、rangeの範囲まで飛ばし、shootableMaskのレイヤーのオブジェクトだけ衝突するように飛ばす。もしヒットしたら、EnemyHealthコンポーネントを衝突対象から取得。衝突対象がEnemyであれば(壁とか障害物じゃなければ)EnemyHealthコンポーネントが取得できているはずなので、TakeDamageメソッドを、damagePerShotとshootHit.pointを渡して実行。最後に弾道を表示する直線の二個目の点(今回の場合は終点)を衝突地点に設定する。
もしRayが何にも衝突しなければ、弾道のLineの終点はRayのrange終点と等しくなる。

PlayerをPrefabにApplyしておく

ここまでいろいろ実装してきたPlayerの変更をPrefabに反映させるためApplyする

実装確認

無事に動くは動くが、Enemy死亡時にFatalErrorが発生。これは、Enemyに設定してあるNavMeshAgentが消えることで、SetDestinationメソッドが実行できなくなっているために起きているエラー。エネミー死亡時に、同時にNavMeshをFalseにしてやることで発生しなくなる。

長かったーー!!終わり!

第8回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.8

第6回はコチラ

Unity公式チュートリアルSurvival Shooter日本語実践Chapter.6

恋は雨上がりのように 感想

映画版を見てよかったので感想をまとめておく。原作も、連載当初から追って途中間は空いたが最後まで読んだ。ネタバレあり。

最初、「年の差恋愛どうする?!」みたいな漫画だと思ってた。途中まででいったん読むのをやめてしまったのも、そこに興味を持てなくなったからだった。でも劇場公開されて評判を見るとどうも違うらしい。そこで改めて最後まで読んでみるとこれは、夢に向けてくすぶるおじさんの再生の物語だった。女子高生あきらの青春を描く物語であり、中年店長が青春を取り戻す物語であった。

おそらくこの作品のターゲットは、店長に感情移入するような人間たちだ。かつて夢を抱き、もがき、やがて大人という立場がそれを許さなくなり、諦めたツラをして日々を送っている、そういう「われわれ」。

そこに登場する「橘あきら」という女子高生。彼女の存在は、「われわれ」にとって非常にもどかしく、「なぜ?」と問いかけずにいられない。「なぜ、足のケガで陸上をすべて諦めてしまったのか」。

「若さ」には力強いエネルギーが宿る。「やりたいからやるんだ」「とにかくこれだけをやり続けるんだ」。しかし時にこのエネルギーは盲信を招く。「これしかないんだ」「少しでも失敗したらもう意味がないんだ」。
「われわれ」の目には、あきらが陸上を諦めたのはこの極端な「イチかゼロか」のように映る。リハビリして、少しずつ慣らしていけばいいじゃないか。まだ若いんだから。そう思う。
でも彼女たちは「3年間」という単位で生きている。常に自分の能力が右肩上がりでなければならないと感じている。今までがそうだったように。一度でも下降に転じてしまったら、「もうやり直しても仕方ない」「本当はかくあるべきだった自分にはたどり着けない」。そう断じてしまう。それが「若さ」の罪である。

僕自身も、大学受験の時に似たようなことがあった。さあ受験の天王山と煽り立てられ臨んだ高3の夏休み、始めのうちは根詰めてガリガリ勉強していたものの、1日のサボりが3日になり、3日が7日になり、結局ほぼ丸々勉強せずに過ごした。
その時頭の中を堂々めぐっていたのは「いまさら勉強したって意味がない」「周りは俺がサボっている間にも進んでしまったのだから、今から追いつけるわけがない」、というまさに極端な「イチかゼロか」思考だった。

あきらにとって、ひいては「若さ」にとって、一度の挫折はそれほど大きなものだ。でも「われわれ」から見るとそれが非常にもったいない。「3年間」のその先もあるとわかっているから、諦めるなと口を出したくなる。口を出していると思う。「はて、自分は人のことを言えるほどご立派だろうか」。

「もう若くないから」「仕事があるから」「家族があるから」。そういう理由でごまかしていた何かが、鎌首をもたげてくる。やりきって諦めたならいいだろう。でも自分はそうじゃなかった。やりきってみる前に、何かのせいにして見ないフリをしていた、努力したって才能がない、無駄だとわかったようなツラこいてたクセに、本当はまだ、恋をしている。僕だって「イチかゼロか」に囚われていたじゃないか。

あきらの恋愛をエンジンとした彼女の青春の再生、「あまやどり」のお話でありながら、それをリードするのは店長自身の鬱屈である。店長自身もあきらとのかかわりを通じて自らと向き合い、長い「あまやどり」を終え再び歩き出していく。僕も長いこと片思いをしていたけれど、最近自分でゲームを作り始めてようやく「あまやどり」が終わったような気がしている。恋は、雨上がりのように。