LoginSignup
2
2

More than 1 year has passed since last update.

DXライブラリによるゲーム作成【Windows版のゲーム作成(2Dスクロール)編】

Posted at

0.はじめに

DXライブラリというC++用ライブラリを使用し、ゲーム作成をしてみたので、
環境準備や実装内容などを整理し、備忘録として残しておく。

本記事では、上記3編構成のうち、太字の内容について記載する。

※免責事項(必ず、ご一読ください)
本記事は、筆者以外の環境でも同様に成功することを保証するものではなく、
本記事により生じた、いかなる損害や損失についても、筆者は一切の責任を負いかねます。
古い情報が含まれていたり誤情報が含まれていたりする可能性もありますので、
必ずしも正確性を保証するものではありませんのでご了承ください。

1.前回のおさらい

  • VS2019、DXライブラリをインストールした
  • VS2019の設定をした
  • 60FPSで画面の反映をするプログラムを実装、動作確認した

2.ゲームの内容

ゲームの内容として、
 ゲームを起動すると、
 タイトル画面が表示され、
 Enterキー押下でゲーム開始し、
 ゴール地点を目指して進み、
 ゴールするとクリアタイムを更新する、
というものを作ってみる。

ゴール地点に到達したり、ゲーム終了の入力(キーボードのESC)があったりしたら、タイトル画面に戻るようにする。
また、ステージから落ちたり、敵に触れたりした場合もタイトル画面に戻るようにする。

キーボード入力で遊べるようにしているが、
手元にDualShock4があるので、DualShock4の入力でも遊べるようにする。
(デフォルトではdefineで無効にしておく)

3.完成イメージ

次章以降の準備や実装をおこなうと、以下の動画のゲームができる。(実装後の実行動画)

4.準備するもの

  • 画像ファイル(筆者は全てGimpでテキトーに作成)

    • タイトル用画像(Title.png):660x450
      Title.png

    • ステージ用背景画像(Back.png):30x30
      Back.png

    • ステージ用ブロック画像(Block_1.png ~ Block_5.png):30x30
      Block_1.png Block_2.png Block_3.png Block_4.png Block_5.png

    • メインキャラ画像(MainChara.png):30x30
      MainChara.png

    • エネミー画像(Enemy_1.png):30x30
      Enemy_1.png

  • ステージブロック座標ファイル(Stage_1_1.txt):横220、縦15ブロック分のブロック情報

Stage_1_1.txt
2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 2 2 0 0 2 0 0 0 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 2 2 2 0 2 0 0 0 3 3 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 3 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 2 2 0 2 2 0 0 0 3 3 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 2 2 0 0 2 0 0 0 3 3 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 2 2 0 0 2 0 0 0 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 3 0 0 0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 4 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 3 3 0 0 3 3 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 2 2 0 0 0 0 0 0 2 2 0 0 0 0 2 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 2 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 3 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 2 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 4 0 0 0 0 5 5 5 5 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 3 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 2 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 0 1 0 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
  • ステージクリアタイム用ファイル(Stage_1_1_result.txt)
Stage_1_1_result.txt
100.000000
100.000000
100.000000
100.000000
  • 音源(筆者は全てCakewalk by Bandlabでテキトーに作成)
    • BGM用音源(bgm.wav)
    • ジャンプ用音源(jump.wav)

5.構成

まず、準備編では以下のモジュールを作成していた。

  • ProjConf.h:設定用モジュール
  • Sub.h:フォントや色、入出力などの補助用モジュール
  • Main.cpp:メインモジュール

今回は上記の3つのモジュールにも少し手を加えつつ、
以下のモジュールも新規追加し、実装していく。

  • Picture.h:画像読み込み用モジュール
  • Stage.h:ステージ用モジュール
  • Scene.h:シーン管理用モジュール
  • Title.h:タイトル用モジュール
  • Action.h:アクション用モジュール

また、画像ファイル用に「picture」フォルダ、音源用に「sound」フォルダ、ステージ用に「stage」フォルダを作成し、以下のような形で格納しておく。

sound/
└ bgm.wav
└ jump.wav
stage/
└ Stage_1_1.txt
└ Stage_1_1_result.txt
picture/
└ Title.png
└ Back.png
└ Block_1.png
└ Block_2.png
└ Block_3.png
└ Block_4.png
└ Block_5.png
└ MainChara.png
└ Enemy_1.png

6.ProjConf.hの実装

準備編では、システム、ウィンドウ、キーボード、FPSの設定用のdefineを実装したが、
今回は以下の設定の実装を追加する。

  • シーン(タイトル、アクション)設定
  • タイトル設定
  • ステージ設定
  • クリアタイム設定

また、デフォルトでは、JoyPad(DualShock4)のdefine「DEF_JOYPAD_VALID」は無効。
(有効にした場合は、PCに接続した状態でゲームを開始するようにする)

ProjConf.h
#pragma once

#define ON		1				// ON
#define OFF		0				// OFF

/*** システム設定 ***/
#define DEF_SOUND_VALID			// サウンド有効
//#define DEF_JOYPAD_VALID		// JoyPad(DUALSHOCK4)有効

/*** ウィンドウ設定 ***/
#define WIN_MAX_X 660			// ウィンドウのX最大値
#define WIN_MAX_Y 450			// ウィンドウのY最大値
#define WIN_MIN_X 0				// ウィンドウのX最小値
#define WIN_MIN_Y 0				// ウィンドウのY最小値
#define WIN_POS_X 0				// ウィンドウの初期位置X
#define WIN_POS_Y 0				// ウィンドウの初期位置Y

/*** キーボード設定 ***/
#define DEF_KEY_PRESS_TIME 100	// キーボード長押し回数

/*** FPS設定 ***/
#define MicroSecond 1000000.0f	// 1マイクロ秒
#define MillSecond 1000.0f		// 1ミリ秒
#define WaitTimeMill 3000		// 最大で待てるミリ数
#define GameFPS 60				// 設定したいFPS

/*** シーン設定 ***/
enum SCE						// シーン種類
{
	SCE_00_TIT,					// タイトルシーン
	SCE_01_ACT,					// アクションシーン
	SCE_02_MAX
};

