128 lines
5.7 KiB
C++
128 lines
5.7 KiB
C++
// 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);
|
||
}
|
||
} |