[6] UE C++ FlappyBird

啊賢發表於2024-04-03

FlappyBird專案

引入Paper2D外掛

[6] UE C++ FlappyBird

//渲染資產的元件
class UPaperSpriteComponent* RenderLandComponent;
//資產
class UPaperSprite* LandSprite;

GameModeBase.h.cpp

UE列舉宣告

//列舉(用於描述狀態資訊)
UENUM()
enum EBirdStateTest //傳統列舉
{
	EBS_Wait,//等待遊戲開始狀態的小鳥
	EBS_Fly,//遊戲開始小鳥可以飛
	EBS_Dead//遊戲結束,小鳥標記為死亡狀態
};
UENUM()
enum EColor //傳統列舉
{
	EC_Wait,//等待遊戲開始狀態的小鳥
	EC_Fly,//遊戲開始小鳥可以飛
	EC_Dead//遊戲結束,小鳥標記為死亡狀態
};
UENUM()
namespace  EBirdStateSecondTest //加名稱空間的列舉,明明空間名是列舉名
{
	enum Type 
    {
		Wait,//等待遊戲開始狀態的小鳥
		Fly,//遊戲開始小鳥可以飛
		Dead//遊戲結束,小鳥標記為死亡狀態
	};
}
UENUM()
enum class EBirdState:uint8 //C++11的列舉(UE中常見)
{
	Wait,//等待遊戲開始狀態的小鳥
	Fly,//遊戲開始小鳥可以飛
	Dead//遊戲結束,小鳥標記為死亡狀態
};
//使用
    //在一個列舉用於純C++原始碼而不應用於藍圖的時候,需要先寫一個列舉變數記錄狀態,再新增一個函式用於修改狀態

	EBirdStateTest CurrentBirdStateTest1;//記錄狀態(傳統列舉)
	void SetBirdStateTest(EBirdStateTest BirdState);//高版本Rider不允許成員的名字與區域性變數的名字衝突

	//A::B//B屬於A這個作用域

	EBirdStateSecondTest::Type CurrentBirdStateSecondTest;//記錄狀態(加名稱空間的列舉)
	void SetBirdStateSecondTest(EBirdStateSecondTest::Type BirdState);

	EBirdState	 CurrentBirdState;//記錄狀態(C++11的列舉)
	void SetBirdState(EBirdState BirdState);

void ABirdPawn::SetBirdState(EBirdState BirdState)
{
	CurrentBirdState = BirdState; //成員屬性被引數修改(傳值)
	//根據不同的狀態做出不同的響應
	switch (CurrentBirdState)
	{
	case EBirdState::Wait: RenderBirdComponent->SetSimulatePhysics(false);
		break;
	case EBirdState::Fly: RenderBirdComponent->SetSimulatePhysics(true);
		break;
	case EBirdState::Dead: RenderBirdComponent->SetSimulatePhysics(false);
		break;
	}
}

獲取類的型別資訊

DefaultPawnClass = ABirdPawn::StaticClass();

列舉賦值

CurrentGameState = EGameState::BeginUI;

生成一個SpawnActor

GetWorld()->SpawnActor<ABackgroundActor>(ABackgroundActor::StaticClass(), FVector(0, -50, 0),
                                         FRotator::ZeroRotator);

獲取當前遊戲世界的指標

GetWorld()

BackgroundActor.h.cpp

建立根元件 > 並設定附加關係

RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));  //建立USceneComponent的元件
RenderBackgroundComponent = CreateDefaultSubobject<UPaperSpriteComponent>(TEXT("RenderBackgroundComponent"));//建立UPaperSpriteComponent型別的元件
RenderBackgroundComponent->SetupAttachment(RootComponent); //把RenderBackgroundComponent附著到RootComponent上

載入ue專案檔案

LoadObject<UPaperSprite>(nullptr,TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Background/bg_day_Sprite.bg_day_Sprite'"));

獲取隨機數

FMath::RandRange(1,100)

渲染圖片

//渲染資產的元件
    class UPaperSpriteComponent* RenderBackgroundComponent;
//資產
    class UPaperSprite* DayBackgroundTexture;
//載入檔案物件
DayBackgroundTexture=LoadObject<UPaperSprite>(nullptr,TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Background/bg_day_Sprite.bg_day_Sprite'"));
//設定渲染
RenderBackgroundComponent->SetSprite(NightBackgroundTexture);

BirdHUD.h.cpp

宣告2d向量

FVector2D PlayButtonSize;
PlayButtonSize = FVector2D(116, 70); //按鈕大小

宣告&&載入紋理&&渲染紋理&&新增命中事件

UTexture* PlayButtonTexture; 
//載入紋理
PlayButtonTexture = LoadObject<UTexture>(
		nullptr,TEXT("/Script/Engine.Texture2D'/Game/FlappyBird/Textures/UI/button_play.button_play'"))
//渲染簡單紋理
DrawTextureSimple(PlayButtonTexture, Canvas->SizeX / 2 - (PlayButtonSize.X / 2),Canvas->SizeY / 2 - (PlayButtonSize.Y / 2));
//Canvas->SizeX 獲取螢幕X寬度 Canvas->SizeY Y軸
//新增命中事件
AddHitBox(FVector2D(Canvas->SizeX / 2 - (PlayButtonSize.X / 2), Canvas->SizeY / 2 - (PlayButtonSize.Y / 2)),PlayButtonSize,TEXT("Play"), true);

//觸發名字是 Play 的命中事件
void ABirdHUD::NotifyHitBoxRelease(FName BoxName)
{
	Super::NotifyHitBoxRelease(BoxName);
	if (BoxName == TEXT("Play"))
	{
		UE_LOG(LogTemp, Log, TEXT("單機"))
	}
}

設定單擊事件和顯示滑鼠

// Cast<To>()
BirdPawnInstancePointer = Cast<ABirdPawn>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
PC->SetShowMouseCursor(true);
PC->bEnableClickEvents = true;

繪製矩形包括RGBA通道

//RectAlpha 0.38 -> 0.0
DrawRect(FLinearColor(0, 0, 0, RectAlpha), 0, 0, Canvas->SizeX, Canvas->SizeY);

BirdPawn.h.cpp

輸入事件

[6] UE C++ FlappyBird

virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void ABirdPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	InputComponent->BindAction(TEXT("Fly"), EInputEvent::IE_Pressed, this, &ABirdPawn::FlyCallBack);//操作
	InputComponent->BindAxis(TEXT("Test"),this,&ABirdPawn::TestBindAxisFunction);//軸對映
}
//回撥函式
void ABirdPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	InputComponent->BindAction(TEXT("Fly"), EInputEvent::IE_Pressed, this, &ABirdPawn::FlyCallBack);//操作對映
	InputComponent->BindAxis(TEXT("Test"),this,&ABirdPawn::TestBindAxisFunction);//軸對映
}

void ABirdPawn::FlyCallBack() //操作對映
{
	UE_LOG(LogTemp, Log, TEXT("按下"))
	RenderBirdComponent->SetPhysicsLinearVelocity(FVector::Zero()); //清除物體當前的線性速度
	RenderBirdComponent->AddImpulse(FVector(0, 0, 2000));//物體施加一個衝量
}

void ABirdPawn::TestBindAxisFunction(float AxisValue) //軸對映
{
	UE_LOG(LogTemp, Log, TEXT("按下A "))
}

void ABirdPawn::BeginPlay()
{
	Super::BeginPlay();
    //也可以在BeginPlay呼叫
	InputComponent->BindAction();//如果沒有SetupPlayerInputComponent就可以在Begin函式中使用Actor類下的成員屬性呼叫成員函式BindAction或者BindAxis
	RenderBirdComponent->SetSimulatePhysics(true);
    
    RenderBirdComponent->OnComponentHit.AddDynamic(this,&ABirdPawn::OnComponentHit);//繫結一個Hit事件並執行回撥函式
	SphereCollision->OnComponentBeginOverlap.AddDynamic(this, &ABirdPawn::OnComponentBeginOverlap);

}

設定物理

RenderBirdComponent->SetSimulatePhysics(true)//設定物理
RenderBirdComponent->GetBodyInstance()->bNotifyRigidBodyCollision = true; //如果元件開啟了物理模擬,是否還響應Hit事件
RenderBirdComponent->SetCollisionProfileName(TEXT("BlockAll"));//將碰撞預設修改為BlockAll
RenderBirdComponent->SetCollisionObjectType(ECC_Pawn); //將Object(元件)型別修改為Pawn
RenderBirdComponent->OnComponentHit.AddDynamic(this, &ABirdPawn::OnComponentHit); //繫結一個Hit事件並執行回撥函式

//列印被擊中的Actor名稱
void ABirdPawn::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
                               FVector NormalImpulse, const FHitResult& Hit)
{
	APipeActor* Pipe = Cast<APipeActor>(OtherActor); //轉成bird

	if (Pipe)
	{
		UE_LOG(LogTemp, Log, TEXT("PipeInstanceName = %s"), *Pipe->GetName())
	}

	if (Cast<ALandActor>(OtherActor))
	{
	}
}

//建立一個球體設定碰撞型別和大小
SphereCollision = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));
SphereCollision->SetupAttachment(RenderBirdComponent);
SphereCollision->SetCollisionProfileName(TEXT("OverlapAll"));
SphereCollision->SetSphereRadius(26.f);
SphereCollision->bHiddenInGame = false;

衝量

void ABirdPawn::FlyCallBack()
{
   if (CurrentBirdState != EBirdState::Fly)return;
   //UE_LOG(LogTemp, Log, TEXT("按下"))
   RenderBirdComponent->SetPhysicsLinearVelocity(FVector::Zero()); //清除原有的速度
   RenderBirdComponent->AddImpulse(FVector(0, 0, 1300));
}

設定相對旋轉

ArmComponent->SetRelativeRotation(FRotator(0, -90, 0));

設定相機模式&&和正交

MainCamera->SetProjectionMode(ECameraProjectionMode::Orthographic);
MainCamera->OrthoWidth = 1000.f;

新增本地偏移

RenderLandComponent->AddLocalOffset(FVector(_Speed,0,0));

PipeActor.h.cpp

自定義FString的名稱

FString SceneName = FString::Printf(TEXT("Scene%d"),i);//字串的格式化輸出
FName _SceneName = *SceneName;//當*用到FString變數的前邊的時候,代表的是強制型別轉換(由FString強轉為TCHAR型別的陣列,TEXT("")宏本質就是一個TCHAR型別的陣列)
USceneComponent* Scene=CreateDefaultSubobject<USceneComponent>(_SceneName);//假設迴圈第1次 地址是0X123

匿名載入紋理

ConstructorHelpers::FObjectFinder<UPaperSprite> UpPipePath(TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Prop/pipe_up_Sprite.pipe_up_Sprite'"));
ConstructorHelpers::FObjectFinder<UPaperSprite> DownPipePath(TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Prop/pipe_down_Sprite.pipe_down_Sprite'"));

在UE C++使用過載運算子

//宣告
struct FPipeInfo
{
	bool bPlayCoinSound; //控制加分和播放加分音效的屬性
	USceneComponent* SceneInstancePointer; //每組管道的父元件的物件地址
	FPipeInfo(bool bPlayCoin)
	{
		this->bPlayCoinSound = bPlayCoin;
		UE_LOG(LogTemp, Log, TEXT("FPipeInfo(bool bPlayCoin)"))
	}

	FPipeInfo(USceneComponent* SceneInstance)
	{
		UE_LOG(LogTemp, Log, TEXT("FPipeInfo(USceneComponent* SceneInstance)"))
		this->SceneInstancePointer = SceneInstance;
		bPlayCoinSound = true;
	}

	//將FPipeInfo強制型別轉換到USceneComponent型別
	operator USceneComponent*()
	{
		return SceneInstancePointer;
	}

	//運算子過載 ->
	USceneComponent* operator->()
	{
		return SceneInstancePointer;
	}

	operator bool() //將當前的型別強制轉換為bool型別
	{
		return bPlayCoinSound;
	}

	bool operator=(bool Value)
	{
		this->bPlayCoinSound = Value;
		return bPlayCoinSound;
	}
};

TArray<FPipeInfo> SceneArray;//使用

//.cpp 
//新增
APipeActor::APipeActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
    RootComponent=CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
    ConstructorHelpers::FObjectFinder<UPaperSprite> UpPipePath(TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Prop/pipe_up_Sprite.pipe_up_Sprite'"));
    ConstructorHelpers::FObjectFinder<UPaperSprite> DownPipePath(TEXT("/Script/Paper2D.PaperSprite'/Game/FlappyBird/Textures/Prop/pipe_down_Sprite.pipe_down_Sprite'"));

	PipeSpace=220.f;
	for(int32 i=0;i<3;i++)
	{
		FString SceneName = FString::Printf(TEXT("Scene%d"),i);//字串的格式化輸出
		FName _SceneName = *SceneName;//當*用到FString變數的前邊的時候,代表的是強制型別轉換(由FString強轉為TCHAR型別的陣列,TEXT("")宏本質就是一個TCHAR型別的陣列)
		USceneComponent* Scene=CreateDefaultSubobject<USceneComponent>(_SceneName);//假設迴圈第1次 地址是0X123

		FString UpName = FString::Printf(TEXT("Up%d"),i);
		FName _UpName = *UpName;
		UPaperSpriteComponent*Up=CreateDefaultSubobject<UPaperSpriteComponent>(_UpName);

		FString DownName = FString::Printf(TEXT("Down%d"),i);
		FName _DownName = *DownName;
		UPaperSpriteComponent*Down=CreateDefaultSubobject<UPaperSpriteComponent>(_DownName);

		Scene->SetupAttachment(RootComponent);
		Up->SetupAttachment( Scene);
		Down->SetupAttachment( Scene);

		//匯入管道的資產
		if(UpPipePath.Object&&DownPipePath.Object)
		{
			Up->SetSprite(UpPipePath.Object);
			Down->SetSprite(DownPipePath.Object);
		}
		//設定每組上下管道之間的間距
        Up->SetRelativeLocation(FVector(0,0,-210));
        Down->SetRelativeLocation(FVector(0,0,260));
		//重置了宣告
		SceneArray.Add(Scene);//Add本來需要一個FPipeInfo型別物件,但是因為有強制型別轉換到
	}
	SetPipeSpace();//設定管道的左右間距
	RandPipeHeight();//設定隨機高度
	//PipeMoveSpeed=170.f;
	PipeMoveSpeed=0.f;
	bAddScoreToggle=true;
	CoinSound=nullptr;
}
void APipeActor::UpdatePipeMove(float DeltaTime)
{   float _Speed=DeltaTime*PipeMoveSpeed*-1;
	if(SceneArray.Num()) //過載了布林
	{
		for(int32 i=0;i<SceneArray.Num();i++)
		{
			//設定本次迴圈中一根管道的移動
			SceneArray[i]->AddRelativeLocation(FVector(_Speed,0,0)); //過載了 ->
			//移動過程中實時監控管道的位置是不是已經到達需要折返的位置(折返點 -280)
			if(SceneArray[i]->GetRelativeLocation().X<=-280)//折返點
			{
				int32 index=i-1;
				if(index<0)
				{
					index=2;
				}
				FVector OldLocation=SceneArray[i]->GetRelativeLocation();
				OldLocation.X=SceneArray[index]->GetRelativeLocation().X+PipeSpace;
				SceneArray[i]->SetRelativeLocation(OldLocation);
				RandPipeHeight(SceneArray[i]);
				//bAddScoreToggle=true;
				//SceneArray[i].bPlayCoinSound=true;
				SceneArray[i]=true; //過載了 =
			}
			if(SceneArray[i]->GetRelativeLocation().X<=-90)//加分點
			{
				if(BirdGameState)
				{
					//if(bAddScoreToggle)
					if(SceneArray[i])
					{
						BirdGameState->AddScore();
						UGameplayStatics::PlaySound2D(GetWorld(),CoinSound);
						//bAddScoreToggle=false;//本次已經加分完成,關閉加分開關
						//SceneArray[i].bPlayCoinSound=false;
						SceneArray[i]=false; //過載了 =
					}
				}
			}
		}
	}
}

相關文章