/*** タイトル設定 ***/
#define TIT_ENTER_POS_X 220		// 'Press Enter' X位置
#define TIT_ENTER_POS_Y 400		// 'Press Enter' Y位置

/*** ステージ設定 ***/
#define PIC_BACK 0								// Back
#define PIC_BLO1 1								// BLOCK1
#define PIC_BLO2 2								// BLOCK2
#define PIC_BLO3 3								// BLOCK3
#define PIC_BLO4 4								// BLOCK4
#define PIC_BLO5 5								// BLOCK5
#define PIC_MCHAR 6								// MChara
#define PIC_ENEM 7								// Enemy

#define CELL 30									// 1ブロックのサイズ[pix]
#define STG_X_MAX 6600							// ステージ最大X位置[pix]
#define STG_Y_MAX 450							// ステージ最大Y位置[pix]
#define STG_X_MIN 0								// ステージ最小X位置[pix]
#define STG_Y_MIN 0								// ステージ最小Y位置[pix]
#define STG_BLOCK_X_MAX (STG_X_MAX / CELL) 		// ステージ最大X位置[ブロック]
#define STG_BLOCK_Y_MAX (STG_Y_MAX / CELL) 		// ステージ最大Y位置[ブロック]
#define STG_FALL_Y (STG_Y_MAX - CELL)			// ステージ落下判定位置(ステージ最大Y位置-1CELL)[pix]

#define CHA_POS_X_INI (STG_X_MIN + 2 * CELL)	// キャラ初期位置X
#define CHA_POS_Y_INI (STG_Y_MAX - 3 * CELL)	// キャラ初期位置Y(下から3ブロック目)

#define ENEMY_HIT_RANGE	5						// 敵当たり判定閾値[pix]
#define ENE1_POS_X_INI	120						// 敵1初期位置X
#define ENE1_POS_Y_INI	(STG_Y_MAX - 3 * CELL)	// 敵1初期位置Y(下から3ブロック目)
#define ENE2_POS_X_INI	360						// 敵2初期位置X
#define ENE2_POS_Y_INI	(STG_Y_MAX - 3 * CELL)	// 敵2初期位置Y(下から3ブロック目)

#define DIR_NONE	0x00000000					// 移動方向なし
#define DIR_RI		0x00000001					// 右方向
#define DIR_LE		0x00000002					// 左方向
#define DIR_UP		0x00000004					// 上方向
#define DIR_DO		0x00000008					// 下方向

#define JUMP_OFF		0						// ジャンプなし
#define JUMP_UP			1						// ジャンプ上昇中
#define JUMP_GRAVITY	2						// 自由落下

#define MOVEX		(CELL / 10)					// X方向移動量(通常時)
#define MOVEX_D		(CELL / 5)					// X方向移動量(ダッシュ時)
#define MOVEY_H_MAX	(4 * CELL)					// ジャンプ時の最大高さ(4ブロック目)

#define GOAL_POS_X (5400 - WIN_MAX_X / 2)		// GOAL X位置

/*** クリアタイム ***/
enum RANK										// シーン種類
{
	RANK_1ST,
	RANK_2ND,
	RANK_3RD,
	RANK_4TH,
	RANK_MAX
};
#define RANK_TIME_INI	100.0f					// クリアタイム初期値[s]
#define RANK_POS_X		(WIN_MAX_X - 90)		// クリアタイム表示位置X
#define RANK_POS_Y		10						// クリアタイム表示位置Y
#define RANK_DISP_NUM	3						// クリアタイム表示数
#define TIME_POS_X		RANK_POS_X				// タイム表示位置X
#define TIME_POS_Y		(RANK_POS_Y + 30)		// タイム表示位置Y

#define FPS_POS_X		10						// FPS表示位置X
#define FPS_POS_Y		10						// FPS表示位置Y
#define COMD_POS_X		FPS_POS_X				// コマンド説明表示位置X
#define COMD_POS_Y		(FPS_POS_Y + 20)		// コマンド説明表示位置Y

7.Sub.hの実装

補助用モジュールとして前回Sub.hを作成した。
Sub.hに以下の修正、追加の実装を行う。

  • soundクラスの修正(jump音(JumpSound)を追加、音量調整(20%)を追加)
  • JoyPadクラスの追加(DualShock4を使用するため)

修正、追加の実装を行った後のSub.hは以下。

Sub.h
#pragma once

/*** Fontクラス ***/
class
{
public:
	int FH[30 + 1];

	void Read()
	{
		for (int i = 0; i < 30 + 1; i++)
		{
			FH[i] = CreateFontToHandle("MS ゴシック", i, 6, DX_FONTTYPE_NORMAL);
		}
	}
private:

}Fon;

/*** Colorクラス ***/
class
{
public:
	int Black;
	int Red;
	int Green;
	int Blue;
	int White;

	void Read()
	{
		White = GetColor(255, 255, 255);
		Red = GetColor(255, 0, 0);
		Green = GetColor(0, 255, 0);
		Blue = GetColor(0, 0, 255);
		Black = GetColor(0, 0, 0);
	}
private:

}Col;

#ifdef DEF_SOUND_VALID
/*** Soundクラス ***/
class
{
public:
	int BgmSound;
	int JumpSound;

	void Read()
	{
		BgmSound = LoadSoundMem("./sound/bgm.wav");
		JumpSound = LoadSoundMem("./sound/jump.wav");
		ChangeVolumeSoundMem(255 * 20 / 100, BgmSound);		// 音量を20%に調整
		ChangeVolumeSoundMem(255 * 20 / 100, JumpSound);	// 音量を20%に調整
	}

	void PlayBGMSound()
	{
		PlaySoundMem(BgmSound, DX_PLAYTYPE_LOOP);
	}

	void PlayJumpSound()
	{
		PlaySoundMem(JumpSound, DX_PLAYTYPE_BACK);
	}
private:

}Snd;
#endif /* DEF_SOUND_VALID */

/*** FPSクラス ***/
class
{
public:
	LONGLONG FirsttakeTime = 0;		// 1フレーム目の計測時間
	LONGLONG NowtakeTime = 0;		// 現在の計測時間
	LONGLONG OldtakeTime = 0;		// 以前の計測時間

