コンテンツにスキップ

最初のオーバーレイを作る

このチュートリアルで作るもの

Section titled “このチュートリアルで作るもの”

SteamVR のダッシュボードに、数字とボタンが出るオーバーレイを作ります。ボタンを押すと、数字が1つ増えます。

完成した画面は、青い四角の中に大きな数字と赤いボタンが縦に並んだものです。

作業は3段です。

  1. 色の四角をダッシュボードに出す
  2. 四角を数字に置き換え、数字を書き換える
  3. ボタンを足して、押すと数字を増やす

各段の終わりに、HMD の中で確かめる点を1つ置いています。

用意するものは次の3つです。

  • .NET 10 の SDK(dotnet --version が 10. で始まる)
  • SteamVR(起動できる状態で、HMD をつないである)
  • 空のコンソールアプリ

準備の手順は、ガイドの開発環境を用意するにあります。動作環境の一覧は、リファレンスの動作環境です。足りないものがあれば、リンク先で用意してから戻ってください。

空のコンソールアプリは、次の2つのコマンドで作ります。1行ずつ実行します。

ターミナルウィンドウ
dotnet new console -n FirstOverlay
ターミナルウィンドウ
dotnet add FirstOverlay package FloatSoda

FirstOverlay フォルダーの中に Program.cs ができます。以降は、このファイル(FirstOverlay/Program.cs)の中身を段ごとに置き換えます。

1. オーバーレイを SteamVR に出す

Section titled “1. オーバーレイを SteamVR に出す”

最初の段では、青い四角を1つ出します。Program.cs を次の内容に置き換えてください。

using FloatSoda;
using FloatSoda.Geometrics;
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 Align
{
Alignment = Alignment.Center,
Child = new SizedBox
{
Width = 300,
Height = 300,
Child = new ColoredBox
{
Color = SKColors.CornflowerBlue
}
}
};
app.CreateWindow(new DashboardWindow
{
Title = "FirstOverlay",
Child = root
});
await host.RunAsync();

画面は Align、SizedBox、ColoredBox の3つを入れ子にして書きます。Align が中央に寄せ、SizedBox が 300 × 300 の大きさを決め、ColoredBox が色を塗ります。DashboardWindow は、この画面を SteamVR のダッシュボードに出す指定です。Title が、ダッシュボードのタブ名になります。

SteamVR を起動してから、FirstOverlay フォルダーを作った場所で、次のコマンドを実行します。

ターミナルウィンドウ
dotnet run --project FirstOverlay

そのまま HMD を被り、SteamVR のダッシュボードを開きます。

できた? ダッシュボードの「FirstOverlay」のタブに、青い四角が出た。

できなかったら

起動で例外が出たときは、メッセージを控えて、ガイドの起動で例外が出る: SteamVR または HMD につながらないを開きます。例外が出ないのにタブがないときは、同じページの起動で警告が出るが、表示はされるも確かめてください。

AddFloatSoda() や Host.CreateApplicationBuilder の意味は、ガイドの起動と Generic Hostで説明しています。ここでは、この数行を毎回の書き出しとして、そのまま使ってください。

2. 数字を表示して、書き換える

Section titled “2. 数字を表示して、書き換える”

2段目では、四角を数字の表示に置き換え、1秒ごとに数字を増やします。画面が持つ値を書き換えて画面を描き直すには、StatefulWidget<T> と State<T> を使います。値を書き換える操作が SetState() です。

Program.cs を次の内容に置き換えてください。

using FloatSoda;
using FloatSoda.Animation;
using FloatSoda.Elements;
using FloatSoda.Geometrics;
using FloatSoda.Painting;
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>();
app.CreateWindow(new DashboardWindow
{
Title = "FirstOverlay",
Child = new CounterPanel()
});
await host.RunAsync();
record CounterPanel : StatefulWidget<CounterPanel>
{
public override State<CounterPanel> CreateState() => new CounterPanelState();
}
class CounterPanelState : TickerProviderState<CounterPanel>
{
private int _count;
private WidgetTicker? _ticker;
public override void InitState()
{
_ticker = CreateTicker(elapsed =>
{
var seconds = (int)elapsed.TotalSeconds;
if (seconds != _count)
{
SetState(() => _count = seconds);
}
});
_ticker.Start();
}
public override void Dispose()
{
_ticker?.Dispose();
base.Dispose();
}
public override Widget Build(IBuildContext context)
{
return new Align
{
Alignment = Alignment.Center,
Child = new SizedBox
{
Width = 300,
Height = 300,
Child = new ColoredBox
{
Color = SKColors.CornflowerBlue,
Child = new Center
{
Child = new Text(_count.ToString())
{
Style = new TextStyle
{
Color = SKColors.White,
FontSize = 120,
FontWeight = 700
}
}
}
}
}
};
}
}

