Major - Implémente le contrôleur de mille-pattes - V2.0.0

This commit introduces a centipede controller subsystem, managing centipede spawning, movement, and segment behavior.

It adds centipede body materials, movement logic, collision detection, and head assignment.
The controller handles segment following, direction changes at boundaries, and dynamic head updates upon segment destruction.
This commit is contained in:
2025-10-17 16:32:27 +02:00
parent df25b61b8f
commit 987856ca09
10 changed files with 225 additions and 136 deletions

View File

@@ -94,3 +94,6 @@ ConnectionType=USBOnly
bUseManualIPAddress=False bUseManualIPAddress=False
ManualIPAddress= ManualIPAddress=
[CoreRedirects]
+ClassRedirects=(OldName="/Script/M4_CPP.M4_CentipedeController",NewName="/Script/M4_CPP.M4_CentipedeController")

Binary file not shown.

Binary file not shown.

View File

@@ -10,21 +10,43 @@ AM4_CentipedeBody::AM4_CentipedeBody()
GetStaticMeshComponent()->SetStaticMesh(MeshRef.Object); GetStaticMeshComponent()->SetStaticMesh(MeshRef.Object);
} }
// Charger les matériaux par défaut (ajustez les chemins selon vos assets)
static ConstructorHelpers::FObjectFinder<UMaterialInterface> HeadMatRef(TEXT("/Game/CTP/05_Material/MI_Head.MI_Head"));
if (HeadMatRef.Succeeded())
{
HeadMaterial = HeadMatRef.Object;
}
static ConstructorHelpers::FObjectFinder<UMaterialInterface> BodyMatRef(TEXT("/Game/CTP/05_Material/MI_Body.MI_Body"));
if (BodyMatRef.Succeeded())
{
BodyMaterial = BodyMatRef.Object;
}
GetStaticMeshComponent()->SetRelativeScale3D(FVector(1.f, 0.4f, 0.4f)); GetStaticMeshComponent()->SetRelativeScale3D(FVector(1.f, 0.4f, 0.4f));
GetStaticMeshComponent()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
GetStaticMeshComponent()->SetGenerateOverlapEvents(true);
GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable); GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable);
GetStaticMeshComponent()->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
GetStaticMeshComponent()->SetGenerateOverlapEvents(true);
GetStaticMeshComponent()->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
GetStaticMeshComponent()->SetCollisionProfileName(TEXT("OverlapAll"));
} }
void AM4_CentipedeBody::SetAsHead(bool bHead) void AM4_CentipedeBody::SetAsHead(bool bHead)
{ {
bIsHead = bHead; bIsHead = bHead;
if (bIsHead)
{ if (bIsHead)
GetStaticMeshComponent()->SetDefaultCustomPrimitiveDataVector4(0, FVector4(1.0, 0.0, 0.0, 1.0)); {
} if (HeadMaterial)
else {
{ GetStaticMeshComponent()->SetMaterial(0, HeadMaterial);
GetStaticMeshComponent()->SetDefaultCustomPrimitiveDataVector4(0, FVector4(0.0, 1.0, 0.0, 1.0)); }
} }
} else
{
if (BodyMaterial)
{
GetStaticMeshComponent()->SetMaterial(0, BodyMaterial);
}
}
}

View File