	float Deltatime = 0.000001f;	// デルタタイム(経過時間)
	int FrameCount = 1;				// 現在のフレーム数(1フレーム目からMAXフレーム目まで)
	float Average = 0.0f;			// 平均のFPS値

	void FPSInit() {
		FirsttakeTime = GetNowHiPerformanceCount();

		NowtakeTime = FirsttakeTime;
		OldtakeTime = FirsttakeTime;
		Deltatime = 0.000001f;
		FrameCount = 1;
		Average = 0.0f;

		return;
	}

	void FPSCheck() {
		NowtakeTime = GetNowHiPerformanceCount();
		Deltatime = (NowtakeTime - OldtakeTime) / MicroSecond;
		OldtakeTime = NowtakeTime;

		if (FrameCount == GameFPS)
		{
			LONGLONG TotalFrameTIme = NowtakeTime - FirsttakeTime;
			float CalcAverage = static_cast<float>(TotalFrameTIme) / GameFPS;
			Average = MicroSecond / CalcAverage;
			FirsttakeTime = GetNowHiPerformanceCount();
			FrameCount = 1;
		}
		else
		{
			FrameCount++;
		}
		return;
	}

	void FPSWait() {
		int wait = 0;
		wait = static_cast<int>(((MicroSecond / GameFPS * FrameCount) - (NowtakeTime - FirsttakeTime)) / MillSecond);	/* wait時間(msec) = 理想の時間 - 実際の時間 */

		if (wait > 0 && wait <= WaitTimeMill) {
			WaitTimer(wait);
		}
		return;
	}
private:

}Fps;

/*** Keyクラス ***/
class
{
public:
	int input[256];		// キーボード入力情報

	int GetKey()
	{
		char allkey[256];
		GetHitKeyStateAll(allkey);
		for (int i = 0; i < 256; i++)
		{
			if (allkey[i] == 1) // 特定のキーは押されているか
			{
				if (input[i] < DEF_KEY_PRESS_TIME) // 長押し上限まで押されているかどうか
				{
					input[i] = input[i] + 1; // 保存
				}
			}
			else if (allkey[i] == 0) // 特定のキーは押されていないか
			{
				input[i] = 0;
			}
		}
		return 0;
	}
private:

}Key;

#ifdef DEF_JOYPAD_VALID
/*** Joypadクラス ***/
class
{
public:
	DINPUT_JOYSTATE input;				// JoyPad入力情報
	unsigned char input_X_Z1 = 0;		// JoyPad「×」前回値

private:

}JPad;
#endif /* DEF_JOYPAD_VALID */

8.Picture.hの実装

Picture.hは画像読み込み用モジュールとして実装。

Picture.h
#pragma once

/*** Pictureクラス ***/
class
{
public:
	int Back;
	int Block1;
	int Block2;
	int Block3;
	int Block4;
	int Block5;
	int MChara;
	int Enemy1;
	int Title;

	void Read()
	{
		Back   = LoadGraph("./picture/Back.png");
		Block1 = LoadGraph("./picture/Block_1.png");
		Block2 = LoadGraph("./picture/Block_2.png");
		Block3 = LoadGraph("./picture/Block_3.png");
		Block4 = LoadGraph("./picture/Block_4.png");
		Block5 = LoadGraph("./picture/Block_5.png");
		MChara = LoadGraph("./picture/MainChara.png");
		Enemy1 = LoadGraph("./picture/Enemy_1.png");
		Title  = LoadGraph("./picture/Title.png");
	}

private:

}Pic;

9.Stage.hの実装

Stage.hはステージ座標やステージクリアタイムを扱い、ステージの描画処理をするモジュールとして作成する。
以下の実装を行う。

  • ステージ座標の読み込み
  • ステージクリアタイムの読み込み
  • クリアタイム更新(ステージクリアタイムのファイルに書き込み)
  • ステージ描画処理
Stage.h
#pragma once

/*** Stageクラス ***/
class
{
public:
	void Read()
	{
		FILE* fp_stage_1_1;			// ステージ位置情報のファイル
		FILE* fp_stage_1_1_rslt;	// ステージクリアタイムのファイル

		/*** ファイルオープン ***/
		fp_stage_1_1 = fopen("./stage/Stage_1_1.txt", "r");
		if (fp_stage_1_1 == NULL)
		{
			printf("ファイルオープンエラー");
			exit(EXIT_FAILURE);
		}

		fp_stage_1_1_rslt = fopen("./stage/Stage_1_1_result.txt", "r");
		if (fp_stage_1_1_rslt == NULL)
		{
			printf("ファイルオープンエラー");
			exit(EXIT_FAILURE);
		}

		/*** ステージ座標読み込み ***/
		for (int y = 0; y < STG_BLOCK_Y_MAX; y++)
		{
			for (int x = 0; x < STG_BLOCK_X_MAX; x++)
			{
				(void)fscanf(fp_stage_1_1, "%d", &Cood.Blo[x][y]);
				for (int y_exp = 0; y_exp < CELL; y_exp++)
				{
					for (int x_exp = 0; x_exp < CELL; x_exp++)
					{
						Cood.Pix[x_exp + x * CELL][y_exp + y * CELL] = Cood.Blo[x][y];
					}
				}
			}
		}

		/***クリアタイム読み込み ***/
		for (int x = 0; x < RANK_MAX; x++)
		{
			(void)fscanf(fp_stage_1_1_rslt, "%f", &Rank[x]);
		}

		/*** クローズ ***/
		fclose(fp_stage_1_1);
		fclose(fp_stage_1_1_rslt);
	}

