内容へ移動
WildTree Wiki
ユーザ用ツール
ログイン
サイト用ツール
検索
ツール
文書の表示
以前のリビジョン
バックリンク
最近の変更
メディアマネージャー
サイトマップ
ログイン
>
最近の変更
メディアマネージャー
サイトマップ
トレース:
ハイハイスクールアドベンチャー_qt版
この文書は読取専用です。文書のソースを閲覧することは可能ですが、変更はできません。もし変更したい場合は管理者に連絡してください。
====== ハイハイスクールアドベンチャー Qt版 ====== ===== あらすじ ===== 2019年神奈山県立ハイ高等学校は 地盤が弱く校舎の老朽化も進んだため、 とうとう廃校にする以外方法がなく なってしまった。 ところで大変な情報を手に入れた。 それは、 「ハイ高校にATOMIC BOMBが仕掛けられている。」 と、いうものだ。 どうやらハイ高が廃校になった時、 気が狂った理科の先生がATOMIC BOMBを 学校のどこかに仕掛けてしまったらしい。 お願いだ。我が母校のコナゴナになった 姿を見たくはない。 早くATOMIC BOMBを取り除いてくれ……!! 行動は英語で、"<動詞>" 或いは、"<動詞>"+"<目的語>"のように入れていただきたい。 例えば、"look room"と入れれば部屋の様子を見ることが出来るという訳だ。 それでは Good Luck!!!............ ===== 概要 ===== M5版を作った勢いで、SDL2版を作ろうと思ったのだが、Textureの扱いがイマイチ判然とせず、動き始めたものの表示が不規則に切り替わり、それを直すくらいならと、Qtにスイッチしたのがこのバージョンである。 入出力部以外はM5版と同じである。 ===== ダウンロードとインストール ===== いくつかの環境用にビルドしたパッケージを用意してあるので、それを使ってインストールすればいろいろ面倒なことなく遊べます。 下にある説明に従ってビルドしてもいいかと思います。 * [[https://www.wildtree.jp/~araki/QHHSAdv110.zip|Windows版]] * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb|Ubuntu 24.04(x64)版]] * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_ubuntu25.04_amd64.deb|Ubuntu 25.04(x64)版]] * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_debian12_arm64.deb|Debian12(ARM64)版]] Windows版はインストーラを走らせるだけです。 Linux版はdeb形式のパッケージになっています。 今どきのデスクトップ環境だと、メニューにも登録されるかと思います。 ご自分の環境にあったパッケージを取得したら、 <code bash> $ sudo apt install -y ./qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb </code> などとしてインストールしてください。 ===== ビルド ===== QtCreatorを使用して作成したので、QtCreatorを使ってビルドするのが簡単だが、cmakeで直接ビルドすることも可能である。 ソースはM5版同様[[https://github.com/wildtree/qhhsadv.git|GitHub]]から取得可能である。 [[https://www.wildtree.jp/~araki/HHSAdv_data.zip|データファイル]]は実行時に自動的に ~/.local/share/WildTreeJP/QHHSAdv の下に展開される。 GitHubからは git clone で取得する。 <code bash> $ git clone https://github.com/wildtree/qhhsadv.git </code> QtCreatorを使用する場合には、CMakeLists.txt がプロジェクトファイルになる。 cmakeでビルドするなら、 <code bash> $ mkdir build $ cd build $ cmake -DCMAKE_INSTALL_PREFIX=/ .. $ make $ sudo make install </code> と、通常の cmakeの作法に従えばいい。 実行は qhhsadv を実行する。 インストールは /opt/qhhsadv に実行ファイル、リソースなどをインストールし、/usr/bin/qhhsadv に起動用のラッパーを、また、デスクトップ環境用のメニューへの追加用の設定ファイルもインストールされる、 {{:hhsadv:qhhsadv.png?400|ハイハイスクールアドベンチャー}} ===== あれこれ ===== ==== ダイアログについて ==== M5版とは異なり、自由度の高いダイアログなどを用意できる関係で、「ストーリー」や「このプログラムについて」などのダイアログを実装したが、スタッフクレジットが意外と長くて、環境によっては画面に収まらないことが判明。 だったら、スクロールするようにすればいいじゃんと思ったのだが、これが以外にもめんどくさかった。 {{:hhsadv:qhhsadv_about.png?400|スクロールバーつきのダイアログ}} Qtではこの種のダイアログは QMessageBox::about() を使って表示するのだが、残念なことにスクロールバーがつくことはない。 なので、自力でそのようなダイアログをつくらなければならない。 普通に考えれば、QDialogのインスタンスを作って、そこに必要な部品をはめていけばいいのだが、うっかり、QMessageBoxクラスを継承して、うまいことごにょごにょすればいいんじゃないかと思ってしまった。 それが、間違いだったが、始めてしまったからには仕方がない。 方針としては、QMessageBoxで表示してる QLabelをスクロールバーつきにしてやればいいのだ。 そうだ、そんなに難しいことではない……と、思ってた。 ウィジェットをいくつも詰め込む場合にはレイアウトも必要になるが、今回は QLabelひとつを QScrollAreaに突っ込むだけなので大したことはない。 <code> QScrollArea *scrollArea = new QScrollArea(); QLabel *label = new QLabel(text); scrollArea->setWidget(label); </code> これを、元々表示している QLabelと入れ替えてやればいい。 QMessageBoxには QGridLayout がはりついていて、そこにQLabel がはまっているので、そのGridLayoutに対して差し替えをさせればいい。 <code> QGridLayout *l = qobject_cast<QGridLayout*>(this->layout()); l->addWidget(scrollArea, 0, 2, 1, -1); // アプリケーションアイコンがある場合。ない場合は 2->1に置き換える </code> ところが、これやったら、ダイアログの左上に出てくるだけで、狙ったところにスクロールバーはつかない。 ((後で考えたらもしかすると QGridLayout *l = qobject_cast<QGridLayout*>(this->widget()->layout() ); とするべきだったのかもしれない。)) もうなんか、アイコンのあるなしで場所代わるし、レイアウト触るんじゃなくて、表示してる QLabel の中身をスクロールバーつきの QLabelに変えちゃえばいいんじゃね? ということに気づいた。 勿論、正しいやり方かどうかは知らない。 そこまでQtに詳しいわけじゃない。 <code> QLabel *label = this->findChild<QLabel*>("qt_msgbox_label"); QLabel *content = new QLabel(label->text()); content->setWordWrap(true); content->setOpenExternalLinks(true); label->clear(); _scrollArea = new QScrollArea(label); _scrollArea->setWidget(content); _scrollArea->setWidgetResizable(true); _scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); _scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); _scrollArea->setMinimumSize(label->width(),label->height()); label->setMinimumSize(label->width(),label->height()); </code> 最後、最小サイズを指定してやらないとダイアログが極小になってしまうのでこれがある意味キモ。 なお、QMessageBoxを継承した場合は void about()も定義しなおしておかないといけない。 最後の setMinimunSize()に関しては、StyleSheetでやっている例もあったが、QLabel { min-width: 300 px; min-height: 300 px; }みたいにしてしまうと、textだけでなく、アイコンの方もそのサイズにされてしまうので、アイコン付きの about()では都合が悪いのだ。 <code> void ScrollMessageBox::about(QWidget *parent, const QString &title, const QString &text) { ScrollMessageBox *scrBox = new ScrollMessageBox(QMessageBox::Icon::Information, title, text, QMessageBox::StandardButton::Ok, parent); scrBox->setAttribute(Qt::WA_DeleteOnClose); QIcon icon = scrBox->windowIcon(); QSize size = icon.actualSize(QSize(64, 64)); scrBox->setIconPixmap(icon.pixmap(size)); scrBox->exec(); } </code> Qt::WA_DeleteOnClose をセットしておくことで、ダイアログを閉じたら勝手に削除してくれるので staticな関数なのに、オブジェクトを返さずにすむ。 お手軽だが、こんなの、Qtのソースみないとわからない。 ==== Qt5/Qt6の互換性 ==== 主に[[uConsole]]でコード書いていた関係で、Qt5を使って開発したため、Qt6で動くかどうかとか全然ちゃんと調べてなかった。 [[DevTerm]]の方は逆にQt6をインストールしてあったので、そっちでビルドできるかトライしたらあっさりこけた。 ただし、こけたのは QMediaPlayer 周り。 音鳴らす機能はおまけなのでなくてもいい。 とりあえず、動作チェックのために、Qt6 なら飛ばすようにした。 <code> #if QT_VERSION < QT_VERSION_CHECK(6,0,0) _mp.setMedia(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str())); _mp.setVolume(100); _mp.play(); #endif </code> ざっくり、Qt6.0.0より前のバージョンの時だけコンパイルされるようにした。 でも待って。Qt6のリファレンスにサンプルコードあるじゃん。 ってことで、速攻で Qt6用のコードも追加した。 音もちゃんと鳴った。 <code> #if QT_VERSION < QT_VERSION_CHECK(6,0,0) _mp.setMedia(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str())); _mp.setVolume(100); _mp.play(); #else _mp.setAudioOutput(&_audio); _mp.setSource(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str())); _audio.setVolume(100); _mp.play(); #endif </code> ボリュームの制御が QAudioOutput に移されてて、あとは setMedia()が setSource()になっただけ。 とはいえ、違いは違い。 ==== テーマ対応 ==== [[ハイハイスクールアドベンチャー Android版]]、[[ハイハイスクールアドベンチャー Windows版]]と、テーマ対応を成し遂げてきたのであれば、Qt版でも対応するのが筋というものでしょう。 Qtのテーマ対応は、ちょっと見ただけだとそれほど難しくも手間でもなさそうです。 但し、完璧を期さないのであれば、ですが。 === テーマ対応の概要 === いくつかやり方はあるようですが、テーマファイル((QSS: Qt Style Sheet))で作って、それを適用するのが柔軟性がありそうでよさそうです。 しかも、Windows版と違って、書くこと少なくていいです。 <file css light.qss> /* light qss */ QWidget { background-color: #ffffff; color: #000000; } QPushButton { background-color: #e0e0e0; border: 1px solid #888; padding: 4px 8px; } </file> <file css dark.qss> /* dark qss */ QWidget { background-color: #2b2b2b; color: #dddddd; } QPushButton { background-color: #444444; border: 1px solid #666; padding: 4px 8px; } </file> <code cpp> void applyTheme(bool dark) { QString fileName = QString(theme_file(dark).c_str()); QFile f(fileName); if (f.open(QFile::ReadOnly)) { QString style = QLatin1String(f.readAll()); qApp->setStyleSheet(style); } } </code> === やっぱりタイトルバー === Qtもテーマ対応をしたところで、タイトルバーは「対象外」なんだとのことです。 もちろん、Windows版同様、カスタムタイトルバーを使うことで対応できるようです。 そして、Windows版同様、ボタンの実装や処理もやらないといけないようです。 ここで問題となるのが、マルチプラットフォーム対応の場合。 要するに、どこまでやれるのかがまだよくわかってない((調査中))ので、一応この部分はまだ保留にしてあります。 === システムテーマの検出 === Qt6.5.0以降は機種非依存でシステムテーマを取り出すことができる。 なので、この機能を使えば、機種ごとに頑張ってテーマの検出コードを書く必要がない。 頑張って機種ごとのテーマ検出コード書くくらいなら、世の中のQtが全部6.5以降になるのを期待して、Qt6.5以降のみ、「システム追随」が機能するってことで良しとしています。 <code cpp> bool dark = false; if (theme == ThemeType::System) { #if QT_VERSION >= QT_VERSION_CHECK(6,5,0) dark = qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark; #endif } else { dark = (theme == ThemeType::Dark); } this->applyTheme(dark); </code> === QSSの限界 === WindowsのXAMLでもHTMLにおけるCSSでも、コーディングなしでスライドスイッチ風ウィジェットを実現できます。 QtでもQSSでできるのかなあと思ってAIに聞いたら「できます」というので、やってみたら、全然できませんでした。 最終的に、AIも「それはQSSの限界ですね」といって、あっさり、カスタムウィジェット+カスタムスタイルで逃げちゃいました。 QSSはCSSのサブセットみたいなものですが、本当に装飾的な部分しか扱えないようです。 === チェックボックスをスライドスイッチに === {{::qhhsadv:prefs.png?400|}} チェックボックスでも機能的には十分なのですが、今どき、スライドスイッチ風があるべき姿でしょう。 QSSでできると期待したのにできなかった奴です。 なお、現状のバージョンはスライドスイッチをアニメーションで切り替えたりはできません。 ザクっとON/OFFが切り替わるやつです。 スイッチ部品をテーマに対応させるために、プロパティだけを提供するためのラッパークラスを用意します。 <file cpp switchbox.h> #ifndef SWITCHBOX_H #define SWITCHBOX_H #include <QObject> #include <QCheckBox> class SwitchBox : public QCheckBox { Q_OBJECT Q_PROPERTY(QColor switchColorOn READ switchColorOn WRITE setSwitchColorOn) Q_PROPERTY(QColor switchColorOff READ switchColorOff WRITE setSwitchColorOff) Q_PROPERTY(QColor switchButtonColor READ switchButtonColor WRITE setSwitchButtonColor) public: explicit SwitchBox(QWidget *parent = nullptr) : QCheckBox(parent), m_on(Qt::blue), m_off(Qt::gray), m_button(Qt::white) {} QColor switchColorOn() const { return m_on; } void setSwitchColorOn(const QColor &c) { m_on = c; } QColor switchColorOff() const { return m_off; } void setSwitchColorOff(const QColor &c) { m_off = c; } QColor switchButtonColor() const { return m_button; } void setSwitchButtonColor(const QColor &c) { m_button = c; } private: QColor m_on; QColor m_off; QColor m_button; }; #endif // SWITCHBOX_H </file> これだけでは足りないので、スイッチボックス風のスタイルを提供するクラスを作ります。 <file cpp switchstyle.h> #ifndef SWITCHSTYLE_H #define SWITCHSTYLE_H #include <QProxyStyle> #include <QStyleOption> #include <QStyleOptionButton> #include <QPainter> #include <QRect> #include <QColor> class SwitchStyle : public QProxyStyle { public: using QProxyStyle::QProxyStyle; // QSS から色を取得(プロパティベース) QColor getSwitchColor(const QWidget *widget, bool checked) const { const char *propName = checked ? "switchColorOn" : "switchColorOff"; QVariant colorProp = widget ? widget->property(propName) : QVariant(); if (colorProp.canConvert<QColor>()) return colorProp.value<QColor>(); return checked ? QColor("#1E90FF") : QColor("#AAAAAA"); // デフォルト } QColor getButtonColor(const QWidget *widget) const { QVariant colorProp = widget ? widget->property("switchButtonColor") : QVariant(); if (colorProp.canConvert<QColor>()) return colorProp.value<QColor>(); return Qt::white; // デフォルト } QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const override { if (element == SE_CheckBoxIndicator) { // チェックボックス全体の矩形 QRect fullRect = option->rect; // インジケータのサイズ(例:40x20) int w = 40; int h = 20; // fullRect の右端にインジケータを配置 int x = fullRect.right() - w; int y = fullRect.top() + (fullRect.height() - h) / 2; return QRect(x, y, w, h); } return QProxyStyle::subElementRect(element, option, widget); } QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const override { QSize sz = QProxyStyle::sizeFromContents(type, option, contentsSize, widget); if (type == CT_CheckBox) { sz.setHeight(std::max(sz.height(), 24)); sz.setWidth(std::max(sz.width(), 80)); // ← 幅を確保 } return sz; } int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override { if (metric == PM_IndicatorWidth) return 40; if (metric == PM_IndicatorHeight) return 20; return QProxyStyle::pixelMetric(metric, option, widget); } void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override { if (element == PE_IndicatorCheckBox) { if (!option) return; QRect r = option->rect; painter->setRenderHint(QPainter::Antialiasing); bool checked = option->state & State_On; QColor trackColor = getSwitchColor(widget, checked); // 背景(トラック) painter->setBrush(trackColor); painter->setPen(Qt::NoPen); painter->drawRoundedRect(r, r.height()/2, r.height()/2); // つまみ(白い円) int knobDiameter = r.height() - 4; int x = checked ? r.right() - knobDiameter - 2 : r.left() + 2; QRect knobRect(x, r.top() + 2, knobDiameter, knobDiameter); painter->setBrush(getButtonColor(widget)); painter->drawEllipse(knobRect); return; } QProxyStyle::drawPrimitive(element, option, painter, widget); } }; #endif // SWITCHSTYLE_H </file> 最初は、描画するdrawPrimitive()だけだったのですが、QSSから色情報をとりたいとか、スイッチをダイアログの右端に配置しようとしたら、右にはみ出したり、ラベルの高さより高くなってしたが削られたりしたのでsizeFromContents()を投入したり、右にはみ出さなくなったけれど、今度は、右端の部分しか表示されないのでsubElementRect()で十分な幅が得られるようにしたりと、この分量になりました。 このスイッチは、色について三つのプロパティを持っています。 * qproperty-switchColorOn Onの時の背景色 * qproperty-switchColorOff Offの時の背景色 * qproperty-switchButtonColor ボタン色 QSSにはこんな感じで設定します。 <code CSS> SwitchBox { qproperty-switchColorOn: #4CC2FF; qproperty-switchColorOff: #CCCCCC; qproperty-switchButtonColor: #FFFFFF; } </code> 使い方はこんな感じで。 <code cpp> SwitchBox *chkSound = new SwitchBox(); chkSound->setStyle(new SwitchStyle(chkSound->style())); chkSound->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); chkSound->setChecked(_sound); QObject::connect(chkSound, SIGNAL(stateChanged(int)), this, SLOT(setSound(int))); QGridLayout *grid0 = new QGridLayout; grid0->addWidget(new QLabel("音を鳴らす"), 0, 0, Qt::AlignLeft); grid0->addWidget(chkSound, 0, 1, Qt::AlignRight); </code> SwitchBox のインスタンスを作ったら、setStyleで SwitchStyleのインスタンスを割り当てます。 ここまでしてやるとスライドスイッチ風のウィジェットが出来上がります。 中身は見ての通り CheckBoxそのものなんですけれどね。 ==== 環境非依存化 ==== Qtはマルチプラットフォームのツールキットなので、適切にコーディングすれば、Linuxだけでなく、WindowsやMacOSでも動作させることができます。 当初、Qt版はLinux上で開発していたため、GUIまわり以外の部分は、UNIX由来のサービスを使いまくっていたので、Windows版Qtでビルドしようとしたら、山ほどエラーが出まして、これを機に環境依存コードを排除して、全面的に書き直しました。 ファイル入出力も、Qtのコールに書き換えました。 これは、WindowsとUNIX(とMacOS)で改行コードの扱いが違うことで、ファイル操作の際に余計な処理が入る関係です。 とりあえず、WindowsとLinux ((Ubuntu 24.04LTS, Ubuntu 25.04、Debian 12(ARM64)で確認))でのビルドが可能なことは確認しました。 ((なお、Apple系のデバイスは持ってないし買う気もないのでできるかどうかはどなたか試してください。)) ==== Windows版 ==== Windows版を実行したら、コンソールウィンドウが開く、コンソールアプリでした。 調べたところ、エントリーポイントが main()だとコンソールアプリ、WinMain()だと非コンソールアプリになるとか。 え、てことは #if とかでエントリポイントを変える感じ? と、思ったら他にもやり方がありました。 CMakeLists.txt でコントロールできるそうで。 qt_add_executable()に WIN32 ってつければいいんだとか。 結局 if(WIN32)とかで切り替えるわけですが((WIN32は関係ない環境では無視されるらしいので、別にifいらんのですが、アイコンの処理とかの関連もあるので。))、プログラム側でやるよりはすっきりして異様に思います。 <code cmake> if(WIN32) set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/qhhsadv.rc") list(APPEND PROJECT_SOURCES "${app_icon_resources_windows}") endif() if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) if(WIN32) qt_add_executable(qhhsadv WIN32 ${PROJECT_SOURCES} qhhsadv.rc ) else() qt_add_executable(qhhsadv ${PROJECT_SOURCES} ) endif() qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) else() if(ANDROID) add_library(qhhsadv SHARED ${PROJECT_SOURCES} ) else() add_executable(qhhsadv ${PROJECT_SOURCES} ) endif() qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) endif() </code> ==== リソースフォルダーのコピー ==== リソースデータは AppLocalDataLocation/WildTreeJP/QHHSAdv の下にあることを想定しています。 なければ、プログラムのあるディレクトリにある data ディレクトリの中身をコピーしてから動作を開始します。 なので、ソースツリーの中で管理されているリソースディレクトリを、ビルド時に、ビルドディレクトリにコピーしてほしいのです。 CMakeLists.txt を利用する場合は次のようになります。 <code cmake> add_custom_command(TARGET qhhsadv POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/data $<TARGET_FILE_DIR:qhhsadv>/data ) </code> ==== インストーラとパッケージ ==== Qtはマルチプラットフォームなので、ビルド後の扱いをまじめに考えるとちょっとめんどくさい。 基本的に、[[https://github.com/wildtree/qhhsadv.git|GitHub]]から持って行って、自分でビルドして遊んで、でもいいんですが、Linuxはともかく、WindowsにいちいちQtをインストールしてっていうのはなかなかにハードルが高く、遊んでもらえません。 Linuxにしても、今どきのユーザはパッケージをインストールして使うスタイルの人も多いのでしょう。 何しろ、環境をセットアップして最初にやるべきことが build-essentialsをインストールすることなのですから。 なので、インストールとパッケージについてあれこれ書いておきます。 === Linux === Linuxの場合は、ビルドしてインストールすることから始めます。 パッケージも、インストールされたイメージをもとに作るので、まずはインストールなのです。 make を走らせるのだから、make install すればいいだろうということですが、そのためには installターゲットが生成されなければなりません。 Makefileは cmakeが作るのだから、cmakeに作るように指示しなければなりません。 <code cmake> if(UNIX AND NOT APPLE) # 実行ファイルを /opt/qhhsadv に配置 install(TARGETS qhhsadv DESTINATION /opt/qhhsadv) # data ディレクトリを /opt/qhhsadv にコピー install(DIRECTORY ${CMAKE_BINARY_DIR}/data DESTINATION /opt/qhhsadv) # ラッパースクリプトを /usr/bin にインストール install(PROGRAMS ${CMAKE_SOURCE_DIR}/scripts/qhhsadv.sh DESTINATION /usr/bin RENAME qhhsadv) # .desktop ファイル install(FILES ${CMAKE_SOURCE_DIR}/resources/linux/qhhsadv.desktop DESTINATION /usr/share/applications) # アイコン(48x48 の例) install(FILES ${CMAKE_SOURCE_DIR}/resources/icons/qhhsadv.png DESTINATION /usr/share/icons/hicolor/48x48/apps) endif() </code> ビルドはマルチターゲットなので、Linuxは **UNIXかつ非APPLE**という扱いになります。 ここに、インストールするものとその行先をずらずらと書いていきます。 DESTINATIONに行き先を指定するわけですが、PREFIXとかつけないでいいのか? といわれれば、それはcmakeがよしなにやってくれるから、気にせず、/から書いておけばいいのです。 データリソースの扱いは、本来、UNIX系の慣習に倣うなら、/usr/share/qhhsadv の下に置くべきなのでしょうが、Windowsっぽく、プログラムバイナリのある場所にある想定なので、今更、ソースから書き換えるのもめんどくさいです。 なので、こういう感じでインストールする先として、最近では /opt が使われるので、/opt/qhhsadv の下に、プログラムもリソースも一緒くたに置くことにしています。 /usr/bin には /opt/qhhsadv/qhhsadv を呼び出すだけのスクリプトを置いておきます。 あとは、 <code bash> $ cmake -DCMAKE_INSTALL_PREFIX=/ .. $ make $ sudo make install </code> とすれば、インストールしてくれます。 == パッケージング == 以前なら、Slackware系、RedHat系、Debian系がしのぎを削っていましたが、今どきはもうDebian系一択でいいでしょう。 deb形式のパッケージにまとめることを想定しています。 <del>パッケージングするためには、仮想ルートフォルダの下にイメージをインストールします。 <code bash> $ make install DESTDIR=$PWD/package-root </code> これで、./package-root ができて、その下にインストールされた形になります。 次に、パッケージ用のファイルを作ります。 <code bash> $ mkdir ./package-root/DEBIAN </code> ./package-root/DEBAIN というフォルダを作ったら、その下に control という名前のファイルを作ります。 これは Ubuntu24.04.02 用のファイルになります。 <file ini control> Package: qhhsadv Version: 1.0.0 Section: games Priority: optional Architecture: amd64 Maintainer: Hiroyuki Araki <wildtree@gmail.com> Depends: libc6 (>= 2.31), libqt6core6t64 (>= 6.4), libqt6multimedia6 (>=6.4), libqt6widgets6t64 (>= 6.4) Description: High High School Adventure for Qt A Qt-based High High School Adventure implementation. </file> あとは、パッケージの作成を行います。 <code bash> $ dpkg-deb --root-owner-group --build package-root qhhsadv_1.0.0_ubutu24.04.2_amd64.deb </code></del> パッケージングでcontrolファイルを毎回作るの面倒くさいとAIにもちかけたら、CPackにすればいいじゃん、といわれました。 なにそれ? <code cmake> if(UNIX AND NOT APPLE) # パッケージの基本情報 set(CPACK_PACKAGE_NAME ${APP_NAME}) set(CPACK_PACKAGE_VERSION ${APP_VERSION}) set(CPACK_PACKAGE_CONTACT "wildtree@gmail.com") # Debian パッケージ用のメタ情報 set(CPACK_GENERATOR "DEB") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Hiroyuki Araki") # 必須 set(CPACK_DEBIAN_PACKAGE_SECTION "games") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6core6 (>= 6.4.0), libc6 (>= 2.34), libqt6widget6 (>= 6.4.0)") # アーキテクチャ指定(自動判定も可能) string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" DETECTED_ARCH) if(DETECTED_ARCH STREQUAL "x86_64") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") elseif(DETECTED_ARCH STREQUAL "aarch64") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64") elseif(DETECTED_ARCH MATCHES "armv7") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "armhf") else() set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${DETECTED_ARCH}") # fallback endif() include(CPack) endif() </code> インストールターゲットとか特には必要としません。 パッケージの作成までは以下の手順になります。 <code bash> $ git clone https://github.com/wildtree/qhhsadv.git $ cd qhhsadv $ cmake -B build -DCMAKE_BUILD_TYPE=Release $ cmake --build build --target all $ cpack -G DEB -C Release --config build/CPackConfig.cmake </code> インストールは dpkgでも aptでも好きなのでインストーラを走らせるだけです。 カレントフォルダに qhhsadv_1.1.1_Linux.deb のようなファイルができます。 バージョン番号は、CMakeLists.txtの頭の方に書いてあります。 <code cmake> # Application basic information set(APP_NAME "qhhsadv") set(APP_VERSION_MAJOR "1") set(APP_VERSION_MINOR "1") set(APP_VERSION_PATCH "1") set(APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}") </code> <code bash> $ sudo apt install -y ./qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb </code> === Windows === Windowsの場合は、Linuxのように、直接 make でインストールするのは現実的ではありません。 なので、インストーラを作ってそれでインストールする形をとります。 WiXかInno Setup 6のどっちかが定番らしいですが、WiXは基本的に Visal Studio 2022のプラグインとして使うのが普通なので、Qt Creator や cmakeでやるのはちょっとめんどくさそうです。 なので、Inno Setup 6を使うことにします。 Inno Setup 6は winget でインストールできます。 <code powershell> PS C:\> winget install JRSoftware.InnoSetup </code> DEBIANのcontrol同様、Inno Setup には Setup.issという設定ファイルを作る必要があります。 <file ini setup.iss.in> ; ハイハイスクールアドベンチャー インストーラ (Inno Setup) [Setup] AppName=High High School Adventure Qt AppVersion=@APP_VERSION@ AppPublisher=WildTreeJP DefaultDirName={commonpf}\QHHSAdv DefaultGroupName=ハイハイスクールアドベンチャー UninstallDisplayIcon={app}\qhhsadv.exe OutputBaseFilename=qhhsadv_setup Compression=lzma SolidCompression=yes [Languages] Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" [Files] ; Qtアプリ本体と依存DLLをまとめたフォルダを指定 Source: "deploy\qhhsadv.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "deploy\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion ; リソースディレクトリ Source: "deploy\data\*"; DestDir: "{app}\data"; Flags: recursesubdirs createallsubdirs ignoreversion [Icons] Name: "{group}\ハイハイスクールアドベンチャー"; Filename: "{app}\qhhsadv.exe" Name: "{commondesktop}\ハイハイスクールアドベンチャー"; Filename: "{app}\qhhsadv.exe"; Tasks: desktopicon [Run] ; インストール完了後に起動するオプション Filename: "{app}\qhhsadv.exe"; Description: "{cm:LaunchProgram,ハイハイスクールアドベンチャー}"; Flags: nowait postinstall skipifsilent </file> デプロイするのに必要なファイルやフォルダを、インストーラの作業ディレクトリの下の deploy にまとめることにします。 この方針に合うように CMakeLists.txt に WIN32用の記述を追加していきます。 <code cmake> if(WIN32) # デプロイ先ディレクトリ set(DEPLOY_DIR ${CMAKE_BINARY_DIR}/installer/deploy) # コピー対象 set(APP_EXE $<TARGET_FILE:qhhsadv>) set(DATA_DIR ${CMAKE_BINARY_DIR}/data) # デプロイ処理 add_custom_command( TARGET qhhsadv POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPLOY_DIR} COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:qhhsadv> ${DEPLOY_DIR} COMMAND ${CMAKE_COMMAND} -E copy_directory ${DATA_DIR} ${DEPLOY_DIR}/data COMMENT "Copying files to installer/deploy..." ) # Qt DLL やプラグインを集める場合(windeployqt) # Qt bin ディレクトリを探す get_target_property(QT_QMAKE_EXECUTABLE Qt6::qmake IMPORTED_LOCATION) get_filename_component(QT_BIN_DIR "${QT_QMAKE_EXECUTABLE}" DIRECTORY) set(WINDEPLOYQT_EXECUTABLE "${QT_BIN_DIR}/windeployqt.exe") add_custom_command( TARGET qhhsadv POST_BUILD COMMAND "${WINDEPLOYQT_EXECUTABLE}" --dir ${DEPLOY_DIR} $<TARGET_FILE:qhhsadv> COMMENT "Running windeployqt..." ) add_custom_command( TARGET qhhsadv POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/installer COMMENT "Copying setup.iss..." ) configure_file(${CMAKE_SOURCE_DIR}/installer/setup.iss.in ${CMAKE_BINARY_DIR}/installer/setup.iss @ONLY) # Inno Setup コンパイラを探す find_program(INNO_SETUP_COMPILER NAMES ISCC iscc HINTS "$ENV{LOCALAPPDATA}/Programs/Inno Setup 6" ) # PATH に入っている場合も探す if(NOT INNO_SETUP_COMPILER) find_program(INNO_SETUP_COMPILER NAMES ISCC iscc) endif() if(INNO_SETUP_COMPILER) message(STATUS "Inno Setup found: ${INNO_SETUP_COMPILER}") # .iss スクリプトの場所 set(INNO_SCRIPT ${CMAKE_BINARY_DIR}/installer/setup.iss) # 出力先(例: build ディレクトリ内) set(INSTALLER_OUTPUT ${CMAKE_BINARY_DIR}/installer/qhhsadv_setup.exe) add_custom_command( OUTPUT ${INSTALLER_OUTPUT} COMMAND "${INNO_SETUP_COMPILER}" "${INNO_SCRIPT}" DEPENDS qhhsadv # アプリ本体がビルドされてから実行 WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Building Inno Setup installer..." ) add_custom_target(installer ALL DEPENDS ${INSTALLER_OUTPUT} ) else() message(WARNING "Inno Setup (ISCC.exe) not found. Skipping installer target.") endif() endif() </code> Windows版Qtには、必要なDLLなどをまとめてくれる windeployqt.exe というツールが入っていて、これがDLLをかき集めてくれます。 ランタイムパッケージなどの提供がないQtなので、これはWindows版をデプロイするのには必須です。 生成されたプログラム、リソースなどをコピーして、DLLなどをかき集めたら、ISCC.exe を呼び出して、インストーラに仕上げます。 ここまでを、Windows環境でビルドを走らせると一気にやってくれます。
ハイハイスクールアドベンチャー_qt版.txt
· 最終更新:
2025/10/01 05:02
by
araki
ページ用ツール
文書の表示
以前のリビジョン
バックリンク
文書の先頭へ