tengri/Source/TengriPlatformer/World/Interactive/TengriPendulumActor.cpp

128 lines
5.7 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Request Games © All rights reserved
#include "TengriPendulumActor.h"
#include "Components/StaticMeshComponent.h"
#include "PhysicsEngine/PhysicsConstraintComponent.h"
#include "DrawDebugHelpers.h"
#include "TengriPlatformer/World/Interactive/TengriPickupActor.h"
ATengriPendulumActor::ATengriPendulumActor()
{
PrimaryActorTick.bCanEverTick = true;
// 1. SceneRoot - теперь главный.
// Он Movable, чтобы мы могли двигать актор в рантайме, если захотим.
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
RootComponent = SceneRoot;
SceneRoot->SetMobility(EComponentMobility::Movable);
// 2. AnchorMesh - крепим к руту.
// ВАЖНО: Ставим Movable. Это решает проблему с исчезновением.
// Физику выключаем, коллизию оставляем (чтобы камень мог удариться об крюк).
AnchorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("AnchorMesh"));
AnchorMesh->SetupAttachment(SceneRoot);
AnchorMesh->SetMobility(EComponentMobility::Movable);
AnchorMesh->SetSimulatePhysics(false);
AnchorMesh->SetCollisionProfileName(TEXT("BlockAll"));
// 3. SwingMesh - ТОЖЕ крепим к руту (не к анкору!).
// Это позволяет скейлить анкор, не плюща при этом свечу.
SwingMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SwingMesh"));
SwingMesh->SetupAttachment(SceneRoot);
SwingMesh->SetMobility(EComponentMobility::Movable);
SwingMesh->SetSimulatePhysics(true);
SwingMesh->SetCollisionProfileName(TEXT("PhysicsActor"));
// 4. Constraint - крепим к руту.
// Позиционируем его в (0,0,0) - там же, где и Анкор (логически).
ConstraintComp = CreateDefaultSubobject<UPhysicsConstraintComponent>(TEXT("ConstraintComp"));
ConstraintComp->SetupAttachment(SceneRoot);
// Настройки лимитов (те же самые)
ConstraintComp->SetDisableCollision(true);
ConstraintComp->SetLinearXLimit(LCM_Locked, 0.f);
ConstraintComp->SetLinearYLimit(LCM_Locked, 0.f);
ConstraintComp->SetLinearZLimit(LCM_Locked, 0.f); // Или Limited, если хочешь цепь
ConstraintComp->SetAngularSwing1Limit(ACM_Limited, 45.f);
ConstraintComp->SetAngularSwing2Limit(ACM_Limited, 45.f);
ConstraintComp->SetAngularTwistLimit(ACM_Locked, 0.f);
}
void ATengriPendulumActor::BeginPlay()
{
Super::BeginPlay();
// Явно связываем компоненты.
// Даже если они соседи по иерархии, констрейнт должен знать, кого держать.
if (AnchorMesh && SwingMesh && ConstraintComp)
{
ConstraintComp->SetConstrainedComponents(AnchorMesh, NAME_None, SwingMesh, NAME_None);
}
// Подписка на хит (без изменений)
if (SwingMesh)
{
SwingMesh->OnComponentHit.AddDynamic(this, &ATengriPendulumActor::OnSwingMeshHit);
}
}
float ATengriPendulumActor::GetCurrentSwingAngle() const
{
if (!SwingMesh) return 0.0f;
// Вектор "вниз" для маятника в покое
const FVector RestVector = -FVector::UpVector;
// Текущий вектор "вниз" объекта (используем UpVector * -1 или просто ForwardVector, зависит от меша)
// Предполагаем, что меш импортирован нормально и его локальный Z смотрит вверх
const FVector CurrentVector = -SwingMesh->GetUpVector();
// Dot Product дает косинус угла
const float Dot = FVector::DotProduct(RestVector, CurrentVector);
// Acos возвращает радианы -> переводим в градусы
return FMath::RadiansToDegrees(FMath::Acos(Dot));
}
void ATengriPendulumActor::OnSwingMeshHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// Проверяем, что в нас попал именно поднимаемый предмет (камень/кость)
// Если хочешь, чтобы работало и от персонажа, убери этот каст
ATengriPickupActor* Pickup = Cast<ATengriPickupActor>(OtherActor);
if (Pickup)
{
// Вычисляем направление удара (от предмета к центру маятника или по нормали)
// NormalImpulse, который дает UE, иногда бывает странным для physx interaction,
// надежнее взять скорость входящего объекта.
FVector ImpulseDir = Pickup->GetVelocity().GetSafeNormal();
// Если предмет уже почти остановился, берем вектор от предмета к нам
if (ImpulseDir.IsNearlyZero())
{
ImpulseDir = (GetActorLocation() - OtherActor->GetActorLocation()).GetSafeNormal();
}
// "ЧИТЕРСКИЙ" ИМПУЛЬС
// Мы умножаем массу на множитель, чтобы игрок почувствовал вес удара
const float Mass = SwingMesh->GetMass();
const FVector FinalImpulse = ImpulseDir * HitImpulseMultiplier * Mass;
SwingMesh->AddImpulse(FinalImpulse);
if (bDebugPhysics)
{
DrawDebugLine(GetWorld(), Hit.ImpactPoint, Hit.ImpactPoint + FinalImpulse * 0.1f, FColor::Red, false, 2.0f, 0, 2.0f);
UE_LOG(LogTemp, Warning, TEXT("Pendulum Hit! Angle: %.2f, Added Impulse Force: %.2f"), GetCurrentSwingAngle(), FinalImpulse.Size());
}
}
}
void ATengriPendulumActor::AddSwingImpulse(FVector Direction, float Force)
{
if (SwingMesh)
{
SwingMesh->AddImpulse(Direction.GetSafeNormal() * Force, NAME_None, true);
}
}