メニュー画面の実装について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ヨッシー
記事: 13
登録日時: 2年前

メニュー画面の実装について

#1

投稿記事 by ヨッシー » 2年前

c/c++とDxLibを使ってシューティングゲームを作ろうとしているものです。
前回は「弾がうまく発射できない」というトピックで質問させていただきました。
今回はタイトルにもあるようにメニュー画面の実装について助力をいただきたいと思い質問させていただきます。

コード:


MenuScene.h

#pragma once
#include "AbstractScene.h"

class MenuScene : public AbstractScene
{
public:
	MenuScene(IOnSceneChangedListener* impl, const Parameter& parameter);
	virtual ~MenuScene() = default;
	void update() override;
	void draw() const override;
};

MenuScene.cpp

#include "MenuScene.h"
#include "Pad.h"
#include "Keyboard.h"
#include <DxLib.h>

MenuScene::MenuScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter)
{

}

void MenuScene::update()
{
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE)) {
		Parameter parameter;
		const bool stackClear = false;
		_implSceneChanged->onSceneChanged(eScene::Game, parameter, stackClear);
		return;
	}
}

void MenuScene::draw() const
{
	DrawString(100, 100, "メニュー画面", GetColor(255, 255, 255));
}

GameScene.cpp

#include "GameScene.h"
#include <DxLib.h>
#include "Macro.h"
#include "Background01Spell.h"
#include "Keyboard.h"
#include "Pad.h"

using namespace std;

const char* GameScene::ParameterTagStage = "ParameterTagStage"; //パラメータのタグ「ステージ」
const char* GameScene::ParameterTagLevel = "ParameterTagLevel"; //パラメータのタグ「レベル」

GameScene::GameScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter)
{
	
	_backgroundSpell = make_shared<Background01spell>();
	_player = make_shared<Player>();
	_playerbullet = make_shared<PlayerBulletCollection>(); //共有されているクラスはPlayerBulletCollectionであることに注意
	_playeroption = make_shared<PlayerOptionCollection>(); //共有されているクラスはPlayerOptionCollectionであることに注意
	_board = make_shared<Board>();
}

void GameScene::update()
{
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE) == 1) {
		Parameter parameter;
		const bool stackClear = true;
		_implSceneChanged->onSceneChanged(eScene::Menu, parameter, stackClear);
		return;
	}
	_backgroundSpell->update();
	_playerbullet->update();
	_playeroption->update();
	_player->update();
	_board->update();
}

void GameScene::draw() const
{
	_backgroundSpell->draw();
	_playerbullet->draw();
	_playeroption->draw();
	_player->draw();
	_board->draw();
}

GameScene.cppでEscキーを押したらメニュー画面に移るようにしてあります。(GameScene::update()参照)
また、MenuScene.cppでEscキーを押したらゲーム画面に戻るようにしてあります。(MenuScene::update()参照)
実際にデバッグしてみると、一瞬メニュー画面に移るのですが、すぐにゲーム画面に戻ってしまいました。
試行錯誤の末、シーン転換のトリガーに同じEscキーを使っていることが原因であることがわかりましたが、キーを押し続けてもシーンが移らないようにできないでしょうか。(もう一回押すことで処理が行われる様にしたい)

さらに、メニュー画面からゲーム画面に戻ると最初の場面に戻ってしまいます。
これを最初の場面に戻らずメニュー画面に移った時の場面に戻るようにしたく、アドバイスをお願いします。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: メニュー画面の実装について

#2

投稿記事 by みけCAT » 2年前

GameScene::update() では

コード:

