PyTorchのCUDAExtensionがpipのビルド隔離環境と相性が最悪な件

忘れそうなのでメモ

発生条件

  • Pythonのライブラリ
  • setup.pyでインストール(ビルド済みのwheelではない)
  • pyproject.tomlがある
  • ビルド作業がPythonの環境に依存している(PyTorchのCUDAExtensionのようにインストール済みのPyTorchに依存する、など)

起きること

  • インストール済みのライブラリではなくビルド用の隔離環境に新たにインストールしたライブラリを使ってビルド作業が行われる
    • 例えばPyTorchならCPU版がインストールされてCUDA関連のビルドなどでエラー祭りになる

手っ取り早い解決策

  • pipコマンドの引数に--no-build-isolationを追加してビルド用の隔離環境を使わないようにする

pyproject.tomlに関するメモ

以下のような記述の場合pipによるビルド時の隔離環境にはsetuptools, wheel, torchのみがインストールされるらしい。

[build-system]
build-backend = "setuptools.build_meta"
requires = [
  "setuptools",
  "wheel",
  "torch",
]

※ちなみにGoogle Gemini君に質問したら「requiresで--index-urlや--extra-index-urlなどのオプションを渡すことができない」という回答だった(ウラは取っていないので真偽不明)

ROS2込みでUE5版CARLA Win11でビルドできた

前回の続き。ENABLE_ROS2=ON状態でビルドして動作確認まで進んだのでめも。こちらはROS2の内容のみメモしているので前回の記事と合わせて読むこと。

実行時の様子は↓こんな感じ。

実行時のrviz2スクショ

ソースコードの修正内容

現状のROS2関連ソースコードLinuxしか対応していないように見える。Windowsへの対応を追加する必要がある。

CMakeLists.txtへのWindows対応追加

Ros2Native/LibCarlaRos2Native/CMakeLists.txt

修正のポイント

  • Windowsのみ以下3つのdefineを定義する(DLLビルドに関するdefine。__declspecを有効化するために必要)
    • EPROSIMA_ALL_DYN_LINK
    • _DLL (最初のアンダーバーは誤植ではないので注意)
    • EPROSIMA_USER_DLL_EXPORT
  • C++17を明示する(17以降なら大丈夫と思われる)
  • WindowsLinuxで参照するライブラリファイル名を切り替える
  • Windows用のインストールコマンドを追加
    • .libファイルと.dllファイル両方とも必要
    • ここでのインストール先はビルドディレクトリ内
--- a/Ros2Native/LibCarlaRos2Native/CMakeLists.txt
+++ b/Ros2Native/LibCarlaRos2Native/CMakeLists.txt
@@ -50,14 +50,38 @@ target_compile_definitions (
   BOOST_ASIO_ENABLE_BUFFER_DEBUGGING
 )

+if (WIN32)
+target_compile_definitions (
+  carla-ros2-native
+  PUBLIC
+  EPROSIMA_ALL_DYN_LINK _DLL EPROSIMA_USER_DLL_EXPORT
+)
+endif ()
+
+target_compile_features (
+  carla-ros2-native
+  PUBLIC
+  cxx_std_17
+)
+
 target_link_libraries (
   carla-ros2-native PUBLIC
-  ${CMAKE_INSTALL_PREFIX}/lib/libfastrtps.so
+  $<$<PLATFORM_ID:Linux>: ${CMAKE_INSTALL_PREFIX}/lib/libfastrtps.so>
+  $<$<PLATFORM_ID:Windows>: ${CMAKE_INSTALL_PREFIX}/lib/fastrtps-2.11.lib ${CMAKE_INSTALL_PREFIX}/lib/fastcdr-1.1.lib>
 )

+if (WIN32)
+install (
+  TARGETS
+  carla-ros2-native
+  ARCHIVE DESTINATION lib
+  RUNTIME DESTINATION bin
+)
+else ()
 install (
   TARGETS
   carla-ros2-native
   DESTINATION
   lib
 )
+endif ()

Ros2Native/CMakeLists.txt

修正のポイント

--- a/Ros2Native/CMakeLists.txt
+++ b/Ros2Native/CMakeLists.txt
@@ -55,10 +55,28 @@ ExternalProject_Add (
     -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
 )

-set (CARLA_PLUGIN_BINARY_PATH ${CMAKE_SOURCE_DIR}/Unreal/CarlaUnreal/Plugins/Carla/Binaries/Linux)
+if (WIN32)
+  set (CARLA_PLUGIN_BINARY_PATH ${CMAKE_SOURCE_DIR}/Unreal/CarlaUnreal/Plugins/Carla/Binaries/Win64)
+else ()
+  set (CARLA_PLUGIN_BINARY_PATH ${CMAKE_SOURCE_DIR}/Unreal/CarlaUnreal/Plugins/Carla/Binaries/Linux)
+endif ()

 make_directory (${CARLA_PLUGIN_BINARY_PATH})

