[C++] 253 SDL : テトリス画面にベストスコア5つを表示 その1 ChatGPT

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリス画面にベストスコア5つを表示するためのxmlファイル作成関数をChatGPTに書かせました。

いともあっさり書いてくれました。検証はしていませんが以下の通りです。おそらく新データは何位であろうと5番目になるはずなので、後で降順ソートが必要でしょう。

ChatGPT Plusは現在月額20ドルですが、今後DAZNのようにハイペースで値上げさせないためにも競合AIの頑張りに期待したいところです。

Q: xmlファイルのentryデータは5個まででscoreが5つの最小値を上回った場合はデータを入れ替える、というコードを書いてください

以下は5つ以下のエントリーがある場合のコードです。

void AddDataToXML(string fileName, string score, string date, string playTime)
{
    // ファイルをパースする
    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(fileName.c_str());

    if (!result)
    {
        std::cerr << "XMLの読み込みに失敗しました。" << std::endl;
        return;
    }

    // エントリーを格納するノードを取得する
    pugi::xml_node entryNode = doc.child("data").child("entry");

    // 新しいエントリーを作成する
    pugi::xml_node newEntryNode = doc.append_child("entry");
    newEntryNode.append_child("score").text() = score.c_str();
    newEntryNode.append_child("date").text() = date.c_str();
    newEntryNode.append_child("playtime").text() = playTime.c_str();

    // エントリー数をカウントする
    int entryCount = 0;
    for (pugi::xml_node node = entryNode; node; node = node.next_sibling("entry"))
    {
        entryCount++;
    }

    // エントリーが5個未満の場合は新しいエントリーを追加する
    if (entryCount < 5)
    {
        return;
    }

    // スコアが5つの最小値を上回っている場合は新しいエントリーを追加し、古いエントリーを削除する
    pugi::xml_node minScoreNode = entryNode;
    int minScore = std::atoi(entryNode.child_value("score"));
    for (pugi::xml_node node = entryNode.next_sibling("entry"); node; node = node.next_sibling("entry"))
    {
        int scoreVal = std::atoi(node.child_value("score"));
        if (scoreVal < minScore)
        {
            minScoreNode = node;
            minScore = scoreVal;
        }
    }

    if (std::atoi(score.c_str()) > minScore)
    {
        doc.remove_child(minScoreNode);
        doc.append_child(newEntryNode);
    }

    doc.save_file(fileName.c_str());
}

[C++] 252 SDL : テトリス画面に現在時刻と経過時間を表示

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスの画面に現在時刻と経過時間を表示させました。ゲームオーバー時に経過時間をストップさせる仕組みを作るのに一手間掛かりました。メモリを消費する書き方になっていますが、vectorの要素数を常に1個になるようにすれば改善できます。

次はテトロミノのホールド、これから生成するテトロミノの表示あたりでしょうか。このような機能を付けるとかなりヌルくなりますね。

ChatGPTは型推論のautoを多用しますが、使わないよう指示すれば正確な型で書いてくれます。

ここまでプログラミングのハードルが下がるとホビープログラマがかなり増えるのではないでしょうか。あくまで基本を押さえていないと使いこなせないとは思いますが。

extern std::chrono::steady_clock::time_point start;
vector<string> times;

