[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を使用してジョイスティックを初期化し、ジョイスティックの状態を定期的に取得するループを実行しています。

[C++] 243 SDL : テトリス製作 その2 既存ソース改変

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

GitHubから入手したソースコードをNO IDE仕様に改変しました。今回はChatGPTのサポートなしです。

Debug.hの一部を書き換え、フォントファイル clacon2.ttfを入手してHomeの/Library/Fontsディレクトリに置き、ビルドを成功させました。

ソースコードはIntel Mac時代のものですが、Apple Siliconでも問題なく動きます。

次はPS4のワイヤレスコントローラで操作できるようにします。

#if defined _MSC_VER
#define HP_BREAK __debugbreak();
#elif defined __arm__
#define HP_BREAK __builtin_trap();
#elif defined __GNUC__
// #define HP_BREAK __asm__ ("int $3"); // コメント化
#define HP_BREAK __builtin_trap();    // この行を追加
#else
#error Unsupported compiler
#endif
# コンパイラ設定他
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config sdl2 --cflags --libs) -std=c++11
LDFLAGS =

# includeパス(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/sdl2/2.26.2/include \
		-I/opt/homebrew/Cellar/sdl2_ttf/2.20.2/include \
		-I/Volumes/DATA_m1/code/cpp/mylib/include

# ライブラリ(-l)
LIBRARY0 = -lsdl2 -lsdl2_ttf

# ライブラリパス(-L)
LIBRARY = -L/opt/homebrew/Cellar/sdl2/2.26.2/lib \
		-L/opt/homebrew/Cellar/sdl2_ttf/2.20.2/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(shell find $(SRCDIR) -type f)

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(addprefix $(OBJDIR), $(patsubst ./src/%.cpp,/%.o,$(SRCS)))

# 実行ファイル
TARGETDIR = ./bin
TARGET = Tetris
	
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LIBRARY) $(LDFLAGS)

# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
テスト操作(60秒)

GitHub(sdl2-tetris)

[C++] 242 SDL : テトリス製作 その1 Bing-ChatGPT

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

Bing-ChatGPTにアシストしてもらいながらテトリスを作っていきます。

ネットから見つけてきたサンプルコードが旧バージョンのSDLで作られていたため、旧関数をSDL2の新関数に置き換えつつ修正を加えました。

何とか真っ暗な画面から何かが描画されている状態にまで持ってこれましたが、そもそもの設計があまり筋が良いとも思えないので、GitHubで見つけた他のソースを土台にすることにしました。次回以降記事にします。

最後までGoogle検索せずにSDLのソースコード読み込みとChatGPTだけで形にできました。Google検索だったら1日以上掛かってもおかしくない修正が数時間に短縮できて生産性は爆上がりです。

[C++] 241 SDL : ChatGPTを使う

ChatGPTにテトリスのプログラムについて質問したところ、まずSDL描画の骨組みを説明しましたが、字数制限でテトリスの内容にまではたどり着けませんでした。

[C++] 240で紹介したMacOS特有のトラブルへの解決策も盛り込まれたなかなかのサンプルコードです。

ChatGPTを使えばStackOverflow英語版のお世話になることもなくなるのでしょうか。私にとってプログラミング学習のターニングポイントになるかもしれません。

ここまでAIが進化してくると、人間としては何を知っているか、何をクリエイトできるか、だけではない新たな価値観を模索する時代になったと認識せざるを得ません。

[C++] 240 SDL : ウィンドウの表示 MacOS固有のトラブル

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

SDL2を使ってウィンドウを表示させるところでつまずきました。

イベントをチェックするループを設定しないとダメなようです。日本語ユーザーの方がおなじみのStackOverflow英語版で解決法を見つけてくれていました。

ただこのコードではマウスクリックやキー押下に反応してしまうので、スクリーンショットが撮れません。少し修正が必要です。

一応MacOSでも使えるようにしてくれていますが、初心者は実質門前払いのような状況です。

クロスプラットホームと言いながらdmgインストーラにはMacの文字は見当たらず。どう見てもSDLの開発者はMacユーザーに冷たいですね。ゲーム界隈の方はApple嫌いが多いとか?

#include "SDL2/SDL.h"
#include <stdio.h>

