Monday, August 14, 2017

Unreal 4 Tips

Unreal 4 C++ Tips


Are you starting a new game with Unreal 4? Are you looking to write it in C++?

Let me help by giving you some tips that I've learned over the last few months.


1. Get used to the Unreal engine blowing up. A lot.

If you do anything wrong, the engine will blow up. It won't pop up and error and gracefully move on. It will explode with a stack trace (if you are lucky). This is normal behavior. I've never done C++ game development before, so maybe this is normal, but it is not normal for production code in business applications (which is my day job). In business applications you wrap exceptions with a catch and gracefully move around an issue.

Because it blows up a lot, you need to be used to building from Visual Studio. Get in a habit of compiling there.


2. If your class is intended to live in the engine, you MUST extend one of their classes and make each of your variables a UPROPERTY. 

If you do not it WILL garbage collect your variables and you will slowly go out of your mind. It took me days to figure out wtf was going on and a few more days of converting my code to extend UObject and pray the fix worked (and it did).

class YEGAME_API UMyClass : public UObject {
  GENERATED_BODY()
public:
  UPROPERTY()
  UAnotherClass* MyObject;
}


3. Any class that extend one of their classes is under the control of the Unreal Engine. 

You cannot construct it. This is so they can track instances of it for memory management and garbage collection. Get used to this syntax. It is your lifeblood for creating objects:

UGameTime* CurrentGameTime = NewObject<UGameTime>();

This means everything is a memory pointer. I'm ok with that. At least it is consistent.


4. The name of a mesh and material must be fully pathed in the constructor of an Actor.

AGeneratedActor::AGeneratedActor(const class FObjectInitializer& PCIP) : Super(PCIP) {
  ...
  static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMesh(TEXT("StaticMesh'/Game/StarterContent/Props/SM_Rock.SM_Rock'"));
  ...
  static ConstructorHelpers::FObjectFinder<UMaterial> Material(TEXT("Material'/Game/StarterContent/Props/Materials/M_Rock.M_Rock'"));


5. You CAN tell the Actor constructor what mesh/material you want via a singleton. 

I will leave it to you to sort that out, but that is a huge hint to get around their frustrating Actor construction API.


6. Foliage materials must have this damn check mark checked: "Used with Instanced Static Meshes"

I probably wasted a week on that one thing.


7. Foliage cannot handle transparent materials

Makes sense I guess. BTW, transparency is the enemy of fluid performance. If you have a lot of transparency on screen, you will get stutter. You should use it VERY sparingly.


8. Lighting of Foliage Meshes is the same for all instances. 

This can be weird and I do not use it for ground.


9. The coordinate system for Unreal is Y over and X up. 

This is called a Left Handed coordinate system and bears no resemblance to mathematical coordinate systems that are X over and Y up. Blender uses the correct coordinate system. Blender gets a cookie. Unreal? They do not get a cookie.

From everything I've read on it, it is Microsoft's fault. DirectX operates this way because it was originally for flight simulators which are traditionally Left Coordinate systems.

I don't care what your excuse is, Y over and X up is pure insanity. There is no excuse for this. It is X over, Y up and Z in the 3rd dimension. Kinda like a tabs versus spaces argument (tabs are correct btw...). If you are putting objects on the screen via C++ code, you need to know this upfront.


10. Unreal cannot handle nested variables

You cannot do this: TArray<TArray>

I guess this is hard to implement. Java, C++ Structs and Ruby can do it. I use nested containers all the time. I don't quite understand this quirk.

There is a way around this. You create a class that has your second container on it as a UPROPERTY. I do this in my game and it works like a champ.


11. What is up with these damn macros?

I'm talking about these:
DECLARE_LOG_CATEGORY_EXTERN(YeLogName, Log, All);
UCLASS()
GENERATED_BODY()
UPROPERTY()

You probably know this, but your code is pre-processed into a new class before compilation. All those "myclass.generated.h" are the result of this. They are found in the intermediate folder.

When writing a new class, you can include the "generated.h" header even though it doesn't exist.

Expect nothing to highlight correctly in Visual Studio until everything compiles. Even then, Visual Studio is often confused.

If you code works, that is the true test.


12. Logging is painful

OMG is it annoying. Each class must have its own logger and everything must be converted perfectly for it to log.

If you pass the wrong type, the log may show a variable from a prior log that was for that type. That drove me crazy for a while. Also, don't forget to * your FString variable when logging it.

This is a great page on logging:
https://wiki.unrealengine.com/Logs,_Printing_Messages_To_Yourself_During_Runtime

13. And finally, never give up and never surrender.

You want to make a C++ game for Unreal? Well, its gonna suck. Hard. But... it doesn't suck as hard as writing a game engine from scratch. Now that I've got a lot of C++ code in place, I encounter a lot fewer explosions or surprises.

I like to think as Unreal as my team of engineers adding new features for me. It is painful to get started but they are a large team working for you. They have given you their entire code base to look through.

Hopefully this helps someone. If I knew all of this, it would have save me weeks of pain and suffering.

No comments:

Post a Comment