段1のコードから、3か所が変わっています。

  • 画面を CounterPanel というウィジェットに分けた。値を持つウィジェットは、StatefulWidget<T> を継承した型と、値を入れる State<T> の2つで書く
  • Build() が、_count の今の値で画面を返す。SetState() を呼ぶと、_count が書き換わり、Build() がもう一度呼ばれて画面が描き直される
  • 数字を増やすきっかけに、WidgetTicker を使った。WidgetTicker は、フレームごとに開始からの経過時間を渡す部品で、フレームを処理するメインのスレッドから呼ばれる

きっかけを System.Threading.Timer にしないのは、別のスレッドから SetState() を呼ぶと、画面の更新が失われることがあるためです(追跡: #150)。WidgetTicker はフレームを処理するスレッドで呼ばれるので、このコードのままで SetState() を呼べます。

もう一度、SteamVR を起動してから dotnet run --project FirstOverlay を実行し、ダッシュボードを開きます。

できた? 青い四角の中央に数字が出て、1秒ごとに1つずつ増えていく。

できなかったら

数字が 0 のまま変わらないときは、ガイドの数字や文字が変わらないを開きます。数字そのものが出ないときは、文字や画像が出ないです。

SetState() が画面を描き直す仕組みは、ガイドの状態と作り直しで説明しています。

3. ボタンを押して、数字を増やす

Section titled “3. ボタンを押して、数字を増やす”

3段目では、数字の下にボタンを足し、押すと数字が1つ増えるようにします。GestureDetector で、押された操作を受け取る領域を作り、見た目は他のウィジェットで組み立てます。

Program.cs を次の内容に置き換えてください。1秒ごとに増やす WidgetTicker は外し、TickerProviderState を State に戻しています。

using FloatSoda;
using FloatSoda.Elements;
using FloatSoda.Geometrics;
using FloatSoda.Painting;
using FloatSoda.Widgets;
using FloatSoda.Widgets.Gesture;
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>();
app.CreateWindow(new DashboardWindow
{
Title = "FirstOverlay",
Child = new CounterPanel()
});
await host.RunAsync();
record CounterPanel : StatefulWidget<CounterPanel>
{
public override State<CounterPanel> CreateState() => new CounterPanelState();
}
class CounterPanelState : State<CounterPanel>
{
private int _count;
public override Widget Build(IBuildContext context)
{
return new Align
{
Alignment = Alignment.Center,
Child = new SizedBox
{
Width = 300,
Height = 300,
Child = new ColoredBox
{
Color = SKColors.CornflowerBlue,
Child = new Flex
{
Direction = Axis.Vertical,
MainAxisAlignment = MainAxisAlignment.Center,
CrossAxisAlignment = CrossAxisAlignment.Center,
Children =
{
new Text(_count.ToString())
{
Style = new TextStyle
{
Color = SKColors.White,
FontSize = 120,
FontWeight = 700
}
},
new GestureDetector
{
OnTap = () => SetState(() => _count++),
Child = new SizedBox
{
Width = 200,
Height = 80,
Child = new ColoredBox
{
Color = SKColors.Tomato,
Child = new Center
{
Child = new Text("+1")
{
Style = new TextStyle
{
Color = SKColors.White,
FontSize = 50,
FontWeight = 700
}
}
}
}
}
}
}
}
}
}
};
}
}

画面の組み立てとタップの処理の変更点は、次のとおりです。

  • Flex に Direction = Axis.Vertical を指定して、数字とボタンを縦に並べた
  • 赤い四角を GestureDetector で包み、タップされたときに OnTap の処理で SetState() を呼んで数字を増やす

前の実行をターミナルの Ctrl+C で終了してから、SteamVR を起動した状態で dotnet run --project FirstOverlay を実行します。HMD を被り、ダッシュボードを開いて、コントローラーのレーザーで赤い四角を押します。

できた? 赤い四角を押すたびに、数字が1つ増える。

できなかったら

押しても反応しないときは、ガイドのボタンを押しても反応しないを開きます。DashboardWindow ではなく WorldSpaceWindow や DeviceTrackedWindow で試した人は、リファレンスのできないこと・まだ使えないものを確かめてください。SteamVR 上のオーバーレイでポインターの座標が届く範囲も、そのページにあります(追跡: #182)。

ボタンの組み立て方は、ガイドのボタンやトグルを組み立てるで詳しく説明しています。

動くオーバーレイができました。次に進むページは、目的で選べます。