UE4C++獨立遊戲開發-守護神石

UE4C++獨立遊戲開發-守護神石

來自專欄 Game Development


目錄

一.遊戲內容

二.遊戲操作視頻

三.關於本遊戲初衷

四.遊戲程序基礎介紹

五.遊戲美術介紹

六.攝像機切換動畫代碼分享

七.遊戲鏈接地址


大家好,我叫人宅。

這次分享的是關於UE4C++獨立遊戲開發經驗。

在自己學習UE4的時候非常渴望有一部能夠完善的講解UE4C++遊戲案例,(現在UE4C++已經很多了,但是知識點普遍離散,想要學習完全,要很高的時間成本,但離散意味著有整合的空間,有統一起來的趨勢,這道是一個很好的消息),在一兩年前(2018年以前),網站上僅僅能找到都是UE4藍圖。不管是免費還是不免費,官網上對UE4C++描述也是輕描淡寫(現在更新的也不是很全),大部分資料都需要通過國外網站進口,由於語言和使用習慣,在沒有基礎的情況下學習難度還是蠻大的,有的知識,技術和經驗不到一定程度確實很難理解;

到此,特別想出UE4遊戲開發方面的,系統的案例教程:

於是,有了這部作品,這部遊戲想儘可能展示出UE4引擎運用在遊戲行業應該具備的基礎能力,讓學習到這個遊戲的同學儘可能掌握完全UE4基礎技能,只需要一套教程就可以入門UE4C++,學完後,直接可以勝任UE4方面的工作,何樂不為?該遊戲內容如下:

一.遊戲內容:

有一片神秘的大陸,那裡歡歌笑語,一顆巨大的神石屹立大陸中央,世世代代守護當地的居民,好景不長,神石不翼而飛,沒有神石守護的大陸失去了往日的生機,大批邪惡勢力襲來,國王命令安妮尋找破碎的神石,拯救大陸:

主角安妮和自己的小夥伴們踏上了尋找神石碎片的路上。

守護神石-??

本遊戲一共5個章節(作者精力有限,就做了5個)

遊戲通關後有兩種結局,第一種是正常結局,第二種是完美的結局。玩家需要完成每個關卡的任務,正確評分為3??,5個章節都是3??,開啟美好結局。

(一).遊戲角色:

1.安妮:

守護神石-主角 安妮

受國王之名尋找神石碎片,雖然受到阻撓,但仍不放棄,性格堅毅。

2.夏洛:

守護神石-配角 夏洛

和安妮一同尋找碎片,是安妮的小跟班。

3.果果:

守護神石-反派 果果

也是在尋找神石碎片,在遊戲中多次和安妮對戰;

(二).塔類型:

TVP:

守護神石-TVP

攻擊塔,火力攻擊,高傷害,被動技能可以增加友方攻擊力,火焰燃燒3秒。

NorMan:

守護神石-NorMan

輔助塔提供友好的生命值防禦加。 塔可以用作坦克。 在所有主塔死亡後,塔被用作屏障。

Robin:

守護神石-Robin

攻擊塔,閃電攻擊,讓敵人產生持續的閃電傷害,被動技能,對於友方傷害加成

Edgar:

守護神石-Edgar

一個攻擊塔,在該範圍內傷害敵人.

Carey:

守護神石-Carey

防禦塔為友軍提供額外的防禦。 戰鬥中有冰雪滲透,使敵人減速。

Anne:

守護神石-Anne

輔助防禦塔,提供友好的防禦,提供健康。

Alice:

守護神石-Alice

防禦主塔,在友軍的範圍內提供強大的被動節能支持

Gun:

守護神石-Gun

攻擊塔向友軍提供攻擊,受攻擊的敵人有機會入睡。

(三).敵人介紹:

小兵們:

守護神石-小兵們

有強於攻擊,有強於防禦,有強於速度,有平平常常攻擊,屬於炮灰級小兵;

Boss:

守護神石-Boss

擁有強防禦強血值強攻擊慢速度的角色。

二.遊戲操作視頻如下:

  • 守護神石:

守護神石 https://www.zhihu.com/video/1016671007687540736


