Hi all,
I’m making a dynamic semi automated “smart” camera for my game Hana
I will try to follow the gidelines said in this GDC conference: http://gdcvault.com/play/1020460/50-Camera (while deleting some and adding new ones)
The purpose of this thread is to both share my code with you (and get better dynamic camera in games) and seek help to fix bugs.
Because it seems unreal doesn’t let you enter and exit a sequence cinematic without a cut and because features like “set view target with blend” is highly player centric I will make a Actor.
(I’m okay to rewrite it as a component if someone has very good argument and fix…)
Well, To begin with this camera I made a very first draft in blueprint but because of performance, reliability between unreal version, readability (the math code is gets very ugly with bp IMO…) and because I’m more confortable with C++ I rewrote it in C++… (if you want the bp version I can post it, just ask).
So here is what I did:
UCLASS()
class HANA_API ASmartCamera : public AActor {
GENERATED_BODY()
private:
ACharacter* _player;
FRotator _lastControlRotation;
void _autoLookAt(float, bool, bool);
float _checkObstructions(FVector, FVector);
FVector _autoRotateAroundPlayer(float, float, float, float);
FVector _autoDistanceToPlayer(float, float);
float _distance(FVector, FVector);
FVector _rotateAroundPivotAndAxis(FVector, FVector, FVector, float);
void _controlRotationForwarding();
UCameraComponent* _camera;
USphereComponent* _collider; // to avoid near clip plane problems
public:
ASmartCamera();
virtual void BeginPlay() override;
virtual void Tick( float DeltaSeconds ) override;
};
no UFunction or UProperty yet but it will come…
ASmartCamera::ASmartCamera() : _lastControlRotation(0.0f, 0.0f, 0.0f),
_camera(CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"))),
_collider(CreateDefaultSubobject<USphereComponent>(TEXT("Collider"))) {
PrimaryActorTick.bCanEverTick = true;
_collider->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
_collider->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
_collider->SetSimulatePhysics(true);
_collider->SetEnableGravity(false);
_collider->SetSphereRadius(48);
SetRootComponent(_collider);
_camera->SetupAttachment(_collider);
_collider->bHiddenInGame = false; // to localise the camera in gameplay (after eject)
}
For some reason I need to rotate a forward vector “FVector(1.0, 0.0, 0.0)” but in my blueprint code it’s “FVector(-1.0, 0.0, 0.0)”, if someone can explain this weirdness.
void ASmartCamera::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
if (_lastControlRotation != _player->GetControlRotation()) { // the player choices should always be the priority
_lastControlRotation = _player->GetControlRotation();
SetActorLocation(_player->GetControlRotation().RotateVector(FVector(1.0, 0.0, 0.0)) * GetDistanceTo(_player) + _player->GetActorLocation(), true, nullptr, ETeleportType::TeleportPhysics);
_autoLookAt(DeltaTime, false, false); // need to be done after the "setLocation" or some suttering will appear at low/medium framerate (like 30)
return;
}
FVector rd = GetActorForwardVector() * (_collider->GetScaledSphereRadius() + 1); // displacement to avoid the raycast conflict with _collider
_checkObstructions(GetActorLocation() + rd, _player->GetActorLocation()); // do I see the character ?
// "whiskers generation
const float whiskersDiff = 10.0f;
float RObstruction = _checkObstructions(GetActorLocation() + rd, _rotateAroundPivotAndAxis(_player->GetActorLocation(), GetActorLocation(), GetActorUpVector(), whiskersDiff));
float LObstruction = _checkObstructions(GetActorLocation() + rd, _rotateAroundPivotAndAxis(_player->GetActorLocation(), GetActorLocation(), GetActorUpVector(), -whiskersDiff));
FVector loc = _autoRotateAroundPlayer(LObstruction, RObstruction, DeltaTime, 20); // rotating the camera automatically when detecting a potential obstacle
const float maxDistance = 1000;
const float minDistance = 500;
loc += _autoDistanceToPlayer(maxDistance, minDistance); // automatically make the camera follow the player if it's too far, or go backward if too near (automatic wall avoidance isn't implemented yet)
SetActorLocation(loc, true, nullptr, ETeleportType::TeleportPhysics);
_autoLookAt(DeltaTime, false, false);
_controlRotationForwarding(); // update the control of the player because the camera has been moved without its input
}
void ASmartCamera::_autoLookAt(float deltaTime, bool Centred, bool thirdOnLeft) {
float interpSpeed = FMath::Lerp(10, 0, UKismetMathLibrary::Dot_VectorVector((_player->GetActorLocation() - GetActorLocation()).GetUnsafeNormal(), GetActorForwardVector())); //avoid cut when changing focus point
FRotator r = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), _player->GetActorLocation());
if (thirdOnLeft && !Centred) { // very naive implementation of the [rule of third](https://en.wikipedia.org/wiki/Rule_of_thirds)
r.Yaw += 30.0f;
} else if (!thirdOnLeft && !Centred) {
r.Yaw += -30.0f;
}
SetActorRotation(FMath::RInterpTo(GetActorRotation(), r, deltaTime, interpSpeed));
}
This part work well except 2 things:
- It need some polish on the movement speed.
-
All command on the player are reversed (forward goes back, left goes right etc…) I have no idea why if someone can help.
Except the “FVector(-1.0, 0.0, 0.0)” I don’t have any difference with the BP and I don’t have this bug in BP…
Here is some of my utility function if it can help debug:
void ASmartCamera::_controlRotationForwarding() {
_lastControlRotation = UKismetMathLibrary::MakeRotFromX((GetActorLocation() - _player->GetActorLocation()).GetUnsafeNormal());
_player->GetController()->SetControlRotation(_lastControlRotation);
}
float ASmartCamera::_checkObstructions(FVector start, FVector end) {
FHitResult outHit;
if (!UKismetSystemLibrary::LineTraceSingle_NEW(GetWorld(), start, end, ETraceTypeQuery::TraceTypeQuery1, false, TArray<AActor*>(), EDrawDebugTrace::ForOneFrame, outHit, true)) { // I don't know if there is a better raycast function...
return 0.0f;
}
return _distance(outHit.TraceStart, outHit.TraceEnd);
}
Thank you if you came to help, you are welcome if find it useful.