void Game::DrawPlaying( Renderer& renderer )
{
	<中略>

	char text[128];
	snprintf( text, sizeof(text), "Lines: %u", m_numLinesCleared );
	renderer.DrawText( text, 20, 50, 0xffffffff );
	snprintf( text, sizeof(text), "Level: %u", m_level );
	renderer.DrawText( text, 20, 90, 0xffffffff );
	snprintf( text, sizeof(text), "Score: %u", m_score );
	renderer.DrawText( text, 20, 130, 0xffffffff );
	snprintf( text, sizeof(text), "High score: %u", hiScore );
	renderer.DrawText( text, 20, 170, 0xffffffff );

	// 現在日時を取得する
	auto now = std::chrono::system_clock::now();
	auto time_t = std::chrono::system_clock::to_time_t(now);
	std::tm tm = *std::localtime(&time_t);

	// 日時を指定されたフォーマットで文字列に変換する
	std::stringstream ss1;
	ss1 << std::put_time(&tm, "%Y/%m/%d");
	std::string str1 = ss1.str();

	std::stringstream ss2;
	ss2 << std::put_time(&tm, "%H:%M:%S");
	std::string str2 = ss2.str();

	snprintf(text, sizeof(text), "Current time: %s", str1.c_str());
	renderer.DrawText(text, 850, 50, 0xffffffff);

	snprintf(text, sizeof(text), str2.c_str());
	renderer.DrawText(text, 1100, 90, 0xffffffff);

	// 現在時刻を取得する
    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
	
	// 経過時間を算出する
	std::chrono::milliseconds elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

	// 経過時間を表示する
	if (m_gameState != kGameState_GameOver){
		// 経過時間を"MM:SS"形式で表示する
		snprintf(text, sizeof(text), "Elapsed time: %02d:%02d", elapsed.count() / 1000 / 60, elapsed.count() / 1000 % 60);

		string str(text, text + sizeof(text));
		// cout << "str: " << str << endl;
		times.push_back(str);
		renderer.DrawText(text, 850, 130, 0xffffffff);

	} else {
		string time = times.back();

		// cout << "プレイ時間表示: " << time << endl;
		snprintf(text, sizeof(text), "%s", time.c_str());
		renderer.DrawText(text, 850, 130, 0xffffffff);
	}
}

[C++] 251 SDL : テトリスの最高スコアをxmlファイルに保存

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

製作中のテトリスはアプリ起動中の最高スコアを表示していて、閉じるとデータは消えてしまいます。

そこでxmlファイルに最高スコアを保存し、起動の度にxmlファイルを読み込み、ゲームオーバー時に更新するようにしました。同時にバックアップファイルも作成しています。

xmlファイル作成・読込はJavaアプリで扱ったことはありますが、今回はChatGPTを使ってサクッと作りました。Homebrewでインストールしたpugixmlライブラリを使っています。

ある程度プログラミングが分かる方はChatGPT Plusを活用されることをお勧めします。学習・作業効率がこれまでと段違いです。

#include "pugixml.hpp"

extern string xmlFile;
extern string xmlFileBackup;

int readXML(){
	pugi::xml_document doc;

	cout << "readXML関数 xmlFile: " << xmlFile << endl;

    // XMLファイルを読み込む
    pugi::xml_parse_result result = doc.load_file(xmlFile.c_str());

    if (!result) {
        std::cerr << "XMLの読み込みに失敗しました。" << std::endl;
        return -1;
    }

    // ルートノードを取得する
    pugi::xml_node root = doc.child("properties");

    // HISCOREのエントリーを取得する
    pugi::xml_node hiscore_node = root.find_child_by_attribute("entry", "key", "HISCORE");

    // エントリーの値を取得する
    hiScore = hiscore_node.text().as_int();

    std::cout << "HISCOREの値は" << hiScore << "です。" << std::endl;

	return hiScore;
}

int writeXML(){
	pugi::xml_document doc;

	cout << "writeXML関数 xmlFile: " << xmlFile << endl;

    // XMLファイルを読み込む
    pugi::xml_parse_result result = doc.load_file(xmlFile.c_str());

    if (!result) {
        std::cerr << "XMLの読み込みに失敗しました。" << std::endl;
        return -1;
    }

    // ルートノードを取得する
    pugi::xml_node root = doc.child("properties");

    // HISCOREのエントリーを取得する
    pugi::xml_node hiscore_node = root.find_child_by_attribute("entry", "key", "HISCORE");

    // エントリーの値を書き換える
    hiscore_node.text().set(hiScore);

    // XMLファイルを上書きする
    doc.save_file(xmlFile.c_str());

    std::cout << "更新するHISCOREの値は" << hiScore << "です。" << std::endl;

	// ファイルを読み込む
    std::ifstream file(xmlFile);
    if (!file.good()) {
        std::cerr << "ファイルの読み込みに失敗しました。" << std::endl;
        return -1;
    }

    // バックアップファイルを作成する
    std::ofstream backup(xmlFileBackup);
    if (!backup.good()) {
        std::cerr << "バックアップファイルの作成に失敗しました。" << std::endl;
        return -1;
    }

    // バックアップファイルにデータを書き込む
    backup << file.rdbuf();

    // ファイルを閉じる
    file.close();
    backup.close();

    std::cout << "バックアップファイルを作成しました。" << std::endl;
}

