[9] UE C++ Snake

啊賢發表於2024-04-17

思維導圖

背景地圖製作

建立瓦片集

[9] UE C++ Snake

[9] UE C++ Snake

角色素材

[9] UE C++ Snake

GameMode功能

遊戲開始控制食物的生成

[9] UE C++ Snake

食物生成池(效能最佳化)

/*
 *形參如果是一個引用,且沒有新增const關鍵字,代表實參想要藉助形參修改值
 * param 是否指定生成時候的地址
 */
void ASnakeGameModeBase::SpawnFood(FVector& SpawnLocation)
{
   AFoodActor* Food = nullptr;
   SpawnLocation.X = FMath::RandRange(-7950.f, 7950.f);
   SpawnLocation.Y = 0;
   SpawnLocation.Z = FMath::RandRange(-7950.f, 7950.f);
   //Food=GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass());
   Food = GetFoodInstance(); //沒有引數的函式
   Food->SetActorLocation(SpawnLocation);
}

/*
 * 函式呼叫者如果想提供位置,就呼叫這個函式
 */
AFoodActor* ASnakeGameModeBase::GetFoodInstance()
{
   AFoodActor* Food = nullptr;
   //判斷陣列(存放已經需要Destroy也就是被吃掉的廢棄食物的容器)中有沒有元素,有,Food賦值為廢棄食物的地址
   if (EatFoodPointerArray.Num())
   {
      Food = EatFoodPointerArray.Last(); //獲取到陣列中最後一個元素(廢棄食物的地址)
      EatFoodPointerArray.RemoveAt(EatFoodPointerArray.Num() - 1); //因為廢棄的食物已經被重新使用了,所以移除陣列
      //Food->SetFoodSprite();//重置素材的方法1:直接獲取到素材並設定
      Food->RenderFood->SetSprite(Food->GetSprite()); //重置素材的方法2:找到元件並呼叫函式,設定給元件素材
   }
   return Food ? Food : GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass());
   //Food如果存在一個值,代表需要返回廢棄食物,如果為nullptr,代表陣列中已經沒有廢棄食物使用了,就重新生成一個
}

/*
 * 函式呼叫者如果不想提供位置,就呼叫這個函式
 */
AFoodActor* ASnakeGameModeBase::GetFoodInstance(FVector& SpawnLocation)
{
   AFoodActor* Food = nullptr;
   SpawnLocation.X = FMath::RandRange(-7950.f, 7950.f);
   SpawnLocation.Y = 0;
   SpawnLocation.Z = FMath::RandRange(-7950.f, 7950.f);

   //判斷陣列(存放已經需要Destroy也就是被吃掉的廢棄食物的容器)中有沒有元素,有,Food賦值為廢棄食物的地址
   if (EatFoodPointerArray.Num())
   {
      Food = EatFoodPointerArray.Last(); //獲取到陣列中最後一個元素(廢棄食物的地址)
      EatFoodPointerArray.RemoveAt(EatFoodPointerArray.Num() - 1); //因為廢棄的食物已經被重新使用了,所以移除陣列
      Food->SetActorLocation(SpawnLocation);
      //Food->SetFoodSprite();//重置素材的方法1:直接獲取到素材並設定
      Food->RenderFood->SetSprite(Food->GetSprite()); //重置素材的方法2:找到元件並呼叫函式,設定給元件素材
   }
   return Food
             ? Food
             : GetWorld()->SpawnActor<AFoodActor>(AFoodActor::StaticClass(), SpawnLocation, FRotator::ZeroRotator);
}

減少場景中食物的數量

void ASnakeGameModeBase::SubFood()
{
   CurrentFoodCounter--;
   if (CurrentFoodCounter <= 1950) //如果場景中的食物數量到達1970就重新生成食物
   {
      FVector SpawnLocation;
      for (int32 i = 0; i < 50; i++)
      {
         SpawnFood(SpawnLocation);
      }
      CurrentFoodCounter += 50;
   }
}

SnakePawn 蛇身蛇頭

生成蛇的身體邏輯

//SnakePawn.cpp 
/*
 * SnakeBodyPointer 蛇生指標存在代表還有後續蛇身
 */