@@ -1,40 +1,110 @@
#include "M4_CentipedeController.h" #include "M4_CentipedeController.h"
#include "M4_LOG.h"
#include "M4_Gamemode.h" #include "M4_Gamemode.h"
AM4_CentipedeController::AM4_CentipedeController() UM4_CentipedeController::UM4_CentipedeController()
{ {
PrimaryActorTick.bCanEverTick = true;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
RootComponent = Root;
} }
void AM4_CentipedeController::BeginPlay() void UM4_CentipedeController::Initialize(FSubsystemCollectionBase& Collection)
{ {
Super::BeginPlay(); Super::Initialize(Collection);
SpawnCentipede();
PreviousPositions.SetNum(BodyCount); UE_LOG(M4_CPP, Warning, TEXT("Centipede Controller Initialized"));
for (int i = 0; i < BodyCount; ++i)
}
void UM4_CentipedeController::Deinitialize()
{
UE_LOG(M4_CPP, Warning, TEXT("Centipede Controller Deinitialized"));
BodySegments.Empty();
PreviousPositions.Empty();
SegmentDirections.Empty();
Super::Deinitialize();
}
void UM4_CentipedeController::StartCentipede(FVector SpawnLocation)
{
if (BodySegments.Num() > 0)
{ {
PreviousPositions[i] = BodySegments[i]->GetActorLocation(); UE_LOG(M4_CPP, Warning, TEXT("Centipede already spawned"));
return;
} }
for (int32 i = 0; i < BodyCount; ++i)
{
FVector SegmentLocation = SpawnLocation - FVector(0.f, 0.f, i * CellSize);
AM4_CentipedeBody* Body = GetWorld()->SpawnActor<AM4_CentipedeBody>(
AM4_CentipedeBody::StaticClass(),
SegmentLocation,
FRotator::ZeroRotator
);
if (Body)
{
BodySegments.Add(Body);
if (i > 0)
{
Body->PreviousBody = BodySegments[i - 1];
BodySegments[i - 1]->NextBody = Body;
}
Body->SetAsHead(i == 0);
}
}
// Initialiser l'historique
SegmentHistory.SetNum(BodyCount);
for (int32 i = 0; i < BodyCount; ++i)
{
if (BodySegments.IsValidIndex(i) && BodySegments[i])
{
FVector InitialPos = BodySegments[i]->GetActorLocation();
// Remplir avec position initiale
int32 InitialHistorySize = SegmentSpacing * (i + 1) + MaxHistorySize;
for (int32 j = 0; j < InitialHistorySize; ++j)
{
SegmentHistory[i].Add(InitialPos);
}
}
}
for (AM4_CentipedeBody* Segment : BodySegments) for (AM4_CentipedeBody* Segment : BodySegments)
{ {
SegmentDirections.Add(Segment, FVector2D(0.f, 1.f)); SegmentDirections.Add(Segment, FVector2D(0.f, 1.f));
} }
UE_LOG(M4_CPP, Warning, TEXT("Centipede spawned with %d segments"), BodySegments.Num());
} }
void AM4_CentipedeController::SpawnCentipede() TStatId UM4_CentipedeController::GetStatId() const
{ {
FVector SpawnLocation = GetActorLocation(); RETURN_QUICK_DECLARE_CYCLE_STAT(UM4_CentipedeController, STATGROUP_Tickables);
}
void UM4_CentipedeController::SpawnCentipede()
{
AM4_Gamemode* GM = Cast<AM4_Gamemode>(GetWorld()->GetAuthGameMode());
if (!GM) return;
UE_LOG(LogTemp, Warning, TEXT("Controller spawn location: X=%.2f, Y=%.2f, Z=%.2f"), FVector SpawnLocation = FVector(
GM->MushroomSpawnBounds.Max.X,
0.f,
0.f
);
UE_LOG(M4_CPP, Warning, TEXT("Controller spawn location: X=%.2f, Y=%.2f, Z=%.2f"),
SpawnLocation.X, SpawnLocation.Y, SpawnLocation.Z); SpawnLocation.X, SpawnLocation.Y, SpawnLocation.Z);
if (SpawnLocation.IsZero()) if (SpawnLocation.IsZero())
{ {
UE_LOG(LogTemp, Error, TEXT("Controller position is zero! Check spawn parameters.")); UE_LOG(M4_CPP, Error, TEXT("Controller position is zero! Check spawn parameters."));
return; return;
} }
@@ -63,80 +133,73 @@ void AM4_CentipedeController::SpawnCentipede()
} }
} }
void AM4_CentipedeController::Tick(float DeltaTime) void UM4_CentipedeController::Tick(float DeltaTime)
{ {
Super::Tick(DeltaTime); Super::Tick(DeltaTime);
if (BodySegments.Num() == 0) return; if (BodySegments.Num() == 0) return;
TimeSinceLastMove += DeltaTime; // Déplacer la tête
AM4_CentipedeBody* Head = BodySegments[0];
if (TimeSinceLastMove >= MoveInterval) if (Head && Head->bIsHead)
{ {
TimeSinceLastMove = 0.f; FVector CurrentPos = Head->GetActorLocation();
FVector2D SegmentDirection = GetSegmentDirection(Head);
TArray<FVector> TempPositions;
TempPositions.SetNum(BodySegments.Num()); bool bShouldDescend = CheckCollision(Head, SegmentDirection);
for (int32 i = 0; i < BodySegments.Num(); ++i) if (bShouldDescend)
{ {
TempPositions[i] = BodySegments[i]->GetActorLocation(); FVector NewPos = CurrentPos - FVector(0.f, 0.f, CellSize);
Head->SetActorLocation(NewPos);
SegmentDirection.Y *= -1.f;
SetSegmentDirection(Head, SegmentDirection);
} }
else
for (int32 i = 0; i < BodySegments.Num(); ++i)
{ {
AM4_CentipedeBody* Segment = BodySegments[i]; FVector NewPos = CurrentPos + FVector(0.f, SegmentDirection.Y * CentipedeSpeed * DeltaTime, 0.f);
Head->SetActorLocation(NewPos);
if (!Segment) continue;
if (Segment->bIsHead)
{
FVector CurrentPos = Segment->GetActorLocation();
FVector2D SegmentDirection = GetSegmentDirection(Segment);
bool bShouldDescend = CheckCollision(Segment, SegmentDirection);
if (bShouldDescend)
{
FVector NewPos = CurrentPos - FVector(0.f, 0.f, CellSize);
Segment->SetActorLocation(NewPos);
SegmentDirection.Y *= -1.f;
SetSegmentDirection(Segment, SegmentDirection);
UE_LOG(LogTemp, Warning, TEXT("Head at index %d descending"), i);
}
else
{
FVector NewPos = CurrentPos + FVector(0.f, SegmentDirection.Y * CellSize, 0.f);
Segment->SetActorLocation(NewPos);
}
}
else
{
if (i > 0)
{
Segment->SetActorLocation(PreviousPositions[i - 1]);
if (BodySegments[i - 1])
{
FVector2D PrevDirection = GetSegmentDirection(BodySegments[i - 1]);
SetSegmentDirection(Segment, PrevDirection);
}
}
}
}
for (int32 i = 0; i < BodySegments.Num(); ++i)
{
PreviousPositions[i] = BodySegments[i]->GetActorLocation();
} }
UpdateHeadStatus(); // Enregistrer position de la tête
SegmentHistory[0].Add(Head->GetActorLocation());
if (SegmentHistory[0].Num() > MaxHistorySize)
{
SegmentHistory[0].RemoveAt(0);
}
} }
// Faire suivre les autres segments
for (int32 i = 1; i < BodySegments.Num(); ++i)
{
AM4_CentipedeBody* Segment = BodySegments[i];
if (!Segment) continue;
// Récupérer position dans l'historique du segment précédent
int32 PrevIndex = i - 1;
int32 HistoryLookback = SegmentSpacing;
if (SegmentHistory[PrevIndex].Num() > HistoryLookback)
{
int32 HistoryIndex = SegmentHistory[PrevIndex].Num() - HistoryLookback - 1;
FVector TargetPos = SegmentHistory[PrevIndex][HistoryIndex];
Segment->SetActorLocation(TargetPos);
// Enregistrer position actuelle
SegmentHistory[i].Add(Segment->GetActorLocation());
if (SegmentHistory[i].Num() > MaxHistorySize)
{
SegmentHistory[i].RemoveAt(0);
}
}
}
UpdateHeadStatus();
} }
bool AM4_CentipedeController::CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction) bool UM4_CentipedeController::CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction)
{ {
if (!Segment) return false; if (!Segment) return false;
@@ -154,13 +217,10 @@ bool AM4_CentipedeController::CheckCollision(AM4_CentipedeBody* Segment, FVector
return true; return true;
} }
// TODO: Ajouter vérification collision avec mushroom
// if (IsMushroomAt(NextPos)) return true;
return false; return false;
} }
FVector2D AM4_CentipedeController::GetSegmentDirection(AM4_CentipedeBody* Segment) FVector2D UM4_CentipedeController::GetSegmentDirection(AM4_CentipedeBody* Segment)
{ {
if (SegmentDirections.Contains(Segment)) if (SegmentDirections.Contains(Segment))
{ {
@@ -169,7 +229,7 @@ FVector2D AM4_CentipedeController::GetSegmentDirection(AM4_CentipedeBody* Segmen
return FVector2D(0.f, 1.f); return FVector2D(0.f, 1.f);
} }
void AM4_CentipedeController::SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction) void UM4_CentipedeController::SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction)
{ {
if (Segment) if (Segment)
{ {
@@ -177,7 +237,7 @@ void AM4_CentipedeController::SetSegmentDirection(AM4_CentipedeBody* Segment, FV
} }
} }
void AM4_CentipedeController::UpdateHeadStatus() void UM4_CentipedeController::UpdateHeadStatus()
{ {
for (int32 i = 0; i < BodySegments.Num(); ++i) for (int32 i = 0; i < BodySegments.Num(); ++i)
{ {
@@ -193,37 +253,29 @@ void AM4_CentipedeController::UpdateHeadStatus()
} }
} }
void AM4_CentipedeController::OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment) void UM4_CentipedeController::OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment)
{ {
if (!DestroyedSegment) return; if (!DestroyedSegment) return;
int32 SegmentIndex = BodySegments.Find(DestroyedSegment); int32 SegmentIndex = BodySegments.Find(DestroyedSegment);
if (SegmentIndex == INDEX_NONE) return; if (SegmentIndex == INDEX_NONE) return;
UE_LOG(LogTemp, Warning, TEXT("Segment %d destroyed"), SegmentIndex);
if (SegmentIndex + 1 < BodySegments.Num()) if (SegmentIndex + 1 < BodySegments.Num())
{ {
AM4_CentipedeBody* NewHead = BodySegments[SegmentIndex + 1]; AM4_CentipedeBody* NewHead = BodySegments[SegmentIndex + 1];
if (NewHead) if (NewHead)
{ {
NewHead->SetAsHead(true); NewHead->SetAsHead(true);
NewHead->PreviousBody = nullptr; NewHead->PreviousBody = nullptr;
UE_LOG(LogTemp, Warning, TEXT("New head created at index %d"), SegmentIndex + 1);
} }
} }
if (SegmentIndex > 0 && BodySegments[SegmentIndex - 1]) if (SegmentIndex > 0 && BodySegments[SegmentIndex - 1])
{ {
BodySegments[SegmentIndex - 1]->NextBody = nullptr; BodySegments[SegmentIndex - 1]->NextBody = nullptr;
} }
BodySegments.RemoveAt(SegmentIndex);
PreviousPositions.RemoveAt(SegmentIndex);
SegmentDirections.Remove(DestroyedSegment);
// TODO: Spawner un mushroom à la position du segment détruit BodySegments.RemoveAt(SegmentIndex);
// SpawnMushroomAt(DestroyedSegment->GetActorLocation()); SegmentHistory.RemoveAt(SegmentIndex);
SegmentDirections.Remove(DestroyedSegment);
} }

