Cài đặt tối thiểu môi trường phát triển C++ với Rider
- Cài đặt Visual Studio Build Tools mà không cần cài Visual Studio
- Chọn Desktop development with C++
- Trong tab Thành phần riêng lẻ (Individual components), đảm bảo chọn các mục sau MSVC v143 - VS 2022 C++ x64/x86 build tools C++ core features Windows SDK Windows 10 SDK (10.0.19041.0) hoặc mới hơn (UE5 khuyến nghị 10.0.19041+) CMake (Một số tính năng của UE phụ thuộc vào)
- Trong các thành phần riêng lẻ, chọn .NET SDK 8.0 (nếu đã cài trước đó thì không cần)
- C++ AddressSanitizer, vcpkg package manager, testing tools core features - build tools, có thể không chọn
- Sử dụng Rider để mở trực tiếp .uproject, thay vì .sln (có thể báo lỗi WorkloadAutoImportPropsLocator)
Cấu hình Build trong quá trình phát triển
- Khi phát triển C++, DebugGame cung cấp nhiều thông tin chẩn đoán và gỡ lỗi chi tiết hơn so với Development, nhưng tốc độ biên dịch sẽ chậm hơn một chút.
- Khi IDE sử dụng DebugGame để biên dịch, cần khởi chạy Unreal Editor từ IDE, vì mặc định khi mở dự án UE từ Launcher sẽ sử dụng chế độ Development, dẫn đến vấn đề mã không phải là phiên bản mới nhất. Nếu sau này không cần thêm nội dung C++, có thể chuyển sang chế độ Development biên dịch một lần rồi khởi động từ Launcher
Hàm trả về tham chiếu
- Hàm trả về tham chiếu về bản chất giống với trả về con trỏ, đều trả về địa chỉ, nếu không hàm sẽ trả về bản sao của dữ liệu Lưu ý: Không nên sử dụng tham chiếu được tạo trong hàm (cục bộ) làm giá trị trả về của hàm
// Biến toàn cục
int gia_tri = 10;
// Định nghĩa hàm trả về tham chiếu
int& TraVeThamChieu()
{
return gia_tri;
}
// Sẽ kích hoạt một lần gọi hàm sao chép (sao chép nội dung đối tượng mà tham chiếu trỏ đến vào đối tượng mới)
int gia_tri_1 = TraVeThamChieu();
// Cách này không kích hoạt sao chép, sử dụng tham chiếu để ràng buộc đối tượng trả về
int& gia_tri_2 = TraVeThamChieu();
- UFUNCTION và DELEGATE trong蓝图 khi trả về UStruct chỉ có thể sử dụng tham chiếu hoặc giá trị
- UFUNCTION và DELEGATE trong蓝图 khi trả về UClass có thể trả về con trỏ trần
- Đa năng multicast khi trả về tham chiếu cấu trúc, phải thêm const
// Trả về * sẽ báo lỗi, Inappropriate '*' on variable of type 'FLevelUpInfo'
UFUNCTION()
FKieuCauTruc* LayThongTinCauTruc(int32 DiemXu);
// Trả về tham chiếu OK
UFUNCTION()
FKieuCauTruc& LayThongTinCauTruc(int32 DiemXu);
// UClass trả về * OK
UFUNCTION()
AActor* LayNhanVatKeThu();
// GiaTriMoi nếu không thêm const sẽ báo lỗi, nhưng NhanVat cua tra ve * OK
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FDelegateNaoDo, const FThongTinThuocTinh&, DuLieu, AActor*, NhanVat, const FVector&, GiaTriMoi);
Tham số hàm là tham chiếu
- Tham số không const trong蓝图 sẽ được sử dụng như đầu ra Pin
// Tham muc DichDen se duoc su dung nhu dau ra Pin
UFUNCTION(BlueprintCallable)
void TaoDanBieu(FVector& DichDen);
- Tham số const trong蓝图 sẽ được sử dụng như đầu vào Pin
// DichDen du dinh nghia la const, se duoc su dung nhu dau vao Pin, nhung DichDen khong the duoc sua doi trong than ham
UFUNCTION(BlueprintCallable)
void TaoDanBieu(const FVector& DichDen);
// Ho su dung UPARM(ref) macro, su dung macro nay co the sua doi DichDen trong than ham
UFUNCTION(BlueprintCallable)
void TaoDanBieu(UPARM(ref) FVector& DichDen);
Thay đổi tên hiển thị giá trị trả về/tham số hàm
- Sử dụng macro UPARAM có thể thay đổi tên hiển thị của tham số hoặc giá trị trả về hàm
UPARAM(DisplayName = "Nguoi") FString LayTen(UPARAM(DisplayName = "ID") float id);
Về các cách sử dụng định danh UPARAM
- Khuyến nghị sử dụng tham số tham chiếu làm giá trị trả về hàm
// Trong nút blueprint, đầu ra Pin có hiệu quả tương tự giá trị trả về hàm, đồng thời sử dụng tham số tham chiếu còn có thể trả về nhiều giá trị
UFUNCTION(BlueprintCallable)
void LayNhanVatGiaoNhau(TArray<AActor*>& NhanVatRa);
Con trỏ trong Unreal
- Đối với con trỏ thông minh TSharedPtr, TWeakPtr, and TUniquePtr không thể dùng cho loại UObject (UCALSS), UObject sử dụng hệ thống phản chiếu riêng của Unreal để quản lý
- Khi nào dùng con trỏ trần Biến cục bộ của hàm và giá trị trả về hàm thường dùng con trỏ trần, khi vượt ra phạm vi, biến cục bộ sẽ tự động bị hủy (không phải bộ nhớ nó trỏ đến)
- Khi nào dùng TObjectPtr Biến thành viên của lớp có kiểu UObject có thể sử dụng TObjectPtr thay thế con trỏ trần, lưu ý: bất kỳ loại con trỏ nào cũng phải được đánh dấu UPROPERTY để Unreal theo dõi thu gom rác. Nếu không muốn đánh dấu biến là UPROPERTY có thể sử dụng TWeakObjectPtr (không phải TWeakPtr), khi UObject nó trỏ đến bị hủy, nó sẽ được đặt thành null thay vì trở thành con trỏ treo.
- Khi nào dùng tham chiếu Khi chắc chắn biến có giá trị; tham số hình thức của hàm; giá trị trả về hàm; các trường hợp này nên ưu tiên sử dụng tham chiếu
- Khi nào dùng con trỏ thông minh Không thể là kiểu UObject. Vì vậy USTRUCT rất phù hợp để sử dụng. USTRUCT không thuộc hệ thống UObject, không được quản lý bởi thu gom rác, nhưng nó có thể sử dụng cơ chế thu gom rác do con trỏ thông minh cung cấp để ngăn chặn con trỏ USTRUCT bị treo khi UObject bị hủy
Xử lý đối tượng trong engine
- UObject không còn được tham chiếu hoặc đã được đánh dấu rõ ràng để hủy sẽ được cơ chế thu gom rác định kỳ xóa bỏ
- Chỉ các con trỏ biến và con trỏ được lưu trong container của AActor hoặc UActorComponent được đánh dấu UProperty mới được tự động gán giá trị null (= nullptr) khi dọn dẹp
- Đối với con trỏ trần không được đánh dấu UProperty, khi UObject nó tham chiếu bị hủy sẽ không được gán giá trị null, sẽ trở thành con trỏ treo.
- UStruct được xử lý như kiểu giá trị và không được thu gom rác, UStruct được tạo bằng new có thể được quản lý và thu hồi bằng con trỏ thông minh
IsValid
IsValid vừa kiểm tra nullptr vừa kiểm tra xem có sắp bị hủy (đang chờ thu gom rác) không
// Khi Enemy có thể bị hủy, sử dụng IsValid
if(IsValid(Enemy)) {...}
// Nếu chỉ đơn giản là Property, có thể sử dụng
if(MaterialInstance) {...}
Định nghĩa giao diện trong C++
- Đối với các phương thức không trả về giá trị có thể được triển khai trong Event Graph (tương tự custom event), đối với các phương thức có trả về giá trị cần được triển khai thông qua Override Function.
- Khi gọi giao diện trong blueprint, có thể gọi như hàm tĩnh (không cần Cast), nếu Target không triển khai hàm đó thì việc gọi hàm sẽ bị bỏ qua
/**
* Định nghĩa hàm là BlueprintImplementableEvent
* Chỉ ra rằng phương thức này chỉ có thể được triển khai trong blueprint, không thể triển khai trong C++
* Hàm không thể thêm virtual, sẽ gây lỗi biên dịch
* Sử dụng const để biến tham chiếu thành đầu vào PIN (nếu không sẽ là đầu ra PIN)
* BlueprintCallable trong giao diện chỉ ra rằng hàm này sẽ được gọi như hàm tĩnh trong blueprint
*/
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)
void ThucHienVanDong(const FVector& DichChieu);
/**
* Định nghĩa hàm là BlueprintNativeEvent
* Chỉ ra rằng có thể triển khai trong blueprint hoặc có thể định nghĩa triển khai mặc định trong C++
* Hàm không thể thêm virtual, sẽ gây lỗi biên dịch
* Để triển khai hàm trong C++ cần thêm _Implementation
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void DatMucDanhChien(AActor* MucDanhChien);
Gọi giao diện BlueprintNativeEvent trong C++, cần gọi phiên bản triển khai Native C++
// Kiểm tra đã triển khai giao diện chưa
if (NhanVat->Implements<IGiaoDanhChien>())
{
// Gọi phiên bản triển khai Native C++
if (!IGiaoDanhChien::Execute_ChiDaChet(NhanVat))
{
NhanVatRa.AddUnique(NhanVat);
}
}
Sử dụng BlueprintNativeEvent trả về tham chiếu trong giao diện gặp lỗi (loại trả về không khớp)
// Báo lỗi
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
TArray<FTaggableMontage>& LayDanhSachTanCong();
// Cách sửa
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void LayDanhSachTanCongTag(TArray<FTaggableMontage>& DanhSachRa);
Lưu ý với DELEGATE
- Đối số DELEGATE DYNAMIC cần thêm tên
/* Đối số DYNAMIC delegate cần thêm tên */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FSuKienThayDoiGiaTri, float, GiaTri);
- Đối số DELEGATE không DYNAMIC không thể thêm tên, nếu không sẽ báo lỗi 'quá nhiều đối số'
/* Nên tham khảo cách định nghĩa chính thức */
DECLARE_MULTICAST_DELEGATE_OneParam(FSuKienThayDoiGiaTriBanDau, float /* GiaTriMoi */);
DELEGATE DYNAMIC duy trì mối quan hệ tham chiếu yếu với đối tượng được ràng buộc, sau khi đối tượng bị hủy sẽ không còn gọi hàm được ràng buộc. Nhưng lưu ý khi UserWidget Remove from parent và sau đó ràng buộc lại sẽ gây ra việc ràng buộc tích tụ lặp lại, lý do có thể là do không được thu hồi ngay lập tức, cần gọi thủ tục Unbind
Chuỗi ký tự
- Macro TEXT() (lưu ý không phải FText) sẽ tự động thêm 'L' vào trước chuỗi tùy theo môi trường, ví dụ TEXT("AB") tương đương với L"AB", còn gọi là chuỗi rộng
- Chuỗi có thêm 'L' ở đầu có nghĩa là giải mã chuỗi theo Unicode, nếu không sẽ được giải mã theo ANSI. Mỗi ký tự Unicode chiếm 2 byte, trong khi ANSI chỉ chiếm 1 byte và dễ gây lỗi font chữ
FunctionLibrary thông dụng
// Tạo AudioComponent và gắn vào thành phần chỉ định, đi theo vật thể và phát âm liên tục
UGameplayStatics::SpawnSoundAttached
// Phát âm tại vị trí chỉ định, hiệu ứng âm thanh một lần
UGameplayStatics::PlaySoundAtLocation
// Tạo NiagaraComponent tại vị trí chỉ định
UNiagaraFunctionLibrary::SpawnSystemAtLocation
// Các phương thức DrawDebug bổ sung
UKismetSystemLibrary::DrawDebugArrow
// Các hàm kiểm tra vật lý khác nhau
UKismetSystemLibrary::SphereTraceSingle
Xem mã nguồn Graph Node
- Mã nguồn nút vật liệu UnrealEngine\Engine\Source\Runtime\Engine\Private\Materials\MaterialExpressions.cpp