關於這部遊戲的製作,在這裡簡單介紹一下;

三.關於本遊戲的初衷:

遊戲本身可能不好玩,典型的塔防即時戰略類型遊戲。在遊戲的程序方面,人宅想儘可能將編程應該實現的功能盡量往上堆積,為開發者提供一個思路,節約時間成本。看似多餘的功能,但一直保留,基本上都沒有刪除,(會公布完整版源代碼)。

四.遊戲程序內容基礎介紹:

該遊戲具備有哪些可以借鑒的內容:

  • 角色搭建:

如何設計角色基類,功能復用等;

  • AI搭建:

邏輯,AI與AI戰鬥,用C++實現一套自己的AI系統;

  • 遊戲作弊功能:

這個沒什麼可說的,對開發人員來說最常用的就是調試;

  • 建造系統:

講解如何讓UI和場景內元素通訊,如果架構這一塊系統,使得耦合度降低;

  • 攝像機管理:

特別的效果,和移動動畫切換等;

  • 特效:

特效,教大家做基礎特效與材質效果,並且實現;

  • 動畫蒙太奇:

待機,攻擊等動畫效果,詳細可閱讀源代碼工程;

  • 代理與綁定:

講解如何在遊戲中使用動態多播代理,實現我們的功能;

  • UI控制項:

主要使用的是UMG,用代碼去實現圖標拖拽等功能設計;

  • HUD:

繪製小地圖功能;

  • 材質和材質函數:

使用和創建,比如CD旋轉;

  • 遊戲設置功能:

例如:解析度,鋸齒等,遊戲數據讀取(存檔)

  • 遊戲設置數據讀取:

存本地的遊戲配置

  • 遊戲資源管理:

音頻,視頻等

  • 遊戲資料庫管理:

UE4自帶資料庫;

  • 額外的彩蛋內容:

播放視頻,播放過場動畫,繪製圖畫,web訪問等;


五.遊戲美術介紹:

美術場景資源使用是《無盡之劍》《風格化渲染》,畢竟官網公開免費的資源,使用上也沒什麼好說的,對資源的二次利用。官網提供的這些資源,大部分都帶有LOD,這樣省了很多事情,不用在為製作LOD而煩心。

在使用風格化渲染的時候,並沒有完全用風格化原來的場景,而是藉助了風格化的材質系統實現了一兩套不同的地圖。

UE4 粉刷地面

通過筆刷繪製地表:

UE4風格化渲染 三個材質

通過這三個材質可以自定義地表效果,詳細內容可以參考這三個材質的藍圖代碼:

UE4 通過該地編面板對地貌進行設置

可以看到一下幾種不同的植物:

UE4不同植物

通過這個設置我們可以自由的在地圖上刷植物;

六.攝像機切換動畫代碼分享:

關於攝像機切換,比較簡單:

守護神石 - 攝像機切換

前面的聲明:

#define CUSTOM_TIME (1.0f)//Matineenamespace EMatineeArray{ enum Type { Matinee_Coming, Matinee_Max };}//Cameranamespace ECameraArray{ enum Type { Camera_NewGame, Camera_Save, Camera_Settings, Camera_Web, Camera_Author, Camera_Quit, Camera_Coming, Camera_Leave, Camera_IntoGame, //進入遊戲 Camera_Max };}UCLASS()class STRONDEFENCE_API ATowerDefenceHallGameMode : public AGameModeBase{ GENERATED_BODY() //描邊時間 float StrokeCurrentTime;public: ATowerDefenceHallGameMode(); virtual void BeginPlay()override; //刪除攝像機和Matinee void DelectCamramAndMatinee( ACameraActor* CameraActor ,AMatineeActor* NewMatineeActor = nullptr); //播放當前的 void PlayCurrentCameraAnimation(ECameraArray::Type CameraActor, EMatineeArray::Type NewMatineeActor = EMatineeArray::Matinee_Max,float BlendTime = 0); //播放描邊效果 void PlayStrokeAnimation(float DeltaSeconds = 0); //獲取描邊時間 void GetStrokeTime();private: //Matinee TArray<TWeakObjectPtr<AMatineeActor>> MatineeArray; TArray<TWeakObjectPtr<ACameraActor>>CameraArray; //描邊材質 UMaterialInstanceDynamic* M_PostProcessBase;};