View File

@@ -98,16 +98,13 @@ void AM4_Gamemode::BeginPlay()
// PRINT SCREEN Max.X value // PRINT SCREEN Max.X value
PRINT_SCREEN(*FString::Printf(TEXT("Mushroom Spawn Bounds Max.X: %.2f"), MushroomSpawnBounds.Max.X), FColor::Green); PRINT_SCREEN(*FString::Printf(TEXT("Mushroom Spawn Bounds Max.X: %.2f"), MushroomSpawnBounds.Max.X), FColor::Green);
CentipedeController = GetWorld()->SpawnActor<AM4_CentipedeController>( UM4_CentipedeController* Controller = GetWorld()->GetSubsystem<UM4_CentipedeController>();
AM4_CentipedeController::StaticClass(), if (Controller)
CentipedeSpawnLocation,
FRotator::ZeroRotator
);
if (CentipedeController)
{ {
CentipedeController->BodyCount = CentipedeBodyCount; Controller->BodyCount = CentipedeBodyCount;
CentipedeController->CellSize = CellSize; Controller->CellSize = CellSize;
Controller->StartCentipede(CentipedeSpawnLocation);
} }
} }

View File

@@ -31,9 +31,8 @@ AM4_Mushroom::AM4_Mushroom()
const FVector2D MushroomScale = FVector2D(0.3f, 0.25f); const FVector2D MushroomScale = FVector2D(0.3f, 0.25f);
GetStaticMeshComponent()->SetRelativeScale3D(FVector(1.f, 0.45f, 0.20f)); GetStaticMeshComponent()->SetRelativeScale3D(FVector(1.f, 0.45f, 0.20f));
// Custom preset for more advanced collision configuration // Set collision profile to overlap all
GetStaticMeshComponent()->SetCollisionProfileName(UCollisionProfile::CustomCollisionProfileName); GetStaticMeshComponent()->SetCollisionProfileName(TEXT("OverlapAll"));
GetStaticMeshComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
} }

