UE4C++獨立遊戲開發-守護神石
來自專欄 Game Development
目錄
一.遊戲內容
二.遊戲操作視頻三.關於本遊戲初衷四.遊戲程序基礎介紹五.遊戲美術介紹
六.攝像機切換動畫代碼分享七.遊戲鏈接地址
大家好,我叫人宅。
這次分享的是關於UE4C++獨立遊戲開發經驗。
在自己學習UE4的時候非常渴望有一部能夠完善的講解UE4C++遊戲案例,(現在UE4C++已經很多了,但是知識點普遍離散,想要學習完全,要很高的時間成本,但離散意味著有整合的空間,有統一起來的趨勢,這道是一個很好的消息),在一兩年前(2018年以前),網站上僅僅能找到都是UE4藍圖。不管是免費還是不免費,官網上對UE4C++描述也是輕描淡寫(現在更新的也不是很全),大部分資料都需要通過國外網站進口,由於語言和使用習慣,在沒有基礎的情況下學習難度還是蠻大的,有的知識,技術和經驗不到一定程度確實很難理解;
到此,特別想出UE4遊戲開發方面的,系統的案例教程:
於是,有了這部作品,這部遊戲想儘可能展示出UE4引擎運用在遊戲行業應該具備的基礎能力,讓學習到這個遊戲的同學儘可能掌握完全UE4基礎技能,只需要一套教程就可以入門UE4C++,學完後,直接可以勝任UE4方面的工作,何樂不為?該遊戲內容如下:
一.遊戲內容:
有一片神秘的大陸,那裡歡歌笑語,一顆巨大的神石屹立大陸中央,世世代代守護當地的居民,好景不長,神石不翼而飛,沒有神石守護的大陸失去了往日的生機,大批邪惡勢力襲來,國王命令安妮尋找破碎的神石,拯救大陸:
主角安妮和自己的小夥伴們踏上了尋找神石碎片的路上。
本遊戲一共5個章節(作者精力有限,就做了5個)
遊戲通關後有兩種結局,第一種是正常結局,第二種是完美的結局。玩家需要完成每個關卡的任務,正確評分為3??,5個章節都是3??,開啟美好結局。
(一).遊戲角色:
1.安妮:
受國王之名尋找神石碎片,雖然受到阻撓,但仍不放棄,性格堅毅。
2.夏洛:
和安妮一同尋找碎片,是安妮的小跟班。
3.果果:
也是在尋找神石碎片,在遊戲中多次和安妮對戰;
(二).塔類型:
TVP:
攻擊塔,火力攻擊,高傷害,被動技能可以增加友方攻擊力,火焰燃燒3秒。
NorMan:
輔助塔提供友好的生命值防禦加。 塔可以用作坦克。 在所有主塔死亡後,塔被用作屏障。
Robin:
攻擊塔,閃電攻擊,讓敵人產生持續的閃電傷害,被動技能,對於友方傷害加成
Edgar:
一個攻擊塔,在該範圍內傷害敵人.
Carey:
防禦塔為友軍提供額外的防禦。 戰鬥中有冰雪滲透,使敵人減速。
Anne:
輔助防禦塔,提供友好的防禦,提供健康。
Alice:
防禦主塔,在友軍的範圍內提供強大的被動節能支持
Gun:
攻擊塔向友軍提供攻擊,受攻擊的敵人有機會入睡。
(三).敵人介紹:
小兵們:
有強於攻擊,有強於防禦,有強於速度,有平平常常攻擊,屬於炮灰級小兵;
Boss:
擁有強防禦強血值強攻擊慢速度的角色。
二.遊戲操作視頻如下:
- 守護神石:
守護神石 https://www.zhihu.com/video/1016671007687540736
關於這部遊戲的製作,在這裡簡單介紹一下;
三.關於本遊戲的初衷:
遊戲本身可能不好玩,典型的塔防即時戰略類型遊戲。在遊戲的程序方面,人宅想儘可能將編程應該實現的功能盡量往上堆積,為開發者提供一個思路,節約時間成本。看似多餘的功能,但一直保留,基本上都沒有刪除,(會公布完整版源代碼)。
四.遊戲程序內容基礎介紹:
該遊戲具備有哪些可以借鑒的內容:
- 角色搭建:
如何設計角色基類,功能復用等;
- AI搭建:
邏輯,AI與AI戰鬥,用C++實現一套自己的AI系統;
- 遊戲作弊功能:
這個沒什麼可說的,對開發人員來說最常用的就是調試;
- 建造系統:
講解如何讓UI和場景內元素通訊,如果架構這一塊系統,使得耦合度降低;
- 攝像機管理:
特別的效果,和移動動畫切換等;
- 特效:
特效,教大家做基礎特效與材質效果,並且實現;
- 動畫蒙太奇:
待機,攻擊等動畫效果,詳細可閱讀源代碼工程;
- 代理與綁定:
講解如何在遊戲中使用動態多播代理,實現我們的功能;
- UI控制項:
主要使用的是UMG,用代碼去實現圖標拖拽等功能設計;
- HUD:
繪製小地圖功能;
- 材質和材質函數:
使用和創建,比如CD旋轉;
- 遊戲設置功能:
例如:解析度,鋸齒等,遊戲數據讀取(存檔)
- 遊戲設置數據讀取:
存本地的遊戲配置
- 遊戲資源管理:
音頻,視頻等
- 遊戲資料庫管理:
UE4自帶資料庫;
- 額外的彩蛋內容:
播放視頻,播放過場動畫,繪製圖畫,web訪問等;
五.遊戲美術介紹:
美術場景資源使用是《無盡之劍》《風格化渲染》,畢竟官網公開免費的資源,使用上也沒什麼好說的,對資源的二次利用。官網提供的這些資源,大部分都帶有LOD,這樣省了很多事情,不用在為製作LOD而煩心。
在使用風格化渲染的時候,並沒有完全用風格化原來的場景,而是藉助了風格化的材質系統實現了一兩套不同的地圖。
通過筆刷繪製地表:
通過這三個材質可以自定義地表效果,詳細內容可以參考這三個材質的藍圖代碼:
可以看到一下幾種不同的植物:
通過這個設置我們可以自由的在地圖上刷植物;
六.攝像機切換動畫代碼分享:
關於攝像機切換,比較簡單:
前面的聲明:
#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)
八.教程鏈接:
虛幻引擎UE4編程C++獨立遊戲全流程教程(上) | ABOUTCG視頻教程虛幻引擎UE4編程C++獨立遊戲全流程教程(下) | ABOUTCG視頻教程UE4 Visual Studio調試技巧實戰案例教程UE4虛幻引擎設計藍圖功能C++初級編程教學 | ABOUTCG視頻教程
九.作者其他文章地址鏈接:
人宅:UE4 Visual Studio調試技巧與UE4性能優化
推薦閱讀: