弾幕シューティングゲーム制作その15 会話シーンの追加
前回ノベルゲーム用のスクリプトエンジンを制作しました。今回はこれを使って実際にゲーム内に組み込む方法を説明します。そして、会話シーンを追加する前にゲームの流れを止めるポーズ機能を実装します。
ポーズ機能と会話シーン追加する手順
- CShootingGame.csにポーズ用フラグの追加
- ポーズを実行し解除するまで制御するためのCPauseクラスの作成
- CPlayerにポーズを生成するプログラムを追加
- 前回作ったCScriptControl.csの追加
- CScriptControlを制御するCScriptSceneクラスを作成
- CCommandManager.csにCCreateScriptクラスを作成
- 5で作成した CCreateScriptをLoadScriptにて追加
以上の手順になります。なぜポーズを制御するためにわざわざCPauseを作るかというと、メインループに直書きしてしまうと、会話シーン中にも、ポーズを解除できたりしてしまうためです。
なので、ポーズを制御するのは必ずCSeneceクラスの派生クラスで行う仕様にします。
ポーズ処理
ポーズ用フラグの追加
CShootingGame.csにポーズ用フラグの追加
public bool Paused { set; get; } = false;
public void MainLoop()
{
if (0 < PlayerBuf.Count) { Player.AddRange(PlayerBuf); PlayerBuf.Clear(); }
if (0 < EnemyBuf.Count) { Enemy.AddRange(EnemyBuf); EnemyBuf.Clear(); }
if (0 < ShotBuf.Count) { Shot.AddRange(ShotBuf); ShotBuf.Clear(); }
if (0 < BulletBuf.Count) { Bullet.AddRange(BulletBuf); BulletBuf.Clear(); }
if (0 < EffectBuf.Count) { Effect.AddRange(EffectBuf); EffectBuf.Clear(); }
if (0 < ItemBuf.Count) { Item.AddRange(ItemBuf); ItemBuf.Clear(); }
if (!Paused)
{
CommandManager.Run();
// ここに処理を追加する
// Lifeが0より多いなら処理する
foreach (var back in Back.Where(x => 0 < x.Life)) back.Move();
foreach (var player in Player.Where(x => 0 < x.Life)) player.Move();
foreach (var shot in Shot.Where(x => 0 < x.Life)) shot.Move();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Move();
foreach (var boss in Boss.Where(x => !x.DeathFlag)) boss.Move();
foreach (var bullet in Bullet.Where(x => 0 < x.Life)) bullet.Move();
foreach (var item in Item.Where(x => 0 < x.Life)) item.Move();
foreach (var effect in Effect.Where(x => 0 < x.Life)) effect.Move();
foreach (var borad in Borad.Where(x => 0 < x.Life)) borad.Move();
}
foreach (var scene in Scene.Where(x => 0 < x.Life)) scene.Move();
if (Bright != 255) DX.SetDrawBright(Bright, Bright, Bright);
foreach (var back in Back.Where(x => 0 < x.Life)) back.Draw();
foreach (var player in Player.Where(x => 0 < x.Life)) player.Draw();
foreach (var enemy in Enemy.Where(x => 0 < x.Life)) enemy.Draw();
foreach (var boss in Boss.Where(x => !x.DeathFlag)) boss.Draw();
foreach (var shot in Shot.Where(x => 0 < x.Life)) shot.Draw();
foreach (var bullet in Bullet.Where(x => 0 < x.Life)) bullet.Draw();
foreach (var item in Item.Where(x => 0 < x.Life)) item.Draw();
foreach (var effect in Effect.Where(x => 0 < x.Life)) effect.Draw();
if (Bright != 255) DX.SetDrawBright(Bright, Bright, Bright);
foreach (var borad in Borad.Where(x => 0 < x.Life)) borad.Draw();
foreach (var scene in Scene.Where(x => 0 < x.Life)) scene.Draw();
// X,Y座標の描画
DX.DrawString(35, 35, $"Effect{Effect.Count}", DX.GetColor(255, 255, 255));
// Lifeが0以下の要素を削除する
Back.RemoveAll(s => s.Life <= 0);
Player.RemoveAll(s => s.Life <= 0);
Shot.RemoveAll(s => s.Life <= 0);
Bullet.RemoveAll(s => s.Life <= 0);
Enemy.RemoveAll(s => s.Life <= 0);
Boss.RemoveAll(s => s.DeathFlag);
Effect.RemoveAll(s => s.Life <= 0);
Item.RemoveAll(s => s.Life <= 0);
Borad.RemoveAll(s => s.Life <= 0);
Scene.RemoveAll(s => s.Life <= 0);
CFpsControl.DrawFps(0, 465);
CFpsControl.FpsWait();
// 裏画面の内容を表画面に反映する
DX.ScreenFlip();
}
ポーズの制御をするSceneクラスを除く、メインループ内のMove関数をポーズフラグがfalseの時のみ実行するように変更してます。それと今回一番下でダウンロードできるプロジェクトは55行目のシーンの削除が抜けてましたので追加してください。
CPauseクラスの作成
public class CPause : CScene
{
bool FirstLoop = true;
public CPause()
{
SGP.Paused = true;
}
public override void Move()
{
// ポーズの解除
if (GetPadKey(EINPUT_TYPE.Start) == 1 && !FirstLoop)
{
SGP.Paused = false;
Life = 0;
}
// FirstLoopは前のシーケンスの時に同じキー判定にしても、すぐに次の画面に行かないようにするためのもの
if (FirstLoop)
FirstLoop = false;
}
}
CPauseクラスを生成したらCShootingGameクラスのPausedフラグをtrueにして、スペースキーを押すとPausedフラグをtrueにして解除します。この時に1ループ目で一気に解除されないように、FirstLoopを使って、2ループ目以降のみポーズを解除できる仕様になってます。
ポーズを実行するプログラムを追加
上で作ったCPauseクラスを生成するプログラムをCNormalPlayerクラスのMove関数に追加します。
public override void Move()
{
// ポーズ
if (GetPadKey(EINPUT_TYPE.Start) == 1)
{
SGP.Scene.Add(new CPause());
return;
}
}
それと、EINPUT_TYPE.Startがエスケープキーになっていたので、スペースキーに変更しました。
103行目をPadKey[start] = Math.Max(PadKey[start] , CheckStateKey(DX.KEY_INPUT_SPACE));とするだけです。
会話シーンの追加
会話用スクリプトの追加
前回作成したスクリプトエンジンの中身を以下に全部載せました。使えれば問題ないので、この下のプログラムは理解しなくても大丈夫です。
スクリプトを制御するクラスの作成
次にCScriptControlを利用するためのCScriptSceneを作ります。
この時の以下のポイント
- 7行目の初期化処理でfile_nameでCScriptControlを生成
- 13行目でScriptControl.MainEngne()でスクリプトの解釈を行う
- 14行目のScriptControl.CheckKey()でキー入力の判定を行う
- 26行目のScriptControl.TextDisp()で描画を行う
public class CScriptScene : CScene
{
CScriptControl ScriptControl = null;
public CScriptScene(string file_name)
{
SGP.Paused = true;
ScriptControl = new CScriptControl(file_name);
}
public override void Move()
{
if(ScriptControl != null)
{
bool flag = ScriptControl.MainEngne();
ScriptControl.CheckKey();
if (!flag)
{
Life = 0;
SGP.Paused = false;
}
}
}
public override void Draw()
{
if(ScriptControl != null)
{
ScriptControl.TextDisp();
}
}
}
スクリプトを生成するコマンドを追加
ここまできたら実際にスクリプト処理を生成するコマンドをCCommandManager.csに追加しましょう。
// 会話用スクリプト生成コマンド
public class CCreateScript : ICommand
{
string FileName;
public CCreateScript(string file_name)
{
FileName = file_name;
}
public void Run()
{
SGP.Scene.Add(new CScriptScene(FileName));
}
};
引数には会話用スクリプトが置いてあるパスが格納されています。そして、LoadScript関数の一番下に以下のプログラムを追加してください。
else if (param[0] == "script")
{
if (param.Length < 2)
{
error_string.Add($"{file_name}{cnt}行目のscriptコマンドのパラメーターが足りません");
continue;
}
Command.Add(new CCreateScript(param[1]));
}
実際に読み込むスクリプトの中身
bgm 0
wait 240
script dat\\story\\story1.txt
wait 120
enemy enemy2 ex 200 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 5 bl_col 0
enemy enemy2 ex 250 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 5 bl_col 1
wait 120
enemy enemy3 ex 200 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 6 bl_col 1
enemy enemy3 ex 250 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 6 bl_col 2
wait 120
enemy enemy4 ex 200 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 3 bl_col 0
enemy enemy4 ex 250 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 3 bl_col 1
wait 120
enemy enemy5 ex 200 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 3 bl_col 2
enemy enemy5 ex 250 ey 0 hit_s 12 life 3 w_time 60 bl_pat 0 gr_type 3 bl_col 3
wait 120
enemy enemy1 ex 200 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 2
enemy enemy1 ex 250 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 2
wait 10
enemy enemy1 ex 150 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 1
enemy enemy1 ex 300 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 1
wait 10
enemy enemy1 ex 100 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 2
enemy enemy1 ex 350 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 2
wait 10
enemy enemy1 ex 50 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 1 bl_col 3
enemy enemy1 ex 400 ey 0 hit_s 12 life 5 0 w_time 60 bl_pat 0 gr_type 1 bl_col 3
wait 160
enemy enemy0 ex 200 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 0 bl_col 0
enemy enemy0 ex 250 ey 0 hit_s 12 life 5 w_time 60 bl_pat 0 gr_type 0 bl_col 1
wait 400
fadeout 0 255 0 4000
wait 240
bgm 1
boss boss1 240 0
//gameover
setcolor,255,255,255
loadgraph,Girl1,dat\img\char\body2.png
loadgraph,Girl2,dat\img\boss\boss_eff0.png
drawbox
chardisp,Girl2,40,100
リンダ あれ?お客さんかな?
リンダ いらっしゃい~
enter
strclear
chardisp,Girl1,200,100
ミイナ ん~お客さんというよりは道を尋ね
ミイナ たずねたいんだけれど…
ミイナ 案内お願いできるかしら?
enter
chardisp,Girl2,40,100
リンダ はいは~い
リンダ でもその前に?
enter
リンダ あたしの弾幕でおもてなしするよっ
end
以上で完成です。
実行結果
今回のプロジェクトをダウンロードする。 プロジェクトに画像は同梱してませんのでこちらからダウンロードしてください。画像を置く階層はデバッグモードで実行するなら、bin/Debugでリリースモードで実行するならbin/Releaseです。
残すところステージごとの分岐処理だけになりました!ようやく次回で弾幕シューティングゲーム講座は最後になります。