	void UpdateTime()
	{
		float tmp_rank = static_cast<float>((GetNowCount() - StartCount) / MillSecond);

		/* クリアタイムをソート */
		Sta.Rank[RANK_4TH] = tmp_rank;
		std::sort(Sta.Rank, Sta.Rank + RANK_MAX);

		/* クリアタイムがランクインしていたら、クリアタイム更新 */
		if (Sta.Rank[RANK_4TH] != tmp_rank)
		{
			FILE* fp_stage_1_1_rslt;	// ステージクリアタイムのファイル

			/*** ファイルオープン ***/
			fp_stage_1_1_rslt = fopen("./stage/Stage_1_1_result.txt", "w");
			if (fp_stage_1_1_rslt == NULL)
			{
				printf("ファイルオープンエラー");
				exit(EXIT_FAILURE);
			}

			/***クリアタイム書き込み ***/
			for (int x = 0; x < RANK_MAX; x++)
			{
				fprintf(fp_stage_1_1_rslt, "%f\n", Sta.Rank[x]);
			}

			/*** クローズ ***/
			fclose(fp_stage_1_1_rslt);
		}
	}

	void Out(int* PosX)
	{
		int pic = 0;
		for (int x = 0; x < STG_BLOCK_X_MAX; x++)
		{
			for (int y = 0; y < STG_BLOCK_Y_MAX; y++)
			{
				switch (Cood.Blo[x][y])
				{
				case 0:
					pic = Pic.Back;
					break;
				case 1:
					pic = Pic.Block1;
					break;
				case 2:
					pic = Pic.Block2;
					break;
				case 3:
					pic = Pic.Block3;
					break;
				case 4:
					pic = Pic.Block4;
					break;
				case 5:
					pic = Pic.Block5;
					break;
				default:
					break;
				}
				DrawGraph(CELL * x + *PosX, CELL * y, pic, TRUE);
			}
		}
	}

	// ステージ座標
	struct
	{
		int Blo[STG_BLOCK_X_MAX][STG_BLOCK_Y_MAX];
		int Pix[STG_X_MAX][STG_Y_MAX];
	}Cood;

	// ステージクリアタイム
	float Rank[RANK_MAX] = { RANK_TIME_INI, RANK_TIME_INI, RANK_TIME_INI, RANK_TIME_INI };

	// 開始カウント
	int StartCount = 0;

private:

}Sta;

10.Scene.hの実装

シーン管理をこのモジュールで行う。
シーン毎の出力処理をシーンに合わせて呼び出す実装を行う。

Title.h
#pragma once

/*** Sceneクラス ***/
class
{
public:
	/* シーン出力 */
	void Out()
	{
		switch (now)
		{
		case SCE::SCE_00_TIT:
			/* タイトルシーン出力 */
			now = Tit.Out();
			break;
		case SCE::SCE_01_ACT:
			/* アクションシーン出力 */
			now = Act.Out();
			break;
		default:
			break;
		}
	}

private:
	// 現在のシーン
	int now = SCE::SCE_00_TIT;

}Sce;

11.Title.hの実装

タイトルシーンになっている際の描画処理の実装を行う。

Title.h
#pragma once

/*** Titleクラス ***/
class
{
public:
	int Out()
	{
		int ret = SCE::SCE_00_TIT;

		/*** タイトル画面の描画 ***/
		DrawGraph(0, 0, Pic.Title, TRUE);

		/*** Press Enterの描画 ***/
		DrawFormatStringFToHandle(TIT_ENTER_POS_X, TIT_ENTER_POS_Y, Col.Black, Fon.FH[30], "Press Enter");

		/*** クリアタイムの描画 ***/
		for (int i = 0; i < RANK_DISP_NUM; i++)
		{
			DrawFormatStringFToHandle(RANK_POS_X, static_cast<float>(RANK_POS_Y + i * 10), Col.Black, Fon.FH[10], "No.%d:%6.2f s", i + 1, Sta.Rank[i]);
		}

		/*** アクションシーンに移行 ***/
		if ((Key.input[KEY_INPUT_RETURN] > 0)		// Enter押下
#ifdef DEF_JOYPAD_VALID
			|| (JPad.input.Buttons[9] == 128)		// // JoyPad:「options」ボタン押下
#endif /* DEF_JOYPAD_VALID */
		)
		{
			Sta.StartCount = GetNowCount(); // 開始カウント設定
			ret = SCE::SCE_01_ACT; // アクションシーンに移行
		}
		return ret;
	}

private:

}Tit;

12.Action.hの実装

Action.hでは、アクションシーンとなっている際に、接触判定や移動計算などを行い描画する実装を行う。
基本的にはActクラスのOut関数を実行周期(60FPS)毎に実行する。
ActクラスのOut関数は実行周期ごとに以下の処理をする。

  • Update():1実行周期ごとの更新処理
  • Judge():ダッシュ判定、ブロック接触予測判定、移動方向判定、ゴール判定、終了判定
  • Cal():敵の移動(接触判定)、X方向移動計算、Y方向移動計算
  • Sta.Out():ステージ描画
  • Cha():メインキャラ描画
  • Ene():敵キャラ描画
  • Disp():アクションシーンの表示系(クリアタイム、コマンド説明)描画
  • アクションシーン終了の場合、クリアタイム更新処理、タイトルシーン移行

Action.hの実装を以下に記載する。

Action.h
#pragma once

/*** Actionクラス ***/
class ACTION
{
public:
	ACTION()
	{
		Sta_PosX = STG_X_MIN;

		MainChar.Pos.X = CHA_POS_X_INI;
		MainChar.Pos.Y = CHA_POS_Y_INI;
		MainChar.Pos.Yin = CHA_POS_Y_INI;
		MainChar.Dir = DIR_NONE;
		MainChar.PicDir = DIR_RI;
		MainChar.Touch = DIR_NONE;
		MainChar.Fall = FALSE;
		Mov.JumpState = JUMP_OFF;
		Mov.Y = 0;
		Cou = 0;

		Enem1.Pos.X = ENE1_POS_X_INI;
		Enem1.Pos.Y = ENE1_POS_Y_INI;
		Enem1.Dir = DIR_RI;
		Enem1.Touch = DIR_NONE;

		Enem2.Pos.X = ENE2_POS_X_INI;
		Enem2.Pos.Y = ENE2_POS_Y_INI;
		Enem2.Dir = DIR_RI;
		Enem2.Touch = DIR_NONE;

		Goal = FALSE;
		EndFlag = FALSE;
	}

