Advertisement

Unreal Engine vs Unity Engine

Started by March 15, 2017 06:42 PM
80 comments, last by Dirk Gregorius 7 years, 6 months ago

For example there are no abstract objects

Of course there are - you just can't make them via the editor, because it expects you to work with GameObjects when arranging a scene. I don't think this is much different from Unreal, where everything in your scene would be an Actor.

Even GUI elements need a 3d transform This is fundamentally weird.

You can put your canvas Render Mode into World Space. So the third dimension is used to account for that.

Advertisement

It has some fundamental drawbacks that would create a jam in long haul projects. For example there are no abstract objects. Even GUI elements need a 3d transform This is fundamentally weird.

That drawback doesn't actually exist. Dig deeper.

It is universally true that anything placed in the game world needs to be derived from some game object class. It applies to all engines. I've seen it in everything from tiny 66 MHz systems up through today's most powerful machines. If it can be placed in the world it needs to have information regarding the world. Call it a GameObject, an Actor, a SceneItem, or anything else, the net result is that if you can place it in the scene it needs some common functionality.

Nothing prevents you from having classes in your code that never exist in the scene's world, or from having entire systems that are completely unrelated to Unity's libraries. Inventory systems, networking systems, concurrent processing systems, logging systems, messaging systems, synchronization systems, localization/translation systems, database interactions, they can all exist in a Unity application without actually touching Unity.

I've worked on several games where the core game logic is completely independent of Unity. The scene manipulations happened in Unity, there were components that interacted with the game's core logic, but the actual game processing was independent.

official UE4 documentation keeped on harping about creating blueprints, to me mixing the idea of a game object / prefab and a gameplay script into one vague thing I wasn't sure how to interact with.

It is very much the same as Unity in a way that in Unity code is link to a prefab. The largest difference is that in Unity create a code attachment where in Unreal every Blueprint has a code attachment; if it's not used it's discarded at cleanup.

In Unreal if you want to use code that isn't object focused you use the Level blueprint, if you used Kismet before it's the same thing,

Lets say I wanted to create my own character controller, that would be different to the stock character controller, and I wanted to keep it as much in C++ as possible.

The easiest way to describe Blueprints to programmers would be to call them classes, when you create a Blueprint class it's similar to coding a new class with C++. Dragging the Blueprint/ class into the engine creates a Instance/ Object of the class.

The first three common classes are Actor, Pawn and Character. Think of each of these as a class and the next one is a sub class.

So Actor is a empty class, empty with what makes it work in the engine.

The Pawn is a sub class from actor with some basic input stuff added on to the class.

The Character is a sub class of Pawn, with some AI stuff added in.

The order is Actor-> Pawn ->Character

So, if you want your own Character class, that is completely different from Unreal's Character class. You then use the Pawn class, this makes your new Class as a sub class of Pawn just like Unreal's Character class.

Now you can make the player move as you want, however you can't use the AI options or advance movement options from the Character unless you copy and paste it into your new character class.

The new order is Actor-> Pawn ->CustomCharacter

If you only wanted a new character it would have been better to use the Character class, so that you could have access to all of the options the class has.

The new order is Actor-> Pawn -> Character ->CustomCharacter

The Actor is the nearest to a empty class you can find, because all Blueprints are part of the engine.

A truly empty class can be made in C++ and then defined as a new parent class, this is redundant as you will need all of the stuff in the Actor class to have it working properly as a parent class. You would also still have to look in the search bar for it, unless you alter the engine source.

Edit: See frob's comment above mine to understand why this is. :)

So you can just make your own Parent class by making a sub Actor class, then just look in the search bar under the "common classes".

So if you have a Blueprint/class that is a subclass of Actor and call it Bob, you can make a new Blueprint/class called BobJunior using Bob as the parent class.

The new order is Actor-> Bob ->BobJunior

Now the other two Blueprints is PlayerContoller and GameMode.

PlayerController is a great optional tool, if used the Player posses things instead of directly controlling things.

So if you made a GTA game you could allow the player to possess the Character and then to switch possession when entering the vehicle. Because a player can only possess one thing at a time, it's a easy way to monitor input for network games.

There is no need to use this class, at all, it just makes complex things easier.

GameMode is a class over the whole game. Most common use for it is to store rules for game modes and scores.

GameMode works with the PlayerController class, so it's possible to change input options. This means that if your game has cut-scenes you can have a game mode called Playing and a other called Cinematic that you switch to during cut-scenes.

(another thing I struggled with a lot with the official C++ docs), and needs to be attached to a Blueprint "thingie" (the non-Visualscript Blueprint thingie))?

You need a node as a input, to show where the custom C++ script goes. If you don't have it then your script just floats randomly in space, never to be executed.

The node you make runs the code, it's the same as making a function.

You need a node as a input, to show where the custom C++ script goes. If you don't have it then your script just floats randomly in space, never to be executed.

If you have actors in the world, they can be Ticked, so you can just put code in there which will be called regularly if the actor is in the world. No visual scripting or node connections required, if I remember correctly.

If you have actors in the world, they can be Ticked, so you can just put code in there which will be called regularly if the actor is in the world. No visual scripting or node connections required, if I remember correctly.

Yes, plus you can also just override a bunch of different virtual methods (not only Tick but OnBeginPlay, ...), hook up delegates, etc... all in C++. So to say that Blueprint is required is a false statement. All C++-classes you create can be used directly in the editor, say if you subclass an AActor with your AMyPlayer, then you can place that thing directly in the world if you want to. But you can do the same thing with Blueprints, which is probably one of the coolest features of UE4 - being able to use Blueprints & C++ in a seamless fashion.