if (Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE) == 1) {
というように1と比較しているのに対し、MenuScene::update()では

コード:

if (Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE)) {
と、0以外の任意の値の時に処理を行うようにしていますね。
Keyboard::getIns()->getPressingCount の仕様がわからないので確実ではないですが、
MenuSceneでもGameSceneと同じ用に返り値が1のときだけ処理を行うようにしてみてはどうでしょうか?

メニュー画面からゲーム画面に戻った時に前の場面に戻るようにするには、
メニュー画面からゲーム画面に戻る時にゲーム画面の状態を初期化しないようにするといいと思います。
具体的にどこで初期化されているかは提示されたコードからは読み取れず、
そもそもメニュー画面からゲーム画面に戻る時にゲーム画面の状態が初期化されている、
という推測もハズレているかもしれませんが…
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヨッシー
記事: 13
登録日時: 2年前

Re: メニュー画面の実装について

#3

投稿記事 by ヨッシー » 2年前

返信が遅くなってしまいすみません。
シーン管理はスタックで行っていたので、新しくゲーム画面をpushせず、そのままメニュー画面をpopすればいいのではと思い、やってみたところうまくいきました。

そこでもう一つ相談なのですが、ゲーム中に流れる音楽をメニュー画面ではストップさせたいと思ったのですが、StopSoundMemでサウンドハンドルを指定するのは面倒だし、自分のプログラムの仕組み上難しい気がしたので自分なりに再生中の音楽ファイルを探し、それを返す関数を作ってみたのですがうまく機能してくれませんでした。
下にソースコード載せときます。

コード:

ImageMusic.h

#pragma once
#include "Singleton.h"
#include <vector>

class ImageMusic final : public Singleton<ImageMusic> {

public:
	ImageMusic();
	~ImageMusic() = default;
	void load() {} //LooperのコンストラクタでImageのコンストラクタを起動するために空っぽ
	void release(int);
	int getMusic(); //再生中の音データを調べ、その変数を返す

	int getTitle() { return Title; }
	int getStage01() { return Stage01; }
	int getStage02() { return Stage02; }
	int getStage03() { return Stage03; }
	int getStage04() { return Stage04; }
	int getStage05() { return Stage05; }
	int getStage06() { return Stage06; }
	int getStageEX() { return StageEX; }

private:
	int myLoadSoundMem(char*);

	std::vector<int> _images;
	int Title;
	int Stage01;
	int Stage02;
	int Stage03;
	int Stage04;
	int Stage05;
	int Stage06;
	int StageEX;
};

ImageMusic.cpp

#include "ImageMusic.h"
#include <DxLib.h>
#include "Macro.h"

ImageMusic::ImageMusic()
{
	Title = myLoadSoundMem("タイトル用の音楽のパス");
	Stage01 = myLoadSoundMem("1面用");
	Stage02 = myLoadSoundMem("2面用");
	Stage03 = myLoadSoundMem("3面用");
	Stage04 = myLoadSoundMem("4面用");
	Stage05 = myLoadSoundMem("5面用");
	Stage06 = myLoadSoundMem("6面用");
	StageEX = myLoadSoundMem("EX面用");
}

int ImageMusic::myLoadSoundMem(char* fileName)
{
	int ret = LoadSoundMem(fileName);
	_images.push_back(ret);
	return ret;
}

void ImageMusic::release(int fileName)
{
	int ret = fileName;
	DeleteSoundMem(ret);
}

int ImageMusic::getMusic()
{
	int ret;
	const int size = _images.size();
	for (int i = 0; i < size; i++) {
		if (CheckSoundMem(_images[i])) {
			ret = _images[i];
			break;
		}
	}
	return ret;
}
簡単な概要としては、myLoadSoundMem関数で音データをそれぞれの変数と_imagesという名前のvectorに格納し、getMusic関数内でCheckSoundMem関数を用いて、それぞれの音データが再生中かを調べています。
その戻り値をStopSoundMem(ImageMusic::getIns() -> getMusic) みたいな感じで引数に用いればうまくいくと思ったのですがダメでした。ちなみにgetIns()はメンバにアクセスするためのもので、ここ以外で使った時にもうまく機能しているのでここがおかしいわけではないと思います。(Singletonパターンだったはず) アドバイスお願いいたします。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: メニュー画面の実装について

#4

投稿記事 by みけCAT » 2年前

ヨッシー さんが書きました:
2年前
その戻り値をStopSoundMem(ImageMusic::getIns() -> getMusic) みたいな感じで引数に用いればうまくいくと思ったのですがダメでした。
「みたいな感じ」って具体的にどのような感じなのでしょうか?
getMusic関数を呼ばず、関数のポインタをStopSoundMemに渡す感じ、ということですか?
「ダメでした」「うまく機能してくれませんでした」とは、具体的にどううまく行かなかったのですか?
(デタラメな値が帰る、帰る値はそれっぽいけど再生の停止ができない、など)

また、このgetMusicの実装だと、_images の要素に再生中の音声が無い場合に
未初期化(不定)のretの値がそのまま返され、危ないかもしれません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヨッシー
記事: 13
登録日時: 2年前

Re: メニュー画面の実装について

#5

投稿記事 by ヨッシー » 2年前

別のプロジェクトで試してみたところ、同じプログラムで、再生も停止も問題なくできました。
ソース載せときます。

コード:

#include <DxLib.h>
#include "Bullet.h"
#include "Define.h"
#include "ImageMusic.h"


using namespace std;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    ChangeWindowMode(TRUE);
    if (DxLib_Init() == -1) return -1;
    SetDrawScreen(DX_SCREEN_BACK);

        ClearDrawScreen(); //裏画面を削除
        PlaySoundMem(ImageMusic::getIns()->getStage03(), DX_PLAYTYPE_LOOP, FALSE);
        printfDx("サウンドハンドル %d", ImageMusic::getIns()->getStage03());
        printfDx("サウンドハンドル %d", ImageMusic::getIns()->getMusic());
        ScreenFlip();
        WaitKey();
        StopSoundMem(ImageMusic::getIns()->getMusic());
        WaitKey();
        PlaySoundMem(ImageMusic::getIns()->getStage02(), DX_PLAYTYPE_LOOP, FALSE);
        WaitKey();
        DxLib_End();//DXライブラリ終了処理
        return 0;
}
vectorに格納された値を調べてみましたが、戻り値はどちらも同じでした。
前回うまくいかなかった理由は、再生と停止を別のcppファイルもしく別のシーン(再生はTitleScene, 停止はGameScene)でやっていたからではと思い、GameScene内だけで管理してみることにしましたが、これもうまくいきませんでした。