	/*** 更新用関数 ***/
	void Update()
	{
		MainChar.Dir = DIR_NONE;
		MainChar.Touch = DIR_NONE;

		/*** メインキャラの四角座標情報更新 ***/
		MainChar.Cor.RiUp.Ce = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y];
		MainChar.Cor.RiDo.Ce = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y + CELL - 1];
		MainChar.Cor.LeUp.Ce = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y];
		MainChar.Cor.LeDo.Ce = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y + CELL - 1];
	}

	/*** 判定 ***/
	void Judge()
	{
		/*** ダッシュ判定 ***/
		if ((Key.input[KEY_INPUT_F] > 0) // F押下(長押しも有効)
#ifdef DEF_JOYPAD_VALID
			|| (JPad.input.Buttons[0] == 128) // JoyPad:「□」押下
#endif /* DEF_JOYPAD_VALID */
			)
		{
			Mov.Dash = ON;
			Mov.X = MOVEX_D;	// X方向移動量(ダッシュ時)
		}
		else
		{
			Mov.Dash = OFF;
			Mov.X = MOVEX;		// X方向移動量(通常時)
		}

		/* メインキャラの四角の左右は、Mov.Xをもとに接触予測 */
		MainChar.Cor.RiUp.Ri = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1 + Mov.X][MainChar.Pos.Y];
		MainChar.Cor.RiDo.Ri = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1 + Mov.X][MainChar.Pos.Y + CELL - 1];
		MainChar.Cor.LeUp.Le = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX - Mov.X][MainChar.Pos.Y];
		MainChar.Cor.LeDo.Le = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX - Mov.X][MainChar.Pos.Y + CELL - 1];

		/* メインキャラの四角の上下は、Y位置の1pixel移動した場合の接触予測 */
		MainChar.Cor.RiUp.Up = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y - 1];
		MainChar.Cor.LeUp.Up = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y - 1];
		MainChar.Cor.RiDo.Do = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y + CELL];
		MainChar.Cor.LeDo.Do = Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y + CELL];

		/*** 接触予測判定 ***/
		// ブロック接触判定 右
		if (MainChar.Cor.RiUp.Ri != PIC_BACK || MainChar.Cor.RiDo.Ri != PIC_BACK)	// 右に行こうとした際、右上、右下の右側の属性がBack以外になるか
		{
			MainChar.Touch |= DIR_RI;
		}
		// ブロック接触判定 左
		if (MainChar.Cor.LeUp.Le != PIC_BACK || MainChar.Cor.LeDo.Le != PIC_BACK)	// 左に行こうとした際、左上、左下の左側の属性がBack以外になるか
		{
			MainChar.Touch |= DIR_LE;
		}
		// ブロック接触判定 上
		if (MainChar.Cor.RiUp.Up != PIC_BACK || MainChar.Cor.LeUp.Up != PIC_BACK)	// 上に行こうとした際、右上、左上の上側の属性がBack以外になるか
		{
			MainChar.Touch |= DIR_UP;
		}
		// ブロック接触判定 下
		if (MainChar.Cor.RiDo.Do != PIC_BACK || MainChar.Cor.LeDo.Do != PIC_BACK)	// 下に行こうとした際、右下、左下の下側の属性がBack以外になるか
		{
			MainChar.Touch |= DIR_DO;
		}

		/*** 移動方向判定 ***/
		if ((Key.input[KEY_INPUT_W] == 1 || Key.input[KEY_INPUT_UP] == 1)	// W(or↑)押下(長押しは無効)
#ifdef DEF_JOYPAD_VALID
			|| ((JPad.input.Buttons[1] == 128) && (JPad.input_X_Z1 == 0)) // JoyPad「×」押下(JPad.input.Buttons[1]==128)
#endif /* DEF_JOYPAD_VALID */
		)
		{
			if ((Mov.JumpState == JUMP_OFF) && ((MainChar.Touch & DIR_DO) == DIR_DO)) // ジャンプしていないとき、かつ地面に接してるとき
			{
				Mov.JumpState = JUMP_UP; // ジャンプする

#ifdef DEF_SOUND_VALID
				/*** Jump音再生 ***/
				Snd.PlayJumpSound();
#endif /*  DEF_SOUND_VALID */
			}
		}
		else if ((Key.input[KEY_INPUT_D] > 0 || Key.input[KEY_INPUT_RIGHT] > 0) // D(or→)押下(長押しも有効)
#ifdef DEF_JOYPAD_VALID
			|| (JPad.input.POV[0] > 0 && JPad.input.POV[0] < 18000) // JoyPad「→」押下
#endif /* DEF_JOYPAD_VALID */
		)
		{
			MainChar.Dir = DIR_RI;		// メインキャラ右向き
			MainChar.PicDir = DIR_RI;	// メインキャラ右向き(描画用)
		}
		else if ((Key.input[KEY_INPUT_A] > 0 || Key.input[KEY_INPUT_LEFT] > 0) // A(or←)押下(長押しも有効)
#ifdef DEF_JOYPAD_VALID
			|| (JPad.input.POV[0] > 18000 && JPad.input.POV[0] < 36000) // JoyPad「←」押下
#endif /* DEF_JOYPAD_VALID */
		)
		{
			MainChar.Dir = DIR_LE;		// メインキャラ左向き
			MainChar.PicDir = DIR_LE;	// メインキャラ左向き(描画用)
		}

		/*** ゴール判定 ***/
		if (abs(Sta_PosX) > GOAL_POS_X)
		{
			Goal = TRUE;
		}

		/*** 終了判定 ***/
		if ((Key.input[KEY_INPUT_ESCAPE] > 0) ||	// Esc押下
			(MainChar.Fall == TRUE) ||				// 落下判定
			(Enem1.Touch != DIR_NONE) ||			// 敵1当たり判定
			(Enem2.Touch != DIR_NONE) ||			// 敵2当たり判定
			(Goal == TRUE)							// ゴール判定
#ifdef DEF_JOYPAD_VALID
			|| (JPad.input.Buttons[12] == 128)		// JoyPad「PS」押下
#endif /* DEF_JOYPAD_VALID */
		)
		{
			EndFlag = TRUE;
		}
	}

	/*** 移動計算 ***/
	void Cal()
	{
		/*** 敵1移動 ***/
		if (Enem1.Dir == DIR_RI)
		{
			Enem1.Pos.X = Enem1.Pos.X + Mov.X; /* この敵はメインキャラのダッシュしたい気分に合わせて速度を変える */
			if (Enem1.Pos.X >= WIN_MAX_X - CELL)
			{
				Enem1.Dir = DIR_LE;
			}
		}
		else
		{
			Enem1.Pos.X = Enem1.Pos.X - Mov.X; /* この敵はメインキャラのダッシュしたい気分に合わせて速度を変える */
			if (Enem1.Pos.X <= WIN_MIN_X)
			{
				Enem1.Dir = DIR_RI;
			}
		}

		/*** 敵1接触判定右 ***/
		if ((MainChar.Pos.X + CELL >= Enem1.Pos.X + ENEMY_HIT_RANGE) &&
			(MainChar.Pos.X + CELL < Enem1.Pos.X + CELL) &&
			(MainChar.Pos.Y >= Enem1.Pos.Y) &&
			(MainChar.Pos.Y < Enem1.Pos.Y + CELL))
		{
			Enem1.Touch = DIR_RI;
		}
		/*** 敵1接触判定左 ***/
		else if ((MainChar.Pos.X >= Enem1.Pos.X) &&
			(MainChar.Pos.X < Enem1.Pos.X + CELL - ENEMY_HIT_RANGE) &&
			(MainChar.Pos.Y >= Enem1.Pos.Y) &&
			(MainChar.Pos.Y < Enem1.Pos.Y + CELL))
		{
			Enem1.Touch = DIR_LE;
		}

		/*** 敵2移動 ***/
		if (Enem2.Dir == DIR_RI)
		{
			Enem2.Pos.X = Enem2.Pos.X + Mov.X; /* この敵はメインキャラのダッシュしたい気分に合わせて速度を変える */
			if (Enem2.Pos.X >= WIN_MAX_X - CELL)
			{
				Enem2.Dir = DIR_LE;
			}
		}
		else
		{
			Enem2.Pos.X = Enem2.Pos.X - Mov.X; /* この敵はメインキャラのダッシュしたい気分に合わせて速度を変える */
			if (Enem2.Pos.X <= WIN_MIN_X)
			{
				Enem2.Dir = DIR_RI;
			}
		}

		/*** 敵2接触判定右 ***/
		if ((MainChar.Pos.X + CELL >= Enem2.Pos.X + ENEMY_HIT_RANGE) &&
			(MainChar.Pos.X + CELL < Enem2.Pos.X + CELL) &&
			(MainChar.Pos.Y >= Enem2.Pos.Y) &&
			(MainChar.Pos.Y < Enem2.Pos.Y + CELL))
		{
			Enem2.Touch = DIR_RI;
		}
		/*** 敵2接触判定左 ***/
		else if ((MainChar.Pos.X >= Enem2.Pos.X) &&
			(MainChar.Pos.X < Enem2.Pos.X + CELL - ENEMY_HIT_RANGE) &&
			(MainChar.Pos.Y >= Enem2.Pos.Y) &&
			(MainChar.Pos.Y < Enem2.Pos.Y + CELL))
		{
			Enem2.Touch = DIR_LE;
		}

		/*** X方向移動計算 ***/
		/* 右移動 */
		if (MainChar.Dir == DIR_RI)
		{
			if ((MainChar.Touch & DIR_RI) != DIR_RI) // 右に行こうとしても接触しないか
			{
				if (MainChar.Pos.X < WIN_MAX_X / 2)														// キャラのX位置がウィンドウ中央より左の場合
				{
					MainChar.Pos.X = MainChar.Pos.X + Mov.X;											// キャラを右に動かす
				}
				else if ((MainChar.Pos.X >= WIN_MAX_X / 2) && (abs(Sta_PosX) < STG_X_MAX - WIN_MAX_X))	// キャラのX位置がウィンドウ中央以上右で、ステージの一番右に到達していない場合
				{
					Sta_PosX = Sta_PosX - Mov.X;														// ステージを左に動かす
				}
			}
		}
		/* 左移動 */
		else if (MainChar.Dir == DIR_LE)
		{
			if ((MainChar.Touch & DIR_LE) != DIR_LE) // 左に行こうとしても接触しないか
			{
				if (MainChar.Pos.X > STG_X_MIN)										// キャラが一番左にいない場合(キャラのX位置がステージの一番左より右)
				{
					MainChar.Pos.X = MainChar.Pos.X - Mov.X;						// キャラを左に動かす
				}
				else if ((MainChar.Pos.X <= STG_X_MIN) && (Sta_PosX < STG_X_MIN))	// キャラが一番左にいて、ステージも一番左ではない場合(キャラのX位置がステージの一番左以下、かつステージ位置も一番左ではない)
				{
					Sta_PosX = Sta_PosX + Mov.X;									// ステージを右に動かす
				}
			}
		}

		/*** Y方向移動計算 ***/
		/* ジャンプ上昇 */
		if (Mov.JumpState == JUMP_UP)
		{
			Cou = Cou + 1;
			T = T_k * ((double)Cou / 60.0);
			// HUMAN PosY cal
			Mov.Y = (int)(pow(T, 2.0));
			for (int y = 1; y <= Mov.Y; y++)
			{
				if ((MOVEY_H_MAX == MainChar.Pos.Yin - MainChar.Pos.Y) ||									// ジャンプの最大高さか
					(Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y - 1] != PIC_BACK) ||			// 左上に何かある
					(Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y - 1] != PIC_BACK))	// 右上に何かある
				{
					Mov.JumpState = JUMP_GRAVITY;				// 自由落下に移行
					Cou = 0;
					break;
				}
				else
				{
					MainChar.Pos.Y = MainChar.Pos.Y - 1;
				}
			}
		}
		/* 自由落下または接触判定(下)なし */
		else if ((Mov.JumpState == JUMP_GRAVITY) || (MainChar.Touch & DIR_DO) != DIR_DO)
		{
			/* 落下していないかどうか判定 */
			if (MainChar.Pos.Y < STG_FALL_Y)
			{
				Cou = Cou + 1;
				T = T_k * ((double)Cou / 60.0);
				// HUMAN PosY cal
				Mov.Y = (int)(pow(T, 2.0));
				for (int y = 1; y <= Mov.Y; y++)
				{
					if ((Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX][MainChar.Pos.Y + CELL] != PIC_BACK) ||				// 左下に何かある
						(Sta.Cood.Pix[MainChar.Pos.X - Sta_PosX + CELL - 1][MainChar.Pos.Y + CELL] != PIC_BACK))	// 右下に何かある
					{
						MainChar.Pos.Yin = MainChar.Pos.Y;	// このY位置を初期Y位置に設定しなおす(ジャンプ終了後の初期位置設定)
						Mov.JumpState = JUMP_OFF;
						Cou = 0;
						break;
					}
					else // 何もなければ移動できるのでY位置を更新
					{
						MainChar.Pos.Y = MainChar.Pos.Y + 1;
					}
				}
			}
			else // 落下
			{
				MainChar.Fall = TRUE;
			}
		}
	}

	/*** メインキャラ描画 ***/
	void Cha()
	{
		if (MainChar.PicDir == DIR_RI)		// 右向きの場合
		{
			DrawGraph(MainChar.Pos.X, MainChar.Pos.Y, Pic.MChara, TRUE);		// 右向きの描画
		}
		else if (MainChar.PicDir == DIR_LE)	// 左向きの場合
		{
			DrawTurnGraph(MainChar.Pos.X, MainChar.Pos.Y, Pic.MChara, TRUE);	// 左向きの描画
		}
	}

	/*** 敵描画 ***/
	void Ene()
	{
		DrawGraph(Enem1.Pos.X, Enem1.Pos.Y, Pic.Enemy1, TRUE); /*** 敵1描画 ***/
		DrawGraph(Enem2.Pos.X, Enem2.Pos.Y, Pic.Enemy1, TRUE); /*** 敵2描画 ***/
	}

	/*** 表示系描画 ***/
	void Disp()
	{
		/*** クリアタイムの描画 ***/
		for (int i = 0; i < RANK_DISP_NUM; i++)
		{
			DrawFormatStringFToHandle(RANK_POS_X, static_cast<float>(RANK_POS_Y + i * 10), Col.Black, Fon.FH[10], "No.%d:%6.2f s", i + 1, Sta.Rank[i]);
		}
		/*** 現在タイムの描画 ***/
		DrawFormatStringFToHandle(TIME_POS_X, TIME_POS_Y, Col.Black, Fon.FH[10], "Time:%6.2f s", static_cast<float>((GetNowCount() - Sta.StartCount) / MillSecond));

		/*** コマンド説明の描画 ***/
		DrawFormatStringFToHandle(COMD_POS_X, COMD_POS_Y, Col.Black, Fon.FH[10], "右移動:[→]or[D]");
		DrawFormatStringFToHandle(COMD_POS_X, COMD_POS_Y + 10, Col.Black, Fon.FH[10], "左移動:[←]or[A]");
		DrawFormatStringFToHandle(COMD_POS_X, COMD_POS_Y + 20, Col.Black, Fon.FH[10], "ジャンプ:[↑]or[W]");
		DrawFormatStringFToHandle(COMD_POS_X, COMD_POS_Y + 30, Col.Black, Fon.FH[10], "ダッシュ:[F]");
		DrawFormatStringFToHandle(COMD_POS_X, COMD_POS_Y + 40, Col.Black, Fon.FH[10], "Titleに戻る:[Esc]");
	}

	/*** Actシーン終了時初期化 ***/
	void EndInit()
	{
		/*** 初期化 ***/
		Sta_PosX = STG_X_MIN;

		MainChar.Pos.X = CHA_POS_X_INI;
		MainChar.Pos.Y = CHA_POS_Y_INI;
		MainChar.Pos.Yin = CHA_POS_Y_INI;
		MainChar.Dir = DIR_NONE;
		MainChar.PicDir = DIR_RI;
		MainChar.Touch = DIR_NONE;
		MainChar.Fall = FALSE;
		Mov.JumpState = JUMP_OFF;
		Mov.Y = 0;
		Cou = 0;

		Enem1.Pos.X = ENE1_POS_X_INI;
		Enem1.Pos.Y = ENE1_POS_Y_INI;
		Enem1.Dir = DIR_RI;
		Enem1.Touch = DIR_NONE;

		Enem2.Pos.X = ENE2_POS_X_INI;
		Enem2.Pos.Y = ENE2_POS_Y_INI;
		Enem2.Dir = DIR_RI;
		Enem2.Touch = DIR_NONE;

		Goal = FALSE;
		EndFlag = FALSE;
	}

	/*** Actシーン本処理 ***/
	int Out()
	{
		int ret = SCE::SCE_01_ACT;

		/*** 更新 ***/
		Update();

		/*** 判定 ***/
		Judge();

		/*** 移動計算 ***/
		Cal();

		/*** ステージ描画 ***/
		Sta.Out(&Sta_PosX);

		/*** メインキャラ描画 ***/
		Cha();
		
		/*** 敵描画 ***/
		Ene();

		/*** 表示系描画 ***/
		Disp();

		/*** ENDフラグ有効時、タイトルシーンに移行 ***/
		if (EndFlag == TRUE)
		{
			/*** クリアタイム更新 ***/
			if (Goal == TRUE)
			{
				Sta.UpdateTime();
			}

			/*** Actシーン終了時初期化 ***/
			EndInit();

			/*** タイトルシーンに移行 ***/
			ret = SCE::SCE_00_TIT;
		}

		return ret;
	}

	// ステージX座標
	int Sta_PosX = STG_X_MIN;

	// メインキャラ構造体
	struct
	{
		struct
		{
			int X = CHA_POS_X_INI;
			int Y = CHA_POS_Y_INI;
			int Yin = Y;			// Y方向移動前初期値
		}Pos;
		struct
		{
			struct
			{
				int Ri = 0;
				int Up = 0;
				int Ce = 0;
			}RiUp;
			struct
			{
				int Le = 0;
				int Up = 0;
				int Ce = 0;
			}LeUp;
			struct
			{
				int Ri = 0;
				int Do = 0;
				int Ce = 0;
			}RiDo;
			struct
			{
				int Le = 0;
				int Do = 0;
				int Ce = 0;
			}LeDo;
		}Cor;

		int Dir = DIR_NONE;
		int PicDir = DIR_RI;
		int Touch = DIR_NONE;
		int Fall = FALSE;
	}MainChar;

	// 敵キャラ構造体
	struct ENEMY
	{
		struct
		{
			int X = 0;
			int Y = STG_X_MAX - 3 * CELL;
		}Pos;
		struct
		{
			struct
			{
				int Ri = 0;
				int Up = 0;
				int Ce = 0;
			}RiUp;
			struct
			{
				int Le = 0;
				int Up = 0;
				int Ce = 0;
			}LeUp;
			struct
			{
				int Ri = 0;
				int Do = 0;
				int Ce = 0;
			}RiDo;
			struct
			{
				int Le = 0;
				int Do = 0;
				int Ce = 0;
			}LeDo;
		}Cor;

		int Dir = DIR_NONE;
		int Touch = DIR_NONE;
	};

	// 移動用構造体
	struct
	{
		int X = MOVEX;				// X方向移動量
		int Y = 0;					// Y方向移動量
		int JumpState = JUMP_OFF;	// ジャンプ状態
		bool Dash = OFF;			// ダッシュ
	}Mov;

	ENEMY Enem1;
	ENEMY Enem2;
