Getting Started
- .NET 10 SDK
- SteamVR がインストール済みで、アプリ実行前に起動していること
- OpenVR ランタイム(SteamVR に同梱)
サンプルアプリを動かす(最速)
Section titled “サンプルアプリを動かす(最速)”リポジトリをクローンしたらまずサンプルアプリを起動してフレームワークの動作を確認できます。
# SteamVR を起動してから実行するdotnet run --project samples/FloatSoda.Samples.OverlayApp起動すると SteamVR ダッシュボードに、レイアウト、時計、アニメーション、カウンターのデモ用タブが追加されます。左コントローラー追従とワールド座標固定の時計オーバーレイも生成されます(samples/FloatSoda.Samples.OverlayApp/Program.cs)。
SteamVR を終了するか VREvent_Quit を受信するとアプリも自動終了します。
サンプルには
StatefulWidgetを使った時計ウィジェット(WatchWidget.cs)が含まれており、SetState()による毎秒の再ビルドで時刻が更新されます。
用途別のサンプル一覧
Section titled “用途別のサンプル一覧”samples/ には目的の異なる5つのプロジェクトがあります。
| プロジェクト | 内容 | SteamVR |
|---|---|---|
FloatSoda.Samples.OverlayApp |
レイアウト・時計・アニメーション・カウンター・ドラッグの総合デモ。3種のオーバーレイを同時に生成する | 必要 |
FloatSoda.Samples.GettingStarted |
下の「最小構成のコード」とほぼ同じ最小アプリ | 必要 |
FloatSoda.Samples.PointerRegion |
ホバー・押下・取り消しの状態を画面に出す入力デモ。PointerRegion と Listener の挙動を目で確かめられる |
必要 |
FloatSoda.Samples.PrimitiveOverlay |
ウィジェット層を使わず、FloatSoda.OVR の低レベル API だけでオーバーレイを出す |
必要 |
FloatSoda.Samples.PaintingSample |
Widget / RenderObject / Layer の各ツリーを PNG へ書き出す | 不要 |
PaintingSample だけは SteamVR も HMD もいりません。 FloatSoda.Testing のヘッドレスレンダラーで
ツリーを画像化し、デスクトップへ widget_tree_output.png などを保存します。
レイアウトの結果だけを確かめたいときは、HMD をかぶらずにこれで見られます。
dotnet run --project samples/FloatSoda.Samples.PaintingSample新しいアプリを作る
Section titled “新しいアプリを作る”1. プロジェクトを作成する
Section titled “1. プロジェクトを作成する”dotnet new console -n MyOverlayAppcd MyOverlayAppdotnet add reference ../path/to/FloatSoda/src/FloatSoda/FloatSoda.csproj2. 最小構成のコードを書く
Section titled “2. 最小構成のコードを書く”Program.cs を以下のように書き換えます。Widget ベースの書き方が推奨です。
using FloatSoda;using FloatSoda.Widgets;using FloatSoda.Widgets.Layout;using FloatSoda.Widgets.Paint;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using SkiaSharp;
var builder = Host.CreateApplicationBuilder(args);builder.Services.AddFloatSoda();
using var host = builder.Build();var app = host.Services.GetRequiredService<FloatSodaApp>();
Widget root = new Center{ Child = new ColoredBox { Color = SKColors.CornflowerBlue, Child = new SizedBox { Width = 400, Height = 200 } }};
// オーバーレイのサイズは root ウィジェットのレイアウト結果に自動追従します。app.CreateWindow(new DashboardWindow { Title = "HelloWorld", Child = root });
await host.RunAsync();# SteamVR を起動してから実行dotnet runWindow の作成は Host 側で行います。host.RunAsync() は SteamVR が終了するまで待機し、SteamVR の終了イベント、Ctrl+C、または Host の停止要求を受けると正常終了します。
Widget の実装状況: レイアウト系(
Center,Align,Row,Column,Padding,Container,Stack,Wrap,Expanded,AspectRatioなど)、描画系(ColoredBox,DecoratedBox,Opacity,Transform,Clip*)、入力系(GestureDetector,Listener)は使用可能で、StatefulWidget/InheritedWidgetも動作します。internalのため公開 API から使えないのは、スクロール系のListView/GridView/SingleChildScrollViewです。画像とアイコンには描画系のPaint.Image/Paint.Iconを使用できます。Buttonなどの UI コンポーネントはまだ提供していません。 用意する予定の3層構成(FloatSoda.UI/Cream/FizzyPop)は Phase 5 で、現時点では NuGet 未配布・押下も未反応です(→ UILayering)。ボタンはGestureDetectorで組み立ててください(→ WidgetSystem.md)。
RenderObject レベルの直接操作(低レベル API)
using FloatSoda;using FloatSoda.Geometrics;using FloatSoda.RenderObjects.Layout;using FloatSoda.RenderObjects.Painting;using FloatSoda.Widgets;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using SkiaSharp;
var builder = Host.CreateApplicationBuilder(args);builder.Services.AddFloatSoda();
using var host = builder.Build();var app = host.Services.GetRequiredService<FloatSodaApp>();
var root = new RenderPositionedBox{ Child = new RenderConstrainedBox { AdditionalConstraints = BoxConstraints.Tight(400, 200), Child = new RenderColoredBox { Color = SKColors.CornflowerBlue } }};
// CreateWindow の Child は Widget を要求するため、RenderObjectWidget でラップします。Widget widgetRoot = new RawRootWidget { Root = root };app.CreateWindow(new DashboardWindow { Title = "LowLevel", Child = widgetRoot });await host.RunAsync();
// 既存の RenderObject を Widget ツリーのルートへ接続する最小ラッパー。public sealed record RawRootWidget : SingleChildRenderObjectWidget<RenderPositionedBox>{ public required RenderPositionedBox Root { get; init; }
public override RenderPositionedBox CreateRenderObject() => Root;}オーバーレイ種別の選び方
Section titled “オーバーレイ種別の選び方”app.CreateWindow(...) に渡すウィンドウ定義 WindowWidget の種類でオーバーレイ種別を選びます。
Size を指定しない場合、オーバーレイのサイズは Child ウィジェットのレイアウト結果に追従します
(Size を指定するとそのサイズで固定されます)。
| ウィンドウ定義 | オーバーレイ種別 | 位置の管理 | ポインタ入力 |
|---|---|---|---|
DashboardWindow { Title, Child, Size? } |
DashboardOverlay |
SteamVR ダッシュボードが管理(ユーザーが開くタブ) | ✓ 届く |
WorldSpaceWindow { Title, Child, Size?, Position, Rotation } |
WorldSpaceOverlay |
ワールド座標で固定(Vector3 Position、既定は前方1m・高さ1m) |
✗ 届かない |
DeviceTrackedWindow { Title, Child, Size?, Target, Offset, Rotation } |
DeviceTrackedOverlay |
トラッキングデバイスに追従(TrackedDevice 列挙体) |
✗ 届かない |
ポインタ入力が届くのはダッシュボードオーバーレイだけです。 SteamVR はダッシュボード上のレーザーポインターを
マウスイベントとして送ってくるため、FloatSoda はそれをそのままヒットテストへ流せます。
ワールド座標固定とデバイス追従のオーバーレイには、コントローラーレイからポインタ座標を作る経路がまだありません。
これらのウィンドウに GestureDetector を置いてもコンパイルは通り、例外も出ませんが、コールバックは呼ばれません。
Title は SteamVR 上の表示名(ダッシュボードタブ名など)です。OpenVR のオーバーレイキーは
「エントリアセンブリ名 + Title のスネークケース」から自動生成されます
(例: アセンブリ MyOverlayApp + Title = "My Dashboard" → my_overlay_app.my_dashboard)。
// ダッシュボードapp.CreateWindow(new DashboardWindow { Title = "MyDashboard", Child = root });
// ワールド座標固定。Position 省略時はプレイエリア中央から前方1m・高さ1m (0, 1, -1)app.CreateWindow(new WorldSpaceWindow { Title = "MyWorld", Child = root });
// 左コントローラーに追従app.CreateWindow(new DeviceTrackedWindow { Title = "MyHand", Child = root, Target = TrackedDevice.LeftController });フレームレート設定
Section titled “フレームレート設定”FloatSodaOptions でフレームレートを制御できます。
// 固定 FPSvar builder = Host.CreateApplicationBuilder(args);builder.Services.AddFloatSoda(new FloatSodaOptions{ TargetFrameRate = 90});TargetFrameRate を指定しない場合のデフォルトは 60fps です。オーバーレイアプリはシーンアプリではないため、WaitGetPoses によるフレーム同期は利用できません。
- WidgetSystem — 使えるウィジェットの一覧と実装状況
- OVRIntegration — オーバーレイ種別・プロパティ・イベント処理の詳細
- Architecture — フレームワーク内部の全体像