思維導圖
背景地圖製作
建立瓦片集
角色素材
GameMode功能
遊戲開始控制食物的生成
食物生成池(效能最佳化)
/*
*形參如果是一個引用,且沒有新增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);
}
}
}
}
}
}
}