private:
	int Goal = FALSE;
	int EndFlag = FALSE;

	// 2次関数ジャンプ用変数
	int Cou = 0;
	double T = 0.0;
	const double T_k = 20.0;

}Act;

13.Main.cppの実装

Main.cppは前回作成済。
今回作成したモジュールなどをインクルードし、
メイン関数内でシーン出力の本処理を呼び出すように修正した。

Main.cpp
/*** Header File ***/
#define _CRT_SECURE_NO_WARNINGS // C4996無効化

#include <algorithm>
#include<iostream>

#include "DxLib.h"
#include "ProjConf.h"
#include "Sub.h"
#include "Picture.h"
#include "Stage.h"
#include "Title.h"
#include "Action.h"
#include "Scene.h"

// プログラムは WinMain から始まります
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_  HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	ChangeWindowMode(TRUE);							// ウィンドウモードで起動
	if (DxLib_Init() == -1)							// DXライブラリ初期化処理
	{
		return -1;	// エラーが起きたら直ちに終了
	}

	/*** Window Init ***/
	SetWindowText("TEST 2D GAME");					// ウィンドウのタイトル
	SetWindowInitPosition(WIN_POS_X, WIN_POS_Y);	// ウィンドウの位置
	SetGraphMode(WIN_MAX_X, WIN_MAX_Y, 32);			// ウィンドウのサイズ
	SetBackgroundColor(255, 255, 255);				// ウィンドウの背景色
	SetDrawScreen(DX_SCREEN_BACK);					// 描画先画面を裏画面にする
	SetAlwaysRunFlag(TRUE);							// ウインドウ非アクティブ状態でも処理を続行する

	/*** FPS初期化 ***/
	Fps.FPSInit();

	/*** Read ***/
	Col.Read();
	Fon.Read();
	Pic.Read();
	Sta.Read();
