diff --git a/Source/M4_CPP/private/M4_CentipedeBody.cpp b/Source/M4_CPP/private/M4_CentipedeBody.cpp new file mode 100644 index 0000000..f43d0b1 --- /dev/null +++ b/Source/M4_CPP/private/M4_CentipedeBody.cpp @@ -0,0 +1,30 @@ +#include "M4_CentipedeBody.h" + +AM4_CentipedeBody::AM4_CentipedeBody() +{ + PrimaryActorTick.bCanEverTick = false; + + static ConstructorHelpers::FObjectFinder MeshRef(TEXT("/Game/CTP/04_Mesh/SM_Cube.SM_Cube")); + if (MeshRef.Succeeded()) + { + GetStaticMeshComponent()->SetStaticMesh(MeshRef.Object); + } + + GetStaticMeshComponent()->SetRelativeScale3D(FVector(1.f, 0.4f, 0.4f)); + GetStaticMeshComponent()->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + GetStaticMeshComponent()->SetGenerateOverlapEvents(true); + GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable); +} + +void AM4_CentipedeBody::SetAsHead(bool bHead) +{ + bIsHead = bHead; + if (bIsHead) + { + GetStaticMeshComponent()->SetDefaultCustomPrimitiveDataVector4(0, FVector4(1.0, 0.0, 0.0, 1.0)); + } + else + { + GetStaticMeshComponent()->SetDefaultCustomPrimitiveDataVector4(0, FVector4(0.0, 1.0, 0.0, 1.0)); + } +} diff --git a/Source/M4_CPP/private/M4_CentipedeController.cpp b/Source/M4_CPP/private/M4_CentipedeController.cpp new file mode 100644 index 0000000..445d7d8 --- /dev/null +++ b/Source/M4_CPP/private/M4_CentipedeController.cpp @@ -0,0 +1,229 @@ +#include "M4_CentipedeController.h" +#include "M4_Gamemode.h" + +AM4_CentipedeController::AM4_CentipedeController() +{ + PrimaryActorTick.bCanEverTick = true; + Root = CreateDefaultSubobject(TEXT("RootComponent")); + RootComponent = Root; +} + +void AM4_CentipedeController::BeginPlay() +{ + Super::BeginPlay(); + SpawnCentipede(); + + PreviousPositions.SetNum(BodyCount); + for (int i = 0; i < BodyCount; ++i) + { + PreviousPositions[i] = BodySegments[i]->GetActorLocation(); + } + + for (AM4_CentipedeBody* Segment : BodySegments) + { + SegmentDirections.Add(Segment, FVector2D(0.f, 1.f)); + } +} + +void AM4_CentipedeController::SpawnCentipede() +{ + FVector SpawnLocation = GetActorLocation(); + + UE_LOG(LogTemp, Warning, TEXT("Controller spawn location: X=%.2f, Y=%.2f, Z=%.2f"), + SpawnLocation.X, SpawnLocation.Y, SpawnLocation.Z); + + if (SpawnLocation.IsZero()) + { + UE_LOG(LogTemp, Error, TEXT("Controller position is zero! Check spawn parameters.")); + return; + } + + for (int32 i = 0; i < BodyCount; ++i) + { + FVector SegmentLocation = SpawnLocation - FVector(0.f, i * CellSize, 0.f); + + AM4_CentipedeBody* Body = GetWorld()->SpawnActor( + 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); + } + } +} + +void AM4_CentipedeController::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (BodySegments.Num() == 0) return; + + TimeSinceLastMove += DeltaTime; + + if (TimeSinceLastMove >= MoveInterval) + { + TimeSinceLastMove = 0.f; + + TArray TempPositions; + TempPositions.SetNum(BodySegments.Num()); + + for (int32 i = 0; i < BodySegments.Num(); ++i) + { + TempPositions[i] = BodySegments[i]->GetActorLocation(); + } + + for (int32 i = 0; i < BodySegments.Num(); ++i) + { + AM4_CentipedeBody* Segment = BodySegments[i]; + + 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(); + } +} + +bool AM4_CentipedeController::CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction) +{ + if (!Segment) return false; + + FVector CurrentPos = Segment->GetActorLocation(); + FVector NextPos = CurrentPos + FVector(0.f, Direction.Y * CellSize, 0.f); + + AM4_Gamemode* GM = Cast(GetWorld()->GetAuthGameMode()); + if (!GM) return false; + + const float LeftBound = GM->MushroomSpawnBounds.Min.Y; + const float RightBound = GM->MushroomSpawnBounds.Max.Y; + + if (NextPos.Y <= LeftBound || NextPos.Y >= RightBound) + { + return true; + } + + // TODO: Ajouter vérification collision avec mushroom + // if (IsMushroomAt(NextPos)) return true; + + return false; +} + +FVector2D AM4_CentipedeController::GetSegmentDirection(AM4_CentipedeBody* Segment) +{ + if (SegmentDirections.Contains(Segment)) + { + return SegmentDirections[Segment]; + } + return FVector2D(0.f, 1.f); +} + +void AM4_CentipedeController::SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction) +{ + if (Segment) + { + SegmentDirections.Add(Segment, Direction); + } +} + +void AM4_CentipedeController::UpdateHeadStatus() +{ + for (int32 i = 0; i < BodySegments.Num(); ++i) + { + AM4_CentipedeBody* Body = BodySegments[i]; + if (!Body) continue; + + bool bShouldBeHead = (i == 0) || (Body->PreviousBody == nullptr); + + if (Body->bIsHead != bShouldBeHead) + { + Body->SetAsHead(bShouldBeHead); + } + } +} + +void AM4_CentipedeController::OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment) +{ + if (!DestroyedSegment) return; + + int32 SegmentIndex = BodySegments.Find(DestroyedSegment); + + if (SegmentIndex == INDEX_NONE) return; + + UE_LOG(LogTemp, Warning, TEXT("Segment %d destroyed"), SegmentIndex); + + if (SegmentIndex + 1 < BodySegments.Num()) + { + AM4_CentipedeBody* NewHead = BodySegments[SegmentIndex + 1]; + if (NewHead) + { + NewHead->SetAsHead(true); + NewHead->PreviousBody = nullptr; + + UE_LOG(LogTemp, Warning, TEXT("New head created at index %d"), SegmentIndex + 1); + } + } + + if (SegmentIndex > 0 && BodySegments[SegmentIndex - 1]) + { + BodySegments[SegmentIndex - 1]->NextBody = nullptr; + } + + BodySegments.RemoveAt(SegmentIndex); + PreviousPositions.RemoveAt(SegmentIndex); + SegmentDirections.Remove(DestroyedSegment); + + // TODO: Spawner un mushroom à la position du segment détruit + // SpawnMushroomAt(DestroyedSegment->GetActorLocation()); +} \ No newline at end of file diff --git a/Source/M4_CPP/private/M4_Gamemode.cpp b/Source/M4_CPP/private/M4_Gamemode.cpp index 8a98972..27496e7 100644 --- a/Source/M4_CPP/private/M4_Gamemode.cpp +++ b/Source/M4_CPP/private/M4_Gamemode.cpp @@ -1,6 +1,7 @@ // Fill out your copyright notice in the Description page of Project Settings. #include "M4_Gamemode.h" +#include "M4_CTP_Macros.h" #include "M4_Mushroom.h" #include "M4_PlayerController.h" #include "M4_PlayerPawn.h" @@ -45,11 +46,30 @@ void AM4_Gamemode::BeginPlay() for (int32 i = 0; i < MushroomCount; ++i) { FIntPoint Cell; + bool bValidCell; + int32 MaxAttempts = 100; + int32 Attempts = 0; + do { Cell.X = FMath::RandRange(0, GridRows - 1); Cell.Y = FMath::RandRange(0, GridCols - 1); - } while (OccupiedCells.Contains(Cell)); + + // Check if cell and adjacent cells are free + bValidCell = !OccupiedCells.Contains(Cell) && + !OccupiedCells.Contains(FIntPoint(Cell.X - 1, Cell.Y)) && + !OccupiedCells.Contains(FIntPoint(Cell.X + 1, Cell.Y)) && + !OccupiedCells.Contains(FIntPoint(Cell.X, Cell.Y - 1)) && + !OccupiedCells.Contains(FIntPoint(Cell.X, Cell.Y + 1)); + + Attempts++; + } while (!bValidCell && Attempts < MaxAttempts); + + if (!bValidCell) + { + PRINT_SCREEN(TEXT("Could not find valid cell for mushroom"), FColor::Yellow); + continue; + } OccupiedCells.Add(Cell); @@ -58,7 +78,36 @@ void AM4_Gamemode::BeginPlay() SpawnLocation.Y = MushroomSpawnBounds.Min.Y + Cell.Y * CellSize + CellSize / 2.0f; SpawnLocation.X = -400.0f; - GetWorld()->SpawnActor(AM4_Mushroom::StaticClass(), SpawnLocation, FRotator::ZeroRotator); + GetWorld()->SpawnActor( + AM4_Mushroom::StaticClass(), + SpawnLocation, + FRotator::ZeroRotator + ); } } + + // Spawn centipede controller + PRINT_SCREEN(TEXT("Spawning Centipede Controller"), FColor::Green); + + FVector CentipedeSpawnLocation = FVector( + -400.0f, + 0.0f, + MushroomSpawnBounds.Max.X + CellSize + ); + + // PRINT SCREEN Max.X value + PRINT_SCREEN(*FString::Printf(TEXT("Mushroom Spawn Bounds Max.X: %.2f"), MushroomSpawnBounds.Max.X), FColor::Green); + + CentipedeController = GetWorld()->SpawnActor( + AM4_CentipedeController::StaticClass(), + CentipedeSpawnLocation, + FRotator::ZeroRotator + ); + + if (CentipedeController) + { + CentipedeController->BodyCount = CentipedeBodyCount; + CentipedeController->CellSize = CellSize; + } + } diff --git a/Source/M4_CPP/private/M4_Mushroom.cpp b/Source/M4_CPP/private/M4_Mushroom.cpp index 234932a..a315033 100644 --- a/Source/M4_CPP/private/M4_Mushroom.cpp +++ b/Source/M4_CPP/private/M4_Mushroom.cpp @@ -34,6 +34,7 @@ AM4_Mushroom::AM4_Mushroom() // Custom preset for more advanced collision configuration GetStaticMeshComponent()->SetCollisionProfileName(UCollisionProfile::CustomCollisionProfileName); GetStaticMeshComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); + } void AM4_Mushroom::BeginPlay() diff --git a/Source/M4_CPP/public/M4_CentipedeBody.h b/Source/M4_CPP/public/M4_CentipedeBody.h new file mode 100644 index 0000000..14855b5 --- /dev/null +++ b/Source/M4_CPP/public/M4_CentipedeBody.h @@ -0,0 +1,25 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMeshActor.h" +#include "M4_CentipedeBody.generated.h" + +UCLASS() +class M4_CPP_API AM4_CentipedeBody : public AStaticMeshActor +{ + GENERATED_BODY() + +public: + AM4_CentipedeBody(); + + UPROPERTY() + AM4_CentipedeBody* NextBody; + + UPROPERTY() + AM4_CentipedeBody* PreviousBody; + + UPROPERTY(EditAnywhere) + bool bIsHead = false; + + void SetAsHead(bool bHead); +}; \ No newline at end of file diff --git a/Source/M4_CPP/public/M4_CentipedeController.h b/Source/M4_CPP/public/M4_CentipedeController.h new file mode 100644 index 0000000..325b994 --- /dev/null +++ b/Source/M4_CPP/public/M4_CentipedeController.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "M4_CentipedeBody.h" +#include "M4_CentipedeController.generated.h" + +UCLASS() +class M4_CPP_API AM4_CentipedeController : public AActor +{ + GENERATED_BODY() + +public: + AM4_CentipedeController(); + + virtual void BeginPlay() override; + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere) + int32 BodyCount = 10; + + UPROPERTY(EditAnywhere) + float CellSize = 50.f; + + UPROPERTY(EditAnywhere) + float MoveInterval = 0.1f; + + void OnSegmentDestroyed(AM4_CentipedeBody* DestroyedSegment); + +private: + UPROPERTY() + TArray BodySegments; + + UPROPERTY() + USceneComponent* Root; + + float TimeSinceLastMove = 0.f; + + TArray PreviousPositions; + + TMap SegmentDirections; + + void SpawnCentipede(); + void UpdateHeadStatus(); + + bool CheckCollision(AM4_CentipedeBody* Segment, FVector2D Direction); + + FVector2D GetSegmentDirection(AM4_CentipedeBody* Segment); + void SetSegmentDirection(AM4_CentipedeBody* Segment, FVector2D Direction); +}; \ No newline at end of file diff --git a/Source/M4_CPP/public/M4_Gamemode.h b/Source/M4_CPP/public/M4_Gamemode.h index ef577a8..2271f9d 100644 --- a/Source/M4_CPP/public/M4_Gamemode.h +++ b/Source/M4_CPP/public/M4_Gamemode.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" +#include "M4_CentipedeController.h" #include "M4_Gamemode.generated.h" /** @@ -61,6 +62,13 @@ public: private: + + UPROPERTY(EditAnywhere, Category="Centipede") + int32 CentipedeBodyCount = 10; + + UPROPERTY() + TObjectPtr CentipedeController; + int Score = 0; int Lives = 3;