A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
PixoVRWristMenu.cpp
Go to the documentation of this file.
1// Copyright(c) Pixo Group. All Rights Reserved.
2
4#include "PixoVRCharacter.h"
5#include "Components/StaticMeshComponent.h"
6#include "Components/WidgetComponent.h"
7#include "Components/SceneComponent.h"
8#include "Components/ArrowComponent.h"
9#include "Components/AudioComponent.h"
10#include "Kismet/GameplayStatics.h"
11#include "Kismet/KismetMathLibrary.h"
12#include "Materials/MaterialInstance.h"
16#include "Sound/SoundCue.h"
17
18DEFINE_LOG_CATEGORY(LogPixoVRWristMenu);
19
21 : RadialFloatingMesh(nullptr)
22 , RadialArrowMesh(nullptr)
23 , TimelineComponent(nullptr)
24 , bIsActivated(false)
25 , ThumbDeadZone(0.5f)
26 , CurrentNormalAngleIndex(-1)
27 , CurrentLargeAngleIndex(-1)
28 , PreviousNormalAngleIndex(-1)
29 , PreviousLargeAngleIndex(-1)
30 , AnglePerNormalItem(0.0f)
31 , AnglePerLargeItem(0.0f)
32 , CurrentAngle(0.0f)
33 , IconRadialDistance(7.0f)
34 , IconRelativeScale(FVector(1.0f / 25.0f))
35 , CurrentPadDirection(FVector2D::ZeroVector)
36 , CurrentArrowRotation(FRotator::ZeroRotator)
37 , WidgetDataTableDefault(nullptr)
38 , WidgetDataTable(nullptr)
39 , Title(nullptr)
40 , CurrentPointedWidgetComponent(nullptr)
41 , CurrentLargePointedWidgetComponent(nullptr)
42 , RotationSpeed(1000.0f)
43 , bAlignToPlayerHead(false)
44 , MaxNumberOfNormalButtons(8)
45 , MaxNumberOfLargeButtons(6)
46{
47 PrimaryActorTick.bCanEverTick = true;
48 PrimaryActorTick.bStartWithTickEnabled = false;
49
50 bReplicates = true;
51 AActor::SetReplicateMovement(true);
52
53 static ConstructorHelpers::FObjectFinder<USoundCue> SelectSoundCueFinder(TEXT("SoundCue'/PixoCore/Audio/SFX/PixoVR_UI_Menu_select_Cue.PixoVR_UI_Menu_Select_Cue'"));
54 SelectAudioCue = SelectSoundCueFinder.Object;
55
56 static ConstructorHelpers::FObjectFinder<USoundCue> HoverSoundCueFinder(TEXT("SoundCue'/PixoCore/Audio/SFX/PixoVR_UI_Hover_Cue.PixoVR_UI_Hover_Cue'"));
57 HoverAudioCue = HoverSoundCueFinder.Object;
58
59 static ConstructorHelpers::FObjectFinder<UStaticMesh> WristMenuMeshFinder(TEXT("StaticMesh'/PixoCore/WristMenu/SM_Radial_Disk.SM_Radial_Disk'"));
60 UStaticMesh* WristMenuMesh = WristMenuMeshFinder.Object;
61
62 static ConstructorHelpers::FObjectFinder<UMaterialInstance> WristMenuMeshMaterialFinder(TEXT("MaterialInstanceConstant'/PixoCore/WristMenu/DiscMaterial.DiscMaterial'"));
63 UMaterialInstance* WristMenuMeshMaterial = WristMenuMeshMaterialFinder.Object;
64
65 static ConstructorHelpers::FObjectFinder<UStaticMesh> WristMenuArrowMeshFinder(TEXT("StaticMesh'/PixoCore/WristMenu/SM_Radial_Pointer.SM_Radial_Pointer'"));
66 UStaticMesh* WristMenuArrowMesh = WristMenuArrowMeshFinder.Object;
67
68 static ConstructorHelpers::FObjectFinder<UMaterial> WristMenuArrowMaterialFinder(TEXT("Material'/PixoCore/WristMenu/ArrowMaterial.ArrowMaterial'"));
69 UMaterial* WristMenuArrowMaterial = WristMenuArrowMaterialFinder.Object;
70
71 static ConstructorHelpers::FClassFinder<UUserWidget> WristMenuWidget(TEXT("/PixoCore/WristMenu/PixoVRWristMenuWidget"));
72 WristMenuWidgetClass = WristMenuWidget.Class;
73
74 static ConstructorHelpers::FClassFinder<UUserWidget> WristMenuTitleWidget(TEXT("/PixoCore/WristMenu/PixoVRWristMenuTitleWidget"));
75 WristMenuTitleWidgetClass = WristMenuTitleWidget.Class;
76
77 SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("WristMenuSceneComponent"));
78 RootComponent = SceneComponent;
79
80 RadialFloatingMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WristMenuMesh"));
81 RadialFloatingMesh->SetupAttachment(SceneComponent);
82 RadialFloatingMesh->SetStaticMesh(WristMenuMesh);
83 RadialFloatingMesh->SetMaterial(0, WristMenuMeshMaterial);
84 RadialFloatingMesh->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
85 RadialFloatingMesh->SetGenerateOverlapEvents(false);
86 RadialFloatingMesh->SetAbsolute(false, true, false);
87
88 RadialArrowMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WristMenuRadialArrowMesh"));
89 RadialArrowMesh->SetupAttachment(RadialFloatingMesh);
90 RadialArrowMesh->SetStaticMesh(WristMenuArrowMesh);
91 RadialArrowMesh->SetMaterial(0, WristMenuArrowMaterial);
92 RadialArrowMesh->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
93
94 AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComponent"));
95
96 TimelineComponent = CreateDefaultSubobject<UTimelineComponent>(TEXT("TimelineComponent"));
99
100 AActor::SetActorHiddenInGame(true);
101}
102
104{
105 Super::BeginPlay();
106
107 // Remember the default data table. We will use this to reset it after reactivating the wrist menu if wanted.
109 {
111 }
112
114 {
116 }
117}
118
123
125{
126 // If Wrist Menu gets deactivated reset the Data Table if necessary.
127 // This happens when a sub-menu was pressed.
129 {
133 {
135 }
136 }
137}
138
139void APixoVRWristMenu::ActivateWristMenu_Implementation(bool InActivate)
140{
141 // Did the BP programmer specify any menu data table?
142 if(WidgetComponents.Num() == 0 && LargeWidgetComponents.Num() == 0)
143 {
144 // No ...
145 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Can't activate Wrist Menu. It seems the Widget Components aren't created. Did you specify a Wrist Menu Data Table?"));
146 return;
147 }
148
149 bIsActivated = InActivate;
150
151 SetActorTickEnabled(bIsActivated);
152
153 if(Curve)
154 {
156 TimelineComponent->SetTimelineFinishedFunc(TimelineFinished);
157 TimelineComponent->SetLooping(false);
158 TimelineComponent->SetIgnoreTimeDilation(true);
159
160 if(InActivate)
161 {
162 SetActorHiddenInGame(!InActivate);
163
164 TimelineComponent->Play();
165 }
166 else
167 {
168 TimelineComponent->ReverseFromEnd();
169 }
170 }
171 else
172 {
173 SetActorHiddenInGame(!bIsActivated);
175 }
176}
177
178void APixoVRWristMenu::Tick(float DeltaTime)
179{
180 Super::Tick(DeltaTime);
181
182 if(bIsActivated)
183 {
184 TickWristMenu(DeltaTime);
185 }
186}
187
188void APixoVRWristMenu::UpdateTrackpadPosition_Implementation(const FVector2D& Direction)
189{
190 if(!bIsActivated)
191 {
192 return;
193 }
194
195 CurrentPadDirection = Direction;
196}
197
199{
200 const APixoVRCharacter* Character = Cast<APixoVRCharacter>(UGameplayStatics::GetPlayerPawn(this, 0));
201 FRotator Rotator;
202
203 const auto PlatformName = UGameplayStatics::GetPlatformName();
204
205 if(Character && bAlignToPlayerHead)
206 {
207 Rotator = UKismetMathLibrary::FindLookAtRotation(Character->Head->GetComponentLocation(), RootComponent->GetComponentLocation());
208 Rotator.Add(90.0f, 0.0f, 0.0f);
209 }
210 else
211 {
212 Rotator = UKismetMathLibrary::RInterpTo(RadialFloatingMesh->GetComponentRotation(), RootComponent->GetComponentRotation(), DeltaTime, RotationSpeed);
213 }
214
215 RadialFloatingMesh->SetWorldRotation(Rotator);
216
217 // Calculate new orientation of the pointer arrow.
219
220 RadialArrowMesh->SetRelativeRotation(CurrentArrowRotation);
221
223
224 // We have to add and offset to the current angle to make the selection a bit easier.
225 //CurrentAngle += AnglePerNormalItem * 0.5f;
226
229
230 CurrentNormalAngleIndex = (static_cast<int>(CurrentAngle + (AnglePerNormalItem * 0.5f) + 270 + 22.5 * (WidgetComponents.Num() - 1)) % 360)/AnglePerNormalItem;
231 CurrentLargeAngleIndex = (static_cast<int>(CurrentAngle + (AnglePerLargeItem * 0.5f) + 90 + 30 * (LargeWidgetComponents.Num() - 1)) % 360)/AnglePerLargeItem;
232
233 //One set of instructions for the normal buttons
235 {
236 //Return any highlighted buttons to their normal states
238 {
239 Cast<UPixoVRWristMenuItemWidget>(WidgetComponents[PreviousNormalAngleIndex]->GetUserWidgetObject())->SetButtonHighlight(FLinearColor(.2525f, .2525f, .2525f, 1.0f));
240 Cast<UPixoVRWristMenuItemWidget>(WidgetComponents[PreviousNormalAngleIndex]->GetUserWidgetObject())->SetRenderScale(FVector2D(1.0f, 1.0f));
241 }
242
243 // Highlight the new button and update the title text, if available
245 {
247
248 Cast<UPixoVRWristMenuItemWidget>(CurrentPointedWidgetComponent->GetUserWidgetObject())->SetButtonHighlight(DataTableRowsNormal[CurrentNormalAngleIndex]->HighlightColor);
249 Cast<UPixoVRWristMenuItemWidget>(CurrentPointedWidgetComponent->GetUserWidgetObject())->SetRenderScale(FVector2D(1.25f, 1.25f));
250 Cast<UPixoVRWristMenuItemTitleWidget>(Title->GetUserWidgetObject())->SetTitleText(DataTableRowsNormal[CurrentNormalAngleIndex]->TextTitle);
251
252 AudioComponent->SetSound(HoverAudioCue);
253 AudioComponent->Play();
254 }
255 else
256 {
258 }
259 }
260
261 // Another set of instructions for the large buttons.
263 {
264 // Return any highlighted buttons to their normal states
266 {
267 Cast<UPixoVRWristMenuItemWidget>(LargeWidgetComponents[PreviousLargeAngleIndex]->GetUserWidgetObject())->SetButtonHighlight(FLinearColor(.2525f, .2525f, .2525f, 1.0f));
268 //No scaling for large buttons
269 //Cast<UPixoVRWristMenuItemWidget>(WidgetComponents[PreviousLargeAngleIndex]->GetUserWidgetObject())->SetRenderScale(FVector2D(1.0f, 1.0f));
270 }
271
272 // Highlight the new button and update the title text, if available
274 {
276 Cast<UPixoVRWristMenuItemWidget>(CurrentLargePointedWidgetComponent->GetUserWidgetObject())->SetButtonHighlight(DataTableRowsLarge[CurrentLargeAngleIndex]->HighlightColor);
278 //Cast<UPixoVRWristMenuItemWidget>(CurrentLargePointedWidgetComponent->GetUserWidgetObject())->SetRenderScale(FVector2D(1.25f, 1.25f));
279 Cast<UPixoVRWristMenuItemTitleWidget>(Title->GetUserWidgetObject())->SetTitleText(DataTableRowsLarge[CurrentLargeAngleIndex]->TextTitle);
280 Cast<UPixoVRWristMenuItemTitleWidget>(Title->GetUserWidgetObject())->SetTitleColor(DataTableRowsLarge[CurrentLargeAngleIndex]->HighlightColor);
281
282 AudioComponent->SetSound(HoverAudioCue);
283 AudioComponent->Play();
284 }
285 else
286 {
288 }
289 }
290}
291
293{
294 if (Title != nullptr)
295 {
296 Title->UnregisterComponent();
297 const FDetachmentTransformRules DetachmentTransformRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, true);
298 Title->DetachFromComponent(DetachmentTransformRules);
299 Title->ConditionalBeginDestroy();
300 Title = nullptr;
301 }
302
303 for(auto& WidgetComponent : WidgetComponents)
304 {
305 WidgetComponent->UnregisterComponent();
306 FDetachmentTransformRules DetachmentTransformRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, true);
307 WidgetComponent->DetachFromComponent(DetachmentTransformRules);
308 WidgetComponent->ConditionalBeginDestroy();
309 WidgetComponent = nullptr;
310 }
311
312 for (auto& WidgetComponent : LargeWidgetComponents)
313 {
314 WidgetComponent->UnregisterComponent();
315 FDetachmentTransformRules DetachmentTransformRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, true);
316 WidgetComponent->DetachFromComponent(DetachmentTransformRules);
317 WidgetComponent->ConditionalBeginDestroy();
318 WidgetComponent = nullptr;
319 }
320
321 WidgetComponents.Empty();
322 LargeWidgetComponents.Empty();
327}
328
330{
332
333 // Don't do anything if the wrist menu isn't activated.
335 {
336 return MenuCategory;
337 }
338
339 bool isNormalButton = false;
340
342 {
343 MenuCategory = DataTableRowsNormal[CurrentNormalAngleIndex]->MenuCategory;
344 isNormalButton = true;
345 }
346
348 {
349 MenuCategory = DataTableRowsLarge[CurrentLargeAngleIndex]->MenuCategory;
350 isNormalButton = false;
351 }
352
353 // Handle all executable commands. The BP programmer will usually implement this.
354 if(MenuCategory == EWristMenuItemCategory::EXECUTETABLE)
355 {
356 if(EventHandleObjects.Num() > 0)
357 {
358 for(const auto& EventHandleObject : EventHandleObjects)
359 {
360 if(EventHandleObject->Implements<UPixoVRWristMenuInterface>())
361 {
362 if (isNormalButton == true)
363 {
364 IPixoVRWristMenuInterface::Execute_OnWristMenuItemSelected(EventHandleObject, this, *DataTableRowsNormal[CurrentNormalAngleIndex]);
365 }
366 else if (isNormalButton == false)
367 {
368 IPixoVRWristMenuInterface::Execute_OnWristMenuItemSelected(EventHandleObject, this, *DataTableRowsLarge[CurrentLargeAngleIndex]);
369 }
370 }
371 }
372 }
373 }
374 // Handle sub-menu commands.
375 else if(MenuCategory == EWristMenuItemCategory::SUBMENU)
376 {
377 // TODO It might be better to cache this later so that we don't have to load the object over and over again.
378 if (isNormalButton == true)
379 {
380 WidgetDataTable = LoadObject<UDataTable>(nullptr, *DataTableRowsNormal[CurrentNormalAngleIndex]->MenuItemID);
381 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Loading submenu data table: %s"), *DataTableRowsNormal[CurrentNormalAngleIndex]->MenuItemID);
382 }
383
384 else if (isNormalButton == false)
385 {
386 WidgetDataTable = LoadObject<UDataTable>(nullptr, *DataTableRowsLarge[CurrentLargeAngleIndex]->MenuItemID);
387 }
388
390 {
392 {
394 CurrentNormalAngleIndex = INDEX_NONE;
395 CurrentLargeAngleIndex = INDEX_NONE;
396 }
397 else
398 {
399 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Parsing Widget Data Table failed."));
400 }
401 }
402 else
403 {
404 if (isNormalButton == true)
405 {
406 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Couldn't load specified submenu data table: %s"), *DataTableRowsNormal[CurrentNormalAngleIndex]->MenuItemID);
407 }
408
409 if (isNormalButton == false)
410 {
411 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Couldn't load specified submenu data table: %s"), *DataTableRowsLarge[CurrentLargeAngleIndex]->MenuItemID);
412 }
413 }
414 }
415
417 AudioComponent->Play();
418
419 return MenuCategory;
420}
421
423{
424 return bIsActivated;
425}
426
427void APixoVRWristMenu::CalculateRotationFromInput(const FVector2D& Direction)
428{
429 // Rotate input X+Y always point forward relative to the current pawn rotation.
430 FVector A(Direction.X, Direction.Y, 0.0f);
431 // Check whether thumb in near center (ignore rotation overrides).
432 const bool Condition = A.Size() > ThumbDeadZone;
433 if(Condition)
434 {
435 // We should normalize the vector.
436 A.Normalize();
437
438 // The second term adds an offset because the we like to put the zero angle to +90 degrees.
439 CurrentArrowRotation = FRotationMatrix::MakeFromX(A).Rotator() + FRotator(0.0f, 180.0f, 0.0f);
440 }
441}
442
443void APixoVRWristMenu::CalculateRotationByItemIndex(const int32 WristMenuItemIndex)
444{
445 if(WidgetComponents.IsValidIndex(WristMenuItemIndex))
446 {
447 if(const UPixoVRWristMenuItemWidget* Widget = Cast<UPixoVRWristMenuItemWidget>(WidgetComponents[WristMenuItemIndex]->GetWidget()))
448 {
449 CurrentArrowRotation.Yaw = Widget->GetButtonAngle() + 90.f;
450 }
451 }
452}
453
455{
456 for (const auto& Row : DataTableRows)
457 {
458 if(Row->MenuItemID == InNewItemInfo.MenuItemID)
459 {
460 *Row = InNewItemInfo;
463 return;
464 }
465 }
466
468 DataTableRows.Add(NewItem);
471}
472
474{
475 // Check if the BP programmer specified a menu data-table.
476 if(DataTableRows.Num() == 0)
477 {
478 // No ...
479 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Can't create widgets because of some issues with the data table."));
480 return;
481 }
482
484
485 //Add The Title Text Widget
486 Title = NewObject<UWidgetComponent>(this);
487 check(Title);
488 const FAttachmentTransformRules TitleAttachmentTransformRules(EAttachmentRule::KeepRelative, EAttachmentRule::KeepRelative, EAttachmentRule::KeepRelative, true);
489 Title->AttachToComponent(RadialFloatingMesh, TitleAttachmentTransformRules, NAME_None);
490 Title->RegisterComponent();
491 Title->SetCollisionEnabled(ECollisionEnabled::NoCollision);
492 Title->SetGenerateOverlapEvents(false);
493 Title->SetTwoSided(true);
494 Title->SetOpacityFromTexture(1.0f); // Slate UIs have bogus opacity in their texture's alpha, so ignore texture alpha for VR
495 Title->SetBackgroundColor(FLinearColor::Transparent);
496 Title->SetBlendMode(EWidgetBlendMode::Masked);
497 Title->SetRelativeScale3D(IconRelativeScale);
498 Title->SetRelativeLocation(FVector(0.f, 0.f, 3.f));
499
500 const FQuat TitleRotationPitch(FVector::RightVector, FMath::DegreesToRadians(90.0f));
501 const FQuat TitleRotationYaw(FVector::UpVector, FMath::DegreesToRadians(180.0f));
502 const FQuat TitleRotationRot = TitleRotationPitch * TitleRotationYaw;
503
504 Title->SetRelativeRotation(TitleRotationRot);
505
506
507 UPixoVRWristMenuItemTitleWidget *TitleWidget = CreateWidget<UPixoVRWristMenuItemTitleWidget>(GetWorld(), WristMenuTitleWidgetClass);
508 check(TitleWidget);
509
510 Title->SetWidget(TitleWidget);
511
512 //Add the Radial Widgets
513 uint32 WidgetItemPositionIndex = 0;
514 uint32 LargeWidgetItemPositionIndex = 0;
515 float NumberOfNormalButtonItems = 0;
516 float NumberOfLargeButtonItems = 0;
517
518 for (const auto& Row : DataTableRows)
519 {
520 if (Row->ButtonSize == EButtonSize::NORMAL)
521 {
522 ++NumberOfNormalButtonItems;
523 }
524
525 if (Row->ButtonSize == EButtonSize::LARGE)
526 {
527 ++NumberOfLargeButtonItems;
528 }
529 }
530
531 if ((static_cast<float>(NumberOfNormalButtonItems/MaxNumberOfNormalButtons)) + (static_cast<float>(NumberOfLargeButtonItems/MaxNumberOfLargeButtons)) > 1.0)
532 {
533 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("There are too many menu items to fit on the wrist menu."));
534 return;
535 }
536
537 for(const auto& Row : DataTableRows)
538 {
539 UWidgetComponent* WidgetComponent = NewObject<UWidgetComponent>(this);
540 check(WidgetComponent)
541
542 FAttachmentTransformRules AttachmentTransformRules(EAttachmentRule::KeepRelative, EAttachmentRule::KeepRelative, EAttachmentRule::KeepRelative, true);
543 WidgetComponent->AttachToComponent(RadialFloatingMesh, AttachmentTransformRules, NAME_None);
544 WidgetComponent->RegisterComponent();
545 WidgetComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
546 WidgetComponent->SetGenerateOverlapEvents(false);
547 WidgetComponent->SetTwoSided(true);
548 WidgetComponent->SetOpacityFromTexture( 1.0f ); // Slate UIs have bogus opacity in their texture's alpha, so ignore texture alpha for VR
549 WidgetComponent->SetBackgroundColor( FLinearColor::Transparent );
550 WidgetComponent->SetBlendMode( EWidgetBlendMode::Masked );
551
552 float RadialX(0.0f);
553 float RadialY(0.0f);
554
555 if (Row->ButtonSize == EButtonSize::NORMAL)
556 {
557 RadialX = -IconRadialDistance * FMath::Sin((((WidgetItemPositionIndex - 2) % MaxNumberOfNormalButtons) - (0.5f * (NumberOfNormalButtonItems - 1))) * ((2.0f*PI) / MaxNumberOfNormalButtons));
558 RadialY = IconRadialDistance * FMath::Cos((((WidgetItemPositionIndex - 2) % MaxNumberOfNormalButtons) - (0.5f * (NumberOfNormalButtonItems - 1))) * ((2.0f*PI) / MaxNumberOfNormalButtons));
559 }
560
561 if (Row->ButtonSize == EButtonSize::LARGE)
562 {
563 RadialX = -IconRadialDistance * FMath::Sin((((LargeWidgetItemPositionIndex + 1) % MaxNumberOfLargeButtons) - (0.5f * (NumberOfLargeButtonItems - 2))) * ((2.0f*PI) / MaxNumberOfLargeButtons));
564 RadialY = IconRadialDistance * FMath::Cos((((LargeWidgetItemPositionIndex + 1) % MaxNumberOfLargeButtons) - (0.5f * (NumberOfLargeButtonItems - 2))) * ((2.0f*PI) / MaxNumberOfLargeButtons));
565 }
566
567 WidgetComponent->SetRelativeScale3D(IconRelativeScale);
568 WidgetComponent->SetRelativeLocation(FVector(RadialX, RadialY, 2.5f));
569
570 FQuat Rot = TitleRotationPitch * TitleRotationYaw;
571
572 WidgetComponent->SetRelativeRotation(Rot);
573
574 //Set the correct angle based on number of items
575
576 // -------------------------------------------------------------------------
577 // Add the Widget to the WidgetComponent.
578 UPixoVRWristMenuItemWidget* Widget = CreateWidget<UPixoVRWristMenuItemWidget>(GetWorld(), WristMenuWidgetClass);
579 check(Widget);
580
581 WidgetComponent->SetWidget(Widget);
582
583 Widget->SetButtonIcon(Row->IconTexture);
584
585 if (Row->ButtonSize == EButtonSize::NORMAL)
586 {
587 Widget->SetButtonAngle((-22.5f * (NumberOfNormalButtonItems - 1)) + (45.0f * WidgetItemPositionIndex));
588 WidgetItemPositionIndex++;
589 WidgetComponents.Add(WidgetComponent);
590 }
591 else if (Row->ButtonSize == EButtonSize::LARGE)
592 {
593 Widget->SetButtonAngle(180.0f+(-30.00f * (NumberOfLargeButtonItems - 1)) + (60.0f * LargeWidgetItemPositionIndex));
594 Widget->SetButtonScale(FVector2D(1.25f, 1.25f));
595 LargeWidgetItemPositionIndex++;
596 LargeWidgetComponents.Add(WidgetComponent);
597 }
598 // -------------------------------------------------------------------------
599 }
600
602}
603
605{
606 if(!WidgetDataTable)
607 {
608 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Widget Data Table not specified. Can't parse Wrist Menu Table.") )
609 return false;
610 }
611
612 DataTableRows.Empty();
614
616 {
617 return false;
618 }
619
622
623 return true;
624}
625
627{
628 if(DataTableRows.Num() <= 0)
629 {
630 UE_LOG(LogPixoVRWristMenu, Warning, TEXT("Data Table Rows Array is empty. Can't parse Wrist Menu.") )
631 return false;
632 }
633
634 DataTableRowsNormal.Empty();
635 DataTableRowsLarge.Empty();
636
637 for (const auto& Row : DataTableRows)
638 {
639 if (Row->ButtonSize == EButtonSize::NORMAL)
640 {
641 DataTableRowsNormal.Add(Row);
642 }
643 else if(Row->ButtonSize == EButtonSize::LARGE)
644 {
645 DataTableRowsLarge.Add(Row);
646 }
647 }
648
649 return true;
650}
651
652void APixoVRWristMenu::SetEventHandlers(TArray<UObject*>& InEventHandleObjects)
653{
654 EventHandleObjects = InEventHandleObjects;
655}
656
657void APixoVRWristMenu::RebuildWristMenu(UDataTable* NewWidgetDataTable)
658{
659 if (NewWidgetDataTable == nullptr)
660 {
661 UE_LOG(LogTemp, Warning, TEXT("The new widget data table is empty, the rebuild process is stopped"));
662 return;
663 }
664
666
667 WidgetDataTable = NewWidgetDataTable;
668 WidgetDataTableDefault = NewWidgetDataTable;
669
671 {
673 }
674}
675
677{
678 RootComponent->SetWorldScale3D(FVector(Value, Value, Value));
679}
680
682{
683 if(TimelineComponent->GetPlaybackPosition() <= 0.0f)
684 {
685 SetActorHiddenInGame(true);
687 }
688}
DEFINE_LOG_CATEGORY(LogPixoVRWristMenu)
EWristMenuItemCategory
Enum representing the category of a wrist menu item in PixoVR.
Pixo VR Character This class represents the main character in the Pixo VR game. It extends the AVRCha...
UStaticMeshComponent * Head
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "PixoVR", meta = (AllowPrivateAccess = "tru...
void ClearAllWidgets()
Clear all the widgets.
float IconRadialDistance
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
UDataTable * WidgetDataTable
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu", meta = (AllowPrivateAcc...
void TickWristMenu(float DeltaTime)
bool bAlignToPlayerHead
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
UWidgetComponent * Title
UPROPERTY()
EWristMenuItemCategory SelectMenuItem()
Signal that the user has performed a select action on the wrist menu.
UDataTable * WidgetDataTableDefault
UPROPERTY(Transient)
USceneComponent * SceneComponent
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
bool IsActivated()
Check if the wrist menu is activated.
void RebuildWristMenu(UDataTable *NewWidgetDataTable)
Rebuild the wrist menu using a new widget data table.
float CurrentAngle
UPROPERTY(BlueprintReadOnly, Transient, Category = "PixoVR | Wrist Menu")
float ThumbDeadZone
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
bool bIsActivated
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
FVector IconRelativeScale
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
UTimelineComponent * TimelineComponent
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
TArray< UObject * > EventHandleObjects
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
TArray< FPixoVRWristMenuDataTableRow * > DataTableRows
The rows from the data table for easier usage.
TArray< FPixoVRWristMenuDataTableRow * > DataTableRowsNormal
The rows from the data table for easier usage.
FOnTimelineFloat TimelinePostUpdate
Delegate fired when the timeline has a new update.
int PreviousNormalAngleIndex
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
UWidgetComponent * CurrentPointedWidgetComponent
UPROPERTY()
void SetEventHandlers(TArray< UObject * > &InEventHandleObjects)
Set the event handlers for the wrist menu.
virtual void Tick(float DeltaTime) override
TArray< UWidgetComponent * > WidgetComponents
The widget components used for rendering normal wrist menu items.
void HandleTimeLineFloat(float Value)
Handle the update of the timeline.
float RotationSpeed
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
void CreateAllWidgets()
Create all the widgets using the widget data table.
UCurveFloat * Curve
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
FRotator CurrentArrowRotation
The current rotation of the arrow.
int PreviousLargeAngleIndex
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
int CurrentNormalAngleIndex
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
UStaticMeshComponent * RadialFloatingMesh
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
FOnTimelineEvent TimelineFinished
Delegate fired when the timeline reaches the end.
UAudioComponent * AudioComponent
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
void AddIMenuItem(const FPixoVRWristMenuDataTableRow &InNewItemInfo)
Add a new menu item or update an existing one in the wrist menu if found in the DataTableRows array b...
void CalculateRotationFromInput(const FVector2D &Direction)
Calculate the rotation of the pointer arrow based on the input from a controller pad.
float AnglePerNormalItem
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
void CalculateRotationByItemIndex(const int32 WristMenuItemIndex)
Calculate the rotation of the pointer arrow based on the item index.
void IncreaseSelectedItem(int Amount)
Increase the selected item by a specified amount. Use this to do tests with the mouse wheel.
TSubclassOf< class UUserWidget > WristMenuWidgetClass
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu", meta = (AllowPrivateAcc...
TArray< UWidgetComponent * > LargeWidgetComponents
The widget components used for rendering large wrist menu items.
TArray< FPixoVRWristMenuDataTableRow * > DataTableRowsLarge
The rows from the data table for easier usage.
bool ParseWidgetDataTable()
Parse the widget data table and create all the required widgets.
bool ParseWidgetDataTableRows()
Parse the data table rows array and create all the required widgets.
UStaticMeshComponent * RadialArrowMesh
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
USoundCue * HoverAudioCue
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
USoundCue * SelectAudioCue
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu")
TSubclassOf< class UUserWidget > WristMenuTitleWidgetClass
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Wrist Menu", meta = (AllowPrivateAcc...
void HandleTimelineFinished()
Handle the timeline finished event.
FVector2D CurrentPadDirection
The current direction of the pad.
virtual void BeginPlay() override
UWidgetComponent * CurrentLargePointedWidgetComponent
UPROPERTY()
float AnglePerLargeItem
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
void ResetToDefaultTable()
Reset the data table to the default one.
int CurrentLargeAngleIndex
UPROPERTY(BlueprintReadOnly, Category = "PixoVR | Wrist Menu")
UINTERFACE(BlueprintType)
Widget class for the title of a PixoVR wrist menu item.
Widget class for the PixoVR wrist menu item. Use BindWidget to inform UBT that we want to control the...
void SetButtonAngle(float Angle)
UFUNCTION(BlueprintCallable, Category = "PixoVR | Wrist Menu")
void SetButtonIcon(UTexture2D *Texture)
UFUNCTION(BlueprintCallable, Category = "PixoVR | Wrist Menu")
void SetButtonScale(FVector2D Scale)
UFUNCTION(BlueprintCallable, Category = "PixoVR | Wrist Menu")
Struct representing a row in the PixoVR wrist menu data table. Each row contains information about a ...
FString MenuItemID
UPROPERTY(EditAnywhere, BlueprintReadWrite)