[C++] 250 SDL : テトロミノが偏って生成しないようにする Tetris

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

テトリスでテトロミノが偏って生成しないようにするため、ワールドルールに従って7種類あるテトロミノ番号を程よく生成する関数を作成しました。

for文の作成にはChatGPTにも手伝ってもらいました。

書いていて気がつきましたが、配列内要素が残り1つになったらその数を自動的に戻り値にすればいいですね。CPUへの負荷も減ります。改良版として追記しておきます。

vector<int> arr;
int GeneratorCount;

int TetroTypeGenerator()
{	
	int tetroType;

	if (GeneratorCount == 0){
		arr = {0, 1, 2, 3, 4, 5, 6};
	}

	while(1){
		tetroType = rand() % kNumTetrominoTypes;

		if (arr.size() == 0){
			arr = {0, 1, 2, 3, 4, 5, 6};
			cout << "テトロミノ配列が空になったのでリセットしました" << endl;
		}

		for (auto it = arr.begin(); it != arr.end(); ) {
			if (*it == tetroType) {
				it = arr.erase(it);

				cout << "tetroType: " << to_string(tetroType) << endl;

				GeneratorCount++;
				return tetroType;

			} else {
				it++;
			}
		}
	}
}
tetroType: 2
tetroType: 4
tetroType: 1
tetroType: 0
tetroType: 3
tetroType: 5
tetroType: 6
テトロミノ配列が空になったのでリセットしました
tetroType: 6
tetroType: 2
tetroType: 1
tetroType: 5
tetroType: 4
tetroType: 0
tetroType: 3
テトロミノ配列が空になったのでリセットしました
tetroType: 5
tetroType: 3
tetroType: 0

以下、改良版

vector<int> arr;
int GeneratorCount;

int TetroTypeGenerator()
{	
	int tetroType;

	if (GeneratorCount == 0){
		arr = {0, 1, 2, 3, 4, 5, 6};
	}

	while(1){
		tetroType = rand() % kNumTetrominoTypes;

		if (arr.size() == 0){
			arr = {0, 1, 2, 3, 4, 5, 6};
			cout << "テトロミノ配列が空になったのでリセットしました" << endl;
		} else if (arr.size() == 1){
			tetroType = arr.front();
			arr = {};

			cout << "tetroType残り1: " << to_string(tetroType) << endl;

			GeneratorCount++;
			return tetroType;
		}

		for (auto it = arr.begin(); it != arr.end(); ) {
			if (*it == tetroType) {
				it = arr.erase(it);

				cout << "tetroType: " << to_string(tetroType) << endl;

				GeneratorCount++;
				return tetroType;

			} else {
				it++;
			}
		}
	}
}
tetroType: 2
tetroType: 4
tetroType: 0
tetroType: 3
tetroType: 1
tetroType: 5
tetroType残り1: 6
テトロミノ配列が空になったのでリセットしました
tetroType: 3
tetroType: 1
tetroType: 2
tetroType: 0
tetroType: 6
tetroType: 5
tetroType残り1: 4
テトロミノ配列が空になったのでリセットしました
tetroType: 1
tetroType: 2
tetroType: 3

[C++] 249 FLTK : Fl_Menu_Itemの書式

[M1 Mac, Monterey 12.6.3, clang 13.0.0, FLTK 3.8.1, ChatGPT Plus, NO IDE]

SFTP通信アプリのメンテナンスです。即時ファイル移動ができないiCloudの代替ツールとして開発しています。PC間のファイル移動が瞬時にできます(Windows機とはSamba接続)。

アプリに新たなPCを登録する際、Fl_Menu_Itemの書き方を間違えたためにプルダウンメニュークリック時に落ちるようになってしまいました。以前からクセのある書式にトラブルの可能性を感じていたのでまとめておきます。

Fl_Menu_ItemオブジェクトはArrayを作成する際、最後にヌルポインタを入れることでArrayの終了が分かるようになっています。これにより要素数を扱わずに済みます。要素数を入れても問題はないですが、最後のヌルポインタもカウントした数値を使用します。

// 要素数を使わない場合
Fl_Menu_Item pc_type[] = {
        {"Mac mini Intel"},
        {"MacBook Air M1"},
        {"MacBook Intel"},
        {"Windows11PC"},
        {"Windows10PC"},
        {"LinuxPC"},
        {0}
};

// 要素数を使う場合
Fl_Menu_Item pc_type[7] = {
        {"Mac mini Intel"},
        {"MacBook Air M1"},
        {"MacBook Intel"},
        {"Windows11PC"},
        {"Windows10PC"},
        {"LinuxPC"},
        {0}
};

// 最後のヌルポインタは省略可能(見た目では数が合わない)
Fl_Menu_Item pc_type[7] = {
        {"Mac mini Intel"},
        {"MacBook Air M1"},
        {"MacBook Intel"},
        {"Windows11PC"},
        {"Windows10PC"},
        {"LinuxPC"}
};

[C++] 248 SDL : コンストラクタの初期化リスト ChatGPT

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

コンストラクタの初期化リストについてあまりよく分かっていなかったので、ChatGPTに説明してもらいました。

大分理解が深まったように思います。

Q1: SDLアプリの以下のコードを解説してください。
Game::Game() : m_deltaTimeSeconds( 0.0f ) ,
m_framesUntilFall( s_initialFramesPerFallStep ) ,
m_framesPerFallStep( s_initialFramesPerFallStep ) , m_numUserDropsForThisTetronimo( 0 ) ,
m_numLinesCleared( 0 ) ,
m_level( 0 ) ,
m_score( 0 ) ,
m_hiScore( 0 ) ,
m_gameState( kGameState_TitleScreen )
{ m_field.staticBlocks = nullptr; }

このコードは、SDLを使用して作成されたゲームのコンストラクタであり、以下のように各メンバー変数を初期化しています。

  • m_deltaTimeSeconds:前のフレームからの時間差を表す変数を0.0fで初期化しています。
  • m_framesUntilFall:新しいテトロミノが落下するまでのフレーム数を表す変数であり、s_initialFramesPerFallStep(定数)で初期化しています。
  • m_framesPerFallStep:テトロミノが1段落下するのに必要なフレーム数を表す変数であり、s_initialFramesPerFallStepで初期化しています。
  • m_numUserDropsForThisTetronimo:ユーザーがテトロミノを操作した際に、落下させた回数を表す変数であり、0で初期化しています。
  • m_numLinesCleared:ゲームでクリアしたラインの数を表す変数であり、0で初期化しています。
  • m_level:ゲームのレベルを表す変数であり、0で初期化しています。
  • m_score:ゲームのスコアを表す変数であり、0で初期化しています。
  • m_hiScore:過去に達成した最高スコアを表す変数であり、0で初期化しています。
  • m_gameState:ゲームの状態を表す変数であり、kGameState_TitleScreenで初期化しています。

また、m_field.staticBlocksnullptrで初期化しています。m_fieldは、ゲームフィールドを表す構造体であり、staticBlocksは静的なブロックの配列を指すポインタです。初期化することで、メモリが確保されていないことを表しています。後で動的に確保する予定があると思われます。

Q2: 以下のコンストラクタでも問題ないですか?
Game::Game() {
m_deltaTimeSeconds = 0.0f;
m_framesUntilFall = s_initialFramesPerFallStep;
m_framesPerFallStep = s_initialFramesPerFallStep; m_numUserDropsForThisTetronimo = 0;
m_numLinesCleared = 0;
m_level = 0;
m_score = 0;
m_hiScore = 0;
m_gameState = kGameState_TitleScreen;
m_field.staticBlocks = nullptr; }