int main(int argc, char* argv[]) {
    SDL_Window *window;
    SDL_Init(SDL_INIT_VIDEO);

    window = SDL_CreateWindow(
        "SDL Window",
        100,
        100,
        640,
        480,
        0
    );

    if (window == NULL) {
        printf("ウィンドウを生成できません: %s\n", SDL_GetError());
        return 1;
    }

    // MacOS以外では以下ループは不要
    SDL_Event e;
    bool quit = false;
    while (!quit){
        while (SDL_PollEvent(&e)){
            if (e.type == SDL_QUIT){
                quit = true;
            }
            if (e.type == SDL_KEYDOWN){
                quit = true;
            }
            if (e.type == SDL_MOUSEBUTTONDOWN){
                quit = true;
            }
        }
    }

    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

参考サイト

[C++] 239 FLTK : Fl_Boxに高さ不足の画像を表示した際の座標 アスペクト比

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

Fl_Boxに高さ不足の画像を表示した際、画像の左上を原点(0, 0)とするための調整が少しややこしかったので書き留めておきます。

横幅が一致していて高さが足りない画像の場合はその縦位置は適当に置かれているわけではなく、残った空白がアスペクト比で配分されていました。幾何学の世界ですね。

今回はアスペクト比が4:3なので、縦の空白は上部:下部 = 3:4になっています。これにより画像の左上Y座標をゼロにするための調整値が算出できます。

Fl_PNG_Image *png = new Fl_PNG_Image((paths[0]).c_str());
anaW = wInput -> value();
anaH = hInput -> value();

aspect = stof(string(anaW))/stof(string(anaH));

if (aspect > 4/3){
    newH = stof(string(anaH)) * 480/stof(string(anaW));
    cout << "newH: " << newH << endl;
    png_copy = png -> copy(480,round(newH));

    // Y調整値算出
    adjY = round((360 - newH) * 3/7);

} else {
    newW = stof(string(anaW)) * 360/stof(string(anaH));
    cout << "newW: " << newW << endl;
    png_copy = png -> copy(round(newW),360);
}

showBox -> image(png_copy);
showBox -> redraw();

[C++] 238 FLTK : アスペクト比固定、小数点以下の扱い

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

動画編集アプリの左下に画像をコマ送りで表示させるのですが、アスペクト比を元動画のままにして、経過時間を小数点第3位までとしました。

floatは有効桁数が7桁なので小数点以下で四捨五入しても、1.570000のような表示になります。そこでfloatを文字列に変換し、無限ループを利用して末尾が0である限り削除し続けるようにしました。

ただこの方法では小数点以下の桁数を固定できないのがデメリットです。Pythonであれば簡単に桁数を揃えられそうですが、C++は面倒なので最小限の手立てに留めておきます。

void showCB(Fl_Widget*, void*) {
    paths = getFilePath("/Volumes/DATA_m1/VideoEditor/images", "png");
    // スライダーの数値を取得
    slider -> bounds(0, (double)(paths.size() -1));

    showData(fpsFile);
    fpsChar = fpsInput -> value();
    if (fpsChar == NULL){
        return;
    }

    string fpsStr = string(fpsChar);
    sec = frameNum/stof(fpsStr);
    sec2 = round(sec*1000)/1000;
    sec2_str = to_string(sec2);

    while(1){
        if (sec2_str.back() == '0' or sec2_str == "0."){
            sec2_str.pop_back();
        } else {
            break;
        }
    }
    imageSec -> value(sec2_str.c_str());

    imageNumStr = to_string(frameNum +1) + "/" + to_string(paths.size());
    imageNum -> value(imageNumStr.c_str());

    Fl_PNG_Image *png = new Fl_PNG_Image((paths[0]).c_str());
    anaW = wInput -> value();
    anaH = hInput -> value();

    aspect = stof(string(anaW))/stof(string(anaH));

    if (aspect > 4/3){
        newH = stof(string(anaH)) * 480/stof(string(anaW));
        cout << "newH: " << newH << endl;
        png_copy = png -> copy(480,round(newH));
    } else {
        newW = stof(string(anaW)) * 360/stof(string(anaH));
        cout << "newW: " << newW << endl;
        png_copy = png -> copy(round(newW),360);
    }

    showBox -> image(png_copy);
    showBox -> redraw();
}

[C++] 237 FLTK : マウスをドラッグして四角形を描く その3 フレーム描画 Fl_Box

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

FLTKのfl_frame関数でフレームを描くと残像が重なって使えなかったのですが、最新のフレームだけ表示すると正常になりました。

また前のコードでは拡げたエリアを縮小することはできませんでした。このコードでは伸縮自在です。

ただし複数のフレームを描くと直前のフレームが消えてしまうので、これを残す方法を考える必要があります。あくまで見た目の問題であって、フレームの座標自体は保存できるようになっています。

課題はありますが、かなり前進したと言えるでしょう。

#pragma once
#include <FLstd.h>
#include "cppstd.h"

class BoxXY : public Fl_Box {
    Fl_Input* inputFrame1;
    Fl_Input* inputFrame2;
    Fl_Input* inputFrame3;
    Fl_Input* inputFrame4;

    public:
        BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4);
        void draw();
    private:
        int handle(int);
};
#include "BoxXY.h"

extern Fl_Box *show_box;
int xx1, yy1, xx2, yy2, xx0, yy0;

BoxXY::BoxXY(int x, int y, int width_input, int height_input, Fl_Input* input1,Fl_Input* input2, Fl_Input* input3,Fl_Input* input4) : Fl_Box(FL_FLAT_BOX, x, y, width_input, height_input, "") 
{
    this->inputFrame1 = input1;
    this->inputFrame2 = input2;
    this->inputFrame3 = input3;
    this->inputFrame4 = input4;
}

void BoxXY::draw(){
    // 線の色(第1引数)はAからXの24段階グレースケール4値(top, left, bottom, rightの順)
    // 灰色線
    fl_frame("LLLL",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
    // 白線
    // fl_frame("XXXX",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
    // 黒線
    // fl_frame("AAAA",xx1+65, yy1+190, xx0-xx1, yy0-yy1);
}

int BoxXY::handle(int event){
    switch (event) {
        case FL_PUSH:{
            xx1 = Fl::event_x() - 65;
            yy1 = Fl::event_y() - 190;

            inputFrame1->value(to_string(xx1).c_str());
            inputFrame1->textsize(12);

            inputFrame2->value(to_string(yy1).c_str());
            inputFrame2->textsize(12);

            return 1;
        }
        case FL_RELEASE:{
            xx2 = Fl::event_x() - 65;
            yy2 = Fl::event_y() - 190;

            inputFrame3->value(to_string(xx2).c_str());
            inputFrame3->textsize(12);

            inputFrame4->value(to_string(yy2).c_str());
            inputFrame4->textsize(12);

            return 1;
        }
        case FL_DRAG:{
            xx0 = Fl::event_x() - 65;
            yy0 = Fl::event_y() - 190;

            inputFrame3->value(to_string(xx0).c_str());
            inputFrame3->textsize(12);

            inputFrame4->value(to_string(yy0).c_str());
            inputFrame4->textsize(12);

            show_box -> redraw(); // この行を追加
            this->redraw();

            return 1;
        }
        default:
            return Fl_Box::handle(event);
    }
}