void ASnakePawn::SpawnBody()
{
	if (CurrentSnakeState != ESnakeState::Free)return;
	ASnakeBodyActor* SpawnBody = GetWorld()->SpawnActor<ASnakeBodyActor>(ASnakeBodyActor::StaticClass()); //生成
	SpawnBody->SetBodySkinIndex(SnakeHeadSkinIndex);
	SpawnBody->SetSpawnThisBodyHead(this);
	SpawnBodyArray.Add(SpawnBody);

	//找位置
	if (SnakeBodyPointer)
	{
		//頭後有一節蛇身? 找第一節蛇身開始詢問指標是否為空(身後有沒有一節身體)
		SnakeBodyPointer->SetSnakeBody(SpawnBody);
	}
	else
	{ //設定第一節蛇尾
		SnakeBodyPointer = SpawnBody;
		//SpawnBody->SetActorLocation(GetActorLocation()+RenderSnakeHead->GetUpVector()*-60);
		SpawnBody->SetActorLocation(GetActorLocation());
	}
}

//SnakeBodyActor.cpp 鏈式呼叫查詢最後一個尾巴位置
void ASnakeBodyActor::SetSnakeBody(ASnakeBodyActor* Body)
{   if(SnakeBodyPointer)//當前類例項化出的物件是不是身後還有一節蛇身?
	{
		SnakeBodyPointer->SetSnakeBody(Body);
	}else
	{
		SnakeBodyPointer=Body;
		//Body->SetActorLocation(GetActorLocation()+GetActorUpVector()*-45);
		Body->SetActorLocation(GetActorLocation());//出生的位置是直接放在了上一節的身上
	}
}

蛇移動 , 蛇身跟隨的邏輯

//陣列:儲存蛇頭走過的每一個點(點越多,蛇身與蛇頭之間的間距就更大)
//TArray<FVector> SnakeMovePositionArray;
//SnakePawn.cpp
void ASnakePawn::UpdateSnakeMove(float DeltaTime)
{
   if (CurrentSnakeState != ESnakeState::Free)return;
   FVector _SpeedAndDirection = RenderSnakeHead->GetUpVector() * DeltaTime * MoveSpeed;
   AddActorLocalOffset(_SpeedAndDirection);
   // 移動完成之後進行位置的監測(如果碰到上邊界就執行死亡邏輯)
   if (GetActorLocation().Z >= 7990)
   {
          //修改蛇的狀態為死亡
          ChangeSnakeState(ESnakeState::Dead);
       }
   if (SnakeBodyPointer)//存在代表有蛇尾
   {
      SnakeBodyPointer->SnakeMovePositionArray.Add(GetActorLocation()); //Tick每執行一次,就記錄一次蛇頭的位置
   }
}
//SnakeBodyActor.cpp
void ASnakeBodyActor::SnakeBodyFollowSnakeHead()
{
	if(SnakeMovePositionArray.Num()>=15)//蛇頭如果已經走出去10幀 了
	{
		//當前一節身體跟上
		SetActorLocation(SnakeMovePositionArray[0]);
		//自己走一幀 告知自己的下一節身體當前的位置(向下一節身體陣列中新增一個位置)
		if(SnakeBodyPointer)
		{
			SnakeBodyPointer->SnakeMovePositionArray.Add(SnakeMovePositionArray[0]);
		}
		//因為SnakeMovePositionArray[0]這個點已經使用完成(自己更新了位置,並將這個位置告知了下一節身體),這就是一個廢棄的點了
		SnakeMovePositionArray.RemoveAt(0);//移除陣列中下標為0 的值(座標點)
	}
}

Interface

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "SnakeInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class USnakeInterface : public UInterface
{
   GENERATED_BODY()
};

/**
 * 
 */
class SNAKE_API ISnakeInterface
{
   GENERATED_BODY()
public:
   UFUNCTION(/*BlueprintCallable,*/BlueprintNativeEvent)
   class ASnakePawn* GetSnake();
};

AISnakeActorComponent AI元件