在BeginPlay裡面我們做了攝像機狀態類型

void ATowerDefenceHallGameMode::BeginPlay(){ Super::BeginPlay(); //設置視角 if (GetWorld()) { //植入GetWorld() if (UTowerDefenceGameUserSettings::GetTowerDefenceGameUserSettings()) UTowerDefenceGameUserSettings::GetTowerDefenceGameUserSettings()->SetretrieveWorldContext(GetWorld()); ////////////////////////////////////////////////////////////////////////// TArray<AActor*> CurrentLevelActor; //獲取場景內的ACameraActor GETALLACTOR(GetWorld(), ACameraActor::StaticClass(), CurrentLevelActor); CameraArray.SetNum(CurrentLevelActor.Num()); //初始化相機 for (AActor* ElemCamera: CurrentLevelActor) { ACameraActor* CurrentCamera = Cast<ACameraActor>(ElemCamera); if (CurrentCamera) { //遊戲開始的相機 if (CurrentCamera->GetName() == "CamraNewGame") { CameraArray[ECameraArray::Camera_NewGame] = CurrentCamera; continue; } //遊戲存儲的相機 if (CurrentCamera->GetName() == "CameraGameSave") { CameraArray[ECameraArray::Camera_Save] = CurrentCamera; continue; } //遊戲設置的相機 if (CurrentCamera->GetName() == "CameraGameSettings") { CameraArray[ECameraArray::Camera_Settings] = CurrentCamera; continue; } //遊戲教程網站的相機 if (CurrentCamera->GetName() == "CameraWeb") { CameraArray[ECameraArray::Camera_Web] = CurrentCamera; continue; } if (CurrentCamera->GetName() == "CameraAuthor") { CameraArray[ECameraArray::Camera_Author] = CurrentCamera; continue; } //遊戲離開的相機 if (CurrentCamera->GetName() == "CameraQuit") { CameraArray[ECameraArray::Camera_Quit] = CurrentCamera; continue; } //遊戲進入界面的相機 if (CurrentCamera->GetName() == "CameraComing") { CameraArray[ECameraArray::Camera_Coming] = CurrentCamera; continue; } //遊戲離開界面的相機 if (CurrentCamera->GetName() == "CamraLeavel") { CameraArray[ECameraArray::Camera_Leave] = CurrentCamera; continue; } //遊戲進入遊戲界面的相機 if (CurrentCamera->GetName() == "CameraGameBeige") { CameraArray[ECameraArray::Camera_IntoGame] = CurrentCamera; continue; } } } ////////////////////////////////////////////////////////////////////////// //獲取場景內的Matinee CurrentLevelActor.Empty(); GETALLACTOR(GetWorld(), AMatineeActor::StaticClass(), CurrentLevelActor); MatineeArray.SetNum(CurrentLevelActor.Num()); //初始化Matinee for (AActor* ElemMatinee : CurrentLevelActor) { AMatineeActor* CurrentMatinee = Cast<AMatineeActor>(ElemMatinee); if (CurrentMatinee) { //播放進入 if (CurrentMatinee->GetName() == "MatineeComing") { MatineeArray[EMatineeArray::Matinee_Coming] = CurrentMatinee; continue; } } } ////////////////////////////////////////////////////////////////////////// //獲取場景內後期框 CurrentLevelActor.Empty(); APostProcessVolume* TowersProcess = nullptr; GETALLACTOR(GetWorld(), APostProcessVolume::StaticClass(), CurrentLevelActor); //初始化後期框 for (AActor* ElementPocActor: CurrentLevelActor) { APostProcessVolume* ElementPoc = Cast<APostProcessVolume>(ElementPocActor); if (ElementPoc) { if (ElementPoc->GetName() == "TowersDefenceLine") { TowersProcess = ElementPoc; } } } if (TowersProcess) { if (TowersProcess->Settings.WeightedBlendables.Array.IsValidIndex(TD_Z)) { UMaterialInstance* PostProcessBase = Cast<UMaterialInstance>(TowersProcess->Settings.WeightedBlendables.Array[TD_Z].Object); if (PostProcessBase) { M_PostProcessBase = Cast<UMaterialInstanceDynamic>(PostProcessBase); if (!M_PostProcessBase) M_PostProcessBase = UMaterialInstanceDynamic::Create(PostProcessBase, TowersProcess); if (M_PostProcessBase) TowersProcess->Settings.WeightedBlendables.Array[TD_Z].Object = M_PostProcessBase; } } } ////////////////////////////////////////////////////////////////////////// //播放攝像機進入場景 PlayCurrentCameraAnimation(ECameraArray::Camera_Coming, EMatineeArray::Matinee_Coming); }}

播放當前的動畫:

void ATowerDefenceHallGameMode::PlayCurrentCameraAnimation(ECameraArray::Type CameraActor, EMatineeArray::Type NewMatineeActor /*= EMatineeArray::Matinee_Max*/, float BlendTime /*= 0*/){ //視角綁定 if (GetWorld()->GetFirstPlayerController()) { bool IsPlaySuccess = false; //切換攝像機 if (CameraArray.IsValidIndex(CameraActor)) { //設置到攝像機視角 if (CameraArray[CameraActor].Get()) { GetWorld()->GetFirstPlayerController()->SetViewTargetWithBlend(CameraArray[CameraActor].Get(), BlendTime); IsPlaySuccess = true; } } //播放Matinee if (NewMatineeActor != EMatineeArray::Matinee_Max) { if (MatineeArray.IsValidIndex(NewMatineeActor)) { if (MatineeArray[NewMatineeActor].Get()) { MatineeArray[NewMatineeActor].Get()->Play(); } } } if (IsPlaySuccess) { //開始描邊 GetStrokeTime(); } }}

其中核心的一段代碼是:

GetWorld()->GetFirstPlayerController()->SetViewTargetWithBlend(CameraArray[CameraActor].Get(), BlendTime); IsPlaySuccess = true;

剩下代碼:

void ATowerDefenceHallGameMode::GetStrokeTime(){ StrokeCurrentTime = CUSTOM_TIME;}

void ATowerDefenceHallGameMode::PlayStrokeAnimation(float DeltaSeconds){ //播放描邊效果 if (StrokeCurrentTime > 0) { StrokeCurrentTime -= DeltaSeconds; float TimeRatio = StrokeCurrentTime / CUSTOM_TIME; if (M_PostProcessBase) { if (TimeRatio < 0) { M_PostProcessBase->SetScalarParameterValue("Post Process Blend Weight", TD_Z); } else { M_PostProcessBase->SetScalarParameterValue("Post Process Blend Weight", TimeRatio); } } }}void ATowerDefenceHallGameMode::DelectCamramAndMatinee(ACameraActor* CameraActor, AMatineeActor* NewMatineeActor){ //刪除Matinee if (NewMatineeActor) { NewMatineeActor->Destroy(true); NewMatineeActor = NULL; } //刪除攝像機 if (CameraActor) { CameraActor->Destroy(true); CameraActor = nullptr; }}

這是守護神石裡面一小部分代碼,如果有興趣可以體驗一下這個遊戲,下面是遊戲實例:

七.遊戲鏈接地址:

神石守衛(遊戲本體 密碼:ox44)?

pan.baidu.com

八.教程鏈接:

虛幻引擎UE4編程C++獨立遊戲全流程教程(上) | ABOUTCG視頻教程?

www.aboutcg.org圖標虛幻引擎UE4編程C++獨立遊戲全流程教程(下) | ABOUTCG視頻教程?

www.aboutcg.org圖標UE4 Visual Studio調試技巧實戰案例教程?

www.aboutcg.org圖標UE4虛幻引擎設計藍圖功能C++初級編程教學 | ABOUTCG視頻教程?

www.aboutcg.org圖標

九.作者其他文章地址鏈接:

人宅:UE4 Visual Studio調試技巧與UE4性能優化?

zhuanlan.zhihu.com圖標
推薦閱讀:

TAG:獨立遊戲開發 | 虛幻4遊戲引擎 | CC |