#ifdef DEF_SOUND_VALID
	Snd.Read();

	/*** BGM開始 ***/
	Snd.PlayBGMSound();
#endif /* DEF_SOUND_VALID */

	/*** ループ処理 ***/
	while (ScreenFlip() == 0 &&		// 裏画面の内容を表画面に反映
		ClearDrawScreen() == 0 &&	// 画面を初期化
		Key.GetKey() == 0 &&		// キーボード入力情報取得
#ifdef DEF_JOYPAD_VALID
		GetJoypadDirectInputState(DX_INPUT_KEY_PAD1, &JPad.input) == 0 && // JoyPad入力情報取得
#endif /* DEF_JOYPAD_VALID */
		ProcessMessage() == 0)		// ウインドウのメッセージを処理
	{
		/* FPS計測開始 */
		Fps.FPSCheck();

		/* シーン出力 */
		Sce.Out();

		/* FPS表示 */
		DrawFormatStringFToHandle(FPS_POS_X, FPS_POS_Y, Col.Black, Fon.FH[10], "FPS:%5.1f", Fps.Average);

#ifdef DEF_JOYPAD_VALID
		JPad.input_X_Z1 = JPad.input.Buttons[1]; // JoyPad「×」前回値保存
#endif /* DEF_JOYPAD_VALID */

		/* FPSWait */
		Fps.FPSWait();
	}

	WaitKey();						// キー入力待ち

	DxLib_End();					// DXライブラリ使用の終了処理

	return 0;						// ソフトの終了 
}

