Compare commits
No commits in common. "2800262b81d042af5b189c1c25fbf3e1eb5c160e" and "703715888d7b9de471e1dcc858731c9471443fff" have entirely different histories.
2800262b81
...
703715888d
|
|
@ -1,7 +1,6 @@
|
||||||
// Blueprints/BP_MainCharacter.ts
|
// Blueprints/BP_MainCharacter.ts
|
||||||
|
|
||||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||||
import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import { IMC_Default } from '#root/Input/IMC_Default.ts';
|
import { IMC_Default } from '#root/Input/IMC_Default.ts';
|
||||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||||
|
|
@ -72,22 +71,16 @@ export class BP_MainCharacter extends Pawn {
|
||||||
* Order: Toast → Debug → Movement (movement last as it may generate debug output)
|
* Order: Toast → Debug → Movement (movement last as it may generate debug output)
|
||||||
*/
|
*/
|
||||||
EventBeginPlay(): void {
|
EventBeginPlay(): void {
|
||||||
|
// Initialize debug systems first if enabled
|
||||||
if (this.ShowDebugInfo) {
|
if (this.ShowDebugInfo) {
|
||||||
this.ToastSystemComponent.InitializeToastSystem();
|
this.ToastSystemComponent.InitializeToastSystem();
|
||||||
}
|
|
||||||
|
|
||||||
this.InputDeviceComponent.InitializeDeviceDetection(
|
|
||||||
this.ToastSystemComponent
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.ShowDebugInfo) {
|
|
||||||
this.DebugHUDComponent.InitializeDebugHUD(
|
this.DebugHUDComponent.InitializeDebugHUD(
|
||||||
this.MovementComponent,
|
this.MovementComponent,
|
||||||
this.ToastSystemComponent,
|
this.ToastSystemComponent
|
||||||
this.InputDeviceComponent
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize movement system last (may generate debug output)
|
||||||
this.MovementComponent.InitializeMovementSystem();
|
this.MovementComponent.InitializeMovementSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,12 +120,6 @@ export class BP_MainCharacter extends Pawn {
|
||||||
*/
|
*/
|
||||||
ToastSystemComponent = new AC_ToastSystem();
|
ToastSystemComponent = new AC_ToastSystem();
|
||||||
|
|
||||||
/**
|
|
||||||
* Input device detection component - manages input device state and detection
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
InputDeviceComponent = new AC_InputDevice();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Master debug toggle - controls all debug systems (HUD, toasts, visual debug)
|
* Master debug toggle - controls all debug systems (HUD, toasts, visual debug)
|
||||||
* @category Debug
|
* @category Debug
|
||||||
|
|
|
||||||
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
BIN
Content/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -5,7 +5,6 @@ import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
||||||
import type { S_DebugSettings } from '#root/Debug/Structs/S_DebugSettings.ts';
|
import type { S_DebugSettings } from '#root/Debug/Structs/S_DebugSettings.ts';
|
||||||
import { DT_DebugPages } from '#root/Debug/Tables/DT_DebugPages.ts';
|
import { DT_DebugPages } from '#root/Debug/Tables/DT_DebugPages.ts';
|
||||||
import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts';
|
import { WBP_DebugHUD } from '#root/Debug/UI/WBP_DebugHUD.ts';
|
||||||
import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import type { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
import type { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||||
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
||||||
|
|
@ -16,7 +15,6 @@ import { ESlateVisibility } from '#root/UE/ESlateVisibility.ts';
|
||||||
import type { Float } from '#root/UE/Float.ts';
|
import type { Float } from '#root/UE/Float.ts';
|
||||||
import type { Integer } from '#root/UE/Integer.ts';
|
import type { Integer } from '#root/UE/Integer.ts';
|
||||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
||||||
import type { Text } from '#root/UE/Text.ts';
|
|
||||||
import { UEArray } from '#root/UE/UEArray.ts';
|
import { UEArray } from '#root/UE/UEArray.ts';
|
||||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
||||||
|
|
||||||
|
|
@ -242,10 +240,6 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
}
|
}
|
||||||
case E_DebugUpdateFunction.UpdatePerformancePage: {
|
case E_DebugUpdateFunction.UpdatePerformancePage: {
|
||||||
CurrentPage = this.UpdatePerformancePage(CurrentPage);
|
CurrentPage = this.UpdatePerformancePage(CurrentPage);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case E_DebugUpdateFunction.UpdateInputDevicePage: {
|
|
||||||
CurrentPage = this.UpdateInputDevicePage(CurrentPage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,35 +345,6 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update input device information page content
|
|
||||||
* @param Page - Page structure to update
|
|
||||||
* @returns Updated page with current input device data
|
|
||||||
* @category Page Updates
|
|
||||||
*/
|
|
||||||
public UpdateInputDevicePage(Page: S_DebugPage): S_DebugPage {
|
|
||||||
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
|
|
||||||
return {
|
|
||||||
PageID: Page.PageID,
|
|
||||||
Title: Page.Title,
|
|
||||||
Content:
|
|
||||||
`Hardware Device Identifier: ${this.InputDeviceComponent.GetCurrentInputDevice()}` +
|
|
||||||
`Initialized: ${this.InputDeviceComponent.IsInitialized}` +
|
|
||||||
`Last Change: ${this.InputDeviceComponent.LastDeviceChangeTime}`,
|
|
||||||
IsVisible: Page.IsVisible,
|
|
||||||
UpdateFunction: Page.UpdateFunction,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
PageID: Page.PageID,
|
|
||||||
Title: Page.Title,
|
|
||||||
Content: 'Input Device Component Not Found',
|
|
||||||
IsVisible: Page.IsVisible,
|
|
||||||
UpdateFunction: Page.UpdateFunction,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create debug widget instance and add to viewport
|
* Create debug widget instance and add to viewport
|
||||||
* @category Widget Control
|
* @category Widget Control
|
||||||
|
|
@ -426,21 +391,11 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
this.DebugWidget.SetHeaderText(currentPage!.Title);
|
this.DebugWidget.SetHeaderText(currentPage!.Title);
|
||||||
this.DebugWidget.SetContentText(currentPage!.Content);
|
this.DebugWidget.SetContentText(currentPage!.Content);
|
||||||
this.DebugWidget.SetNavigationText(
|
this.DebugWidget.SetNavigationText(
|
||||||
`Page ${this.DebugSettings.CurrentPageIndex + 1}/${visiblePages.length} | ${this.GetControlHints()} - Navigate`
|
`Page ${this.DebugSettings.CurrentPageIndex + 1}/${visiblePages.length} | PageUp/PageDown - Navigate`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private GetControlHints(): Text {
|
|
||||||
if (SystemLibrary.IsValid(this.InputDeviceComponent)) {
|
|
||||||
if (this.InputDeviceComponent.IsGamepad()) {
|
|
||||||
return 'D-Pad Up/Down';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'PageUp/PageDown';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main update loop for debug HUD system
|
* Main update loop for debug HUD system
|
||||||
* Called every frame from game loop
|
* Called every frame from game loop
|
||||||
|
|
@ -502,7 +457,6 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
* Sets up pages, creates widget, runs tests, and starts display
|
* Sets up pages, creates widget, runs tests, and starts display
|
||||||
* @param MovementComponentRef - Reference to movement component to debug
|
* @param MovementComponentRef - Reference to movement component to debug
|
||||||
* @param ToastComponentRef - Reference to toast system for notifications
|
* @param ToastComponentRef - Reference to toast system for notifications
|
||||||
* @param InputDeviceComponentRef - Reference to input device component for device info
|
|
||||||
* @example
|
* @example
|
||||||
* // Initialize debug HUD in main character
|
* // Initialize debug HUD in main character
|
||||||
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
|
* this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent);
|
||||||
|
|
@ -510,12 +464,10 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
*/
|
*/
|
||||||
public InitializeDebugHUD(
|
public InitializeDebugHUD(
|
||||||
MovementComponentRef: AC_Movement,
|
MovementComponentRef: AC_Movement,
|
||||||
ToastComponentRef: AC_ToastSystem,
|
ToastComponentRef: AC_ToastSystem
|
||||||
InputDeviceComponentRef: AC_InputDevice
|
|
||||||
): void {
|
): void {
|
||||||
this.MovementComponent = MovementComponentRef;
|
this.MovementComponent = MovementComponentRef;
|
||||||
this.ToastComponent = ToastComponentRef;
|
this.ToastComponent = ToastComponentRef;
|
||||||
this.InputDeviceComponent = InputDeviceComponentRef;
|
|
||||||
this.IsInitialized = true;
|
this.IsInitialized = true;
|
||||||
this.FrameCounter = 0;
|
this.FrameCounter = 0;
|
||||||
this.LastUpdateTime = 0;
|
this.LastUpdateTime = 0;
|
||||||
|
|
@ -549,13 +501,6 @@ export class AC_DebugHUD extends ActorComponent {
|
||||||
*/
|
*/
|
||||||
public ToastComponent: AC_ToastSystem | null = null;
|
public ToastComponent: AC_ToastSystem | null = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to input device component for device detection
|
|
||||||
* Set externally, used for displaying current input device info
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
public InputDeviceComponent: AC_InputDevice | null = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug system configuration settings
|
* Debug system configuration settings
|
||||||
* Controls visibility, update frequency, and current page
|
* Controls visibility, update frequency, and current page
|
||||||
|
|
|
||||||
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
BIN
Content/Debug/Components/AC_DebugHUD.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -4,5 +4,4 @@ export enum E_DebugPageID {
|
||||||
MovementInfo = 'MovementInfo',
|
MovementInfo = 'MovementInfo',
|
||||||
SurfaceInfo = 'SurfaceInfo',
|
SurfaceInfo = 'SurfaceInfo',
|
||||||
PerformanceInfo = 'PerformanceInfo',
|
PerformanceInfo = 'PerformanceInfo',
|
||||||
InputDeviceInfo = 'InputDeviceInfo',
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
Content/Debug/Enums/E_DebugPageID.uasset (Stored with Git LFS)
BIN
Content/Debug/Enums/E_DebugPageID.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -4,5 +4,4 @@ export enum E_DebugUpdateFunction {
|
||||||
UpdateMovementPage = 'UpdateMovementPage',
|
UpdateMovementPage = 'UpdateMovementPage',
|
||||||
UpdateSurfacePage = 'UpdateSurfacePage',
|
UpdateSurfacePage = 'UpdateSurfacePage',
|
||||||
UpdatePerformancePage = 'UpdatePerformancePage',
|
UpdatePerformancePage = 'UpdatePerformancePage',
|
||||||
UpdateInputDevicePage = 'UpdateInputDevicePage',
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
Content/Debug/Enums/E_DebugUpdateFunction.uasset (Stored with Git LFS)
BIN
Content/Debug/Enums/E_DebugUpdateFunction.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -10,7 +10,7 @@ import { UEArray } from '#root/UE/UEArray.ts';
|
||||||
export const DT_DebugPages = new DataTable<S_DebugPage>(
|
export const DT_DebugPages = new DataTable<S_DebugPage>(
|
||||||
null,
|
null,
|
||||||
new Name('DT_DebugPages'),
|
new Name('DT_DebugPages'),
|
||||||
new UEArray<S_DebugPage & { Name: Name }>([
|
new UEArray<S_DebugPage & { Name: Name }>(
|
||||||
{
|
{
|
||||||
Name: new Name('MovementInfo'),
|
Name: new Name('MovementInfo'),
|
||||||
PageID: E_DebugPageID.MovementInfo,
|
PageID: E_DebugPageID.MovementInfo,
|
||||||
|
|
@ -34,14 +34,6 @@ export const DT_DebugPages = new DataTable<S_DebugPage>(
|
||||||
Content: '',
|
Content: '',
|
||||||
IsVisible: true,
|
IsVisible: true,
|
||||||
UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage,
|
UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage,
|
||||||
},
|
}
|
||||||
{
|
)
|
||||||
Name: new Name('InputDeviceInfo'),
|
|
||||||
PageID: E_DebugPageID.InputDeviceInfo,
|
|
||||||
Title: 'Input Device Info',
|
|
||||||
Content: '',
|
|
||||||
IsVisible: true,
|
|
||||||
UpdateFunction: E_DebugUpdateFunction.UpdateInputDevicePage,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
|
|
|
||||||
BIN
Content/Debug/Tables/DT_DebugPages.uasset (Stored with Git LFS)
BIN
Content/Debug/Tables/DT_DebugPages.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,7 +1,6 @@
|
||||||
// Debug/Tests/FT_DebugNavigation.ts
|
// Debug/Tests/FT_DebugNavigation.ts
|
||||||
|
|
||||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||||
import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||||
|
|
@ -28,8 +27,7 @@ export class FT_DebugNavigation extends FunctionalTest {
|
||||||
EventStartTest(): void {
|
EventStartTest(): void {
|
||||||
this.DebugHUDComponent.InitializeDebugHUD(
|
this.DebugHUDComponent.InitializeDebugHUD(
|
||||||
this.MovementComponent,
|
this.MovementComponent,
|
||||||
this.ToastSystemComponent,
|
this.ToastSystemComponent
|
||||||
this.InputDeviceComponent
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.IfValid('Debug HUD: Navigation invalid initial state', () => {
|
this.IfValid('Debug HUD: Navigation invalid initial state', () => {
|
||||||
|
|
@ -111,10 +109,4 @@ export class FT_DebugNavigation extends FunctionalTest {
|
||||||
* @category Components
|
* @category Components
|
||||||
*/
|
*/
|
||||||
ToastSystemComponent = new AC_ToastSystem();
|
ToastSystemComponent = new AC_ToastSystem();
|
||||||
|
|
||||||
/**
|
|
||||||
* Input device detection system - used for input device debug page testing
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
InputDeviceComponent = new AC_InputDevice();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
Content/Debug/Tests/FT_DebugNavigation.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugNavigation.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -4,7 +4,6 @@ import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||||
import { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.ts';
|
import { E_DebugPageID } from '#root/Debug/Enums/E_DebugPageID.ts';
|
||||||
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
|
import { E_DebugUpdateFunction } from '#root/Debug/Enums/E_DebugUpdateFunction.ts';
|
||||||
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
import type { S_DebugPage } from '#root/Debug/Structs/S_DebugPage.ts';
|
||||||
import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
||||||
|
|
@ -30,8 +29,7 @@ export class FT_DebugPageContentGenerator extends FunctionalTest {
|
||||||
EventStartTest(): void {
|
EventStartTest(): void {
|
||||||
this.DebugHUDComponent.InitializeDebugHUD(
|
this.DebugHUDComponent.InitializeDebugHUD(
|
||||||
this.MovementComponent,
|
this.MovementComponent,
|
||||||
this.ToastSystemComponent,
|
this.ToastSystemComponent
|
||||||
this.InputDeviceComponent
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.DebugHUDComponent.GetTestData().DebugPages.forEach(
|
this.DebugHUDComponent.GetTestData().DebugPages.forEach(
|
||||||
|
|
@ -96,12 +94,6 @@ export class FT_DebugPageContentGenerator extends FunctionalTest {
|
||||||
*/
|
*/
|
||||||
ToastSystemComponent = new AC_ToastSystem();
|
ToastSystemComponent = new AC_ToastSystem();
|
||||||
|
|
||||||
/**
|
|
||||||
* Input device detection system - used for input device debug page testing
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
InputDeviceComponent = new AC_InputDevice();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Working copy of debug page for content generation testing
|
* Working copy of debug page for content generation testing
|
||||||
* Updated during test execution for each page
|
* Updated during test execution for each page
|
||||||
|
|
|
||||||
BIN
Content/Debug/Tests/FT_DebugPageContentGenerator.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugPageContentGenerator.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,7 +1,6 @@
|
||||||
// Debug/Tests/FT_DebugSystem.ts
|
// Debug/Tests/FT_DebugSystem.ts
|
||||||
|
|
||||||
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
import { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts';
|
||||||
import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
import { AC_Movement } from '#root/Movement/Components/AC_Movement.ts';
|
||||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
||||||
import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.ts';
|
import { DataTableFunctionLibrary } from '#root/UE/DataTableFunctionLibrary.ts';
|
||||||
|
|
@ -29,8 +28,7 @@ export class FT_DebugSystem extends FunctionalTest {
|
||||||
EventStartTest(): void {
|
EventStartTest(): void {
|
||||||
this.DebugHUDComponent.InitializeDebugHUD(
|
this.DebugHUDComponent.InitializeDebugHUD(
|
||||||
this.MovementComponent,
|
this.MovementComponent,
|
||||||
this.ToastSystemComponent,
|
this.ToastSystemComponent
|
||||||
this.InputDeviceComponent
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.DebugHUDComponent.GetTestData().IsInitialized) {
|
if (this.DebugHUDComponent.GetTestData().IsInitialized) {
|
||||||
|
|
@ -94,10 +92,4 @@ export class FT_DebugSystem extends FunctionalTest {
|
||||||
* @category Components
|
* @category Components
|
||||||
*/
|
*/
|
||||||
ToastSystemComponent = new AC_ToastSystem();
|
ToastSystemComponent = new AC_ToastSystem();
|
||||||
|
|
||||||
/**
|
|
||||||
* Input device detection system - used for input device debug page testing
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
InputDeviceComponent = new AC_InputDevice();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS)
BIN
Content/Debug/Tests/FT_DebugSystem.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,181 +0,0 @@
|
||||||
// Input/Components/AC_InputDevice.ts
|
|
||||||
|
|
||||||
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
|
||||||
import { ActorComponent } from '#root/UE/ActorComponent.ts';
|
|
||||||
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
|
|
||||||
import type { Float } from '#root/UE/Float.ts';
|
|
||||||
import { InputDeviceSubsystem } from '#root/UE/InputDeviceSubsystem.ts';
|
|
||||||
import type { Integer } from '#root/UE/Integer.ts';
|
|
||||||
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
|
|
||||||
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input Device Detection Component
|
|
||||||
* Minimal wrapper around Unreal Engine's native InputDeviceSubsystem
|
|
||||||
* Provides simple binary classification: Gamepad vs Everything Else
|
|
||||||
*/
|
|
||||||
export class AC_InputDevice extends ActorComponent {
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
// FUNCTIONS
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current active input device type
|
|
||||||
* @returns Current device type (cached from delegate events)
|
|
||||||
* @category Device Queries
|
|
||||||
* @pure true
|
|
||||||
*/
|
|
||||||
public GetCurrentInputDevice(): EHardwareDevicePrimaryType {
|
|
||||||
return this.CurrentDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if current device is keyboard/mouse
|
|
||||||
* @returns True if keyboard is active
|
|
||||||
* @category Device Queries
|
|
||||||
* @pure true
|
|
||||||
*/
|
|
||||||
public IsKeyboard(): boolean {
|
|
||||||
return this.CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if current device is gamepad/controller
|
|
||||||
* @returns True if gamepad is active
|
|
||||||
* @category Device Queries
|
|
||||||
* @pure true
|
|
||||||
*/
|
|
||||||
public IsGamepad(): boolean {
|
|
||||||
return this.CurrentDevice === EHardwareDevicePrimaryType.Gamepad;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize device detection system with delegate registration
|
|
||||||
* @param ToastComponentRef - Toast system for debug notifications
|
|
||||||
* @category System Setup
|
|
||||||
*/
|
|
||||||
public InitializeDeviceDetection(ToastComponentRef: AC_ToastSystem): void {
|
|
||||||
this.ToastComponent = ToastComponentRef;
|
|
||||||
this.RegisterHardwareDeviceDelegate();
|
|
||||||
this.DetectInitialDevice();
|
|
||||||
this.IsInitialized = true;
|
|
||||||
|
|
||||||
if (SystemLibrary.IsValid(this.ToastComponent)) {
|
|
||||||
this.ToastComponent.ShowToast(
|
|
||||||
`Device Detection Initialized: ${this.CurrentDevice}`,
|
|
||||||
E_MessageType.Success
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register OnInputHardwareDeviceChanged delegate
|
|
||||||
* Core of the event-driven approach
|
|
||||||
* @category System Setup
|
|
||||||
*/
|
|
||||||
private RegisterHardwareDeviceDelegate(): void {
|
|
||||||
InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent(
|
|
||||||
this.OnInputHardwareDeviceChanged.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect initial device on system startup
|
|
||||||
* Fallback for getting device before first hardware change event
|
|
||||||
* @category System Setup
|
|
||||||
*/
|
|
||||||
private DetectInitialDevice(): void {
|
|
||||||
this.CurrentDevice =
|
|
||||||
InputDeviceSubsystem.GetMostRecentlyUsedHardwareDevice().PrimaryDeviceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle hardware device change events
|
|
||||||
* Called automatically when input hardware changes
|
|
||||||
* @param UserId - User ID (usually 0 for single-player)
|
|
||||||
* @param DeviceId - Device name string
|
|
||||||
* @category Event Handling
|
|
||||||
*/
|
|
||||||
private OnInputHardwareDeviceChanged(
|
|
||||||
UserId?: Integer,
|
|
||||||
DeviceId?: Integer
|
|
||||||
): void {
|
|
||||||
this.ProcessDeviceChange(
|
|
||||||
InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId ?? 0)
|
|
||||||
.PrimaryDeviceType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process device change with debouncing
|
|
||||||
* Prevents rapid switching and manages device state
|
|
||||||
* @param NewDevice - Newly detected device type
|
|
||||||
* @category Device Management
|
|
||||||
*/
|
|
||||||
private ProcessDeviceChange(NewDevice: EHardwareDevicePrimaryType): void {
|
|
||||||
if (NewDevice !== this.CurrentDevice && this.CanProcessDeviceChange()) {
|
|
||||||
this.CurrentDevice = NewDevice;
|
|
||||||
this.LastDeviceChangeTime = SystemLibrary.GetGameTimeInSeconds();
|
|
||||||
|
|
||||||
if (SystemLibrary.IsValid(this.ToastComponent)) {
|
|
||||||
this.ToastComponent.ShowToast(
|
|
||||||
`Input switched to ${NewDevice}`,
|
|
||||||
E_MessageType.Info
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if device change can be processed (debouncing)
|
|
||||||
* Prevents rapid device switching within cooldown period
|
|
||||||
* @returns True if enough time has passed since last change
|
|
||||||
* @category Debouncing
|
|
||||||
* @pure true
|
|
||||||
*/
|
|
||||||
private CanProcessDeviceChange(): boolean {
|
|
||||||
const HasCooldownExpired = (): boolean =>
|
|
||||||
SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >=
|
|
||||||
this.DeviceChangeCooldown;
|
|
||||||
|
|
||||||
return HasCooldownExpired();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
// VARIABLES
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to toast system for debug notifications
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
private ToastComponent: AC_ToastSystem | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current active input device type
|
|
||||||
* Updated by hardware device change events
|
|
||||||
* @category Device State
|
|
||||||
*/
|
|
||||||
private CurrentDevice: EHardwareDevicePrimaryType =
|
|
||||||
EHardwareDevicePrimaryType.Unspecified;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System initialization status
|
|
||||||
* @category System State
|
|
||||||
*/
|
|
||||||
public IsInitialized: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last device change timestamp for debouncing
|
|
||||||
* @category Debouncing
|
|
||||||
*/
|
|
||||||
public LastDeviceChangeTime: Float = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum time between device changes (prevents flickering)
|
|
||||||
* Recommended: 300-500ms for most games
|
|
||||||
* @category Debouncing
|
|
||||||
* @instanceEditable true
|
|
||||||
*/
|
|
||||||
private DeviceChangeCooldown: Float = 0.3;
|
|
||||||
}
|
|
||||||
BIN
Content/Input/Components/AC_InputDevice.uasset (Stored with Git LFS)
BIN
Content/Input/Components/AC_InputDevice.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Input/Enums/E_InputDeviceType.ts
|
||||||
|
|
||||||
|
export enum E_InputDeviceType {
|
||||||
|
Unknown = 'Unknown',
|
||||||
|
Keyboard = 'Keyboard',
|
||||||
|
Gamepad = 'Gamepad',
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
Content/Input/IMC_Default.uasset (Stored with Git LFS)
BIN
Content/Input/IMC_Default.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,75 +0,0 @@
|
||||||
[//]: # (Input/ManualTestingChecklist.md)
|
|
||||||
|
|
||||||
# Input Device System - Manual Testing Checklist
|
|
||||||
|
|
||||||
## Тестовая среда
|
|
||||||
- **Персонаж:** BP_MainCharacter с ShowDebugInfo = true
|
|
||||||
- **Клавиши:** PageUp/PageDown для навигации в Debug HUD
|
|
||||||
- **Требования:** InputDeviceComponent инициализирован
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Debug HUD Integration
|
|
||||||
|
|
||||||
### 1.1 Input Device Info Page
|
|
||||||
- [ ] **Page 4** отображается как "Input Device Detection"
|
|
||||||
- [ ] **PageUp/PageDown** позволяет перейти на Input Device page
|
|
||||||
- [ ] **Содержимое страницы** показывает:
|
|
||||||
- Primary Type: [тип устройства UE]
|
|
||||||
- Is Initialized: [true/false]
|
|
||||||
|
|
||||||
### 1.2 Real-time Device Detection
|
|
||||||
- [ ] **При использовании мыши/клавиатуры** Primary Type показывает "Keyboard & Mouse"
|
|
||||||
- [ ] **При подключении геймпада** Primary Type автоматически меняется на "Gamepad"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Автоматическая детекция устройств
|
|
||||||
|
|
||||||
### 2.1 Keyboard & Mouse Detection
|
|
||||||
- [ ] **Движение мыши** автоматически переключает на Keyboard & Mouse
|
|
||||||
- [ ] **Нажатие клавиш** (WASD, пробел, etc.) переключает на Keyboard & Mouse
|
|
||||||
- [ ] **Primary Type** показывает "KeyboardAndMouse"
|
|
||||||
|
|
||||||
### 2.2 Gamepad Detection
|
|
||||||
- [ ] **Движение стиков** автоматически переключает на Gamepad
|
|
||||||
- [ ] **Нажатие кнопок геймпада** переключает на Gamepad
|
|
||||||
- [ ] **Primary Type** показывает "Gamepad"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. API Functions Testing
|
|
||||||
|
|
||||||
### 3.1 Device Type Queries (Binary)
|
|
||||||
- [ ] **IsKeyboard()** возвращает true для всех устройств кроме Gamepad
|
|
||||||
- [ ] **IsGamepad()** возвращает true только для геймпадов
|
|
||||||
- [ ] **IsKeyboard() и IsGamepad()** никогда не возвращают одинаковые значения
|
|
||||||
- [ ] **GetCurrentInputDevice()** возвращает корректный EHardwareDevicePrimaryType
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Error Handling
|
|
||||||
|
|
||||||
### 4.1 Edge Cases
|
|
||||||
- [ ] **Отключение устройств** обрабатывается корректно
|
|
||||||
- [ ] **Подключение новых устройств** детектируется автоматически
|
|
||||||
- [ ] **System console** не содержит ошибок input detection
|
|
||||||
- [ ] **Performance** остается стабильной при активном использовании
|
|
||||||
|
|
||||||
### 4.2 Integration Stability
|
|
||||||
- [ ] **Debug HUD** стабильно работает с device detection
|
|
||||||
- [ ] **Частые переключения** устройств не вызывают проблем
|
|
||||||
- [ ] **AC_InputDevice** корректно инициализируется
|
|
||||||
- [ ] **IsGamepad/IsKeyboard** всегда возвращают корректные значения
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Критерии прохождения
|
|
||||||
- [ ] All device types correctly detected and displayed
|
|
||||||
- [ ] Real-time switching works seamlessly through UE subsystem
|
|
||||||
- [ ] Debug HUD shows complete hardware information
|
|
||||||
- [ ] No console errors during normal operation
|
|
||||||
- [ ] API functions return consistent results
|
|
||||||
- [ ] Native UE InputDeviceSubsystem integration works properly
|
|
||||||
|
|
||||||
**Примечание:** Система использует только встроенную InputDeviceSubsystem от Unreal Engine. Никаких симуляций или искусственных переключений.
|
|
||||||
|
|
@ -1,411 +0,0 @@
|
||||||
[//]: # (Input/TDD.md)
|
|
||||||
|
|
||||||
# Input Device Detection System - Техническая Документация
|
|
||||||
|
|
||||||
## Обзор
|
|
||||||
Event-driven система определения типа устройства ввода, основанная на делегате OnInputHardwareDeviceChanged от Unreal Engine 5.3+. Предоставляет простую бинарную классификацию устройств с automatic debouncing и минимальным overhead при отсутствии смены устройства.
|
|
||||||
|
|
||||||
## Архитектурные принципы
|
|
||||||
- **Event-Driven Detection:** Использование OnInputHardwareDeviceChanged delegate вместо polling
|
|
||||||
- **Binary Simplicity:** Только два состояния - геймпад или клавиатура/мышь
|
|
||||||
- **Automatic Debouncing:** Встроенная защита от rapid device switching
|
|
||||||
- **Zero Polling Overhead:** Реакция только на реальные события смены устройства
|
|
||||||
|
|
||||||
## Единственный компонент
|
|
||||||
|
|
||||||
### AC_InputDevice (Event-Driven Wrapper)
|
|
||||||
**Ответственности:**
|
|
||||||
- Event-driven обертка над Unreal Engine InputDeviceSubsystem
|
|
||||||
- Automatic debouncing для предотвращения flickering
|
|
||||||
- Бинарная классификация: IsGamepad() vs IsKeyboard()
|
|
||||||
- Интеграция с Toast notification system для debug
|
|
||||||
|
|
||||||
**Ключевые функции:**
|
|
||||||
- `InitializeDeviceDetection()` - регистрация delegate и initial detection
|
|
||||||
- `IsKeyboard()` / `IsGamepad()` - binary device queries
|
|
||||||
- `GetCurrentInputDevice()` - доступ к cached device state
|
|
||||||
- `OnInputHardwareDeviceChanged()` - event handler для device switching
|
|
||||||
|
|
||||||
**Event-driven архитектура:**
|
|
||||||
```typescript
|
|
||||||
InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent()
|
|
||||||
→ OnInputHardwareDeviceChanged()
|
|
||||||
→ ProcessDeviceChange()
|
|
||||||
→ Update cached state + Toast notification
|
|
||||||
```
|
|
||||||
|
|
||||||
## Система событий и debouncing
|
|
||||||
|
|
||||||
### Event Registration
|
|
||||||
```typescript
|
|
||||||
// Регистрация event handler при инициализации
|
|
||||||
InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent(
|
|
||||||
this.OnInputHardwareDeviceChanged.bind(this)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Processing Flow
|
|
||||||
```typescript
|
|
||||||
OnInputHardwareDeviceChanged(UserId, DeviceId) →
|
|
||||||
GetInputDeviceHardwareIdentifier(DeviceId) →
|
|
||||||
ProcessDeviceChange(PrimaryDeviceType) →
|
|
||||||
CanProcessDeviceChange() (debouncing check) →
|
|
||||||
Update CurrentDevice + LastChangeTime →
|
|
||||||
Toast notification
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automatic Debouncing
|
|
||||||
```typescript
|
|
||||||
// Защита от rapid switching
|
|
||||||
private CanProcessDeviceChange(): boolean {
|
|
||||||
const HasCooldownExpired = (): boolean =>
|
|
||||||
SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >=
|
|
||||||
this.DeviceChangeCooldown; // 300ms по умолчанию
|
|
||||||
|
|
||||||
return HasCooldownExpired();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Классификация устройств
|
|
||||||
|
|
||||||
### Binary Device Logic
|
|
||||||
```typescript
|
|
||||||
// Вся логика классификации:
|
|
||||||
IsGamepad() → CurrentDevice === EHardwareDevicePrimaryType.Gamepad
|
|
||||||
IsKeyboard() → CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse
|
|
||||||
```
|
|
||||||
|
|
||||||
### Device Detection через Hardware Names
|
|
||||||
```typescript
|
|
||||||
// Определение типа устройства по событию:
|
|
||||||
OnInputHardwareDeviceChanged(UserId, DeviceId) →
|
|
||||||
InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId) →
|
|
||||||
.PrimaryDeviceType →
|
|
||||||
Update CurrentDevice state
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mapping UE типов
|
|
||||||
```typescript
|
|
||||||
// Поддерживаемые устройства:
|
|
||||||
EHardwareDevicePrimaryType.Gamepad → IsGamepad() = true
|
|
||||||
EHardwareDevicePrimaryType.KeyboardAndMouse → IsKeyboard() = true
|
|
||||||
EHardwareDevicePrimaryType.Unspecified → fallback to previous state
|
|
||||||
```
|
|
||||||
|
|
||||||
## Производительность
|
|
||||||
|
|
||||||
### Event-Driven преимущества
|
|
||||||
- **Zero polling overhead:** Обновления только при реальных событиях
|
|
||||||
- **Instant response:** Мгновенная реакция на device switching
|
|
||||||
- **Minimal CPU usage:** Нет постоянных проверок в Tick
|
|
||||||
- **Automatic state management:** UE Engine управляет device state
|
|
||||||
|
|
||||||
### Benchmarks
|
|
||||||
- **Инициализация:** <0.1ms (регистрация delegate + initial detection)
|
|
||||||
- **Event processing:** <0.05ms на событие (с debouncing)
|
|
||||||
- **IsKeyboard/IsGamepad:** <0.001ms (cached state access)
|
|
||||||
- **Memory footprint:** ~50 bytes (cached state + timers)
|
|
||||||
|
|
||||||
### Performance considerations
|
|
||||||
- **Event frequency:** Обычно 0-5 событий в секунду при активном switching
|
|
||||||
- **Debouncing cost:** Одно сравнение float времени на событие
|
|
||||||
- **No allocations:** Все операции работают с existing objects
|
|
||||||
- **Toast overhead:** Optional debug notifications не влияют на core performance
|
|
||||||
|
|
||||||
## Debouncing система
|
|
||||||
|
|
||||||
### Cooldown механизм
|
|
||||||
```typescript
|
|
||||||
private DeviceChangeCooldown: Float = 0.3; // 300ms стандартный интервал
|
|
||||||
private LastDeviceChangeTime: Float = 0; // Timestamp последней смены
|
|
||||||
|
|
||||||
// Проверка при каждом событии:
|
|
||||||
if (SystemLibrary.GetGameTimeInSeconds() - LastDeviceChangeTime >= DeviceChangeCooldown) {
|
|
||||||
// Process device change
|
|
||||||
} else {
|
|
||||||
// Ignore rapid switching
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Защита от stick drift
|
|
||||||
Event-driven подход естественно защищает от большинства stick drift проблем:
|
|
||||||
- **Hardware events** срабатывают реже чем input polling
|
|
||||||
- **Debouncing** отфильтровывает rapid oscillation
|
|
||||||
- **Real device changes** (кнопки, отключение) проходят через систему
|
|
||||||
|
|
||||||
## Интеграция с системами
|
|
||||||
|
|
||||||
### С Toast System
|
|
||||||
```typescript
|
|
||||||
// Debug notifications при смене устройства
|
|
||||||
if (SystemLibrary.IsValid(this.ToastComponent)) {
|
|
||||||
this.ToastComponent.ShowToast(
|
|
||||||
`Input switched to ${NewDevice}`,
|
|
||||||
E_MessageType.Info
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### С Debug HUD System
|
|
||||||
```typescript
|
|
||||||
// Новая debug page для input device info:
|
|
||||||
UpdateInputDevicePage(): string {
|
|
||||||
const deviceType = this.InputDeviceComponent.IsGamepad() ? 'Gamepad' : 'Keyboard & Mouse';
|
|
||||||
const isInitialized = this.InputDeviceComponent.IsInitialized ? 'Yes' : 'No';
|
|
||||||
|
|
||||||
return `Current Device: ${deviceType}\n` +
|
|
||||||
`Initialized: ${isInitialized}\n` +
|
|
||||||
`Last Change: ${this.GetTimeSinceLastChange()}s ago`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### С Enhanced Input System (будущая интеграция)
|
|
||||||
```typescript
|
|
||||||
// Этап 6+: Input Mapping Context switching
|
|
||||||
OnDeviceChanged() → Branch: IsGamepad()?
|
|
||||||
True → Remove IMC_Keyboard + Add IMC_Gamepad
|
|
||||||
False → Remove IMC_Gamepad + Add IMC_Keyboard
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### Основные методы
|
|
||||||
|
|
||||||
#### InitializeDeviceDetection()
|
|
||||||
```typescript
|
|
||||||
InitializeDeviceDetection(ToastComponentRef: AC_ToastSystem): void
|
|
||||||
```
|
|
||||||
**Описание:** Инициализация event-driven device detection
|
|
||||||
**Параметры:** ToastComponentRef для debug notifications
|
|
||||||
**Когда вызывать:** EventBeginPlay в main character
|
|
||||||
**Эффекты:** Регистрирует delegate, выполняет initial detection, показывает success toast
|
|
||||||
|
|
||||||
#### IsKeyboard()
|
|
||||||
```typescript
|
|
||||||
IsKeyboard(): boolean
|
|
||||||
```
|
|
||||||
**Описание:** Проверка на клавиатуру/мышь (cached state)
|
|
||||||
**Возвращает:** True для KeyboardAndMouse устройств
|
|
||||||
**Performance:** <0.001ms (direct boolean comparison)
|
|
||||||
**Use case:** UI hints, input prompts
|
|
||||||
|
|
||||||
#### IsGamepad()
|
|
||||||
```typescript
|
|
||||||
IsGamepad(): boolean
|
|
||||||
```
|
|
||||||
**Описание:** Проверка на геймпад/контроллер (cached state)
|
|
||||||
**Возвращает:** True для Gamepad устройств
|
|
||||||
**Performance:** <0.001ms (direct enum comparison)
|
|
||||||
**Use case:** UI hints, control schemes
|
|
||||||
|
|
||||||
#### GetCurrentInputDevice()
|
|
||||||
```typescript
|
|
||||||
GetCurrentInputDevice(): EHardwareDevicePrimaryType
|
|
||||||
```
|
|
||||||
**Описание:** Доступ к полному device type (cached state)
|
|
||||||
**Возвращает:** Native UE enum для device type
|
|
||||||
**Use case:** Debug information, detailed device classification
|
|
||||||
|
|
||||||
### Управление lifecycle
|
|
||||||
|
|
||||||
#### CleanupDeviceDetection()
|
|
||||||
```typescript
|
|
||||||
CleanupDeviceDetection(): void
|
|
||||||
```
|
|
||||||
**Описание:** Очистка системы и отвязка delegates
|
|
||||||
**Когда вызывать:** При уничтожении компонента
|
|
||||||
**Эффекты:** UnbindEvent, reset initialization state
|
|
||||||
|
|
||||||
### Testing и debug
|
|
||||||
|
|
||||||
#### ForceDeviceDetection()
|
|
||||||
```typescript
|
|
||||||
ForceDeviceDetection(): void
|
|
||||||
```
|
|
||||||
**Описание:** Принудительная повторная детекция устройства
|
|
||||||
**Use case:** Testing, debugging device state
|
|
||||||
|
|
||||||
## Система тестирования
|
|
||||||
|
|
||||||
### FT_InputDeviceDetection (Basic Functionality)
|
|
||||||
**Покрывает:**
|
|
||||||
- Успешность инициализации (`IsInitialized = true`)
|
|
||||||
- Корректность device queries (`IsKeyboard()` XOR `IsGamepad()`)
|
|
||||||
- Консистентность cached state с actual device
|
|
||||||
- Initial device detection работает
|
|
||||||
|
|
||||||
### FT_InputDeviceEvents (Event Handling)
|
|
||||||
**Покрывает:**
|
|
||||||
- Event binding и registration
|
|
||||||
- Manual event triggering через `ExecuteIfBound()`
|
|
||||||
- Device state transitions при events
|
|
||||||
- Event handling без errors
|
|
||||||
|
|
||||||
### FT_InputDeviceDebouncing (Performance)
|
|
||||||
**Покрывает:**
|
|
||||||
- Rapid event filtering (10 events → ≤1 change)
|
|
||||||
- Cooldown timing accuracy
|
|
||||||
- No memory leaks при intensive events
|
|
||||||
- Performance под нагрузкой
|
|
||||||
|
|
||||||
### Test Coverage
|
|
||||||
```typescript
|
|
||||||
TestScenarios = [
|
|
||||||
'Инициализация с correct delegate binding',
|
|
||||||
'Initial device detection работает',
|
|
||||||
'IsKeyboard/IsGamepad consistency проверки',
|
|
||||||
'Manual event firing changes device state',
|
|
||||||
'Rapid events properly debounced',
|
|
||||||
'Cleanup properly unbinds delegates',
|
|
||||||
'Toast notifications при device changes',
|
|
||||||
'Performance при intensive event load'
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Интеграция с Main Character
|
|
||||||
|
|
||||||
### Blueprint Integration
|
|
||||||
```typescript
|
|
||||||
// В BP_MainCharacter EventBeginPlay:
|
|
||||||
EventBeginPlay() →
|
|
||||||
Initialize Toast System →
|
|
||||||
Initialize Input Device Detection →
|
|
||||||
Initialize Other Systems...
|
|
||||||
|
|
||||||
// В custom events для UI updates:
|
|
||||||
OnNeedUIUpdate() →
|
|
||||||
Get Input Device Component → IsGamepad() →
|
|
||||||
Branch: Update UI Prompts accordingly
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component References
|
|
||||||
```typescript
|
|
||||||
// В BP_MainCharacter variables:
|
|
||||||
Components:
|
|
||||||
├─ Input Device Component (AC_InputDevice)
|
|
||||||
├─ Toast System Component (AC_ToastSystem)
|
|
||||||
├─ Debug HUD Component (AC_DebugHUD)
|
|
||||||
└─ Movement Component (AC_Movement)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Файловая структура
|
|
||||||
|
|
||||||
```
|
|
||||||
Content/
|
|
||||||
├── Input/
|
|
||||||
│ ├── Components/
|
|
||||||
│ │ └── AC_InputDevice.ts # Main component
|
|
||||||
│ └── Tests/
|
|
||||||
│ ├── FT_InputDeviceDetection.ts # Basic functionality
|
|
||||||
│ ├── FT_InputDeviceEvents.ts # Event handling
|
|
||||||
│ └── FT_InputDeviceDebouncing.ts # Performance testing
|
|
||||||
├── UE/ (Native UE wrappers)
|
|
||||||
│ ├── InputDeviceSubsystem.ts # Event delegate wrapper
|
|
||||||
│ ├── HardwareDeviceIdentifier.ts # UE device info struct
|
|
||||||
│ └── EHardwareDevicePrimaryType.ts # UE device enum
|
|
||||||
├── Debug/
|
|
||||||
│ └── Components/AC_DebugHUD.ts # Integration for debug page
|
|
||||||
└── Blueprints/
|
|
||||||
└── BP_MainCharacter.ts # Main integration point
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### Использование в коде
|
|
||||||
```typescript
|
|
||||||
// ✅ Хорошо - simple binary checks
|
|
||||||
if (this.InputDeviceComponent.IsGamepad()) {
|
|
||||||
this.SetGamepadUI();
|
|
||||||
} else {
|
|
||||||
this.SetKeyboardUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Хорошо - proper initialization order
|
|
||||||
EventBeginPlay() →
|
|
||||||
InitializeToastSystem() →
|
|
||||||
InitializeDeviceDetection() →
|
|
||||||
InitializeOtherSystems()
|
|
||||||
|
|
||||||
// ✅ Хорошо - cleanup в EndPlay
|
|
||||||
EventEndPlay() →
|
|
||||||
this.InputDeviceComponent.CleanupDeviceDetection()
|
|
||||||
|
|
||||||
// ❌ Плохо - checking device type каждый Tick
|
|
||||||
EventTick() →
|
|
||||||
this.InputDeviceComponent.IsGamepad() // Wasteful!
|
|
||||||
|
|
||||||
// ✅ Хорошо - cache result или use events
|
|
||||||
OnDeviceChanged() →
|
|
||||||
this.CachedIsGamepad = this.InputDeviceComponent.IsGamepad()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance recommendations
|
|
||||||
- **Cache device checks** если нужно в hot paths
|
|
||||||
- **Use event-driven UI updates** вместо polling в Tick
|
|
||||||
- **Initialize early** в BeginPlay для immediate availability
|
|
||||||
- **Cleanup properly** для предотвращения delegate leaks
|
|
||||||
|
|
||||||
## Известные ограничения
|
|
||||||
|
|
||||||
### Текущие ограничения
|
|
||||||
1. **Binary classification only** - только Gamepad vs KeyboardMouse
|
|
||||||
2. **UE 5.3+ requirement** - OnInputHardwareDeviceChanged delegate
|
|
||||||
3. **Single device focus** - нет multi-user support
|
|
||||||
4. **Basic debouncing** - фиксированный 300ms cooldown
|
|
||||||
|
|
||||||
### Архитектурные решения
|
|
||||||
- **Event-driven tradeoff:** Зависимость от UE delegate system
|
|
||||||
- **Binary simplicity:** Covers 99% game use cases
|
|
||||||
- **Fixed debouncing:** Простота важнее configurability
|
|
||||||
- **Toast integration:** Debug notifications не essential для core functionality
|
|
||||||
|
|
||||||
### Известные edge cases
|
|
||||||
- **Device disconnection:** Может не trigger event немедленно
|
|
||||||
- **Multiple gamepads:** Нет differentiation между controller 1 vs 2
|
|
||||||
- **Specialized hardware:** Racing wheels, flight sticks = "keyboard"
|
|
||||||
|
|
||||||
## Планы развития (при необходимости)
|
|
||||||
|
|
||||||
### Stage 6+: Enhanced Input Integration
|
|
||||||
1. **Automatic Input Mapping Context switching** based на device type
|
|
||||||
2. **Device-specific action bindings** (разные кнопки для разных геймпадов)
|
|
||||||
3. **Multi-user device tracking** для split-screen scenarios
|
|
||||||
|
|
||||||
### Долгосрочные улучшения
|
|
||||||
1. **Configurable debouncing** через Project Settings
|
|
||||||
2. **Device-specific sub-classification** (Xbox vs PlayStation controllers)
|
|
||||||
3. **Device capability queries** (rumble support, gyro, etc.)
|
|
||||||
4. **Cross-platform consistency** improvements
|
|
||||||
|
|
||||||
### Принцип расширения
|
|
||||||
- **Preserve binary simplicity** как primary API
|
|
||||||
- **Add specialized methods** для advanced use cases
|
|
||||||
- **Maintain event-driven approach** для consistency
|
|
||||||
- **Keep zero polling overhead** для performance
|
|
||||||
|
|
||||||
## Заключение
|
|
||||||
|
|
||||||
Input Device Detection System представляет собой event-driven обертку над Unreal Engine InputDeviceSubsystem, обеспечивающую простую бинарную классификацию устройств ввода с automatic debouncing и zero polling overhead.
|
|
||||||
|
|
||||||
**Ключевые достижения:**
|
|
||||||
- ✅ **Event-driven architecture:** Zero overhead при отсутствии device switching
|
|
||||||
- ✅ **Automatic debouncing:** Built-in защита от flickering и rapid switching
|
|
||||||
- ✅ **Binary simplicity:** IsGamepad() vs IsKeyboard() покрывает 99% use cases
|
|
||||||
- ✅ **UE 5.3+ integration:** Использование latest InputDeviceSubsystem features
|
|
||||||
- ✅ **Production ready:** Comprehensive testing и clean integration points
|
|
||||||
- ✅ **Toast integration:** Debug notifications для development convenience
|
|
||||||
|
|
||||||
**Архитектурные преимущества:**
|
|
||||||
- Event-driven design eliminates polling overhead completely
|
|
||||||
- Cached state обеспечивает instant access к device information
|
|
||||||
- Automatic debouncing решает stick drift и hardware timing issues
|
|
||||||
- Clean integration с existing Toast и Debug systems
|
|
||||||
- Ready для Enhanced Input integration в следующих этапах
|
|
||||||
|
|
||||||
**Performance characteristics:**
|
|
||||||
- Zero CPU overhead при отсутствии device switching
|
|
||||||
- <0.05ms processing time per device change event
|
|
||||||
- Instant device state queries через cached values
|
|
||||||
- Minimal memory footprint (~50 bytes total state)
|
|
||||||
|
|
||||||
Система готова к использованию в production и provides solid foundation для Enhanced Input integration в будущих этапах разработки.
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
// Input/Tests/FT_InputDeviceDetection.ts
|
|
||||||
|
|
||||||
import { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts';
|
|
||||||
import { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
|
|
||||||
import { EFunctionalTestResult } from '#root/UE/EFunctionalTestResult.ts';
|
|
||||||
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
|
|
||||||
import { FunctionalTest } from '#root/UE/FunctionalTest.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functional Test: Input Device Detection System
|
|
||||||
* Tests event-driven device detection with minimal wrapper approach
|
|
||||||
* Validates initialization, device queries, and delegate events
|
|
||||||
*/
|
|
||||||
export class FT_InputDeviceDetection extends FunctionalTest {
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
// GRAPHS
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
|
||||||
// EventGraph
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test entry point - validates complete device detection workflow
|
|
||||||
* Tests initialization, device queries, and simulated device changes
|
|
||||||
*/
|
|
||||||
EventStartTest(): void {
|
|
||||||
// Initialize components
|
|
||||||
this.ToastSystemComponent.InitializeToastSystem();
|
|
||||||
this.InputDeviceComponent.InitializeDeviceDetection(
|
|
||||||
this.ToastSystemComponent
|
|
||||||
);
|
|
||||||
this.TestInitialization();
|
|
||||||
this.TestDeviceQueries();
|
|
||||||
this.FinishTest(EFunctionalTestResult.Succeeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
// TEST METHODS
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test initialization and initial device detection
|
|
||||||
* @returns True if test passed
|
|
||||||
*/
|
|
||||||
private TestInitialization(): void {
|
|
||||||
// Validate initialization
|
|
||||||
if (this.InputDeviceComponent.IsInitialized) {
|
|
||||||
if (
|
|
||||||
this.InputDeviceComponent.GetCurrentInputDevice() !==
|
|
||||||
EHardwareDevicePrimaryType.Unspecified
|
|
||||||
) {
|
|
||||||
// Test passed
|
|
||||||
} else {
|
|
||||||
this.FinishTest(
|
|
||||||
EFunctionalTestResult.Failed,
|
|
||||||
'No initial device detected'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.FinishTest(
|
|
||||||
EFunctionalTestResult.Failed,
|
|
||||||
'Input Device Detection failed to initialize'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test device query functions consistency
|
|
||||||
* @returns True if test passed
|
|
||||||
*/
|
|
||||||
private TestDeviceQueries(): void {
|
|
||||||
const currentDevice = this.InputDeviceComponent.GetCurrentInputDevice();
|
|
||||||
const isKeyboard = this.InputDeviceComponent.IsKeyboard();
|
|
||||||
const isGamepad = this.InputDeviceComponent.IsGamepad();
|
|
||||||
|
|
||||||
// Validate that exactly one device type is active
|
|
||||||
if (!(isKeyboard && isGamepad)) {
|
|
||||||
if (isKeyboard || isGamepad) {
|
|
||||||
const expectedIsKeyboard =
|
|
||||||
currentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse;
|
|
||||||
const expectedIsGamepad =
|
|
||||||
currentDevice === EHardwareDevicePrimaryType.Gamepad;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isKeyboard === expectedIsKeyboard &&
|
|
||||||
isGamepad === expectedIsGamepad
|
|
||||||
) {
|
|
||||||
// Test passed
|
|
||||||
} else {
|
|
||||||
this.FinishTest(
|
|
||||||
EFunctionalTestResult.Failed,
|
|
||||||
'Device query functions inconsistent with GetCurrentInputDevice()'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.FinishTest(
|
|
||||||
EFunctionalTestResult.Failed,
|
|
||||||
'Neither keyboard nor gamepad detected'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.FinishTest(
|
|
||||||
EFunctionalTestResult.Failed,
|
|
||||||
'Both keyboard and gamepad detected simultaneously'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
// VARIABLES
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input device detection system - component under test
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
private InputDeviceComponent = new AC_InputDevice();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toast notification system - required for device detection initialization
|
|
||||||
* @category Components
|
|
||||||
*/
|
|
||||||
private ToastSystemComponent = new AC_ToastSystem();
|
|
||||||
}
|
|
||||||
BIN
Content/Input/Tests/FT_InputDeviceDetection.uasset (Stored with Git LFS)
BIN
Content/Input/Tests/FT_InputDeviceDetection.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)
BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)
Binary file not shown.
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Toasts/Components/AC_ToastSystem.uasset (Stored with Git LFS)
BIN
Content/Toasts/Components/AC_ToastSystem.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Toasts/Tests/FT_ToastsToastCreation.uasset (Stored with Git LFS)
BIN
Content/Toasts/Tests/FT_ToastsToastCreation.uasset (Stored with Git LFS)
Binary file not shown.
|
|
@ -1,3 +0,0 @@
|
||||||
// UE/BitmaskInteger.ts
|
|
||||||
|
|
||||||
export type BitmaskInteger = number;
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// UE/DynamicSubsystem.ts
|
|
||||||
|
|
||||||
import { Name } from '#root/UE/Name.ts';
|
|
||||||
import { Subsystem } from '#root/UE/Subsystem.ts';
|
|
||||||
import { UEObject } from '#root/UE/UEObject.ts';
|
|
||||||
|
|
||||||
export class DynamicSubsystem extends Subsystem {
|
|
||||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
|
||||||
super(outer, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
// UE/EHardwareDevicePrimaryType.ts
|
|
||||||
|
|
||||||
export enum EHardwareDevicePrimaryType {
|
|
||||||
Unspecified = 'Unspecified',
|
|
||||||
KeyboardAndMouse = 'KeyboardAndMouse',
|
|
||||||
Gamepad = 'Gamepad',
|
|
||||||
Touch = 'Touch',
|
|
||||||
MotionTracking = 'MotionTracking',
|
|
||||||
RacingWheel = 'RacingWheel',
|
|
||||||
FlightStick = 'FlightStick',
|
|
||||||
Camera = 'Camera',
|
|
||||||
Instrument = 'Instrument',
|
|
||||||
CustomTypeA = 'CustomTypeA',
|
|
||||||
CustomTypeB = 'CustomTypeB',
|
|
||||||
CustomTypeC = 'CustomTypeC',
|
|
||||||
CustomTypeD = 'CustomTypeD',
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// UE/EngineSubsystem.ts
|
|
||||||
|
|
||||||
import { DynamicSubsystem } from '#root/UE/DynamicSubsystem.ts';
|
|
||||||
import { Name } from '#root/UE/Name.ts';
|
|
||||||
import { UEObject } from '#root/UE/UEObject.ts';
|
|
||||||
|
|
||||||
export class EngineSubsystem extends DynamicSubsystem {
|
|
||||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
|
||||||
super(outer, name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// UE/HardwareDeviceIdentifier.ts
|
|
||||||
|
|
||||||
import type { BitmaskInteger } from '#root/UE/BitmaskInteger.ts';
|
|
||||||
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
|
|
||||||
import { Name } from '#root/UE/Name.ts';
|
|
||||||
import { StructBase } from '#root/UE/StructBase.ts';
|
|
||||||
|
|
||||||
export class HardwareDeviceIdentifier extends StructBase {
|
|
||||||
public InputClassName: Name;
|
|
||||||
public HardwareDeviceIdentified: Name;
|
|
||||||
public PrimaryDeviceType: EHardwareDevicePrimaryType;
|
|
||||||
public SupportedFeaturesMask: BitmaskInteger;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
inputClassName: Name = new Name('Unknown'),
|
|
||||||
hardwareDeviceIdentified: Name = new Name('Unknown'),
|
|
||||||
primaryDeviceType: EHardwareDevicePrimaryType = EHardwareDevicePrimaryType.Unspecified,
|
|
||||||
supportedFeaturesMask: BitmaskInteger = 0
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this.InputClassName = inputClassName;
|
|
||||||
this.HardwareDeviceIdentified = hardwareDeviceIdentified;
|
|
||||||
this.PrimaryDeviceType = primaryDeviceType;
|
|
||||||
this.SupportedFeaturesMask = supportedFeaturesMask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// UE/InputDeviceSubsystem.ts
|
|
||||||
|
|
||||||
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
|
|
||||||
import { EngineSubsystem } from '#root/UE/EngineSubsystem.ts';
|
|
||||||
import { HardwareDeviceIdentifier } from '#root/UE/HardwareDeviceIdentifier.ts';
|
|
||||||
import type { Integer } from '#root/UE/Integer.ts';
|
|
||||||
import { Name } from '#root/UE/Name.ts';
|
|
||||||
import { UEObject } from '#root/UE/UEObject.ts';
|
|
||||||
|
|
||||||
class InputDeviceSubsystemClass extends EngineSubsystem {
|
|
||||||
private readonly currentDevice: HardwareDeviceIdentifier;
|
|
||||||
private readonly registeredCallbacks: ((
|
|
||||||
UserId: Integer,
|
|
||||||
DeviceId: Integer
|
|
||||||
) => void)[] = [];
|
|
||||||
|
|
||||||
constructor(outer: UEObject | null = null, name: Name | string = Name.NONE) {
|
|
||||||
super(outer, name);
|
|
||||||
|
|
||||||
// Initialize with default keyboard/mouse
|
|
||||||
this.currentDevice = new HardwareDeviceIdentifier(
|
|
||||||
new Name('EnhancedInput'),
|
|
||||||
new Name('KeyboardMouse'),
|
|
||||||
EHardwareDevicePrimaryType.KeyboardAndMouse,
|
|
||||||
0xff // Full feature support mask
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the most recently used hardware device
|
|
||||||
* This is the primary API for device detection in Unreal Engine
|
|
||||||
* @returns Hardware device identifier structure
|
|
||||||
*/
|
|
||||||
public GetMostRecentlyUsedHardwareDevice(): HardwareDeviceIdentifier {
|
|
||||||
return this.currentDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get hardware device identifier by device ID
|
|
||||||
* @param DeviceId - Device ID to look up
|
|
||||||
* @returns Hardware device identifier structure
|
|
||||||
*/
|
|
||||||
public GetInputDeviceHardwareIdentifier(
|
|
||||||
DeviceId: Integer
|
|
||||||
): HardwareDeviceIdentifier {
|
|
||||||
// In a real implementation, this would look up the device by ID
|
|
||||||
// Here we just return the current device for demonstration purposes
|
|
||||||
console.log(DeviceId);
|
|
||||||
return this.currentDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event fired when input hardware device changes
|
|
||||||
* This is the main delegate for device detection in UE 5.3+
|
|
||||||
*/
|
|
||||||
public OnInputHardwareDeviceChanged = {
|
|
||||||
BindEvent: (
|
|
||||||
callback: (UserId: Integer, DeviceId: Integer) => void
|
|
||||||
): void => {
|
|
||||||
this.registeredCallbacks.push(callback);
|
|
||||||
console.log(
|
|
||||||
'Device change delegate bound, total callbacks:',
|
|
||||||
this.registeredCallbacks.length
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
UnbindEvent: (
|
|
||||||
callback: (UserId: Integer, DeviceId: Integer) => void
|
|
||||||
): void => {
|
|
||||||
const index = this.registeredCallbacks.indexOf(callback);
|
|
||||||
if (index !== -1) {
|
|
||||||
this.registeredCallbacks.splice(index, 1);
|
|
||||||
console.log(
|
|
||||||
'Device change delegate unbound, remaining callbacks:',
|
|
||||||
this.registeredCallbacks.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
UnbindAllEvents: (): void => {
|
|
||||||
const previousCount = this.registeredCallbacks.length;
|
|
||||||
this.registeredCallbacks.length = 0; // Clear array
|
|
||||||
console.log(
|
|
||||||
`All device change delegates unbound. Removed ${previousCount} callbacks.`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InputDeviceSubsystem = new InputDeviceSubsystemClass();
|
|
||||||
|
|
@ -57,8 +57,6 @@
|
||||||
- ✅ Положение тостов адаптируется в зависимости от их количества
|
- ✅ Положение тостов адаптируется в зависимости от их количества
|
||||||
- ✅ Анимация появления и исчезновения тостов плавная и не вызывает рывков
|
- ✅ Анимация появления и исчезновения тостов плавная и не вызывает рывков
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Этап 4: Детекция поверхностей
|
# Этап 4: Детекция поверхностей
|
||||||
**Цель:** Надежное определение типа поверхности под персонажем
|
**Цель:** Надежное определение типа поверхности под персонажем
|
||||||
|
|
||||||
|
|
@ -80,29 +78,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 5: Детекция текущего игрового девайса
|
# Этап 5: Вращение камерой мышкой или стиком
|
||||||
**Цель:** Определение типа устройства ввода (мышь/клавиатура)
|
|
||||||
|
|
||||||
**Результат:** Стабильное определение типа устройства ввода
|
|
||||||
|
|
||||||
**Что реализуем:**
|
|
||||||
- Функции определения типа устройства (E_InputDeviceType)
|
|
||||||
- Функции проверки состояния устройства (IsKeyboard, IsGamepad)
|
|
||||||
- Смена подсказок в HUD в зависимости от устройства
|
|
||||||
- Вывод необходимых значений в Debug HUD
|
|
||||||
- Вывод результатов тестов в HUD
|
|
||||||
|
|
||||||
**Критерии успеха:**
|
|
||||||
- ✅ Корректное определение типа устройства ввода
|
|
||||||
- ✅ Подсказки в HUD меняются в зависимости от устройства
|
|
||||||
- ✅ Легкая интеграция с Enhanced Input System
|
|
||||||
- ✅ Отсутствие ошибок при смене устройства
|
|
||||||
- ✅ Значения корректно отображаются в Debug HUD
|
|
||||||
- ✅ Результаты тестов отображаются в HUD
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Этап 6: Вращение камерой мышкой или стиком
|
|
||||||
**Цель:** Плавное вращение камеры с учетом устройства ввода
|
**Цель:** Плавное вращение камеры с учетом устройства ввода
|
||||||
|
|
||||||
**Результат:** Плавное управление камерой
|
**Результат:** Плавное управление камерой
|
||||||
|
|
@ -123,7 +99,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 7: Базовое движение по земле
|
# Этап 6: Базовое движение по земле
|
||||||
**Цель:** Плавное детерминированное движение по плоским поверхностям
|
**Цель:** Плавное детерминированное движение по плоским поверхностям
|
||||||
|
|
||||||
**Результат:** Отзывчивое управление без рывков и заиканий
|
**Результат:** Отзывчивое управление без рывков и заиканий
|
||||||
|
|
@ -147,7 +123,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 8: Поворот персонажа вслед за движением
|
# Этап 7: Поворот персонажа вслед за движением
|
||||||
**Цель:** Плавный поворот персонажа в сторону движения
|
**Цель:** Плавный поворот персонажа в сторону движения
|
||||||
|
|
||||||
**Результат:** Персонаж естественно реагирует на направление движения
|
**Результат:** Персонаж естественно реагирует на направление движения
|
||||||
|
|
@ -170,7 +146,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 9: Детерминированный Sweep collision
|
# Этап 8: Детерминированный Sweep collision
|
||||||
**Цель:** Полное устранение tunneling через stepped collision detection
|
**Цель:** Полное устранение tunneling через stepped collision detection
|
||||||
**Результат:** Bullet-proof система коллизий
|
**Результат:** Bullet-proof система коллизий
|
||||||
|
|
||||||
|
|
@ -191,7 +167,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 10: Обработка стен и углов
|
# Этап 9: Обработка стен и углов
|
||||||
**Цель:** Плавное скольжение вдоль стен без застреваний
|
**Цель:** Плавное скольжение вдоль стен без застреваний
|
||||||
**Результат:** Качественная навигация в сложной геометрии
|
**Результат:** Качественная навигация в сложной геометрии
|
||||||
|
|
||||||
|
|
@ -213,7 +189,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 11: Движение по склонам
|
# Этап 10: Движение по склонам
|
||||||
**Цель:** Реалистичное поведение на наклонных поверхностях
|
**Цель:** Реалистичное поведение на наклонных поверхностях
|
||||||
**Результат:** Естественное движение по пандусам и скатывание
|
**Результат:** Естественное движение по пандусам и скатывание
|
||||||
|
|
||||||
|
|
@ -235,7 +211,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 12: Разделение физики и рендера
|
# Этап 11: Разделение физики и рендера
|
||||||
**Цель:** Детерминированная физика + плавная визуализация
|
**Цель:** Детерминированная физика + плавная визуализация
|
||||||
**Результат:** AAA-качество визуального движения
|
**Результат:** AAA-качество визуального движения
|
||||||
|
|
||||||
|
|
@ -257,7 +233,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 13: Профессиональная камера система
|
# Этап 12: Профессиональная камера система
|
||||||
**Цель:** Плавная камера уровня AAA-игр
|
**Цель:** Плавная камера уровня AAA-игр
|
||||||
**Результат:** Комфортная камера без рывков
|
**Результат:** Комфортная камера без рывков
|
||||||
|
|
||||||
|
|
@ -280,7 +256,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 14: Adaptive stepping optimization
|
# Этап 13: Adaptive stepping optimization
|
||||||
**Цель:** Оптимизация производительности sweep системы
|
**Цель:** Оптимизация производительности sweep системы
|
||||||
**Результат:** Меньше collision checks без потери качества
|
**Результат:** Меньше collision checks без потери качества
|
||||||
|
|
||||||
|
|
@ -302,7 +278,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 15: Enhanced ground snapping
|
# Этап 14: Enhanced ground snapping
|
||||||
**Цель:** Плавное прилипание к неровным поверхностям
|
**Цель:** Плавное прилипание к неровным поверхностям
|
||||||
**Результат:** Персонаж идет по неровной геометрии без отрыва
|
**Результат:** Персонаж идет по неровной геометрии без отрыва
|
||||||
|
|
||||||
|
|
@ -324,7 +300,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 16: Система прыжков
|
# Этап 15: Система прыжков
|
||||||
**Цель:** Отзывчивое воздушное управление уровня лучших платформеров
|
**Цель:** Отзывчивое воздушное управление уровня лучших платформеров
|
||||||
**Результат:** Качественный платформинг с точным контролем
|
**Результат:** Качественный платформинг с точным контролем
|
||||||
|
|
||||||
|
|
@ -347,7 +323,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 17: Custom Camera Collision System
|
# Этап 16: Custom Camera Collision System
|
||||||
**Цель:** Детерминированная замена SpringArm collision detection
|
**Цель:** Детерминированная замена SpringArm collision detection
|
||||||
**Результат:** Полный контроль над camera collision behavior
|
**Результат:** Полный контроль над camera collision behavior
|
||||||
|
|
||||||
|
|
@ -367,7 +343,7 @@
|
||||||
- ✅ Debug traces показывают camera collision queries
|
- ✅ Debug traces показывают camera collision queries
|
||||||
- ✅ Performance impact <0.1ms per frame
|
- ✅ Performance impact <0.1ms per frame
|
||||||
|
|
||||||
# Этап 18: Воздушная физика
|
# Этап 17: Воздушная физика
|
||||||
**Цель:** Реалистичная но игровая воздушная физика
|
**Цель:** Реалистичная но игровая воздушная физика
|
||||||
**Результат:** Естественное поведение в полете
|
**Результат:** Естественное поведение в полете
|
||||||
|
|
||||||
|
|
@ -390,7 +366,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 19: Продвинутые склоны и поверхности
|
# Этап 18: Продвинутые склоны и поверхности
|
||||||
**Цель:** Сложные взаимодействия с геометрией
|
**Цель:** Сложные взаимодействия с геометрией
|
||||||
**Результат:** Разнообразные типы поверхностей
|
**Результат:** Разнообразные типы поверхностей
|
||||||
|
|
||||||
|
|
@ -413,7 +389,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 20: Wall interactions
|
# Этап 19: Wall interactions
|
||||||
**Цель:** Продвинутые взаимодействия со стенами
|
**Цель:** Продвинутые взаимодействия со стенами
|
||||||
**Результат:** Wall jumping, wall sliding, wall climbing
|
**Результат:** Wall jumping, wall sliding, wall climbing
|
||||||
|
|
||||||
|
|
@ -436,7 +412,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 21: Специальные движения
|
# Этап 20: Специальные движения
|
||||||
**Цель:** Уникальные движения для богатого геймплея
|
**Цель:** Уникальные движения для богатого геймплея
|
||||||
**Результат:** Dash, ground pound, ledge grab и другие
|
**Результат:** Dash, ground pound, ledge grab и другие
|
||||||
|
|
||||||
|
|
@ -459,7 +435,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 22: Performance optimization
|
# Этап 21: Performance optimization
|
||||||
**Цель:** 60 FPS на целевом железе в любых сценариях
|
**Цель:** 60 FPS на целевом железе в любых сценариях
|
||||||
**Результат:** Оптимизированная система коллизий
|
**Результат:** Оптимизированная система коллизий
|
||||||
|
|
||||||
|
|
@ -482,7 +458,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 23: Debug и профилирование tools
|
# Этап 22: Debug и профилирование tools
|
||||||
**Цель:** Профессиональные инструменты для тонкой настройки
|
**Цель:** Профессиональные инструменты для тонкой настройки
|
||||||
**Результат:** Полный контроль над системой
|
**Результат:** Полный контроль над системой
|
||||||
|
|
||||||
|
|
@ -505,7 +481,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 24: Edge cases и stress testing
|
# Этап 23: Edge cases и stress testing
|
||||||
**Цель:** Bullet-proof система для любых условий
|
**Цель:** Bullet-proof система для любых условий
|
||||||
**Результат:** Система работает в экстремальных сценариях
|
**Результат:** Система работает в экстремальных сценариях
|
||||||
|
|
||||||
|
|
@ -528,7 +504,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Этап 25: User experience polish
|
# Этап 24: User experience polish
|
||||||
**Время:** 3-4 дня | **Сложность:** Средняя
|
**Время:** 3-4 дня | **Сложность:** Средняя
|
||||||
**Цель:** Finalized user experience
|
**Цель:** Finalized user experience
|
||||||
**Результат:** Система ощущается как в коммерческой игре
|
**Результат:** Система ощущается как в коммерческой игре
|
||||||
|
|
@ -551,3 +527,25 @@
|
||||||
- ✅ Результаты тестов отображаются в HUD
|
- ✅ Результаты тестов отображаются в HUD
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Этап 25: Детекция текущего игрового девайса
|
||||||
|
**Цель:** Определение типа устройства ввода (мышь/клавиатура)
|
||||||
|
|
||||||
|
**Результат:** Стабильное определение типа устройства ввода
|
||||||
|
|
||||||
|
**Что реализуем:**
|
||||||
|
- Функции определения типа устройства (E_InputDeviceType)
|
||||||
|
- Функции проверки состояния устройства (IsKeyboard, IsGamepad)
|
||||||
|
- Смена подсказок в HUD в зависимости от устройства
|
||||||
|
- Вывод необходимых значений в Debug HUD
|
||||||
|
- Вывод результатов тестов в HUD
|
||||||
|
|
||||||
|
**Критерии успеха:**
|
||||||
|
- ✅ Корректное определение типа устройства ввода
|
||||||
|
- ✅ Подсказки в HUD меняются в зависимости от устройства
|
||||||
|
- ✅ Легкая интеграция с Enhanced Input System
|
||||||
|
- ✅ Отсутствие ошибок при смене устройства
|
||||||
|
- ✅ Значения корректно отображаются в Debug HUD
|
||||||
|
- ✅ Результаты тестов отображаются в HUD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue