I thinking on a 2D space shooter (r-type kind) and managed to make the ship movement, but how would I go about making it automatically always move right and restrict the movement to be only in the screen area?
I figure I can animate the camera through matinee and make a blocking volume on the sides of the screen to limit movement, but then it would also block enemies, which I do not want… so, ideas on how to make this?
you do it via controller easily.
I’m going to post some detail tonight about how I do the 2.5D mouselook, it would have the information you need.
(thanks to chinty that reminded me to do this.)
here is the thread:
Snapping back to screen
- Edit: Apologies in advance for the C++ in the blueprint forum. Still might be useful or possibly convertible to BP functions *
Hi, I’m also working on a sidescrolling shooter and I took on the screen limiting problem today too. I actually tried out using blueprints to check the camera FOV and aspect ratio to position box collision elements around the screen borders and have them track the scroll camera and it sort of worked OK, but felt sloppy and took maneuvering on the collision channels to have them not block other objects.
I spent a while today digging into the geometry of it and I figured I would share my solution to keep the player within screen bounds. If the player goes off screen at all, they are projected back on to the edge, co-planar with the view-plane. This was something I wanted, since I’m hoping to have kind of free-form scrolling.
I implemented the function in the player controller, but it could go anywhere that can access the player controller that is responsible for the viewport being rendered.
I re-used some code out of APlayerController, to initialize a SceneView which has a lot of useful functions for dealing with world/screen space transitions.
What it does is translate the world position into viewprojection coordinates for the current camera. In that coordinate system, the everything within screen bounds are always between -1 and 1 on X and Y regardless of depth. So, once I have the coordinates in that system, clamping X and Y between -1 and 1, and then transforming back to world coordinates gives you a new location exactly on the screen edge, for any FOV or resolution.
I’m pretty curious to see how Epic does it in their SideScroller example once that is available
PlayerController
bool MyPlayerController::SnapToViewFrustum(const FVector& worldPosition, FVector* outSnapped) {
// Gently borrowed from APlayerController::ProjectWorldLocationToScreen to initialize the SceneView.
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player);
if (LocalPlayer != NULL && LocalPlayer->ViewportClient != NULL && LocalPlayer->ViewportClient->Viewport != NULL)
{
// Create a view family for the game viewport
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
LocalPlayer->ViewportClient->Viewport,
GetWorld()->Scene,
LocalPlayer->ViewportClient->EngineShowFlags)
.SetRealtimeUpdate(true));
// Calculate a view where the player is to update the streaming from the players start location
FVector ViewLocation;
FRotator ViewRotation;
FSceneView* SceneView = LocalPlayer->CalcSceneView(&ViewFamily, /*out*/ ViewLocation, /*out*/ ViewRotation, LocalPlayer->ViewportClient->Viewport);
if (SceneView)
{
// Transform our world position into projection coordinates.
auto ProjPosAug = SceneView->ViewProjectionMatrix.TransformPosition(worldPosition);
FVector ProjPos(ProjPosAug);
// Divide by the augmented coord W value.
ProjPos /= ProjPosAug.W;
// Clamp position to -1,1 on x and y (corresponds to on-screen)
ProjPos.X = FMath::Clamp<float>(ProjPos.X, -1, 1);
ProjPos.Y = FMath::Clamp<float>(ProjPos.Y, -1, 1);
// Invert the transform back to world space.
auto AugWorldSnapped = SceneView->InvViewProjectionMatrix.TransformPosition(ProjPos);
FVector SnapPos(AugWorldSnapped);
SnapPos /= AugWorldSnapped.W;
*outSnapped = SnapPos;
return true;
}
}
return false;
}
Then updating each frame in the player tick to stay on screen.
Player Tick function:
void MyShipCharacter::Tick(float dt) {
Super::Tick(dt);
auto PController = (MyPlayerController*)GetController();
FVector SnapPos;
// This can fail if things aren't fully initialized, I believe.
if (PController->SnapToViewFrustum(GetActorLocation(), &SnapPos)) {
// Sweep is false so that it will push through obstacles and really keep the player on screen.
SetActorLocation(SnapPos, false);
}
}