Advertisement

Awesome guys, thanks a lot for all the Input and discussion. I always felt I was missing something with UE4. So I guess you can write code pretty much like you do in Unitys C# Entity-Component System, its just not so neatly presented in the Editor like the Blueprint way of doing things.

Something to look into later this year...

It is universally true that anything placed in the game world needs to be derived from some game object class. It applies to all engines. I've seen it in everything from tiny 66 MHz systems up through today's most powerful machines. If it can be placed in the world it needs to have information regarding the world. Call it a GameObject, an Actor, a SceneItem, or anything else, the net result is that if you can place it in the scene it needs some common functionality.

I'm going off-topic to nitpick, sorry :D
That philosophy is extremely common in games, but is not universally true.
You either make every game object conform to a common scene interface, or you add new code to the scene for each different kind of object. There is a choice there.
I've worked on lots of games without a common GameObject or a "something in the scene" interface.
Scenes can be composed of many disparate systems with no common interface between them, and each system can contain some homogeneous type with a common interface, or again another bag of disparate types.

I would actually go as far as t say that:
class GameObject { virtual void Update(float tick); };
...is an anti-pattern and a code smell. It's terrible. It's a hack.

To bring that back around to the topic though, it's easy to see why Unity/Unreal stick with their smelly designs...
Unity's choice to put a transform in their base class is obviously a breach of data normalisation rules, so it's a diry hack too... But it's 'useful' for 99% of their components so you can understand why they chose wrong-but-works over pure-but-annoying.
Unreal is the same. They've always been trampling all over data normalisation and OO rules and been producing wrong-but-works code, which you can also understand because they're extremely familiar with these design choices, and in the real world familiarity means readability/maintainability, which are just as important as efficiency, or formal engineering doctrine.

I've worked on lots of games without a common GameObject or a "something in the scene" interface.
Scenes can be composed of many disparate systems with no common interface between them, and each system can contain some homogeneous type with a common interface, or again another bag of disparate types.

I would actually go as far as t say that:


class GameObject { virtual void Update(float tick); };
...is an anti-pattern and a code smell. It's terrible. It's a hack.


I'd be interested in seeing what you think would be a better approach. I've worked on games where the game contains a bunch of systems, and each system may have its own updating logic, but buried in there somewhere would be one or more systems that manage Things In The World and those things would have a periodic update like the above.

I've also worked with systems where game objects had to request an update (e.g. clock.schedule_repeat(my_callback, 0.03)) and that is pretty elegant, but can cause some confusion when updates don't happen as predictably as they would with a for entity in entities: entity.update() style of loop.

Consider a top-down, Zelda-like sprite game. You have characters, weapons, particles, all sorts of game objects. Pretty much all of them are rendered with sprites, so a naive approach would make sprite the root base class with a "virtual void Update" method on it, which you'd call on EVERY sprite, every frame. That approach would work fine if all the sprites had the same behaviour - but the thing is, most "sprites" aren't actually that similar, apart from how they're rendered. Sure, they all have positions in the world, most of them probably have collision shapes associated with them, but those are properties of these things, not behaviours. Many or even most sprites just exist to look pretty; they don't move or take actions. The naive implementation would have a derived "movable sprite" to get movement code into that Update function, and an "actor sprite" to get action code into the update function, while the objects that don't do anything would just have empty functions.

But take a step back and think: why would you bother "updating" an object for which that operation is not meaningful? Furthermore, do you really want to deal with the mess of "MovingSprite", "MovingCollidingActingSprite", etc.? Maybe you'd just throw most of your stuff into one class's implementation of Update, but in my experience, that "Update" method can get long and convoluted very quickly as people add more and more things to game objects.

Instead, let's observe that if an object isn't moving, it doesn't need a velocity; if it isn't acting, it doesn't need to know how long it is until the action is finished; that if some data associated with an object is not used (or "valid") given the object's current state, it should not exist. The only data the game object should store in itself is the data that is going to be "valid" for the entire lifetime of the object.

So, for instance, we might have a "movement system." The movement system would have a separate "update" function from the game objects, called from the main loop. The system's one job would be to move objects and tell the animation system about movement. Other systems (like the player input or AI systems) would send it messages that apply accelerations to objects. As long as the object had velocity, it would have a corresponding record in the movement system; the record would be deleted if the object stops.

I've been using this on a side project (and to a lesser extent at my actual job) and I've observed that this has a number of nice design implications:
* objects become smaller and have fewer dependencies (because the transient data and behaviour is on the systems)
* adding new systems is very easy, so adding new features tends to also be quite straightforward
* it becomes easier to reason about dependencies between objects and systems - if a system needs access to the state on another system, you explicitly pass that system into the first system's update method, making the dependency explicit
* it becomes much more straightforward to reason about execution order of component behaviours. For instance, you could now see explicitly that movement is processed after input and before animation, always. Individual object types cannot get this wrong.
* it becomes much harder for objects to go into "invalid" states - and there is far less need to test to see if an object is in a specific state before applying a certain kind of behaviour.
* you can still model inheritance this way, if you need it - but with the nifty feature that you can change the derived type of the object at runtime. Eg. maybe your object was a player before, but then they get a spell cast on them and are turned to stone - making their sprite into scenery.

These things - plus the possible performance advantages, which I'm not going to touch on since others can do that better than I - have compelled me to move away from the monolithic "Update() on every object" model wherever I can.

This topic is closed to new replies.

Advertisement