コード:

GameScene.cpp

#include "GameScene.h"
#include <DxLib.h>
#include "Macro.h"
#include "Background01Spell.h"
#include "Keyboard.h"
#include "Pad.h"
#include "ImageMusic.h"

using namespace std;

const char* GameScene::ParameterTagStage = "ParameterTagStage"; //パラメータのタグ「ステージ」
const char* GameScene::ParameterTagLevel = "ParameterTagLevel"; //パラメータのタグ「レベル」

GameScene::GameScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter)
{
	_backgroundSpell = make_shared<Background01spell>();
	_player = make_shared<Player>();
	_playerbullet = make_shared<PlayerBulletCollection>(); //共有されているクラスはPlayerBulletCollectionであることに注意
	_playeroption = make_shared<PlayerOptionCollection>(); //共有されているクラスはPlayerOptionCollectionであることに注意
	_board = make_shared<Board>();
	PlaySoundMem(ImageMusic::getIns()->getStage01(), DX_PLAYTYPE_LOOP, FALSE);
}

void GameScene::update()
{
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_ESCAPE) == 1) {
		StopSoundMem(ImageMusic::getIns()->getMusic());
		Parameter parameter;
		const bool stackClear = false;
		_implSceneChanged->onSceneChanged(eScene::Menu, parameter, stackClear);
		return;
	}
	_backgroundSpell->update();
	_playerbullet->update();
	_playeroption->update();
	_player->update();
	_board->update();
}