はい、このコードでも問題ありません。このコードは、先に解説したコードと同様に、各メンバー変数を初期化しています。:での初期化リストの代わりに、コンストラクタの本文で各メンバー変数を初期化しています。{}で囲む必要はなく、必要最低限のコードになっているため、より簡潔になっています。ただし、先に解説したコードよりも少し冗長であると言えます。

Q3: 以下のコンストラクタでも問題ないですか?
Game::Game() : m_deltaTimeSeconds( 0.0f ) ,
m_framesUntilFall( s_initialFramesPerFallStep ) ,
m_framesPerFallStep( s_initialFramesPerFallStep ) , m_numUserDropsForThisTetronimo( 0 ) ,
m_numLinesCleared( 0 ) ,
m_level( 0 ) ,
m_score( 0 ) ,
m_hiScore( 0 ) ,
m_gameState( kGameState_TitleScreen ) ,
m_field.staticBlocks(nullptr)
{}

はい、このコードは正しい構文です。初期化リストを使用して各メンバー変数を初期化しており、最後の要素であるm_field.staticBlocksnullptrで初期化しています。また、コンストラクタの本体には、ブロックの終了を示すための{}が含まれています。したがって、このコードは問題ありません。

Q3への回答ではChatGPTは正しいとしていますが、実際はm_field.staticBlocks(nullptr)といった書き方ではエラーになります。メンバ変数のプロパティはこのようには初期化できません。{ m_field.staticBlocks = nullptr; }が正しい表現です。

[C++] 247 SDL : 画面に記号を表示する ChatGPT

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

SDLアプリの初期画面に”Press □ to start”を表示させようとすると文字化けしてしまいます。

ChatGPTの回答の通りフォントを変えたりUnicode表記に変えても解決に至らなかったのですが、質問の仕方を変えてヒントを導き出しました。

TTF_RenderText_Blended()関数をTTF_RenderUTF8_Solid()関数に書き換えるだけでした。

ゲームコントローラーに関するコードを書かせてもデタラメだらけでしたが、公式ドキュメントがしっかりしているカテゴリーについてはそれなりのコードを書いてくれますね。

所要時間は30分程度でした。Google検索活用だったら2,3時間、下手したら半日以上掛かっていたかもしれません。

修正前
修正後
void Renderer::DrawText( const char* text, int x, int y, uint32_t rgba /*= 0xffffffff */ )
{
	SDL_assert( text );

	SDL_Color color = MakeSDL_Colour( rgba );

	SDL_Surface* pSurface = TTF_RenderUTF8_Solid(mFont, text, color);
	// SDL_Surface* pSurface = TTF_RenderText_Blended( mFont, text, color ); 旧コード

	SDL_Texture* pTexture = SDL_CreateTextureFromSurface( m_pSdlRenderer, pSurface );
	int width, height;
	SDL_QueryTexture(pTexture, NULL, NULL, &width, &height);
	SDL_Rect dstRect = { x, y, width, height };
	SDL_RenderCopy( m_pSdlRenderer, pTexture, nullptr, &dstRect );
	SDL_DestroyTexture( pTexture );
	SDL_FreeSurface( pSurface );
}

ChatGPT調査
Q1: SDLアプリの画面に□を文字化けさせずに表示させる方法を教えてください

SDLアプリで正しくUnicodeテキストを表示させるためには、以下の手順を行う必要があります。

  1. 必要なフォントをインストールする
  2. フォントをロードする
  3. テキストを描画する

まず、必要なフォントをインストールする必要があります。□を含むテキストを表示させるためには、Unicodeの範囲に含まれるフォントを使用する必要があります。例えば、Noto Sansフォントは多言語対応のフォントで、Unicodeの範囲に含まれる多くの文字をサポートしています。このフォントをインストールすることをお勧めします。

次に、フォントをロードする必要があります。SDL_ttfライブラリを使用して、フォントをロードできます。フォントをロードするには、まずTTF_Init()関数を使用してライブラリを初期化し、TTF_OpenFont()関数を使用してフォントファイルを開きます。以下は、フォントをロードするためのサンプルコードです。

if (TTF_Init() == -1) {
    printf("TTF_Init failed: %s\n", TTF_GetError());
    return false;
}

