I am attempting to program a PIP (Picture-in-Picture) “binocular” functionality that allows the user to left click the mouse, it will create this PIP and have a viewport that is 10% the size of the current game window (monitor resolution for fullscreen or game viewport resolution for non-fullscreen). The users can continue left clicking to change zoom levels (Field of View).
So, me being extremely new to Unreal, I have looked around online and it seems that this functionality is pretty straight forward. From what I can gather, I should just create a ASceneCapture2D inherited class. Then, in this class, I should just modify a material that takes a texture from the content browser, assign the texture target of the ASceneCapture2D object as the texture used in the material, then when the actor is spawned in the level, it will start automatically start rendering the camera’s viewing angle in the world onto the texture.
Then, I should just be able to place the texture onto the screen and the user will be able to see this. There is additional functionality that I need to perform to have the camera actor and texture follow the mouse on the screen, to allow the user to zoom in on the screen.
So, I created some basic blueprint objects and I was able to get the texture to be rendered with the camera viewport and was able to see it dynamically update as I made modifications to rotation/translation/position of the ASceneCapture2D object.
With this, I started coding a c++ class to handle creation of a UMaterialInstanceDynamic so I can dynamically create a texture of the 10% size and modify the blueprint’s texture parameter to point at this newly created material based on the blueprint material.
But, when I did this in c++, after I load the UMaterial from the data, get a UMaterialInstanceConstant, then call ::Create using the loaded UMaterialInstanceConstant, generated a new UTextureRender2D from the UKismetRenderingLibrary::CreateRenderTarget2D(), and call SetTextureParameterValue() with the generated UTextureRender2D, I notice the parameter doesn’t exist to set, so it just generates a new parameter.
The blueprint that contains the material has a BinocularTextureSample parameter (Param2D) that has a default texture loaded into it. I was hoping, when the ABinocular object is spawned into the world and the C++ code generates all of these new objects and performs the functions, I should be able to click the actor in UE and see the Texture Target containing a texture that looks like the camera viewport. But, when I check this, the variable in the editor shows “None”.
Another detail, I am unsure if I can just drag this ABinocular blueprint object into the BP_SimFloatingPawn blueprint, attaching it to the root component so that when the BP_SimFloatingPawn is spawned into the game, it will automatically spawn the ABinocular object with the facing of the BP_SimFloatingPawn (since it is now a child of the root component).
I am at a loss on how to perform this. I have been working on this issue for almost two weeks and have tried all kinds of things, ideas, theories, ect to get to work. I am going to post some pictures and code below to show what I am doing:
Binoculars.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/SceneCapture2D.h"
#include "Tower3DGameInstance.h"
#include "Binoculars.generated.h"
class UObjectLibrary;
class UMaterialInstanceConstant;
/**
*
*/
UCLASS()
class TOWER3D_API ABinoculars : public ASceneCapture2D
{
GENERATED_BODY()
void LoadBinocularsLibrary();
FVector2D GetNDisplayViewportSize() const;
void GenerateNDisplayCamera();
FVector2D GetNormalViewportSize() const;
void GenerateNormalCamera();
void OnViewportResized(FViewport* Viewport, uint32 Unused);
void OnViewportToggleFullscreen(bool IsFullScreen);
static const FString BLUEPRINT_PATH_BINOCULARS;
static const FName BINOCULAR_TEXTURE_SAMPLE_NAME;
static UObjectLibrary* m_pBinocularsMaterialLibrary;
static UMaterialInstanceConstant* m_pBinocularMaterialInstance;
// delegates
FViewport::FOnViewportResized EventViewportResized;
FOnToggleFullscreen EventViewportToggleFullscreen;
public:
ABinoculars();
UPROPERTY(BlueprintReadOnly)
bool bUsingBinoculars;
UPROPERTY(BlueprintReadOnly)
int ZoomLevel;
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UTextureRenderTarget2D> RenderTarget;
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UMaterialInstanceDynamic> BinocularMaterial;
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UTower3DGameInstance> GameInstance;
virtual void BeginPlay() override;
};
Binoculars.cpp
#include "Binoculars.h"
#include "Engine.h"
#include "Kismet/KismetRenderingLibrary.h"
#include "Kismet/KismetMaterialLibrary.h"
const FString ABinoculars::BLUEPRINT_PATH_BINOCULARS = TEXT("/Game/Tower3D/Blueprints/Binoculars");
const FName ABinoculars::BINOCULAR_TEXTURE_SAMPLE_NAME = TEXT("BinocularTextureSample");
UObjectLibrary* ABinoculars::m_pBinocularsMaterialLibrary = nullptr;
UMaterialInstanceConstant* ABinoculars::m_pBinocularMaterialInstance = nullptr;
ABinoculars::ABinoculars()
: bUsingBinoculars(false)
, ZoomLevel(1)
{
}
void ABinoculars::LoadBinocularsLibrary()
{
if(!m_pBinocularsMaterialLibrary)
{
// the base binocular material has not been loaded, attempt to load it
bool bIsEditor = false;
UWorld* pWorld = GetWorld();
if(pWorld)
{
bIsEditor = pWorld->IsPlayInEditor();
}
m_pBinocularsMaterialLibrary = UObjectLibrary::CreateLibrary(UMaterialInstance::StaticClass(), true, bIsEditor);
m_pBinocularsMaterialLibrary->AddToRoot();
m_pBinocularsMaterialLibrary->LoadAssetDataFromPath(BLUEPRINT_PATH_BINOCULARS);
if(!m_pBinocularsMaterialLibrary->IsLibraryFullyLoaded())
{
m_pBinocularsMaterialLibrary->LoadAssetsFromAssetData();
}
// assets loaded from a PIE (play-in-editor) or uproject will contain only the blueprints
// and their names will NOT contain "_C" at the end
// assets loaded from a cooked/packaged build will contain blueprints and classes generated from blueprints
// and the generated classes will contain "_C" at the end
TArray<FAssetData> assetDataBaseBinocularMaterial;
m_pBinocularsMaterialLibrary->GetAssetDataList(assetDataBaseBinocularMaterial);
UObject* pLoadedAsset = assetDataBaseBinocularMaterial[0].GetAsset();
UBlueprint* pBlueprint = Cast<UBlueprint>(pLoadedAsset);
UClass* pClass = nullptr;
if(pBlueprint)
{
pClass = pBlueprint->GeneratedClass;
}
else
{
pClass = pLoadedAsset->GetClass();
}
// create the binocular material to be used in the render target texture
if(pClass)
{
m_pBinocularMaterialInstance = Cast<UMaterialInstanceConstant>(pClass->GetDefaultObject());
}
}
if(!BinocularMaterial)
{
// create the binocular material to be used in the render target texture
BinocularMaterial = UMaterialInstanceDynamic::Create(m_pBinocularMaterialInstance, this);
}
}
FVector2D ABinoculars::GetNDisplayViewportSize() const
{
UGameUserSettings* pGameUserSettings = UGameUserSettings::GetGameUserSettings();
return FVector2D(pGameUserSettings->GetScreenResolution().X, pGameUserSettings->GetScreenResolution().Y);
}
void ABinoculars::GenerateNDisplayCamera()
{
FVector2D viewPort(GetNDisplayViewportSize());
int nWidth = viewPort.X * 0.1;
int nHeight = viewPort.Y * 0.1;
RenderTarget = UKismetRenderingLibrary::CreateRenderTarget2D(GetWorld(), nWidth, nHeight);
USceneCaptureComponent2D* pSceneComp = GetCaptureComponent2D();
pSceneComp->TextureTarget = RenderTarget;
FHashedMaterialParameterInfo paramInfo(BINOCULAR_TEXTURE_SAMPLE_NAME);
UTexture* pTexture = nullptr;
BinocularMaterial->GetTextureParameterValue(paramInfo, pTexture);
BinocularMaterial->SetTextureParameterValue(BINOCULAR_TEXTURE_SAMPLE_NAME, pSceneComp->TextureTarget);
}
FVector2D ABinoculars::GetNormalViewportSize() const
{
FVector2D vecViewport;
GetWorld()->GetGameViewport()->GetViewportSize(vecViewport);
return vecViewport;
}
void ABinoculars::GenerateNormalCamera()
{
FVector2D viewPort(GetNormalViewportSize());
int nWidth = viewPort.X * 0.1;
int nHeight = viewPort.Y * 0.1;
RenderTarget = UKismetRenderingLibrary::CreateRenderTarget2D(GetWorld(), nWidth, nHeight);
USceneCaptureComponent2D* pSceneComp = GetCaptureComponent2D();
pSceneComp->TextureTarget = RenderTarget;
FHashedMaterialParameterInfo paramInfo(BINOCULAR_TEXTURE_SAMPLE_NAME);
UTexture* pTexture = nullptr;
BinocularMaterial->GetTextureParameterValue(paramInfo, pTexture);
BinocularMaterial->SetTextureParameterValue(BINOCULAR_TEXTURE_SAMPLE_NAME, pSceneComp->TextureTarget);
}
void ABinoculars::OnViewportResized(FViewport* Viewport, uint32 Unused)
{
if(GameInstance->bIsNDisplay)
{
GenerateNDisplayCamera();
}
else
{
GenerateNormalCamera();
}
}
void ABinoculars::OnViewportToggleFullscreen(bool IsFullScreen)
{
if(GameInstance->bIsNDisplay)
{
GenerateNDisplayCamera();
}
else
{
GenerateNormalCamera();
}
}
void ABinoculars::BeginPlay()
{
Super::BeginPlay();
GameInstance = Cast<UTower3DGameInstance>(GetGameInstance());
GEngine->GameViewport->OnToggleFullscreen().AddUObject(this, &ABinoculars::OnViewportToggleFullscreen);
GEngine->GameViewport->Viewport->ViewportResizedEvent.AddUObject(this, &ABinoculars::OnViewportResized);
}
Any additional help would be greatly appreciated!