The Scripting Landscape
Unreal gives you two scripting options: Blueprints (visual) and C++ (code). Unity uses C# almost exclusively. Unity does have a visual scripting package, but the industry standard is writing C# scripts.
If you're coming from C++, C# will feel like a relief — no header files, no forward declarations, no manual memory management, and much friendlier compile times.
Script Lifecycle
Both engines have a well-defined lifecycle for scripts. The names differ, but the concepts map directly:
Lifecycle Methods
| Unreal Engine | Unity | Note |
|---|---|---|
Constructor | Awake() | First initialization — get own components |
BeginPlay() | Start() | Called after all objects are initialized |
Tick(float DeltaTime) | Update() | Called every frame |
TickPhysics / Substepping | FixedUpdate() | Fixed timestep for physics |
(no direct equivalent) | LateUpdate() | After all Updates — great for cameras |
EndPlay() | OnDestroy() | Cleanup when removed |
OnEnable / OnDisable (via SetActorHiddenInGame) | OnEnable() / OnDisable() | Toggling active state |
Basic Script Structure
cpp
// Unreal C++ Actor
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Movement")
float MoveSpeed = 5.0f;
public:
AMyActor();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
};C#
// Unity MonoBehaviour
using UnityEngine;
public class MyActor : MonoBehaviour
{
[SerializeField]
private float moveSpeed = 5f;
void Start()
{
// Initialize
Debug.Log("Hello from Unity!");
}
void Update()
{
// Called every frame
float dt = Time.deltaTime;
}
}Exposing Properties to the Editor
Both engines let you expose variables to the editor for designer tuning:
cpp
// Unreal: UPROPERTY macros
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health = 100.0f;
UPROPERTY(VisibleAnywhere, Category = "Debug")
int32 KillCount;C#
// Unity: Attributes
[SerializeField]
private float health = 100f;
[Header("Debug Info")]
[SerializeField, ReadOnly]
private int killCount;
// Public fields are automatically exposed
public float moveSpeed = 5f;In Unity, prefer
[SerializeField] private over public fields. It keeps your API clean while still exposing values in the Inspector.Events and Delegates
Unreal uses Delegates and Event Dispatchers. Unity uses C# events, Actions, and UnityEvents:
cpp
// Unreal: Declare and broadcast a delegate
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnHealthChanged, float, NewHealth);
UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;
// Broadcasting
OnHealthChanged.Broadcast(CurrentHealth);C#
// Unity: C# events (preferred)
using System;
public static event Action<float> OnHealthChanged;
// Firing the event
OnHealthChanged?.Invoke(currentHealth);
// Subscribing
void OnEnable() =>
HealthSystem.OnHealthChanged += HandleHealthChanged;
void OnDisable() =>
HealthSystem.OnHealthChanged -= HandleHealthChanged;Timers and Coroutines
Unreal has FTimerHandle for delayed/repeating calls. Unity uses Coroutines — a powerful pattern for sequences, delays, and async-like operations:
Deep Dive: Coroutines vs UE Timers
cpp
// Unreal: Timer
FTimerHandle TimerHandle;
GetWorld()->GetTimerManager().SetTimer(
TimerHandle, this,
&AMyActor::DoSomething,
2.0f, // delay
false); // no looping
// Repeating timer
GetWorld()->GetTimerManager().SetTimer(
TimerHandle, this,
&AMyActor::Heartbeat,
0.5f, // interval
true); // loopC#
// Unity: Coroutines are more flexible
IEnumerator DoSomethingDelayed()
{
yield return new WaitForSeconds(2f);
DoSomething();
}
// Repeating heartbeat
IEnumerator Heartbeat()
{
while (true)
{
DoHeartbeat();
yield return new WaitForSeconds(0.5f);
}
}
// Start it
void Start()
{
StartCoroutine(DoSomethingDelayed());
StartCoroutine(Heartbeat());
}Coroutines can
yield return different things: WaitForSeconds, WaitForEndOfFrame, WaitUntil(() => condition), or even other coroutines.Finding Objects
Object Lookup
| Unreal Engine | Unity | Note |
|---|---|---|
GetComponentByClass<T>() | GetComponent<T>() | Get component on same object |
GetOwner()->FindComponentByClass<T>() | GetComponentInParent<T>() | Search up the hierarchy |
UGameplayStatics::GetAllActorsOfClass() | FindObjectsOfType<T>() | Find all instances (expensive) |
Cast<AMyClass>(Actor) | GetComponent<MyClass>() | Type checking / casting |
Tags (Actor Tags) | Tags + CompareTag() | Tag-based filtering |