14.動作確認(遊んでみた)

「3.完成イメージ」にも貼りましたが、実行動画をYoutubeにアップロードしています。
https://www.youtube.com/watch?v=zoAIzxR_2Yo

15.さいごに

ひとまず最低限、2Dスクロールゲームです、と言えるものは作ることができた。

しかし、今回の作成にあたって、
 ・フレームワーク(DXライブラリ)をうまく使用しきれていない
 ・C++機能を使用しきれていない(あまりにあれだったので記事のタグにも入れてない)
 ・再利用性が高くないコードになった、拡張性が低い
といった自分のスキルのなさを痛感しました。
あとはゲーム自体の課題(敵がブロック貫通したりダッシュの気分に合わせて速くなったりというこじつけ仕様)も多い。

まぁ初のゲーム作りということで、
これらはいろいろ勉強して使っていきながら、経験値を積んでいくことにします。

以下の3編構成のうち、Windows版のゲーム作成(2Dスクロール)編の内容がおわりました。

次の「html版のゲーム作成(2Dスクロール)編」では、
今までに作成したC++ソースコードをEmscriptenを使用してWebAssemblyにコンパイルし、
ウェブブラウザ上で実行するところまでを記載予定です(いつ執筆しよう。。)。

さいごに、前回も記載しましたが、
今までゲーム作成をしてこなかった私が少し調べれば作成できたのは、
先人の方々が残してきたものと、今現在も開発を進めている方々のおかげです。
深く感謝申し上げます。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2