TTF_Font* font = TTF_OpenFont("path/to/font.ttf", 24);
if (!font) {
    printf("TTF_OpenFont failed: %s\n", TTF_GetError());
    return false;
}

最後に、テキストを描画する必要があります。TTF_RenderUTF8_Solid()関数を使用して、テキストをサーフェスにレンダリングし、それをテクスチャに変換してレンダラーに描画することができます。以下は、テキストを描画するためのサンプルコードです。

SDL_Surface* surface = TTF_RenderUTF8_Solid(font, "□", { 255, 255, 255 });
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect rect = { 0, 0, surface->w, surface->h };
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);

このコードでは、□をサーフェスにレンダリングし、それをテクスチャに変換してレンダラーに描画しています。

なお、フォントをロードする際に、フォントファイルのパスを正しく指定することが重要です。また、描画する前に、描画色を適切に設定する必要があります。

Q2: SDL_Surface* surface = TTF_RenderUTF8_Solid(font, “□”, { 255, 255, 255 }); SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); 
と以下のコードを統合させてください。
renderer.DrawText( “Press □ to start”, renderer.GetLogicalWidth()/2 – 100, renderer.GetLogicalHeight()/2, 0xffffffff );

以下のように、DrawText関数の内部に上記のコードを追加することができます。

void Renderer::DrawText(const std::string& text, int x, int y, Uint32 color)
{
    SDL_Surface* surface = TTF_RenderUTF8_Solid(m_font, text.c_str(), { 255, 255, 255 });
    SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface);

    SDL_Rect dstRect = { x, y, surface->w, surface->h };

    SDL_SetTextureColorMod(texture, color >> 16, (color >> 8) & 0xff, color & 0xff);

    SDL_RenderCopy(m_renderer, texture, nullptr, &dstRect);

    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

このようにコードを変更すると、DrawText関数を呼び出すときに”Press □ to start”のように□を表示することができます。

[C++] 246 SDL : ゲームコントローラーを認識させる Intel Mac

[Intel Mac, Big Sur 11.6.5, clang 13.0.0, SDL 2.24.1, ChatGPT Plus, NO IDE]

Apple Silicon MacではワイヤレスPS4コントローラーが操作できませんでしたが、Intel Macでは動きました。

void App::Run()
{
	Uint32 lastTimeMs = SDL_GetTicks();
	auto lastTime = std::chrono::high_resolution_clock::now();

	bool bDone = false;
	while( !bDone )
	{
		GameInput gameInput = {};
		SDL_JoystickUpdate();

		// respond to events
		SDL_Event event;
		while( SDL_PollEvent( &event ) )
		{
			if(event.type == SDL_QUIT)
			{
				bDone = true;
			}

			if(event.type == SDL_JOYBUTTONDOWN)
			{
				switch (event.jbutton.button){
					case 3:{	// △ボタン:終了
						bDone = true;
						break;
					}
					case 2:{	// □ボタン:スタート
						gameInput.bStart = true;
						// cout << "□ボタン押下" << endl;
						break;
					}
					case 13:{	// 左ボタン
						gameInput.bMoveLeft = true;
						break;
					}
					case 14:{	// 右ボタン
						gameInput.bMoveRight = true;
						break;
					}
					case 1:{	// ○ボタン:時計回り回転
						gameInput.bRotateClockwise = true;
						break;
					}
					case 0:{	// ×ボタン:反時計回り回転
						gameInput.bRotateAnticlockwise = true;
						break;
					}
					case 12:{	// 下ボタン:一気に落下
						gameInput.bHardDrop = true;
						break;
					}
					case 11:{	// 上ボタン:徐々に落下
						gameInput.bSoftDrop = true;
						break;
					}
					case 4:{	// shareボタン:停止(効かない)
						gameInput.bPause = true;
						// cout << "shareボタン押下" << endl;
						break;
					}
					default:{
						cout << "default" << endl;
						break;
					}
				}
			}
		}

		Uint32 currentTimeMs = SDL_GetTicks();
		Uint32 deltaTimeMs = currentTimeMs - lastTimeMs;
		lastTimeMs = currentTimeMs;
		HP_UNUSED( deltaTimeMs );
		
		auto currentTime = std::chrono::high_resolution_clock::now();
		auto deltaTime = currentTime - lastTime;
		std::chrono::microseconds deltaTimeMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(deltaTime);
		float deltaTimeSeconds = 0.000001f * (float)deltaTimeMicroseconds.count();
		lastTime = currentTime;

		mGame->Update( gameInput, deltaTimeSeconds );

		mRenderer->Clear();
		mGame->Draw(*mRenderer );
		mRenderer->Present();
	}
}

