// 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(TEXT("SceneRoot")); RootComponent = SceneRoot; SceneRoot->SetMobility(EComponentMobility::Movable); // 2. AnchorMesh - крепим к руту. // ВАЖНО: Ставим Movable. Это решает проблему с исчезновением. // Физику выключаем, коллизию оставляем (чтобы камень мог удариться об крюк). AnchorMesh = CreateDefaultSubobject(TEXT("AnchorMesh")); AnchorMesh->SetupAttachment(SceneRoot); AnchorMesh->SetMobility(EComponentMobility::Movable); AnchorMesh->SetSimulatePhysics(false); AnchorMesh->SetCollisionProfileName(TEXT("BlockAll")); // 3. SwingMesh - ТОЖЕ крепим к руту (не к анкору!). // Это позволяет скейлить анкор, не плюща при этом свечу. SwingMesh = CreateDefaultSubobject(TEXT("SwingMesh")); SwingMesh->SetupAttachment(SceneRoot); SwingMesh->SetMobility(EComponentMobility::Movable); SwingMesh->SetSimulatePhysics(true); SwingMesh->SetCollisionProfileName(TEXT("PhysicsActor")); // 4. Constraint - крепим к руту. // Позиционируем его в (0,0,0) - там же, где и Анкор (логически). ConstraintComp = CreateDefaultSubobject(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(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); } }