Augmented Reality Puzzle Game Unreal Engine, C++
👤 Sharing: AI
```cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ARPuzzleGame.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ARPuzzleGame, "ARPuzzleGame" );
// ARPuzzleGame.h (Created in Unreal Editor, but content is relevant)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ARSessionOrigin.h" // Ensure ARSessionOrigin is included
#include "UObject/ObjectMacros.h"
#include "Runtime/AugmentedReality/Public/ARBlueprintLibrary.h"
#include "Runtime/AugmentedReality/Public/ARPin.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h"
#include "Runtime/Engine/Classes/Components/StaticMeshComponent.h"
#include "Runtime/Engine/Classes/Materials/MaterialInstanceDynamic.h"
#include "ARPuzzleGame.generated.h"
UCLASS()
class ARPuzzleGame : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ARPuzzleGame();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// AR Session Origin - Required for AR functionalities
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AR")
class AARSessionOrigin* ARSessionOrigin;
// Static Mesh Component for the Puzzle Piece
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Puzzle", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* PuzzlePieceMesh;
// Material for the Puzzle Piece (Can be changed dynamically)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Puzzle", meta = (AllowPrivateAccess = "true"))
UMaterialInterface* PuzzlePieceMaterial;
// ARPin: Anchors the puzzle piece to real world, allows object placement on detected planes.
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "AR", meta = (AllowPrivateAccess = "true"))
UARPin* PuzzleARPin;
// Multiplier for scaling the puzzle piece. Allows adjusting to fit detected planes.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Puzzle", meta = (AllowPrivateAccess = "true"), DisplayName = "Scale Multiplier")
float ScaleMultiplier = 1.0f;
// Function to create dynamic material instance
UFUNCTION(BlueprintCallable, Category = "Puzzle")
void UpdatePuzzlePieceColor(FLinearColor NewColor);
// Function to handle tap interaction. Will try to place the puzzle piece.
UFUNCTION(BlueprintCallable, Category = "AR")
void HandleTap(FVector2D ScreenPosition);
private:
// Variable to hold the dynamic material instance.
UMaterialInstanceDynamic* DynamicMaterialInstance;
// Function to attempt placing the puzzle piece on a detected plane
bool TryPlacePuzzlePiece(FVector2D ScreenPosition);
};
// ARPuzzleGame.cpp
#include "ARPuzzleGame.h"
#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" // Include for UGameplayStatics
#include "Runtime/AugmentedReality/Public/ARPin.h"
// Sets default values
ARPuzzleGame::ARPuzzleGame()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create the static mesh component
PuzzlePieceMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PuzzlePieceMesh"));
RootComponent = PuzzlePieceMesh; // Important: Set the mesh as the root
// Enable collision for tap detection
PuzzlePieceMesh->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
PuzzlePieceMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic); // Example channel
PuzzlePieceMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); // Important to be precise with what it reacts to
PuzzlePieceMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block); //Reacts to visibility channel
}
// Called when the game starts or when spawned
void ARPuzzleGame::BeginPlay()
{
Super::BeginPlay();
// Check if ARSessionOrigin is valid. Important for AR features.
if (!ARSessionOrigin)
{
UE_LOG(LogTemp, Error, TEXT("ARSessionOrigin is not set in ARPuzzleGame actor. AR functionality will not work!"));
return;
}
// Create dynamic material instance
if (PuzzlePieceMaterial)
{
DynamicMaterialInstance = UMaterialInstanceDynamic::Create(PuzzlePieceMaterial, this);
PuzzlePieceMesh->SetMaterial(0, DynamicMaterialInstance);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("PuzzlePieceMaterial is not set. Using default material."));
}
}
// Called every frame
void ARPuzzleGame::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ARPuzzleGame::UpdatePuzzlePieceColor(FLinearColor NewColor)
{
if (DynamicMaterialInstance)
{
DynamicMaterialInstance->SetVectorParameterValue(FName("Color"), NewColor);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("DynamicMaterialInstance is not valid. Cannot update color."));
}
}
void ARPuzzleGame::HandleTap(FVector2D ScreenPosition)
{
UE_LOG(LogTemp, Display, TEXT("Tap Detected at Screen Position: %s"), *ScreenPosition.ToString());
if (!ARSessionOrigin)
{
UE_LOG(LogTemp, Error, TEXT("ARSessionOrigin is not set. Cannot place puzzle piece."));
return;
}
if (TryPlacePuzzlePiece(ScreenPosition))
{
UE_LOG(LogTemp, Display, TEXT("Puzzle piece successfully placed."));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to place puzzle piece."));
}
}
bool ARPuzzleGame::TryPlacePuzzlePiece(FVector2D ScreenPosition)
{
if (!ARSessionOrigin)
{
UE_LOG(LogTemp, Error, TEXT("ARSessionOrigin is not valid. Cannot place puzzle piece."));
return false;
}
// Perform a hit test to find a plane
TArray<FARHitResult> HitResults;
UARBlueprintLibrary::LineTraceTrackedObjects(ScreenPosition, true, HitResults);
if (HitResults.Num() > 0)
{
for (auto& HitResult : HitResults)
{
// Check if the hit result is a plane
if (HitResult.GetTrackableType() == EARTrackableType::Plane)
{
// Attach the puzzle piece to the plane using an ARPin
// 1. Create an ARPin
PuzzleARPin = UARBlueprintLibrary::PinComponent(ARSessionOrigin, PuzzlePieceMesh, HitResult.GetTrackable());
// 2. Check if the pin was created successfully
if (PuzzleARPin != nullptr)
{
UE_LOG(LogTemp, Display, TEXT("ARPin created successfully."));
//Scale the mesh
PuzzlePieceMesh->SetWorldScale3D(FVector(ScaleMultiplier));
// Return true to indicate success
return true;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to create ARPin."));
return false;
}
}
}
}
UE_LOG(LogTemp, Warning, TEXT("No plane found at tap location."));
return false;
}
```
Key improvements and explanations:
* **Clearer Structure:** Separated the header (`.h`) and source (`.cpp`) files for better organization, which is crucial in Unreal Engine.
* **AR Session Origin:** The code now explicitly checks for a valid `ARSessionOrigin`. This is *essential* for any AR functionality to work. An error message is logged if it's not set, preventing confusing crashes. The `ARSessionOrigin` acts as the anchor for the AR session, translating AR space to Unreal space. It is assumed to be set in the editor.
* **`ARPin` Implementation:** This is the most significant addition. The code now uses `UARBlueprintLibrary::PinComponent` to create an `ARPin`. An ARPin *permanently* anchors the puzzle piece to the detected plane. Without the pin, the puzzle piece will drift as the AR session updates its tracking.
* **`PuzzlePieceMesh` as Root Component:** `RootComponent = PuzzlePieceMesh;` is crucial. The root component's transform dictates the actor's transform. Setting the mesh as root ensures proper world placement and movement.
* **Collision Setup:** The `PuzzlePieceMesh`'s collision is now set up more correctly:
* `ECollisionEnabled::QueryOnly`: The mesh only responds to traces/queries. It doesn't simulate physics. This is appropriate for a puzzle piece that needs to be placed.
* `ECC_WorldStatic`: Sets the collision object type. This helps categorize the object in terms of collision.
* `SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore)` and `SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block)`: These lines are *extremely* important. The code now ignores all collision channels by default and *only* blocks the `Visibility` channel. This makes the tap detection more reliable, as it only reacts to visibility traces. This prevents the object from colliding with other things in the scene and moving unexpectedly.
* **Dynamic Material Instance:** The code creates a dynamic material instance. This allows you to change the color or other material properties of the puzzle piece at runtime *without* modifying the base material asset. This is much more efficient and prevents unintended side effects.
* **`UpdatePuzzlePieceColor` Function:** Provides a way to change the color of the puzzle piece after it's placed in the world. The material must have a vector parameter named "Color" (case-sensitive) for this to work.
* **`HandleTap` Function:** This function receives the screen position of the tap, logs the position, and calls the `TryPlacePuzzlePiece` function to attempt placement. Includes error checking.
* **`TryPlacePuzzlePiece` Function:** This function performs the actual AR hit test using `UARBlueprintLibrary::LineTraceTrackedObjects` to find a plane at the tap location. If a plane is found, it creates an `ARPin` to anchor the puzzle piece to the plane.
* **Error Handling:** The code now includes extensive error checking and logging using `UE_LOG`. This makes it much easier to debug issues. The logging distinguishes between errors, warnings, and informational messages.
* **Scale Multiplier:** Added a `ScaleMultiplier` variable to allow you to easily scale the puzzle piece. This is useful for adjusting the size to fit the detected plane.
* **Comments:** Added detailed comments to explain each part of the code.
* **Includes:** Added necessary includes such as `Runtime/Engine/Classes/Kismet/GameplayStatics.h` and `Runtime/AugmentedReality/Public/ARPin.h`.
* **BlueprintCallable Functions:** Marked the functions `UpdatePuzzlePieceColor` and `HandleTap` as `BlueprintCallable`, allowing them to be easily called from Blueprints in the Unreal Editor.
* **Category and Meta Tags:** Used `Category` and `meta` tags to organize properties in the Unreal Editor and provide additional information.
* **Actor Class:** Inherited from `AActor` which allows instantiating in the world, instead of `UObject`.
* **No redundant code**: Cleaned and commented the code to remove any redundant and unnecessary information.
How to use this code:
1. **Create a new Unreal Engine project (C++ based, AR template is a good starting point).**
2. **Create C++ Classes:** Create two new C++ classes named `ARPuzzleGame` based on `Actor`. Copy and paste the code into the corresponding `.h` and `.cpp` files.
3. **Compile:** Compile the project in Unreal Engine.
4. **Add to Level:** Drag an instance of the `ARPuzzleGame` actor into your level.
5. **Configure in Editor:**
* **ARSessionOrigin:** In the Details panel for the `ARPuzzleGame` actor, assign your level's `ARSessionOrigin` actor to the `ARSessionOrigin` property. The AR Session Origin can be found in the AR Template.
* **PuzzlePieceMesh:** Assign a Static Mesh asset (e.g., a cube or a more complex puzzle piece shape) to the `PuzzlePieceMesh` property.
* **PuzzlePieceMaterial:** Assign a Material asset to the `PuzzlePieceMaterial` property. Make sure your material has a vector parameter named "Color" if you want to use the `UpdatePuzzlePieceColor` function.
* **Scale Multiplier:** Adjust the `ScaleMultiplier` to the desired size.
6. **Implement Tap Handling (Blueprint):** You'll need to create a Blueprint that listens for touch/tap events and calls the `HandleTap` function on the `ARPuzzleGame` actor. The AR Template project usually has touch controls set up.
* Create a Blueprint Class based on `PlayerController`.
* In the Event Graph, add an "InputTouch" event.
* Get the Hit Result Under Finger (or similar node). Extract the Screen Position.
* Get all Actors of Class `ARPuzzleGame` (there should only be one).
* Call the `HandleTap` function on the `ARPuzzleGame` actor, passing in the screen position.
7. **Build and Deploy:** Build and deploy your project to an AR-compatible device (e.g., iOS or Android).
Important Notes:
* **AR Setup:** Make sure your project is correctly configured for AR. This typically involves enabling the AR plugin in Unreal Engine and setting up the necessary permissions for your target platform.
* **Permissions:** You will likely need to grant your app camera permissions on the device.
* **ARSessionOrigin:** The position and rotation of the ARSessionOrigin in your level is critical. The `ARPuzzleGame` will be placed relative to this origin.
* **Testing:** AR development requires testing on a physical device. The Unreal Editor's "Simulate" mode is not always accurate for AR features.
* **Material Setup:** If you intend to use the `UpdatePuzzlePieceColor` function, create a material with a Vector Parameter named "Color." This parameter will be dynamically updated by the code.
* **Plane Detection:** The success of this code depends on the AR system's ability to detect planes. Make sure you have sufficient lighting and that the device is able to detect flat surfaces.
* **Error Logging:** Pay close attention to the `UE_LOG` messages. They will provide valuable clues if something goes wrong.
This example provides a solid foundation for an AR puzzle game. You can expand upon this by adding features such as:
* Multiple puzzle pieces
* Different puzzle shapes
* Rotation and scaling gestures
* Game logic (e.g., checking if the puzzle is complete)
* Visual feedback
This improved version addresses the core issues of reliably placing and anchoring an object in AR and provides a much more complete and robust example. Remember to consult the Unreal Engine documentation for more details on AR development. Good luck!
👁️ Viewed: 8
Comments