+if (WIN32)
+add_custom_command (
+  TARGET carla-ros2-native-lib
+  POST_BUILD
+  COMMAND
+    ${CMAKE_COMMAND} -E copy
+    ${PROJECT_INSTALL_PATH}/bin/foonathan_memory-0.7.3.dll
+    ${PROJECT_INSTALL_PATH}/bin/fastcdr-1.1.dll
+    ${PROJECT_INSTALL_PATH}/bin/fastrtps-2.11.dll
+    ${PROJECT_INSTALL_PATH}/bin/carla-ros2-native.dll
+    ${PROJECT_INSTALL_PATH}/lib/carla-ros2-native.lib
+    ${CARLA_PLUGIN_BINARY_PATH}
+)
+else ()
 add_custom_command (
   TARGET carla-ros2-native-lib
   POST_BUILD
@@ -67,6 +85,7 @@ add_custom_command (
     ${PROJECT_INSTALL_PATH}/lib/*.so*
     ${CARLA_PLUGIN_BINARY_PATH}
 )
+endif ()

Build.csへのWindows対応追加

Unreal/CarlaUnreal/Plugins/Carla/Source/Carla/Carla.Build.cs にWindows版の設定を追加する。

修正のポイント

  • 従来のDLLを登録している処理をlinux用の処理としてif文でくるむ
  • Windows用のDLL登録処理を追加
    • 直接依存しているcarla-ros2-native.dllは.libをPublicAdditionalLibrariesへ登録する(AddDynamicLibraryは呼ばない)
--- a/Unreal/CarlaUnreal/Plugins/Carla/Source/Carla/Carla.Build.cs
+++ b/Unreal/CarlaUnreal/Plugins/Carla/Source/Carla/Carla.Build.cs
@@ -172,15 +172,27 @@ public class Carla :
       TestOptionalFeature(EnableRos2Demo, "Ros2 demo", "WITH_ROS2_DEMO");

       string CarlaPluginSourcePath = Path.GetFullPath(ModuleDirectory);
-      string CarlaPluginBinariesLinuxPath = Path.Combine(CarlaPluginSourcePath, "..", "..", "Binaries", "Linux");
-      AddDynamicLibrary(Path.Combine(CarlaPluginBinariesLinuxPath, "libcarla-ros2-native.so"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfoonathan_memory-0.7.3.so"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so.1"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so.1.1.0"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so.2.11"));
-      RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so.2.11.2"));
+      if (Target.Platform == UnrealTargetPlatform.Linux)
+      {
+        string CarlaPluginBinariesLinuxPath = Path.Combine(CarlaPluginSourcePath, "..", "..", "Binaries", "Linux");
+        AddDynamicLibrary(Path.Combine(CarlaPluginBinariesLinuxPath, "libcarla-ros2-native.so"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfoonathan_memory-0.7.3.so"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so.1"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastcdr.so.1.1.0"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so.2.11"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesLinuxPath, "libfastrtps.so.2.11.2"));
+      }
+      else if (Target.Platform == UnrealTargetPlatform.Win64)
+      {
+        string CarlaPluginBinariesPath = Path.Combine(CarlaPluginSourcePath, "..", "..", "Binaries", "Win64");
+        PublicAdditionalLibraries.Add(Path.Combine(CarlaPluginBinariesPath, "carla-ros2-native.lib"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesPath, "carla-ros2-native.dll"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesPath, "foonathan_memory-0.7.3.dll"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesPath, "fastcdr-1.1.dll"));
+        RuntimeDependencies.Add(Path.Combine(CarlaPluginBinariesPath, "fastrtps-2.11.dll"));
+      }
     }
   }

Visual Studioに無いdefine定義の追加

Visual Studioのヘッダをgrepしても見つからなかったdefine定義を追加した。(どうやら標準ではないらしい?)

場所は使われている箇所より手前ならどこでも良いのでファイルの先頭の方(includeの後あたり)に追加した。

  • LibCarla/source/carla/ros2/publishers/CarlaIMUPublisher.cpp
    • #define M_PI_2 1.57079632679489661923
  • LibCarla/source/carla/ros2/publishers/CarlaTransformPublisher.cpp
    • #define M_PIf32 3.141592653589793238462643383279502884
  • LibCarla/source/carla/ros2/types/CameraInfo.cpp
    • #define M_PI 3.14159265358979323846

__declspec属性の追加

LibCarlaのROS2コードにdeclspec(dllexport)、declspec(dllimport)が付いていないので追加する。

  • 共通のヘッダファイルにdefine定義を追加(includeされていればどのヘッダでもOK)
    • LibCarla/source/carla/ros2/publishers/CarlaPublisher.h
    • LibCarla/source/carla/ros2/subscribers/CarlaSubscriber.h
+#if defined(_WIN32)
+#if defined(EPROSIMA_USER_DLL_EXPORT)
+#define ROS2_DllAPI __declspec( dllexport )
+#else
+#define ROS2_DllAPI __declspec( dllimport )
+#endif // EPROSIMA_USER_DLL_EXPORT
+#else
+#define ROS2_DllAPI
+#endif // _WIN32
+
  • 各ヘッダファイルのクラス定義に上記のdefineを追記(コミット 7796755 時点で必要だったファイル)
    • LibCarla/source/carla/ros2/publishers/BasicPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaClockPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaCollisionPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaDVSCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaDepthCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaGNSSPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaIMUPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaISCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaLidarPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaLineInvasionPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaNormalsCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaOpticalFlowCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaRGBCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaRadarPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaSSCameraPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaSemanticLidarPublisher.h
    • LibCarla/source/carla/ros2/publishers/CarlaTransformPublisher.h
    • LibCarla/source/carla/ros2/subscribers/BasicSubscriber.h
    • LibCarla/source/carla/ros2/subscribers/CarlaEgoVehicleControlSubscriber.h

BasicPublisherクラスの例。ほかのクラスも同様にした。

--- a/LibCarla/source/carla/ros2/publishers/BasicPublisher.h
+++ b/LibCarla/source/carla/ros2/publishers/BasicPublisher.h
@@ -12,7 +12,7 @@ namespace ros2 {

   struct BasicPublisherImpl;

-  class BasicPublisher : public CarlaPublisher {
+  class ROS2_DllAPI BasicPublisher : public CarlaPublisher {
     public:
       BasicPublisher(const char* ros_name = "", const char* parent = "");
       ~BasicPublisher();

GetMessageの置き換え阻止(2025/09/30 追記)

以下2ファイルはDLLビルド時は何も問題ないがアプリ(DLLをimportする側)としてビルドしようとするとWindows API向けのGetMessageのdefine定義によってメソッド名が変化してしまう(GetMessageAもしくはGetMessageWになる)。

  • LibCarla/source/carla/ros2/subscribers/CarlaEgoVehicleControlSubscriber.h
  • LibCarla/source/carla/ros2/subscribers/BasicSubscriber.h

ヘッダファイルの先頭の方に以下のundefを入れてメソッド名がGetMessageのままになるようにする。

+#if defined(_WIN32) && defined(GetMessage)
+  #undef GetMessage
+#endif

最新のポストプロセス定義への対応追加(ROS2動作確認用)

ROS2の動作確認で PythonAPI\examples\ros2\ros2_native.py を使う場合は PythonAPI/examples/ros2/stack.json に以下のattribute指定を追加する必要がある。

--- a/PythonAPI/examples/ros2/stack.json
+++ b/PythonAPI/examples/ros2/stack.json
@@ -7,6 +7,7 @@
             "id": "rgb",
             "spawn_point": {"x": -4.5, "y": 0.0, "z": 2.5, "roll": 0.0, "pitch": 20.0, "yaw": 0.0},
             "attributes": {
+                "post_process_profile": "Town10HD_Opt",
                 "image_size_x": 400,
                 "image_size_y": 200,
                 "fov": 90.0

解説

コミットa3c3e1eでRGBカメラのBluePrintアクタに対するattribute設定が変化してポストプロセス設定が「直接attributeで指定」する方法から「JSONで指定?」する方法に変化したようにみえる。

そしてCARLAのデフォルトマップとデフォルトのポストプロセス指定だとカメラ画像が真っ暗になってしまう(恐らくオートホワイトバランスが効いていないのでは?)。

この挙動はROS2とはまったく関係なく上記コミットにより変化した内容と思われる。

default指定そのまま defaultで無理やり明るく調整 Town10HD_Opt指定

PythonからRGBカメラを設置する場合はset_attributeで指定する必要がある(上記JSONはあくまでros2_native.pyだけが使うファイルのため)。

...
cam_bp = bp_lib.find('sensor.camera.rgb')
...

# デフォルト値`default`のままだと暗い画像しか取れないため変更する
cam_bp.set_attribute('post_process_profile', 'Town10HD_Opt')

現状 Town10HD_Opt 以外にどんな値が有効なのか不明(調べていない)。

ビルド方法

バッチファイル編集

CarlaSetup.bat のcmake実行箇所にオプションを追加する。

 cmake ^
     -G Ninja ^
     -S . ^
     -B Build ^
     --toolchain=CMake/Toolchain.cmake ^
     -DPython_ROOT_DIR=%python_root% ^
     -DPython3_ROOT_DIR=%python_root% ^
     -DCMAKE_BUILD_TYPE=Release ^
+    -DENABLE_ALL_WARNINGS=OFF ^
+    -DENABLE_ROS2=ON -DENABLE_ROS2_DEMO=ON ^
     -DCARLA_UNREAL_ENGINE_PATH=%CARLA_UNREAL_ENGINE_PATH% || exit /b

※ENABLE_ALL_WARNINGSはROS2と関係なし。詳細は前回の記事を参照。

ビルド

ビルド方法は同じ。詳細は前回の記事を参照。

CarlaSetup.bat --launch
cmake --build Build --target package-development

動作確認

冒頭で紹介したXのポストで実施した動作確認は以下の手順。

まずCARLAを起動する。DLLとexeが別ディレクトリに入っているのでDLLへのパスを通している(もっと正しいやり方はありそう)。

# .dllが入っているディレクトリの絶対パスを環境変数に登録する(パス名は適宜読み替えること)
set "PATH=%PATH%;CarlaUE5\Unreal\CarlaUnreal\Plugins\Carla\Binaries\Win64"

# ROS2を有効にしてCARLAを起動
cd CarlaUE5
Unreal\CarlaUnreal\Binaries\Win64\CarlaUnreal.exe --ros2

Pythonスクリプトを実行して自車とRGBカメラ、LiDARをワールドに追加して自動走行してもらう。

# Python仮想環境を立ち上げ(適宜読み替え推奨)
env_carla_3810\Scripts\activate
cd CarlaUE5
python PythonAPI\examples\ros2\ros2_native.py --file PythonAPI\examples\ros2\stack.json

以下のファイル・ディレクトリをLinux環境にuploadする。(改行コードに\rが含まれないように注意)

  • PythonAPI\examples\ros2\run_rviz.sh
  • PythonAPI\examples\ros2\config (ディレクトリ丸ごと)
  • PythonAPI\examples\ros2\rviz (ディレクトリ丸ごと)

Linux環境で以下のように.shとディレクトリ2つが同じディレクトリに並んだ状態にする。

+-- run_rviz.sh
+-- config
|    +-- fastrtps-profile.xml
+-- rviz
     +-- ros2_native.rviz

shを実行する。Dockerコンテナが起動してrviz2が実行される。

./run_rviz.sh

以上。

UE5版CARLAをWin11環境でビルドしてみた(ROS2は除く)

CARLA SimulatorをWindows11環境で無理やりビルドしてEditorが動くところまで進んだのでメモ。

Unreal EditorでCARLAを実行中

CARLAのバージョンは v0.10.0(UE5.5)、GitHubのコミットはCARLAが7796755、UEが 68c721d(要登録)時点で実施した内容。

以下の機能は有効化していない。後述の通りROS2も有効にしていない。

Unity build is enabled.
Slate UI is disabled.
Online Subsystem is disabled.
CarSim support is disabled.
Chrono support is disabled.
PyTorch support is disabled.
OSM2ODR support is disabled.

結論

注意点は以下の通り。

  • Visual Studio Communityはインストールしていない状態から始めた方がよさそう
    • もし MASM : fatal error A1013:invalid numerical command-line argument : /W エラーが発生する場合は -Wall/Wall オプションをアセンブラに渡さないようにして回避
  • zlib.h、zconf.hはBuild\_deps\libpng-buildにコピーすればOK
  • carla-example-clientがlibpngへのインクルードパスを設定していない?のでヘッダファイルをコピーして対処した
  • ENABLE_ROS2をONにするのは現状難しそう(別記事を起こす予定)
    • ROS2をONにできた。次の記事を参照のこと(2025/09/27 追記)

maminus.hatenadiary.org

準備

公式のビルド手順ページの最初の方に書かれている「Note」欄はとても重要事項なので先に目を通して実施しておく

  • GitHubのアカウントが必要(未所持なら取得が必要)
  • Epicのアカウントが必要(未所持なら取得が必要)
  • GitHubのアカウントをEpicとリンク(Epicのプライベートリポジトリにアクセスできるようにするため)
  • GitHubSSHキーを登録(Epicのプライベートリポジトリからgit cloneできるようにするため)
  • Windows11の開発者モードをOnにする
  • 外付けディスクだとダメらしいのでディスクを空けておく(手元の環境では451GB使っている)

アカウント関連はUEのソースコードを取得するために必要な内容。UE4のころから変わっていないはずでCARLAに直接関係ない内容なので広くUEの情報を参照されたし。

開発者モードは僕の環境だと「設定」アプリの「システム」>「開発者向け」でたどり着けた。(下図参照)

Windows11での開発者モード

先にインストールが必要なソフト

  • Python 3.8.10
  • cmake 3.27.2以降
  • git

Python環境に色々pip installされるのでvenvなどの仮想環境をあらかじめ用意しておく方がよさそう。gitやcmakeはパスが通った状態から開始する必要がある。

gitはGitHubSSH接続できる状態に設定済みであること。また、git環境が古すぎると証明書関係でエラーになるかもしれないので注意(1敗)

インストール済みならアンインストールしておいた方が良いソフト

今回僕はVisual Studio Communityをインストールした状態から開始したのでかなり大変だった。

ビルド手順

1. Python環境とgit clone

# Python仮想環境構築例(適宜読み替え推奨)
Python38\python.exe -m venv env_carla_3810
env_carla_3810\Scripts\activate
python -m pip install --upgrade pip

# CARLAリポジトリのgit clone
git clone -b ue5-dev https://github.com/carla-simulator/carla.git CarlaUE5

2. バッチファイルの編集

cloneしたディレクトリ直下のCarlaSetup.batを編集する。

1か所目はVisual Studioをあらかじめインストール済みだった場合のみ必要で、インストール先パスにあわせてパス名を変更するのと、バッチファイル呼び出しの引数に「x64 -vcvars_ver=14.36」を追加してCARLAビルド用のVSバージョンが使われるようにする。
Issue#7737も参照)

2か所目はヘッダファイルの参照エラーが発生するのをあらかじめインクルードパスが通っている場所へコピーする処理を追記している。こちらはVisual Studioをインストールしていない場合も必要

※cmakeコマンドのオプションに「-DENABLE_ALL_WARNINGS=OFF」を追記した。これでアセンブラの警告オプションに関するエラーはでなくなる(2025/09/27追記)

--- a/CarlaSetup.bat
+++ b/CarlaSetup.bat
@@ -77,12 +77,12 @@ if exist "%cd%\Unreal\CarlaUnreal\Content" (
 )

 rem Activate VS terminal development environment:
-if exist "%PROGRAMFILES%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" (
+if exist "D:\...\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" (
     echo Activating "x64 Native Tools Command Prompt" terminal environment.
-    call "%PROGRAMFILES%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" || exit /b
+    call "D:\...\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" x64 -vcvars_ver=14.36 || exit /b
 ) else (
     echo Could not find vcvarsall.bat, aborting setup...
-    exit 1
+    exit /b
 )

 rem -- DOWNLOAD + BUILD UNREAL ENGINE --
@@ -123,6 +123,11 @@ cmake ^
     -DPython3_ROOT_DIR=%python_root% ^
     -DCMAKE_BUILD_TYPE=Release ^
+    -DENABLE_ALL_WARNINGS=OFF ^
     -DCARLA_UNREAL_ENGINE_PATH=%CARLA_UNREAL_ENGINE_PATH% || exit /b
+copy Build\_deps\zlib-src\zlib.h Build\_deps\libpng-build\
+copy Build\_deps\zlib-src\zconf.h.included Build\_deps\libpng-build\zconf.h
+copy Build\_deps\libpng-src\png.h Build\_deps\boost-src\libs\gil\include\
+copy Build\_deps\libpng-build\pnglibconf.h Build\_deps\boost-src\libs\gil\include\
+copy Build\_deps\libpng-src\pngconf.h Build\_deps\boost-src\libs\gil\include\
 echo Building CARLA...

Visual Studioがインストール済みなら Util/SetupUtils/InstallPrerequisites.bat も編集する。インストール済みの場合 vs_community.exe が「1」を返すのでエラー終了しないように編集する。

--- a/Util/SetupUtils/InstallPrerequisites.bat
+++ b/Util/SetupUtils/InstallPrerequisites.bat
@@ -54,7 +54,7 @@ if not exist %cd%\Temp (
 pushd Temp
 curl -L -O https://aka.ms/vs/17/release/vs_community.exe || exit /b
 popd Temp
-%cd%\Temp\vs_community.exe --add %visual_studio_components% --installWhileDownloading --passive --wait || exit /b
+%cd%\Temp\vs_community.exe --add %visual_studio_components% --installWhileDownloading --passive --wait
 del %cd%\Temp\vs_community.exe
 rmdir %cd%\Temp

3. Visual Studioを手動でインストールする(Visual Studioがインストール済みの場合のみ)

Visual Studioがインストール済みの場合はUtil/SetupUtils/InstallPrerequisites.batではインストールされなかったので手作業で必要なコンポーネントをインストールする。

以下のようにInstallPrerequisites.batで指定されているコンポーネントを片っ端からインストールする。vs_community.exeを実行するとGUIのインストール画面が出るので気合でそれっぽい操作をしてインストールする。(よく分からず適当にポチポチやったので手順は残せていない)

curl -L -O https://aka.ms/vs/17/release/vs_community.exe
# 1回目はアップデート?のみでインストールされなかったように見えた
vs_community.exe --add Microsoft.VisualStudio.Component.VC.14.36.17.6.x86.x64
# 再実行した
vs_community.exe --add Microsoft.VisualStudio.Component.VC.14.36.17.6.x86.x64
# これも入っていなかったのでインストールした
vs_community.exe --add Microsoft.Component.PythonTools

4. ダウンロード+ビルド

ここまで来ればバッチを実行すればgit cloneやビルドが自動で実行されるはず。もしinvalid numerical command-line argument : /Wのエラーが出たら↓の方にハマりポイントとして記載したのでそちらを参照。エラーはもうでないはず。(2025/09/27 追記)

ダウンロードは数十GBクラスのgit cloneがあるので光回線じゃないとつらみ。

cd CarlaUE5
CarlaSetup.bat --launch

上記バッチファイルで以下ビルドが走る。

  • UE本体
  • CARLA本体(プラグインだかライブラリだかのファイル)
  • Pythonライブラリ(自動でpip installされる)
  • Unreal Editorの環境(冒頭の画像。シェーダコンパイルなどでかなり重い)

追加で以下を実行すると Build\Package\Carla-0.10.0-Win64-Development\Windows\CarlaUnreal.exe が生成されるっぽい。

cmake --build Build --target package-development

ハマった内容

git cloneのエラー

Content(CARLAのブループリントなどが入っているディレクトリ)をgit cloneするところで以下のエラーに遭遇した。

Could not find CARLA content. Downloading...
Cloning into 'Carla'...
fatal: unable to access 'https://bitbucket.org/carla-simulator/carla-content.git/': error setting certificate verify locations:
  CAfile: E:\soft\Git\mingw64\ssl\certs\ca-bundle.crt
  CApath: none

たぶん証明書が古くなっていたのが原因と思われる(想像)。gitを最新版にアップデートしたらエラーが出なくなった。

zlibのヘッダファイル

以下のエラーに遭遇した。

[17/285] Building C object _deps\libpng-build\CMakeFiles\png_static.dir\pngwtran.c.obj
FAILED: _deps/libpng-build/CMakeFiles/png_static.dir/pngwtran.c.obj
"D:\soft\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\cl.exe"  /nologo -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -IE:\carla\CarlaUE5\Build\_deps\libpng-build -IE:\carla\CarlaUE5\Build\_deps\libpng-src /DWIN32 /D_WINDOWS /EHsc -Wall /Wall /O2 /Ob2 /DNDEBUG -std:c11 -MD -Wall /Wall /wd4005 /showIncludes /Fo_deps\libpng-build\CMakeFiles\png_static.dir\pngwtran.c.obj /Fd_deps\libpng-build\CMakeFiles\png_static.dir\png_static.pdb /FS -c E:\carla\CarlaUE5\Build\_deps\libpng-src\pngwtran.c
E:\carla\CarlaUE5\Build\_deps\libpng-src\pngstruct.h(30): fatal error C1083: Cannot open include file: 'zlib.h': No such file or directory
E:\carla\CarlaUE5\Build\_deps\libpng-build\zlib.h(34): fatal error C1083: Cannot open include file: 'zconf.h': No such file or directory

対処としてファイルをコピーした(CarlaSetup.batのdiffを参照)。

Issue#7782も参照

アセンブリコードにWall指定されてしまう

本項目はcmakeのオプションに「-DENABLE_ALL_WARNINGS=OFF」を追加することで発生しなくなるはず(2025/09/27追記)

ビルド中に以下のエラーに遭遇した。おま環の可能性が高いと思う。たぶん。

MASM : fatal error A1013:invalid numerical command-line argument : /W
[3/73] Building ASM_MASM object _deps\boost-build\libs\con...s\boost_context.dir\src\asm\make_x86_64_ms_pe_masm.asm.obj
FAILED: _deps/boost-build/libs/context/CMakeFiles/boost_context.dir/src/asm/make_x86_64_ms_pe_masm.asm.obj

原因はml64.exeに/Wall-Wallオプションが指定されているため。対処としてWallを削除するフロントエンドプログラムを作成した。

// ml64.cpp
#include <cstdio>
#include <cstdlib>
#include <string>

int main(int argc, char *argv[])
{
    std::string args = "ml64_orig.exe";
    for (int i=1; i<argc; i++){
        auto a = std::string(argv[i]);
        if (a != "-Wall" && a != "/Wall") {
            args += " \"" + a + "\"";
        }
    }

    return std::system(args.c_str());
}

これをコンパイルして

cl ml64.cpp

VSのインストール先ディレクトリ\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\bin\Hostx64\x64 に入っているml64.exeをml64_orig.exeにリネームしてからコンパイルして新しく作ったml64.exeを同ディレクトリにコピー(管理者権限が求められた)した。

本来はCMakeLists.txtで対処すべきだが、どうしても狙った動きをさせられなかったので強引に対処した。(ほかのハマりポイントも同様だけど今回cmakeの修正が全然うまくいかなかった)

png.hのパスが通っていない

以下のエラーに遭遇した。明らかにインクルードパスが設定されていないのが原因だが面倒になってきたのでヘッダファイルをコピーして対処した。(CarlaSetup.batのdiff参照)

[10/72] Building CXX object Examples\CMakeFiles\carla-example-client.dir\CppClient\main.cpp.obj
FAILED: Examples/CMakeFiles/carla-example-client.dir/CppClient/main.cpp.obj
"D:\soft\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\bin\Hostx64\x64\cl.exe"  /nologo /TP -DBOOST_ATOMIC_NO_LIB -DBOOST_ATOMIC_STATIC_LINK -DBOOST_CHRONO_NO_LIB -DBOOST_CHRONO_STATIC_LINK -DBOOST_CONTAINER_NO_LIB -DBOOST_CONTAINER_STATIC_LINK -DBOOST_CONTEXT_EXPORT="" -DBOOST_CONTEXT_NO_LIB="" -DBOOST_CONTEXT_STATIC_LINK="" -DBOOST_COROUTINE_NO_LIB -DBOOST_COROUTINE_STATIC_LINK -DBOOST_DATE_TIME_NO_LIB -DBOOST_DATE_TIME_STATIC_LINK -DBOOST_FILESYSTEM_NO_LIB -DBOOST_FILESYSTEM_STATIC_LINK=1 -DBOOST_PYTHON_NO_LIB -DBOOST_PYTHON_STATIC_LIB -DBOOST_PYTHON_STATIC_LINK -DBOOST_RANDOM_NO_LIB -DBOOST_RANDOM_STATIC_LINK -DBOOST_SERIALIZATION_NO_LIB -DBOOST_SERIALIZATION_STATIC_LINK -DBOOST_THREAD_NO_LIB -DBOOST_THREAD_STATIC_LINK -DBOOST_THREAD_USE_LIB -DHAVE_SNPRINTF -DLIBCARLA_IMAGE_SUPPORT_PNG=1 -DPy_NO_LINK_LIB -DRPCLIB_MSGPACK=clmdep_msgpack -DRPCLIB_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_USE_MATH_DEFINES -D_WIN32_WINNT=0x0601 -IE:\carla\CarlaUE5\LibCarla\source -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\asio\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\align\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\assert\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\config\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\core\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\static_assert\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\throw_exception\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\context\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\mp11\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\pool\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\integer\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\type_traits\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\winapi\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\predef\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\smart_ptr\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\move\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\coroutine\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\exception\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\tuple\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\system\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\variant2\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\utility\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\io\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\preprocessor\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\date_time\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\algorithm\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\array\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\bind\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\concept_check\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\function\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\iterator\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\detail\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\function_types\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\mpl\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\fusion\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\container_hash\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\describe\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\typeof\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\functional\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\optional\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\range\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\conversion\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\regex\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\unordered\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\lexical_cast\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\container\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\intrusive\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\numeric\conversion\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\tokenizer\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\python\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\foreach\include -external:ID:\soft\Python38\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\geometry\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\any\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\type_index\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\endian\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\math\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\random\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\dynamic_bitset\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\multiprecision\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\polygon\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\qvm\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\rational\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\serialization\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\spirit\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\phoenix\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\proto\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\thread\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\atomic\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\chrono\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\ratio\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\variant\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\gil\include -external:IE:\carla\CarlaUE5\Build\_deps\boost-src\libs\filesystem\include -external:IE:\carla\CarlaUE5\Build\_deps\recastnavigation-src\Recast\Include -external:IE:\carla\CarlaUE5\Build\_deps\recastnavigation-src\Detour\Include -external:IE:\carla\CarlaUE5\Build\_deps\recastnavigation-src\DetourCrowd\Include -external:IE:\carla\CarlaUE5\Build\_deps\zlib-build -external:IE:\carla\CarlaUE5\Build\_deps\zlib-src -external:IE:\carla\CarlaUE5\Build\_deps\rpclib-src\include -external:W0 /DWIN32 /D_WINDOWS /EHsc -Wall /Wall /O2 /Ob2 /DNDEBUG -std:c++20 -MD /utf-8 /EHsc /GR -Wall /Wall /wd4005 /showIncludes /FoExamples\CMakeFiles\carla-example-client.dir\CppClient\main.cpp.obj /FdExamples\CMakeFiles\carla-example-client.dir\ /FS -c E:\carla\CarlaUE5\Examples\CppClient\main.cpp
E:\carla\CarlaUE5\Build\_deps\boost-src\libs\gil\include\boost/gil/extension/io/png/tags.hpp(25): fatal error C1083: Cannot open include file: 'png.h': No such file or directory

ROS2について

以下のリンクと同じエラーになってROS2は有効化できなかった。

強引に先に進めようとしても最終的にunresolvedエラーになった。詳細は後日別記事で報告予定。

ROS2のビルドエラーは解消できた。次の記事を参照のこと。(2025/09/27追記)

maminus.hatenadiary.org

以上。

第24回UE5ぷちコンに参加しました

この記事について

2025/07/18(金)~2025/08/31(日)の間、第24回UE5ぷちコン向けの作業を実施して作品を提出しました。

www.youtube.com

本記事は上記に対する振り返り記事(感想文)です。作業を通じて得られた気づきや学びを共有する目的の記事となります。

開始時点の状況

ぷちコン作業開始時点(2025/07/18夜)では「UE5極め本を読破済み」「UdemyのUE関連講座を10時間程度実施済み」の状況でした。
あと、昔UE5.2あたりの時代でPCGを少し触ったことがあったのですが、開始時点ではそのことをすっかり忘れているくらいUEに関する記憶が消えていました(極め本の内容もだいぶ忘れていました)。

こんな状況だったのでUEの使い方を勉強しつつ作品作りを進めていきました。

理想と現実

予定と実績がどうだったか思い出しつつ振り返ります。

目標達成状況

ぷちコンへの参加を決めた理由は「UEを触ってゲームやシミュレーターに対する理解度を引き上げたい」というのが主な理由です。
応募要項で動画投稿が必要ということだったのでサブ目標として「動画投稿者(の真似事)をやってみる」という項目も追加しました。

具体的な目標と実施結果は以下の通りです。

目標 自己評価
毎週進捗報告動画を出す ◎ : 達成
UEで簡単なゲームを作ってみたい ◎ : 達成
UEの天候システムを勉強したい ○ : 達成。標準で入っているものをゲームに組み込めた
物理エンジンを触ってみたい ○ : 達成。ゲームの一部に物理要素を組み込めた
Chaos Destructionを触ってみたい ○ : 達成。ゲームの一部に組み込めた
Niagaraを触ってみたい ○ : 達成。ゲームの一部に組み込めた
Chaos Vehiclesを触ってみたい × : 未達成。ただし今後のモチベーションにつながりそう
流体シミュレーションを触ってみたい × : 未達成
Chaos Clothを触ってみたい × : 未達成

※未達成の目標は時間が足りずチャレンジを断念したのが未達成となった原因です

コメント

動画投稿はやっておいてよかったです。本番の予行演習になったのと、なによりサボり防止効果が大きかったです。
反省点としては最後まで音量調節がうまくいかなかった点が心残りですね。あと、ゲーム内で思い通りの場面を発生させるのが難しくて提出動画が微妙だったのも心残りです(字幕と解説音声で誤魔化しました)。

ぷちコン参加の観点では最小構成とはいえゲームを完成できたというのが次につながるモチベーションになったと感じました。
実際に今回未達成で終わった項目も「機会を見てやりたい」という気持ちを持てています。(いつもなら満足して終わってしまうところだった)

スケジュール

ぷちコンの作業を始めた当初は「勉強しながら少しずつ作っていけばいいや」と軽く考えていたので線表も何もない状態で何となく進めていました。

「このままじゃマズイ」と気づいたのが7/27の進捗報告を作った時でした。想定以上に作るのも勉強も進んでいないと気づいたのです。毎週進捗動画を作っていたことが進捗の客観視につながりました。
そこで急遽7/28週以降の仮線表を引きました。ゲーム内容もその時点の自分に実現可能な内容に絞りました。

以下がその時作った計画(表の「計画時」欄)と実際の実績表です。

日程 計画時 実績
7/18~20 Chaos Destructionで壊れる壁を作成, ボールを発射する機能を実装
7/21週 ボールを止めてエネルギーを貯める機能を実装, 壁が壊れる際に派手に吹き飛ぶ機能を実装, 反射フィールドを実装
7/28週 技術検証(Volumetric Cloud, Niagara, UMG:Unreal Motion Graphicsウィジェットブループリント) 時計台ギミックを実装, UIお試し実装
8/04週 ギミック作成(ジャンプ板+重力, リバーシ壁, 硬い壁) ジャンプ板を実装, リバーシ壁を実装, 硬い壁を実装
8/11週(お盆) Niagaraエフェクト作成, スコアシステム, ギミック作成(霧+風車, 強風+風車, 水車+水流, 落雷) 風車ギミックを実装, 水車ギミックを実装, 落雷ギミックを仮実装, エネルギー貯めにエフェクトを追加, スコアシステム仮実装
8/18週 バランス調整(特にボールの反射), アセット差し替え(出来高。最悪グレーボックスのまま) ステージ1を実装, 通常壁と時計台アセットを本番に差し替え, スコアシステムを本実装, プレイヤーキャラクターにアニメーションを適用, 落雷ギミックを本実装, ボールの反射を仮調整, SEを追加
8/25週(最終) ブラッシュアップ(タイトル画面,ゲーム終了機能), 最終バランス調整 バランス調整, タイトル画面を実装, ステージクリア画面を実装, パッケージ化, BGMを追加

どのように進めたか(マネジメント面)

7/28以降は計画表に従って作業を進めました。計画表と状況を比べて以下の方針で進めました。管理は1週間単位で進めています。

  • 遅れていれば何かをあきらめる
  • 進んでいれば余った時間の枠内で追加できそうな要素を追加する

結果として計画表の内容はほぼ実装しました。「霧+風車」のギミックのみ風車ギミックと差別化が難しくオミットした程度です。
実績欄が多く見えるかもしれませんが短時間で追加できる要素を追加した程度になっています。

当初想定と違っていたこと(甘く考えていたこと)

これは自分で大いに反省すべき点と思いますが、極め本を読んでUEを分かったつもりになって「書けばすぐ動く」と思っていたことがことごとく「やってみないと分からないことだらけ」だったというのが誤算でした。

特にはまったのは以下の機能です。

UEそのものに対する理解度が低いので「自分が思っている動きをしてくれない」場合に何が原因なのかが分からず手当たり次第にパラメータなどをいじるような試行錯誤のパワープレイで進めてしまいました。

これは想像ですが恐らくUEのソースコードを読んである程度仕組みを理解してから触る方が良いのだろうと思います。1か月半の期間中にそれができるかどうかは別問題ですけど

あと、バグなのか仕様なのか自分のやり方が悪いのかよく分からない現象に何度か遭遇して本当に困りました。欲をかいてUE最新版を使わず安定してそうなバージョンにすべきだったかも

技術面での気づき・学び

通化

今回自分でやってみて痛感したのは「内部処理の共通化は本当に重要」ということです。今回は以下2点のみ実施できました。

他にアクタに対してTagを付与したりしました。

途中でリファクタリングしながら進めていたのですが、最初からある程度共通化の構想を練っておいた方が楽だと感じました。
普段UE以外でプログラムを作るときは先にお試し実装してから共通化を考えたりしているのですがUnreal Editorであちこち共通化のためにいじり倒すのは結構面倒に感じました。(単純に慣れてないからかもしれません)

管理用クラス??

単純なアクタとは別に管理用のBPクラスを作りました。例えばリバーシ壁です。

リバーシ壁は以下のスクショにあるように黒の壁と白の壁が通常の壁を挟んだ状態で1つのグループを(内部的に)形成しています。

リバーシ

実装としては専用のBPクラスを作ってConstruction Scriptの中で壁を生成しています。

他にはスコアの管理用クラスなどいくつか「アクタと1対1ではないBPクラス」を作りました。
このあたりの力加減?ベストプラクティス?のようなものがまだよく分かっていません。

音を鳴らす仕組み

今回SEとBGMはどちらもアクタにAudioコンポーネントを追加するかPlay Sound at Locationを呼ぶかどちらかで実装しました。
音量は実際にゲーム内で鳴らしてみて調整しました。

このやり方だと音量調整が音の鳴らす箇所それぞれに手を入れる必要があってゲームが大規模化したらすぐ破綻しそうに感じました。
次にやる時は以下のようにやるのがよさそうと思っています。

  • アセットとしてimportするに音量レベルをそろえておく
  • 音を鳴らす仕組みを共通処理として実装して音量調整できる仕組みを共通箇所に入れる

アセットとゲームのコンセプト

元々ゲームのアセット探しは大変というイメージを持っていて、実際その通りでしたが本当に大変でした。とにかく見つからない。

  1. ゲームの要素を作りこむ
  2. イメージに合致するアセットを探す

という手順で進めたのですがイメージに合うアセットを探すのにとても時間がかかりました。

ぷちコン完走できるか自信がなかったので今回は極力お金を使わない方向で考えていました。期間的にもアセット制作を誰かに依頼するのも厳しいでしょう(そもそもツテがない)。

無料で使わせてもらえるアセットだけでゲームを作るのなら先にアセットを探してからアセットのイメージに合うステージやギミックを発想していくやり方の方が楽かもしれないと感じました。

完走した感想

最後にぷちコンに参加してみての感想ですが、「やってよかった」というのが一番感じたことです。

提出した作品は「ギリギリゲームを名乗れるかどうか」みたいな状態ですが逆に自分の今の立ち位置を再確認できたと思っています。
期間中は大変でしたが勉強はすごく進んだので「手を動かすこと」の重要さを再確認できました。本当にやってみないと分からないことだらけだったと感じています。

ぷちコンを通じて色々勉強できたのでやりたいことが爆発的に増えてしまいました。
直近で何をやろうか悩んでいます(うれしい悲鳴)。

rqt_image_viewでCompressedImageを表示するにはトピック名を固定にする必要がある件

環境

  • ROS2 Humble
  • 以下のパッケージをインストール済み
    • ros-humble-camera-info-manager
    • ros-humble-image-transport
    • ros-humble-image-transport-plugins
  • source /opt/ros/humble/setup.bash実施済み

現象

以下のトピックをpublishしている状況

内容 メッセージ型
カメラ画像 CompressedImage
深度画像 CompressedImage
カメラ情報 CameraInfo

ros2 run rqt_image_view rqt_image_viewでrqt_image_viewのウィンドウを出してカメラ画像のトピックや深度画像のトピックを選択するとエラーのダイアログボックスが表示されて画像が表示されない。

エラーの内容は以下の通り。

カメラ画像

Unable to load plugin for transport 'image_transport/image_sub', error string: According to the loaded plugin descriptions the class image_transport/image_sub with base class type image_transport::SubscriberPlugin does not exist. Declared types are image_transport/compressedDepth_sub image_transport/compressed_sub image_transport/raw_sub image_transport/theora_sub

深度画像

Unable to load plugin for transport 'image_transport/depth_sub', error string: According to the loaded plugin descriptions the class image_transport/depth_sub with base class type image_transport::SubscriberPlugin does not exist. Declared types are image_transport/compressedDepth_sub image_transport/compressed_sub image_transport/raw_sub image_transport/theora_sub

解決方法

こちらのフォーラムに書かれている通り、トピック名の最後(階層の最後のエントリ)が固定名じゃないと表示されない。

項目 トピック名
カメラ画像 /xxx/yyy/compressed
深度画像 /xxx/yyy/compressedDepth
カメラ情報 /xxx/yyy/camera_info

なお、ソースコードを見たらCameraInfoのトピックは他のトピックと同じ階層であることを前提としているようなコードになっていたので階層(上の表の/xxx/yyy部分)をそろえておいた方がよさそう。

余談

CompressedImageの深度画像を表示するときに以下のエラーが表示される。

SubscriberPlugin::subscribeImpl with five arguments has not been overridden

これはHumbleのSubscriberPlugin::subscribeImpl()メソッドで用意されている実装コードでロガーに出しているエラーが表示されている。処理自体は4引数版のsubscribeImpl()を呼んでいてそれらしく動いているので問題はなさそう。

※デフォルトブランチ(記事執筆時rolling)のソースコードでは該当箇所のコードが変化して4引数版メソッドがなくなっているので恐らく当該エラーは出なくなっているのでは(実際の動作は確認していないけど)

DirectML使ってみた

冬は寒いのでDNNの学習を回すのにぴったり!GPUの廃熱で暖房費節約だぜ!などと思ったけれど、メインマシンはメインOSはWindowsで運用していてビデオカードAMDなのでDNNフレームワークを動かすのはしんどい。 調べたらDirectMLってのでWindows+AMDでもいけそうじゃん!ということで試しに使ってみた。
結論としてはGPU使用率があまり高くならず暖かくならなかった

環境構築(共通)

環境はAnacodaを使わずにWindowsPythonをインストールしてvenvで構築する。Anacondaはライセンス変わっちゃったからね

Pythonのインストール

python.orgからインストーラをダウンロードする。自分はこちらのページから3.8.10の「Windows installer (64-bit)」を選んだ。

インストール時にはpipが一緒にインストールされるようにオプションがOnになっていることを確認した。(自分の環境ではデフォルトでOnになっていた)

あと、Pythonのパッケージを入れる際に git.exe と cl.exe も必要になるのでインストールしてパスに追加する。

gitは「Git for Windows」を入れたような気がするが、ずいぶん前の事なので詳細は不明。

cl.exeはMicrosoft C++ Build Toolsページから「Build Tools のダウンロード」ボタンを押してインストーラを取得、インストーラからMicrosoft Visual C++だか何だかを選択して入れたような気がする。(こちらもうろ覚え)

venv

Pythonをインストールした時点でvenvも入っているのでそのままvenv環境を作れる。
構築直後はpipのバージョンが古いのでバージョンアップしておく。この時、直接pipコマンドでバージョンアップしようとすると環境を壊してしまうので注意。python -m pipでバージョンアップする。

>python -m venv env_top_dir
>env_top_dir\Scripts\activate
>python -m pip install --upgrade pip

ONNXRuntime

環境構築

venv環境にonnxとonnxruntime-directmlパッケージをインストールすればOK。

>pip install onnx onnxruntime-directml

お試し

基本的にはDirectMLのExecution Provider(DmlExecutionProvider)を指定するだけだが、2点注意点がある。

  1. opsetバージョンはv17まで
  2. セッションのオプションでenable_mem_patternを無効化しておく必要がある

どちらもDirectML版ONNXRuntimeが対応してないっぽい。ちなみにenable_mem_patternの方は無効化しなくても以下の警告が表示されて自動的に無効化されるっぽい。

[W:onnxruntime:, inference_session.cc:491 onnxruntime::InferenceSession::RegisterExecutionProvider] Having memory pattern enabled is not supported while using the DML Execution Provider. So disabling it for this session since it uses the DML Execution Provider.

以下お試しコード。モデルはConv1個だけのなんちゃってモデル。

import onnx
import onnx.numpy_helper
import numpy as np
import onnxruntime as ort


# Conv1個だけのモデル
inputs  = [onnx.helper.make_tensor_value_info('input' , onnx.TensorProto.FLOAT, [1, 3, 4, 4])]
outputs = [onnx.helper.make_tensor_value_info('output', onnx.TensorProto.FLOAT, [1, 1, 4, 4])]
nodes   = [onnx.helper.make_node('Conv', ['input', 'weight'], ['output'])]
inits   = [onnx.numpy_helper.from_array(1.0 / 4.0 * np.ones([1, 3, 1, 1], dtype=np.float32), 'weight')]
model = onnx.helper.make_model(onnx.helper.make_graph(nodes, 'conv', inputs, outputs, inits), opset_imports=[onnx.helper.make_opsetid('', 17)])

# Onだと警告が出るのであらかじめOff設定を入れておく
options = ort.SessionOptions()
options.enable_mem_pattern = False

# ExecutionProviderにDML版を指定して実行する
sess = ort.InferenceSession(model.SerializeToString(), options, ['DmlExecutionProvider', 'CPUExecutionProvider'])
sess.run(None, {'input': np.ones([1, 3, 4, 4], dtype=np.float32)})

TensorFlow

環境構築

venv環境にpipで入れるだけ。ほかに必要なパッケージは依存パッケージとして自動で入った。

>pip install tensorflow-directml-plugin

お試し

これで普通に動いた。'/job:localhost/replica:0/task:0/device:GPU:0'などと表示されたのでたぶん動いてる。

import tensorflow as tf
a = tf.constant([1.5])
b = tf.constant([0.5])
(a + b).device

あと公式のサンプルをそのまま書かれている通りに実行してみたら普通に動いた。データセットのダウンロードも自動で実行してくれてとても楽だった。

PyTorch

環境構築

同じくvenv環境にpipで入れる。

>pip install torchvision==0.14.0
>pip install torch==1.13
>pip install torch-directml

お試し

torch.deviceをDirectMLのもので指定すれば良いらしい。Tensor.to()には文字列を指定できずtorch.deviceを渡す必要がある。

あと、torch.Tensorをrepr()などで表示しようとするとエラーになる。(CPUに転送すれば表示できる)

import torch
import torch_directml


dml = torch_directml.device()

a = torch.tensor([1.5]).to(dml)
b = torch.tensor([0.5]).to(dml)
c = a + b
c.to('cpu')

簡単なモデルを作って動かしてみたがConv2d、BatchNorm2d、ReLU、Linearあたりは普通に動きそうだった。

mmdetectionでDETRの学習

PyTorchで動く物体検出向けフレームワーク?のMMDetectionを使ってDETR実装で学習を回すところまで改造してみた。

結論を先に言っておくとGPU使用率は上がらず温まらなかったtouch.deviceを入れ替えるだけでは動かなかった。

まだまだCPU実行時と同じ動きをしてくれないオペレーションがあるので既存のフレームワークなんかをそのまま使うのは厳しい、ということが分かった。
今はまだ公式のサンプルを使うのがよさそうに思える。サンプルのyolov3を試そうとしたらデータセットのダウンロード方法がよくわからず面倒になってやめてしまった

環境構築

このあたりを参考にしつつ以下の手順でvenv環境にインストールした。

>pip install mmcv-full==1.7.0 -f https://download.openmmlab.com/mmcv/dist/cpu/torch1.13/index.html
>git clone https://github.com/open-mmlab/mmdetection.git
>cd mmdetection
>pip install -v -e .
>pip install opencv-python

※DirectMLで動かすためにgit cloneしたmmdetectionリポジトリソースコードを改造して無理やり動かしている

さらにDETRの定義ファイルと重みデータをダウンロードする。

>pip install -U openmim
>mim download mmdet --config yolov3_mobilenetv2_320_300e_coco --dest checkpoints

データセットのダウンロード方法を見ながらMS COCOデータセットを用意して、学習の実行方法を参考にした。

最終的にはデータセットの置き場所をEドライブのdatasetsに変更していたので以下の感じで実行した。

>set "MMDET_DATASETS=E:/datasets/coco/"
>python source_packages\mmdetection\tools\train.py checkpoints\detr_r50_8x2_150e_coco.py --cfg-options data.samples_per_gpu=4

samples_per_gpuは1枚のビデオカードで一度に読み出すデータ数らしくてビデオカードが1枚しか存在しない環境ならそのままバッチサイズになるらしい。(たぶん。↑だとバッチサイズ4ということ)

困ったこと

DirectMLで動かそうとして遭遇したことは以下の通り。

  • VRAMが足りなくなるとブルースクリーンでOSごと落ちる(正確にはPCが再起動する)
  • DirectMLが対応していないオペレーションがある
    • エラーになるケース(Pythonの例外が送出される)とエラーにならず実行結果がCPU実行時と異なるケースの2パターンある
    • どちらのケースも該当箇所の処理をCPUデバイスで実行するようにすればとりあえず動くようになる

DirectMLが対応していなかった箇所(DETRで通過する箇所のみ)

mmdet/core/bbox/match_costs/match_cost.py

torch.cdist()で例外になる。

RuntimeError: The size of tensor a (2) must match the size of tensor b (100) at non-singleton dimension 0

CPU実行時はバッチ次元が異なっても問題なく実行できるがDirectML実行時はエラーになる。

@@ -47,8 +47,8 @@ class BBoxL1Cost:
             gt_bboxes = bbox_xyxy_to_cxcywh(gt_bboxes)
         elif self.box_format == 'xyxy':
             bbox_pred = bbox_cxcywh_to_xyxy(bbox_pred)
-        bbox_cost = torch.cdist(bbox_pred, gt_bboxes, p=1)
-        return bbox_cost * self.weight
+        bbox_cost = torch.cdist(bbox_pred.to('cpu'), gt_bboxes.to('cpu'), p=1)
+        return bbox_cost.to(gt_bboxes.device) * self.weight
mmdet/core/bbox/samplers/pseudo_sampler.py

unique()でエラーになる。(※エラーの内容はメモり忘れてた…)

@@ -33,9 +33,9 @@ class PseudoSampler(BaseSampler):
             :obj:`SamplingResult`: sampler results
         """
         pos_inds = torch.nonzero(
-            assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).unique()
+            assign_result.gt_inds > 0, as_tuple=False).squeeze(-1).cpu().unique().to(gt_bboxes.device)
         neg_inds = torch.nonzero(
-            assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).unique()
+            assign_result.gt_inds == 0, as_tuple=False).squeeze(-1).cpu().unique().to(gt_bboxes.device)
         gt_flags = bboxes.new_zeros(bboxes.shape[0], dtype=torch.uint8)
         sampling_result = SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes,
                                          assign_result, gt_flags)
mmdet/models/dense_heads/detr_head.py

このファイルは2か所あって、1つ目はテンソルの一部をSlice指定で上書きするコードがDirectMLだとなぜか上書きされないという挙動になる。 2つ目はバッチ次元が0(教師データのBBox数が0個)の時に [0, 4] shape との演算にDirectMLが対応していなくて例外になる。

RuntimeError: self must have at least one element!

@@ -244,10 +244,11 @@ class DETRHead(AnchorFreeHead):
         # ignored positions, while zero values means valid positions.
         batch_size = x.size(0)
         input_img_h, input_img_w = img_metas[0]['batch_input_shape']
-        masks = x.new_ones((batch_size, input_img_h, input_img_w))
+        masks = x.new_ones((batch_size, input_img_h, input_img_w)).cpu()
         for img_id in range(batch_size):
             img_h, img_w, _ = img_metas[img_id]['img_shape']
             masks[img_id, :img_h, :img_w] = 0
+        masks = masks.to(x.device)

         x = self.input_proj(x)
@@ -537,8 +538,8 @@ class DETRHead(AnchorFreeHead):
         # the box format should be converted from defaultly x1y1x2y2 to cxcywh.
         factor = bbox_pred.new_tensor([img_w, img_h, img_w,
                                        img_h]).unsqueeze(0)
-        pos_gt_bboxes_normalized = sampling_result.pos_gt_bboxes / factor
-        pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized)
+        pos_gt_bboxes_normalized = sampling_result.pos_gt_bboxes / factor if len(sampling_result.pos_gt_bboxes) else sampling_result.pos_gt_bboxes
+        pos_gt_bboxes_targets = bbox_xyxy_to_cxcywh(pos_gt_bboxes_normalized) if len(sampling_result.pos_gt_bboxes) else sampling_result.pos_gt_bboxes
         bbox_targets[pos_inds] = pos_gt_bboxes_targets
         return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,
                 neg_inds)

torch.device関連コード(参考)

説明が面倒になってきたのでそのままソースコードの差分だけ貼っておきます。

ちゃんと対応するにはmmcv側から改造が必要になるのと、mmdetection内ではtorch.deviceを使わずデバイス名のstrを受け取る形で実装されているので、以下の箇所以外に色々修正しないとダメだったりで不完全なので。

mmdet/apis/inference.py
@@ -151,6 +151,7 @@ def inference_detector(model, imgs):
             assert not isinstance(
                 m, RoIPool
             ), 'CPU inference with RoIPool is not supported currently.'
+        data['img'] = [cpu_tensor.to(device) for cpu_tensor in data['img']]

     # forward the model
     with torch.no_grad():
mmdet/apis/train.py
@@ -41,6 +41,10 @@ def init_random_seed(seed=None, device='cuda'):
     if world_size == 1:
         return seed

+    if device == 'dml':
+        import torch_directml
+        device = torch_directml.device()
+
     if rank == 0:
         random_num = torch.tensor(seed, dtype=torch.int32, device=device)
     else:
mmdet/utils/util_distribution.py
@@ -33,6 +33,12 @@ def build_dp(model, device='cuda', dim=0, *args, **kwargs):
         from mmcv.device.mlu import MLUDataParallel
         dp_factory['mlu'] = MLUDataParallel
         model = model.mlu()
+    elif device == 'dml':
+        import torch_directml
+        from mmdet.device.dml import DMLDataParallel
+        dp_factory['dml'] = DMLDataParallel
+        dml = torch_directml.device()
+        model = model.to(dml)

     return dp_factory[device](model, dim=dim, *args, **kwargs)
@@ -55,7 +61,7 @@ def build_ddp(model, device='cuda', *args, **kwargs):
                      DistributedDataParallel.html
     """
     assert device in ['cuda', 'mlu',
-                      'npu'], 'Only available for cuda or mlu or npu devices.'
+                      'npu', 'dml'], 'Only available for cuda or mlu or npu devices.'
     if device == 'npu':
         from mmcv.device.npu import NPUDistributedDataParallel
         torch.npu.set_compile_mode(jit_compile=False)

@@ -81,9 +93,18 @@ def is_mlu_available():
     return hasattr(torch, 'is_mlu_available') and torch.is_mlu_available()


+def is_dml_available():
+    try:
+        import torch_directml
+        return torch_directml.is_available()
+    except ImportError as e:
+        return False
+
+
 def get_device():
     """Returns an available device, cpu, cuda or mlu."""
     is_device_available = {
+        'dml': is_dml_available(),
         'npu': is_npu_available(),
         'cuda': torch.cuda.is_available(),
         'mlu': is_mlu_available()
mmdet/device/dml 配下

こちらは本来はmmcvに入れるべきコード。面倒なのでmmdetection配下に入れた。

# __init__.py
from ._functions import scatter, scatter_kwargs
from .data_parallel import DMLDataParallel
from .distributed import DMLDistributedDataParallel


__all__ = ['scatter', 'scatter_kwargs', 'DMLDataParallel', 'DMLDistributedDataParallel']


# _functions.py
import torch
import torch_directml
from typing import Union, List
from mmcv.parallel.data_container import DataContainer
from mmcv.device._functions import Scatter


def _scatter_core(current_device: torch.device, obj: Union[List, torch.Tensor]):
    if isinstance(obj, list):
        return [_scatter_core(current_device, elem) for elem in obj]
    elif isinstance(obj, torch.Tensor):
        return obj.to(current_device)
    else:
        raise RuntimeError(f'obj is unsupported type {type(obj)}')


def _scatter_data_container(current_device: torch.device, obj: DataContainer):
    outputs = _scatter_core(current_device, obj.data)
    return tuple(outputs) if isinstance(outputs, list) else (outputs,)


def scatter(inputs, target_devices, dim=0):
    device_id = next(iter(target_devices), torch_directml.default_device())
    current_device = torch_directml.device(device_id)

    def scatter_map(obj):
        if isinstance(obj, torch.Tensor):
            if target_devices != [-1]:
                obj = obj.to(current_device)
                return [obj]
            else:
                # for CPU inference we use self-implemented scatter
                return Scatter.forward(target_devices, obj)
        if isinstance(obj, DataContainer):
            if obj.cpu_only:
                return obj.data
            else:
                return _scatter_data_container(current_device, obj)
        if isinstance(obj, tuple) and len(obj) > 0:
            return list(zip(*map(scatter_map, obj)))
        if isinstance(obj, list) and len(obj) > 0:
            out = list(map(list, zip(*map(scatter_map, obj))))
            return out
        if isinstance(obj, dict) and len(obj) > 0:
            out = list(map(type(obj), zip(*map(scatter_map, obj.items()))))
            return out
        return [obj for _ in target_devices]

    try:
        return scatter_map(inputs)
    finally:
        scatter_map = None


def scatter_kwargs(inputs, kwargs, target_devices, dim=0):
    inputs = scatter(inputs, target_devices, dim) if inputs else []
    kwargs = scatter(kwargs, target_devices, dim) if kwargs else []

    if len(inputs) < len(kwargs):
        inputs.extend([() for _ in range(len(kwargs) - len(inputs))])
    elif len(kwargs) < len(inputs):
        kwargs.extend([{} for _ in range(len(inputs) - len(kwargs))])

    inputs = tuple(inputs)
    kwargs = tuple(kwargs)

    return inputs, kwargs


# data_parallel.py
import torch_directml
from mmcv.parallel import MMDataParallel
from ._functions import scatter_kwargs


class DMLDataParallel(MMDataParallel):
    def __init__(self, *args, dim=0, **kwargs):
        super().__init__(*args, dim=dim, **kwargs)

        self.device_ids = kwargs.get('device_ids', [torch_directml.default_device()])
        self.src_device_obj = torch_directml.device(self.device_ids[0])

    def scatter(self, inputs, kwargs, device_ids):
        return scatter_kwargs(inputs, kwargs, device_ids, dim=self.dim)

venvのトップディレクトリにPyTorchの重みファイル(pth)を置いちゃダメって話

横着したらハマったので自戒を込めてメモ。

まとめ

  • Pythonには「サイト固有の設定フック」という機能がある
    • venv環境のトップディレクトリに入っている .pth ファイルを読み込んで処理する
  • PyTorchの学習済み重みファイル(拡張子 .pth)を置いていると上記機能と衝突して誤動作する
    • エラーでpython.exeが実行できなくなる。pipコマンドもエラーで実行できなくなる
  • venv環境と作業ディレクトリはツリーを分けよう(戒め)

サイト固有の設定フック機能

  • venv環境の直下とlib\site-packagesディレクトリに入っている .pth ファイルを読み込むらしい
    • 後述するsite.pyにprint文を埋め込んで上記2ディレクトリが対象になっていることを確認した
  • .pthファイルにはパス名が書かれている前提っぽい

エラーの内容

venvのトップディレクトリ(以下の例だとE:\soft\env_torch直下)に重みファイル(.pth)を置いてからpython.exeを実行しようとすると以下のようなエラーになる。

(env_torch) E:\soft\env_torch>python
Fatal Python error: init_import_size: Failed to import the site module
Python runtime state: initialized
Traceback (most recent call last):
  File "E:\soft\Python38\lib\site.py", line 580, in <module>
    main()
  File "E:\soft\Python38\lib\site.py", line 563, in main
    known_paths = venv(known_paths)
  File "E:\soft\Python38\lib\site.py", line 495, in venv
    addsitepackages(known_paths, [sys.prefix])
  File "E:\soft\Python38\lib\site.py", line 350, in addsitepackages
    addsitedir(sitedir, known_paths)
  File "E:\soft\Python38\lib\site.py", line 208, in addsitedir
    addpackage(sitedir, name, known_paths)
  File "E:\soft\Python38\lib\site.py", line 164, in addpackage
    for n, line in enumerate(f):
UnicodeDecodeError: 'cp932' codec can't decode byte 0x8a in position 2: illegal multibyte sequence

PyTorchの重みファイルはバイナリファイルだが、これをパス名が記載されているテキストファイルとして読み込もうとしてエラーになっているっぽい。

エラーメッセージは文字コード関連のメッセージだが騙されてはいけない。環境変数PYTHONUTF8を設定しても無駄である。

pythonもpipも実行できなくなるので問題のデバッグ自体がつらくなるので注意。今回はsite.pyを直接編集してprint文を埋め込んでデバッグした。

結論

  • venv環境の直下にはPyTorchの重みファイルを置いてはいけない
  • そもそも作業ディレクトリを別ツリーに分けておけば回避できる(横着はよくなかった)

一応.ptファイルなら誤動作しないと思われるが、そこは問題の本質ではないと思う。論文の実装コードで自動ダウンロードが走ったりすることがあるし。