[C++] 245 SDL : ゲームコントローラーを認識させる Apple Silicon

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

以下のコードでPS4のワイヤレスゲームコントローラーを認識させることができましたが、肝心のボタンが反応しません。

有線接続すると操作できるようになりました。

ワイヤレス接続については明日以降Intel Macで試してみます。

bool App::Init( bool bFullScreen, unsigned int displayWidth, unsigned int displayHeight )
{
	if( SDL_Init( SDL_INIT_EVERYTHING ) != 0)
	{
		fprintf( stderr, "SDL failed to initialise: %s\n",SDL_GetError() );
		return false;
	}

	<中略>

	// mJoystick
	mJoystick = SDL_JoystickOpen(0);
    if (!mJoystick) {
        std::cerr << "Failed to open joystick: " << SDL_GetError() << std::endl;
        return false;
    }

	int num_axes = SDL_JoystickNumAxes(mJoystick);
    int num_buttons = SDL_JoystickNumButtons(mJoystick);

	printf("Joystick: %s\n", SDL_JoystickName(mJoystick));
	cout << "JoystickNumAxes: " << num_axes << endl;
	cout << "JoystickNumButtons: " << num_buttons << endl;

	return true;
}
Joystick: PS4 Controller
JoystickNumAxes: 6
JoystickNumButtons: 16

ChatGPT調査
Q1: SDL2アプリに接続したPS4コントローラーがApple Siliconではうまく動作しないという情報はありますか

一部のユーザーが報告しているように、SDL2アプリに接続したPS4コントローラーがApple SiliconベースのMacコンピューターで動作しないことがあります。これは、macOS上でのHIDの処理に関連する問題である可能性があります。

ただし、問題が発生するかどうかは、使用するSDL2バージョンやmacOSのバージョンによって異なります。最新のSDL2バージョンである2.0.16では、この問題が解決されたと報告されています。また、macOS 12(Monterey)では、この問題が修正されると予想されています。

したがって、問題が発生している場合は、まず最新のSDL2バージョンを使用することをお勧めします。また、macOSのアップデートがリリースされるのを待つこともできます。

Q2: 一部ユーザーの報告についてリンクを貼ってください

以下にApple SiliconでのPS4コントローラーの問題に関する報告を示します。

ただし、これらは個人の報告であり、すべてのユーザーに当てはまるわけではありません。また、Apple SiliconでのSDL2アプリケーションでの問題には関する報告もありますので、注意してください。

[C++] 244 SDL : ゲームコントローラーについてChatGPTから情報収集

[M1 Mac, Monterey 12.6.3, clang 13.0.0, SDL 2.26.2, ChatGPT Plus, NO IDE]

FLTKではゲームコントローラーを扱うことはできず、SDLでは可能であるという情報は知っている上でChatGPTに色々聞いてみました。

ChatGPTは平然と嘘をつきますが、追及されるとすぐに謝ります。ネットの情報がすべて正しいと思い込んでいる素直なキャラクターです。

以下、ChatGPTとのやりとりです。

Q1: FLTKでゲームコントローラーを使うことはできますか。

A1:
FLTKは、マウスやキーボードなどの入力デバイスをサポートしていますが、標準的なAPIではゲームコントローラーをサポートしていません。ただし、FLTKのフレームワークは拡張性が高く、さまざまなカスタマイズが可能です。