View File

@@ -22,4 +22,10 @@ public:
bool bIsHead = false; bool bIsHead = false;
void SetAsHead(bool bHead); void SetAsHead(bool bHead);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Materials")
UMaterialInterface* HeadMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Materials")
UMaterialInterface* BodyMaterial;
}; };

View File

@@ -1,50 +1,60 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "M4_CentipedeBody.h" #include "M4_CentipedeBody.h"
#include "Subsystems/WorldSubsystem.h"
#include "M4_CentipedeController.generated.h" #include "M4_CentipedeController.generated.h"
UCLASS() UCLASS()
class M4_CPP_API AM4_CentipedeController : public AActor class M4_CPP_API UM4_CentipedeController : public UTickableWorldSubsystem
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
AM4_CentipedeController(); UM4_CentipedeController();
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
void StartCentipede(FVector SpawnLocation);
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override;
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Centipede")
int32 BodyCount = 10; int32 BodyCount = 10;
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Centipede")
float CellSize = 50.f; float CellSize = 50.f;
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Centipede")
float MoveInterval = 0.1f; float MoveInterval = 0.1f;
UFUNCTION(BlueprintCallable, Category = "Centipede")
void OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment); void OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment);
private: private:
UPROPERTY() UPROPERTY()
TArray<AM4_CentipedeBody*> BodySegments; TArray<AM4_CentipedeBody*> BodySegments;
TArray<TArray<FVector>> SegmentHistory; // Historique pour chaque segment
UPROPERTY() int32 MaxHistorySize = 200;
USceneComponent* Root;
UPROPERTY(EditAnywhere, Category = "Centipede")
int32 SegmentSpacing = 15; // Distance en nombre de frames entre segments
float TimeSinceLastMove = 0.f; float TimeSinceLastMove = 0.f;
float CentipedeSpeed = 400.f;
bool bFirstTick = true;
TArray<FVector> PreviousPositions; TArray<FVector> PreviousPositions;
TMap<AM4_CentipedeBody*, FVector2D> SegmentDirections; TMap<AM4_CentipedeBody*, FVector2D> SegmentDirections;
void SpawnCentipede(); void SpawnCentipede();
void UpdateHeadStatus(); void UpdateHeadStatus();
bool CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction); bool CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction);
FVector2D GetSegmentDirection(AM4_CentipedeBody* Segment); FVector2D GetSegmentDirection(AM4_CentipedeBody* Segment);
void SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction); void SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction);
}; };

View File

@@ -67,7 +67,7 @@ private:
int32 CentipedeBodyCount = 10; int32 CentipedeBodyCount = 10;
UPROPERTY() UPROPERTY()
TObjectPtr<AM4_CentipedeController> CentipedeController; TObjectPtr<UM4_CentipedeController> CentipedeController;
int Score = 0; int Score = 0;
int Lives = 3; int Lives = 3;