Интерфейс для геймпада
В этом руководстве экран делается полностью управляемым геймпадом (или стрелками клавиатуры): фокус, который начинается в нужном месте, компоновка, по которой можно ходить крестовиной, диалоги, удерживающие фокус, и переключатель для игрового процесса. Экран — страница экипировки: сетка кнопок, ряд переключателей, флажок, поле со списком и кнопка «Старт».
Вам понадобится страница, загруженная из разметки (Первый экран). Как работает фокус, описано на странице Ввод, фокус и навигация.
1. Компоновка, по которой можно ходить крестовиной
Стрелки и крестовина переводят фокус на ближайший фокусируемый элемент в этом направлении, поэтому порядок табуляции не нужен. Обычные панели и сетки работают как есть:
<!-- A loadout screen a gamepad can fully use: a grid of buttons, a row of toggles, a check box, a combo box and a button.
The data context is the view model with a Difficulties list. -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Width="360">
<TextBlock FontSize="28" Margin="0,0,0,16">Loadout</TextBlock>
<!-- Arrows and the D-pad move to the nearest control in that direction, so a grid needs no order. -->
<Grid Margin="0,0,0,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="Sword" Grid.Row="0" Grid.Column="0" Margin="0,0,8,8" HorizontalAlignment="Stretch">Sword</Button>
<Button x:Name="Bow" Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" HorizontalAlignment="Stretch">Bow</Button>
<Button x:Name="Shield" Grid.Row="1" Grid.Column="0" Margin="0,0,8,0" HorizontalAlignment="Stretch">Shield</Button>
<Button x:Name="Potion" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch">Potion</Button>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
<ToggleButton x:Name="Easy" Margin="0,0,8,0">Easy</ToggleButton>
<ToggleButton x:Name="Normal" Margin="0,0,8,0">Normal</ToggleButton>
<ToggleButton x:Name="Hard">Hard</ToggleButton>
</StackPanel>
<CheckBox x:Name="Subtitles" Margin="0,0,0,16">Subtitles</CheckBox>
<ComboBox x:Name="Difficulty" ItemsSource="{Binding Difficulties}" SelectedIndex="1" Margin="0,0,0,16" />
<Button x:Name="Start" Padding="24,8" HorizontalAlignment="Left">Start</Button>
</StackPanel>
Надёжность обеспечивают две вещи: оставляйте зазор между элементами вместо наложения и держите связанные элементы в рядах и столбцах, чтобы «ближайший справа» оказывался именно тем, которого ждёт игрок.
2. Сфокусируйте первый элемент при открытии
Если ничего не сфокусировано, крестовине нечего двигать. Сфокусируйте первую кнопку при показе страницы:
/// <summary>Shows a menu page and puts the focus on its first button, so the D-pad has somewhere to start.</summary>
public static void Show(Canvas canvas, UIElement menu)
{
canvas.Add(menu);
canvas.Focus(menu.FindRequiredControl<Button>("Start"));
}
3. Сделайте фокус заметным
У игрока с геймпадом нет указателя, за которым можно следить, поэтому сфокусированный элемент должен выделяться.
Встроенная тема даёт каждому элементу группу FocusStates с синей рамкой; тема игры может сделать её ярче неявным
стилем, как описано в Своих темах. Проверьте с дивана: если фокус не виден с метра, усильте его.
4. Диалоги
Открытые Window или Dialog — область фокуса: крестовина остаётся внутри, B закрывает её, а фокус возвращается на
элемент, который его имел:
/// <summary>A dialog is a focus scope: the D-pad stays inside it, B closes it, and the focus returns to the menu.</summary>
public static async Task<bool> ConfirmQuitAsync(Canvas canvas)
{
DialogResult result = await MessageBox.ShowAsync(canvas, "Quit to desktop?", DialogButtons.YesNo);
return result == DialogResult.Yes;
}
Для собственной панели с тем же поведением установите ей IsFocusScope.
5. Списки и прокрутка
Список или область прокрутки прокручивает сфокусированный элемент в видимую область, а поле со списком открывается по A и пока открыто принимает крестовину вверх и вниз; B закрывает его.
Примечание
Пункты списка пока нельзя выбирать с клавиатуры или геймпада; см. Известные проблемы.
6. Игровой процесс
Пока игрок управляет игрой, крестовина и стик не должны ещё и двигать фокус меню. Отключайте навигацию на время игры и включайте снова для меню:
/// <summary>While the player controls the game, the D-pad and sticks belong to gameplay, not to the menu focus.</summary>
public static void SetGameplay(Canvas canvas, bool playing) => canvas.IsKeyboardNavigationEnabled = !playing;
Сфокусированный элемент по-прежнему получает стрелки, пока навигация отключена (текстовое поле двигает курсор ввода); отключён только перенос фокуса на другой элемент.
Клавиши
| Клавиатура | Геймпад | Действие |
|---|---|---|
| Стрелки | Крестовина | Перевести фокус на ближайший фокусируемый элемент в этом направлении. |
| Tab, Shift+Tab | Нажатие правого / левого стика | Перевести фокус на следующий / предыдущий элемент. |
| Enter | A | Активировать сфокусированный элемент. |
| Escape | B | Закрыть открытый диалог или список. |
Примечание
Эти клавиши пока нельзя переназначить; настраиваемые привязки есть в планах.
Что вы построили
Страница, где до каждого элемента можно добраться и пользоваться им крестовиной, которая стартует с фокусом, держит диалоги самодостаточными и возвращает кнопки игре во время игрового процесса.
Дальше
- Игровое меню: меню, к которому это можно применить.
- Диалоги и окна: области фокуса подробно.