//Pawn.cpp 判斷當前物件是否AI , Ai則新建一個元件附著到上面
void ASnakePawn::BeginPlay()
{
   Super::BeginPlay();
   UGameplayStatics::GetPlayerController(GetWorld(), 0)->SetViewTarget(this); //切換相機為當前類的相機

   //物件生成之後就開始判斷這個物件的型別(玩家控制的角色,非玩家控制的角色)
   if (Cast<APlayerController>(GetController()))
   {
      SnakeHeadFlipbook = LoadObject<UPaperFlipbook>(
         nullptr,TEXT("/Script/Paper2D.PaperFlipbook'/Game/Snake/Animation/SN3/PF_SN3.PF_SN3'"));
      if (SnakeHeadFlipbook)
         RenderSnakeHead->SetFlipbook(SnakeHeadFlipbook);
      SnakeHeadSkinIndex = 3;
   }
   else
   {
      SnakeHeadSkinIndex = FMath::RandRange(1, 3);
      SnakeHeadFlipbook = LoadObject<UPaperFlipbook>(
         nullptr,TEXT("/Script/Paper2D.PaperFlipbook'/Game/Snake/Animation/SN1/PF_SN1.PF_SN1'"));
      if (SnakeHeadFlipbook)
         RenderSnakeHead->SetFlipbook(SnakeHeadFlipbook);
      AISnake = NewObject<UAISnakeActorComponent>(this);
      AISnake->RegisterComponent();
   }

   SphereComponent->OnComponentBeginOverlap.AddDynamic(this, &ASnakePawn::OnComponentBeginOverlap);
   SphereComponent->OnComponentEndOverlap.AddDynamic(this, &ASnakePawn::OnComponentEndOverlap);
}
/*
.h
*/
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "AISnakeActorComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SNAKE_API UAISnakeActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UAISnakeActorComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// 這個類的作用是擴充套件功能使用的

	//尋找擴充套件物件:當前元件類例項化出的物件是在哪個類下的成員
	class ASnakePawn* Owner;

	void CheckEnemy();//檢測敵方
	//制定檢測時間
	UPROPERTY()
    float CheckTimeOffset;//時間累加值

	
};

/*
.cpp
*/
// Fill out your copyright notice in the Description page of Project Settings.


#include "AISnakeActorComponent.h"

#include "SnakePawn.h"

// Sets default values for this component's properties
UAISnakeActorComponent::UAISnakeActorComponent()
{
   // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
   // off to improve performance if you don't need them.
   PrimaryComponentTick.bCanEverTick = true;

   // ...
}


// Called when the game starts
void UAISnakeActorComponent::BeginPlay()
{   Super::BeginPlay();
    Owner= Cast<ASnakePawn>(GetOwner());//得到附著物件目標
   if(Owner)
   {
      UE_LOG(LogTemp, Log, TEXT("AIComponent==========="))
   }
}


// Called every frame
void UAISnakeActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{  Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
   if((CheckTimeOffset+=DeltaTime)>=0.3f)
   {
      CheckTimeOffset=0;
      CheckEnemy();
   }
}
void UAISnakeActorComponent::CheckEnemy()
{
   if(!Owner)return;//有沒有主人
   if(Owner->CurrentSnakeState!=ESnakeState::Free)return;//主人是不是正常移動的狀態
   TArray< FHitResult> OutHits;
   FCollisionShape Shape;
   Shape.SetSphere(58.f);
   bool bHit = Owner->GetWorld()->SweepMultiByChannel(OutHits,
   Owner->GetActorLocation(),Owner->GetActorLocation(),FQuat(0,0,0,0),  ECollisionChannel::ECC_Camera,Shape);
   if(bHit)
   {
      for(const auto& Hit:OutHits)
      {
         if(IsValid(Hit.GetActor()))
         {
            //判斷是不是蛇(蛇頭  蛇身)
            ISnakeInterface* Snake=Cast<ISnakeInterface>(Hit.GetActor());
            if(Snake) //碰到的是蛇
            {
               ASnakePawn* Pawn = Snake->Execute_GetSnake(Hit.GetActor());//呼叫函式,得到蛇頭的地址
               //判斷是不是自身(自身的蛇身)
               if(Pawn==Owner)
               {
                  //是自身
               }else
               {
                  //不是自身:告知主人,轉頭就跑
                  Owner->bAIRunaway=true;
                  FVector RunawayDir = Owner->GetActorLocation()-Pawn->GetActorLocation();
                  if(RunawayDir.Size()>=40.f)
                  {
                     RunawayDir.Normalize();
                     Owner->AIRunawayRotator = RunawayDir.Rotation();
                  }
                  else
                  {
                     Owner->ChangeSnakeState(ESnakeState::Dead);
                  }
                  
               }
            }
         }
      }
   }
}

相關文章