void GameScene::draw() const
{
	_backgroundSpell->draw();
	_playerbullet->draw();
	_playeroption->draw();
	_player->draw();
	_board->draw();
}

こうなる原因はまだよくわかっておらず自分も色々と試しているところですが、とりあえず進捗報告とさせていただきます。

また、ご指摘いただいたgetMusicの実装ですが、以下の様に変更してみました。

コード:

int ImageMusic::getMusic()
{
	int ret;
	const int size = _images.size();
	for (int i = 0; i < size; i++) {
		if (CheckSoundMem(_images[i])) {
			ret = _images[i];
			return ret;
		}
	}
	return 0;
}

ヨッシー
記事: 13
登録日時: 2年前

Re: メニュー画面の実装について

#6

投稿記事 by ヨッシー » 2年前

新しい発見があったので追記します。

コード:

#include <DxLib.h>
#include "Bullet.h"
#include "Define.h"
#include "ImageMusic.h"


using namespace std;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    ChangeWindowMode(TRUE);
    if (DxLib_Init() == -1) return -1;
    SetDrawScreen(DX_SCREEN_BACK);

        ClearDrawScreen(); //裏画面を削除
        PlaySoundMem(ImageMusic::getIns()->getStage03(), DX_PLAYTYPE_LOOP, FALSE);
        WaitKey();
        DeleteSoundMem(ImageMusic::getIns()->getStage03());
        PlaySoundMem(ImageMusic::getIns()->getStage05(), DX_PLAYTYPE_LOOP, FALSE);
        printfDx("%d\n", ImageMusic::getIns()->getStage05());
        printfDx("%d\n", ImageMusic::getIns()->getMusic());
        ScreenFlip();
        WaitKey();
        DxLib_End();//DXライブラリ終了処理
        return 0;
}
このプログラムの様に前に再生していた音データをDeleteSoundMem関数を使って消去すると、なぜかCheckSoundMem関数に引っかかってしまうことがわかりました。

またこの発見によって、getMusic内では、今再生されている音データよりも、前に再生されてデリートされた音データ(自分の場合タイトル画面で再生していた音楽)の方に反応して、想定していた戻り値と違うものが戻ってきていたということがわかりました。

一応これでメニュー画面に切り替わった際に再生が停止するようになりましたが、自分のPCはそれほど性能が良くないので、これから効果音や敵の処理などを実装していった場合にメモリに負荷がかかってしまうのではないかと危惧しています。

それほど負荷がかからないのならいいですが、デリートしても処理がおかしくならないようにはできないでしょうか。一応PCのスペックを載せときます。
デバイス名 DESKTOP-GOO4TMT
プロセッサ Intel(R) Core(TM) i3-7100U CPU @ 2.40GHz 2.40 GHz
実装 RAM 8.00 GB (7.89 GB 使用可能)
情報が少なくて申し訳ないです。ちなみにデバイスID、プロダクトIDは他人に知らせると悪用されるかも的なことが書いてあったので載せていません。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: メニュー画面の実装について

#7

投稿記事 by みけCAT » 2年前

CheckSoundMem関数はエラーの時は -1 を返します。
-1 は、条件式として使った場合は真とみなされます。
削除後のサウンドハンドルを渡すと、エラーになると予想できます。

コード:

		if (CheckSoundMem(_images[i]) == 1) {
として、「再生中」の時のみハンドルを返すようにするとどうでしょうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ヨッシー
記事: 13
登録日時: 2年前

Re: メニュー画面の実装について

#8

投稿記事 by ヨッシー » 2年前

if(ChackSoundMem(_images))という風に書くと0以外なら何でも拾っちゃうんですね、なるほど。
アドバイスのおかげでちゃんとデリートしても大丈夫なようになりました。
また相談したいときは今度は別のトピックでしようと思います。ありがとうございました!
それでは。

返信

“C言語何でも質問掲示板” へ戻る