Game Development
Comprehensive guide for building games across major engines and platforms.
Engine Comparison
| Engine |
Language |
Best For |
Platforms |
| Unity |
C# |
Mobile, indie, VR/AR |
All |
| Unreal |
C++, Blueprints |
AAA, realistic graphics |
All |
| Godot |
GDScript, C# |
2D, indie, open source |
All |
Unity (C#)
Project Structure
Assets/
├── Scripts/
│ ├── Player/
│ │ ├── PlayerController.cs
│ │ └── PlayerInput.cs
│ ├── Enemies/
│ ├── Systems/
│ └── Utils/
├── Prefabs/
├── Scenes/
├── Materials/
├── Animations/
└── Resources/
Player Controller
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float sprintMultiplier = 1.5f;
[SerializeField] private float jumpHeight = 2f;
[SerializeField] private float gravity = -9.81f;
[Header("Look")]
[SerializeField] private float lookSensitivity = 2f;
[SerializeField] private float maxLookAngle = 80f;
private CharacterController _controller;
private Vector2 _moveInput;
private Vector2 _lookInput;
private Vector3 _velocity;
private float _xRotation;
private bool _isSprinting;
private void Awake()
{
_controller = GetComponent<CharacterController>();
Cursor.lockState = CursorLockMode.Locked;
}
private void Update()
{
HandleMovement();
HandleLook();
ApplyGravity();
}
private void HandleMovement()
{
float speed = _isSprinting ? moveSpeed * sprintMultiplier : moveSpeed;
Vector3 move = transform.right * _moveInput.x + transform.forward * _moveInput.y;
_controller.Move(move * speed * Time.deltaTime);
}
private void HandleLook()
{
float mouseX = _lookInput.x * lookSensitivity * Time.deltaTime;
float mouseY = _lookInput.y * lookSensitivity * Time.deltaTime;
_xRotation -= mouseY;
_xRotation = Mathf.Clamp(_xRotation, -maxLookAngle, maxLookAngle);
Camera.main.transform.localRotation = Quaternion.Euler(_xRotation, 0f, 0f);
transform.Rotate(Vector3.up * mouseX);
}
private void ApplyGravity()
{
if (_controller.isGrounded && _velocity.y < 0)
{
_velocity.y = -2f;
}
_velocity.y += gravity * Time.deltaTime;
_controller.Move(_velocity * Time.deltaTime);
}
// Input System callbacks
public void OnMove(InputAction.CallbackContext context) =>
_moveInput = context.ReadValue<Vector2>();
public void OnLook(InputAction.CallbackContext context) =>
_lookInput = context.ReadValue<Vector2>();
public void OnJump(InputAction.CallbackContext context)
{
if (context.performed && _controller.isGrounded)
{
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
}
public void OnSprint(InputAction.CallbackContext context) =>
_isSprinting = context.performed;
}
State Machine
public interface IState
{
void Enter();
void Update();
void Exit();
}
public class StateMachine
{
private IState _currentState;
public void ChangeState(IState newState)
{
_currentState?.Exit();
_currentState = newState;
_currentState.Enter();
}
public void Update() => _currentState?.Update();
}
// Example: Enemy AI States
public class IdleState : IState
{
private readonly EnemyAI _enemy;
public IdleState(EnemyAI enemy) => _enemy = enemy;
public void Enter() => _enemy.Animator.SetTrigger("Idle");
public void Update()
{
if (_enemy.CanSeePlayer())
{
_enemy.StateMachine.ChangeState(new ChaseState(_enemy));
}
}
public void Exit() { }
}
public class ChaseState : IState
{
private readonly EnemyAI _enemy;
public ChaseState(EnemyAI enemy) => _enemy = enemy;
public void Enter() => _enemy.Animator.SetTrigger("Run");
public void Update()
{
_enemy.NavAgent.SetDestination(_enemy.Player.position);
if (_enemy.InAttackRange())
{
_enemy.StateMachine.ChangeState(new AttackState(_enemy));
}
else if (!_enemy.CanSeePlayer())
{
_enemy.StateMachine.ChangeState(new IdleState(_enemy));
}
}
public void Exit() { }
}
Object Pooling
public class ObjectPool<T> where T : Component
{
private readonly T _prefab;
private readonly Transform _parent;
private readonly Queue<T> _pool = new();
public ObjectPool(T prefab, int initialSize, Transform parent = null)
{
_prefab = prefab;
_parent = parent;
for (int i = 0; i < initialSize; i++)
{
CreateInstance();
}
}
public T Get()
{
if (_pool.Count == 0) CreateInstance();
T obj = _pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
public void Return(T obj)
{
obj.gameObject.SetActive(false);
_pool.Enqueue(obj);
}
private void CreateInstance()
{
T obj = Object.Instantiate(_prefab, _parent);
obj.gameObject.SetActive(false);
_pool.Enqueue(obj);
}
}
Unreal Engine (C++)
Actor Component
// PlayerMovementComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PlayerMovementComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UPlayerMovementComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPlayerMovementComponent();
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
UFUNCTION(BlueprintCallable, Category = "Movement")
void Move(FVector2D Input);
UFUNCTION(BlueprintCallable, Category = "Movement")
void Jump();
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(EditAnywhere, Category = "Movement")
float MoveSpeed = 600.0f;
UPROPERTY(EditAnywhere, Category = "Movement")
float JumpForce = 400.0f;
UPROPERTY()
class UCharacterMovementComponent* MovementComponent;
};
// PlayerMovementComponent.cpp
#include "PlayerMovementComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
UPlayerMovementComponent::UPlayerMovementComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UPlayerMovementComponent::BeginPlay()
{
Super::BeginPlay();
if (ACharacter* Owner = Cast<ACharacter>(GetOwner()))
{
MovementComponent = Owner->GetCharacterMovement();
}
}
void UPlayerMovementComponent::Move(FVector2D Input)
{
if (!MovementComponent) return;
FVector Forward = GetOwner()->GetActorForwardVector();
FVector Right = GetOwner()->GetActorRightVector();
FVector Direction = (Forward * Input.Y + Right * Input.X).GetSafeNormal();
MovementComponent->AddInputVector(Direction * MoveSpeed);
}
void UPlayerMovementComponent::Jump()
{
if (ACharacter* Character = Cast<ACharacter>(GetOwner()))
{
Character->Jump();
}
}
Gameplay Ability System (GAS)
// MyGameplayAbility.h
#pragma once
#include "Abilities/GameplayAbility.h"
#include "MyGameplayAbility.generated.h"
UCLASS()
class MYGAME_API UMyGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
UMyGameplayAbility();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
bool bReplicateEndAbility, bool bWasCancelled) override;
protected:
UPROPERTY(EditDefaultsOnly, Category = "Ability")
float Damage = 50.0f;
UPROPERTY(EditDefaultsOnly, Category = "Ability")
TSubclassOf<UGameplayEffect> DamageEffect;
};
Godot (GDScript)
Player Controller
extends CharacterBody3D
@export var move_speed: float = 5.0
@export var jump_velocity: float = 4.5
@export var mouse_sensitivity: float = 0.002
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var camera: Camera3D = $Camera3D
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
rotate_y(-event.relative.x * mouse_sensitivity)
camera.rotate_x(-event.relative.y * mouse_sensitivity)
camera.rotation.x = clamp(camera.rotation.x, -PI/2, PI/2)
func _physics_process(delta: float) -> void:
# Gravity
if not is_on_floor():
velocity.y -= gravity * delta
# Jump
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# Movement
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
velocity.x = direction.x * move_speed
velocity.z = direction.z * move_speed
else:
velocity.x = move_toward(velocity.x, 0, move_speed)
velocity.z = move_toward(velocity.z, 0, move_speed)
move_and_slide()
State Machine (GDScript)
# state_machine.gd
class_name StateMachine
extends Node
@export var initial_state: State
var current_state: State
var states: Dictionary = {}
func _ready() -> void:
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.transitioned.connect(_on_child_transitioned)
if initial_state:
initial_state.enter()
current_state = initial_state
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state:
current_state.physics_update(delta)
func _on_child_transitioned(state: State, new_state_name: String) -> void:
if state != current_state:
return
var new_state: State = states.get(new_state_name.to_lower())
if not new_state:
return
current_state.exit()
new_state.enter()
current_state = new_state
# state.gd
class_name State
extends Node
signal transitioned(state: State, new_state_name: String)
func enter() -> void:
pass
func exit() -> void:
pass
func update(_delta: float) -> void:
pass
func physics_update(_delta: float) -> void:
pass
Game Patterns
Entity Component System (ECS)
// Unity DOTS example
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
public struct MoveSpeed : IComponentData
{
public float Value;
}
public partial class MovementSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = SystemAPI.Time.DeltaTime;
Entities.ForEach((ref LocalTransform transform, in MoveSpeed speed) =>
{
transform.Position += new float3(0, 0, speed.Value * deltaTime);
}).ScheduleParallel();
}
}
Event System
public static class GameEvents
{
public static event Action<int> OnScoreChanged;
public static event Action<float> OnHealthChanged;
public static event Action OnGameOver;
public static void ScoreChanged(int newScore) => OnScoreChanged?.Invoke(newScore);
public static void HealthChanged(float newHealth) => OnHealthChanged?.Invoke(newHealth);
public static void GameOver() => OnGameOver?.Invoke();
}
// Usage
public class UIManager : MonoBehaviour
{
private void OnEnable()
{
GameEvents.OnScoreChanged += UpdateScoreUI;
GameEvents.OnHealthChanged += UpdateHealthUI;
}
private void OnDisable()
{
GameEvents.OnScoreChanged -= UpdateScoreUI;
GameEvents.OnHealthChanged -= UpdateHealthUI;
}
}
Save System
[Serializable]
public class SaveData
{
public int Level;
public float PlayTime;
public Vector3Serializable PlayerPosition;
public List<string> UnlockedAchievements;
}
public static class SaveSystem
{
private static readonly string SavePath =
Path.Combine(Application.persistentDataPath, "save.json");
public static void Save(SaveData data)
{
string json = JsonUtility.ToJson(data, true);
File.WriteAllText(SavePath, json);
}
public static SaveData Load()
{
if (!File.Exists(SavePath)) return new SaveData();
string json = File.ReadAllText(SavePath);
return JsonUtility.FromJson<SaveData>(json);
}
}
Performance Optimization
LOD (Level of Detail)
// Unity LOD Group setup
[RequireComponent(typeof(LODGroup))]
public class LODSetup : MonoBehaviour
{
public Renderer[] lodRenderers;
public float[] screenRelativeTransitions = { 0.5f, 0.2f, 0.01f };
void Start()
{
LODGroup lodGroup = GetComponent<LODGroup>();
LOD[] lods = new LOD[lodRenderers.Length];
for (int i = 0; i < lodRenderers.Length; i++)
{
lods[i] = new LOD(screenRelativeTransitions[i], new[] { lodRenderers[i] });
}
lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();
}
}
Spatial Partitioning
public class QuadTree<T> where T : class
{
private readonly int _maxObjects = 10;
private readonly int _maxLevels = 5;
private readonly int _level;
private readonly List<(Rect bounds, T obj)> _objects = new();
private readonly Rect _bounds;
private QuadTree<T>[] _nodes;
public QuadTree(int level, Rect bounds)
{
_level = level;
_bounds = bounds;
}
public void Insert(Rect objBounds, T obj)
{
if (_nodes != null)
{
int index = GetIndex(objBounds);
if (index != -1)
{
_nodes[index].Insert(objBounds, obj);
return;
}
}
_objects.Add((objBounds, obj));
if (_objects.Count > _maxObjects && _level < _maxLevels)
{
if (_nodes == null) Split();
int i = 0;
while (i < _objects.Count)
{
int index = GetIndex(_objects[i].bounds);
if (index != -1)
{
var item = _objects[i];
_objects.RemoveAt(i);
_nodes[index].Insert(item.bounds, item.obj);
}
else i++;
}
}
}
public List<T> Retrieve(Rect area)
{
List<T> result = new();
RetrieveAll(area, result);
return result;
}
}
Checklist
Pre-Production
Development
Release