ゲームコントローラーをFLTKでサポートするには、以下の手順が必要です。

  1. ゲームコントローラーを識別するためのデバイスドライバーを作成します。これには、コントローラーのデバイスドライバーやAPIが必要になる場合があります。
  2. コントローラーの入力を受け取るためのコールバック関数を作成します。これには、コントローラーのボタンや軸の状態を監視するためのコードが必要になります。
  3. コントローラーの入力を処理するためのゲームロジックを実装します。これには、コントローラーの状態に応じたゲームの動作を定義するためのコードが必要になります。

FLTKをカスタマイズしてゲームコントローラーをサポートすることは可能ですが、手間や時間がかかることがあります。代わりに、よりゲームに特化したフレームワークやライブラリを使用することを検討することもできます。

Q2: FLTKでコントローラーの入力を受け取るためのコールバック関数を作成したいのですが、サンプルコードを教えてください

A2:
FLTKでコントローラーの入力を受け取るためのコールバック関数を作成するには、以下のようなサンプルコードを参考にすることができます。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Joystick.H>
#include <iostream>

void joystick_callback(int, int event, int, int, int, int) {
    // ジョイスティックのイベントを処理するコードをここに書く
    std::cout << "Joystick event: " << event << std::endl;
}

int main() {
    Fl_Window window(300, 200, "Joystick Example");

    // ジョイスティックを初期化する
    Fl_Joystick joystick(0);
    joystick.callback(joystick_callback);

    window.show();
    return Fl::run();
}

このサンプルコードでは、Fl_Joystickを使用してジョイスティックを初期化し、joystick_callback関数をコールバック関数として設定しています。joystick_callback関数は、ジョイスティックのイベントを処理するために使用されます。ここでは、単にイベントの種類をコンソールに出力していますが、実際にはこの関数内にジョイスティックの状態に応じた処理を記述することができます。

FLTKの公式ドキュメントには、より詳細なジョイスティックの使用方法やサンプルコードが含まれていますので、必要に応じて参照してください。

Q3: Fl_Joystickは実在するのですか?リンクを貼ってもらえますか

A3:
申し訳ありません、前回の回答でFl_Joystickが存在すると誤った情報を提供してしまいました。FLTKには、標準のAPIとしてジョイスティックをサポートするクラスはありません。

代わりに、サードパーティ製のライブラリを使用する必要があります。例えば、SDLライブラリは、FLTKと互換性があり、ジョイスティックのサポートが含まれています。

以下は、SDLを使用したジョイスティックのサンプルコードです。

#include <SDL2/SDL.h>
#include <iostream>

int main() {
    // SDLを初期化する
    if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
        std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
        return 1;
    }

    // ジョイスティックを開く
    SDL_Joystick* joystick = SDL_JoystickOpen(0);
    if (!joystick) {
        std::cerr << "Failed to open joystick: " << SDL_GetError() << std::endl;
        return 1;
    }

    // ジョイスティックの状態を取得する
    SDL_JoystickUpdate();

    // ジョイスティックの軸の数を取得する
    int num_axes = SDL_JoystickNumAxes(joystick);

    // ジョイスティックのボタンの数を取得する
    int num_buttons = SDL_JoystickNumButtons(joystick);

    // ジョイスティックの状態を処理する
    while (true) {
        // ジョイスティックの状態を更新する
        SDL_JoystickUpdate();

        // 軸の状態を取得する
        for (int i = 0; i < num_axes; i++) {
            int axis_value = SDL_JoystickGetAxis(joystick, i);
            std::cout << "Axis " << i << ": " << axis_value << std::endl;
        }

        // ボタンの状態を取得する
        for (int i = 0; i < num_buttons; i++) {
            bool button_state = SDL_JoystickGetButton(joystick, i);
            std::cout << "Button " << i << ": " << button_state << std::endl;
        }

        // 終了するためのキーが押されたら、ループを抜ける
        if (SDL_QuitRequested()) {
            break;
        }

        // 短時間のウェイトを入れる
        SDL_Delay(10);
    }

    // ジョイスティックを閉じる
    SDL_JoystickClose(joystick);

    // SDLを終了する
    SDL_Quit();

    return 0;
}

このコードでは、SDLを使用してジョイスティックを初期化し、ジョイスティックの状態を定期的に取得するループを実行しています。