Game-Entwicklung Grundlagen: Unity, Unreal Engine, 3D-Graphics, Physics & Animation
Dieser Beitrag ist eine umfassende Einführung in die Game-Entwicklung Grundlagen – inklusive Unity, Unreal Engine, 3D-Graphics, Physics-Engines und Animation mit praktischen Beispielen.
In a Nutshell
Game-Entwicklung kombiniert Programmierung, 3D-Graphics, Physics und Animation. Unity und Unreal Engine sind die führenden Engines, C# und C++ die Hauptprogrammiersprachen, OpenGL/Vulkan die Grafik-APIs.
Mit praxisnahen Projekten und realen Fallstudien wird der Kurs dir das Wissen vermitteln, das du brauchst, um deine Karriere im Game Development zu starten oder weiter voranzutreiben.
Hier kannst du mehr erfahren: Game Development E-Degree.
Kompakte Fachbeschreibung
Game-Entwicklung ist die Erstellung von interaktiven Unterhaltungssoftware durch Kombination von Programmierung, Grafik, Sound und Gameplay-Mechaniken.
Kernkomponenten:
Game Engines
- Unity: C#-Engine für 2D/3D-Spiele, Cross-Platform
- Unreal Engine: C++-Engine für AAA-Spiele, High-End-Grafik
- Godot: Open-Source-Engine mit GDScript/C#
- CryEngine: C++-Engine für Realistische Grafik
3D-Graphics
- Rendering Pipeline: Vertex → Fragment → Display
- Shaders: GLSL/HLSL für visuelle Effekte
- Lighting: Beleuchtungsmodelle (Phong, PBR)
- Texturing: 2D/3D-Materialien und Mapping
Physics Engines
- Collision Detection: AABB, OBB, Sphere, Mesh
- Physics Simulation: Rigid Bodies, Forces, Constraints
- Integration: Verlet, Euler, RK4
- Optimization: Spatial Hashing, Broad Phase
Animation Systems
- Skeletal Animation: Bones, Weights, Skinning
- Keyframe Animation: Timeline-basierte Animation
- Procedural Animation: Algorithmisch generiert
- Morph Targets: Shape-Interpolation
Prüfungsrelevante Stichpunkte
- Game-Entwicklung: Erstellung von interaktiven Spielen
- Unity: C#-Engine für Cross-Platform-Spiele
- Unreal Engine: C++-Engine für AAA-Spiele
- 3D-Graphics: Rendering Pipeline, Shaders, Lighting
- Physics: Kollisionserkennung, Physik-Simulation
- Animation: Skeletal, Keyframe, Procedural Animation
- Game Loop: Update-Render-Zyklus, Fixed Time Step
- IHK-relevant: Moderne Spieleentwicklung und -technologien
Kernkomponenten
- Game Engine: Grundlage für Spielentwicklung
- Rendering Pipeline: Grafik-Verarbeitung und Darstellung
- Physics Engine: Physik-Simulation und Kollisionserkennung
- Animation System: Bewegung und Charakter-Animation
- Input System: Benutzerinteraktion und Steuerung
- Audio System: Soundeffekte und Musik
- UI System: Benutzeroberfläche und HUD
- Networking: Multiplayer und Online-Funktionen
Praxisbeispiele
1. Unity Game mit C# - 2D Platformer
using UnityEngine;
using System.Collections;
// Player Controller für 2D Platformer
public class PlayerController : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float jumpForce = 10f;
[SerializeField] private LayerMask groundLayer;
[Header("Animation Settings")]
[SerializeField] private Animator animator;
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckRadius = 0.2f;
private Rigidbody2D rb;
private bool isGrounded = false;
private bool facingRight = true;
private float horizontalInput;
private Vector2 velocity = Vector2.zero;
// Physics Constants
private const float GROUND_CHECK_DISTANCE = 0.1f;
private const float COYOTE_TIME = 0.2f;
private const float MAX_FALL_SPEED = 20f;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
if (animator == null)
animator = GetComponent<Animator>();
if (groundCheck == null)
groundCheck = transform;
}
private void Update()
{
HandleInput();
UpdateAnimations();
}
private void FixedUpdate()
{
HandleMovement();
HandlePhysics();
}
private void HandleInput()
{
// Horizontal movement
horizontalInput = Input.GetAxisRaw("Horizontal");
// Jump input
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
}
// Dash input
if (Input.GetButtonDown("Dash"))
{
Dash();
}
// Attack input
if (Input.GetButtonDown("Attack"))
{
Attack();
}
}
private void HandleMovement()
{
// Horizontal movement with smooth acceleration
float targetVelocityX = horizontalInput * moveSpeed;
velocity.x = Mathf.SmoothDamp(velocity.x, targetVelocityX, 0.1f);
// Apply movement
rb.velocity = new Vector2(velocity.x, rb.velocity.y);
// Flip character based on direction
if (horizontalInput != 0 && horizontalInput != transform.localScale.x)
{
Flip();
}
}
private void HandlePhysics()
{
// Ground check
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// Limit fall speed
if (rb.velocity.y < -MAX_FALL_SPEED)
{
rb.velocity = new Vector2(rb.velocity.x, -MAX_FALL_SPEED);
}
// Apply gravity
if (!isGrounded)
{
rb.velocity += Vector2.up * Physics2D.gravity * Time.fixedDeltaTime * 2f;
}
}
private void Jump()
{
// Reset vertical velocity
rb.velocity = new Vector2(rb.velocity.x, 0f);
// Apply jump force
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
// Trigger jump animation
if (animator != null)
{
animator.SetTrigger("Jump");
}
// Play jump sound
PlaySound("Jump");
}
private void Dash()
{
// Apply dash force
float dashForce = 15f;
float dashDirection = facingRight ? 1f : -1f;
rb.velocity = new Vector2(dashForce * dashDirection, rb.velocity.y);
// Trigger dash animation
if (animator != null)
{
animator.SetTrigger("Dash");
}
// Play dash sound
PlaySound("Dash");
// Start dash cooldown
StartCoroutine(DashCooldown());
}
private void Attack()
{
// Trigger attack animation
if (animator != null)
{
animator.SetTrigger("Attack");
}
// Play attack sound
PlaySound("Attack");
// Detect enemies in range
DetectAndDamageEnemies();
}
private void Flip()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
private void UpdateAnimations()
{
if (animator == null) return;
// Set movement parameters
animator.SetFloat("Speed", Mathf.Abs(rb.velocity.x));
animator.SetBool("IsGrounded", isGrounded);
animator.SetFloat("VerticalSpeed", rb.velocity.y);
// Set animation states
if (Mathf.Abs(rb.velocity.x) > 0.1f)
{
animator.SetBool("IsMoving", true);
}
else
{
animator.SetBool("IsMoving", false);
}
}
private void DetectAndDamageEnemies()
{
// Detect enemies in attack range
Collider2D[] enemies = Physics2D.OverlapCircleAll(
transform.position,
1.5f,
LayerMask.GetMask("Enemy")
);
foreach (Collider2D enemy in enemies)
{
EnemyController enemyController = enemy.GetComponent<EnemyController>();
if (enemyController != null)
{
enemyController.TakeDamage(10);
}
}
}
private void PlaySound(string soundName)
{
// Play sound through AudioManager
AudioManager.Instance.PlaySound(soundName);
}
private IEnumerator DashCooldown()
{
// Disable dash for cooldown period
float cooldownTime = 1f;
// Visual feedback
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
Color originalColor = spriteRenderer.color;
spriteRenderer.color = new Color(1f, 1f, 1f, 0.5f);
yield return new WaitForSeconds(cooldownTime);
spriteRenderer.color = originalColor;
}
}
// Collision handling
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
TakeDamage(10);
}
else if (collision.gameObject.CompareTag("Collectible"))
{
CollectItem(collision.gameObject);
}
else if (collision.gameObject.CompareTag("Platform"))
{
// Platform specific logic
HandlePlatformCollision(collision);
}
}
private void TakeDamage(int damage)
{
// Apply damage to player
// This would be handled by a separate health system
// Trigger hurt animation
if (animator != null)
{
animator.SetTrigger("Hurt");
}
// Play hurt sound
PlaySound("Hurt");
// Knockback
Vector2 knockbackDirection = (transform.position - collision.transform.position).normalized;
rb.AddForce(knockbackDirection * 5f, ForceMode2D.Impulse);
// Start invincibility frames
StartCoroutine(InvincibilityFrames());
}
private void CollectItem(GameObject item)
{
// Collect item logic
CollectibleItem collectible = item.GetComponent<CollectibleItem>();
if (collectible != null)
{
collectible.Collect();
}
// Play collect sound
PlaySound("Collect");
// Trigger collect animation
if (animator != null)
{
animator.SetTrigger("Collect");
}
}
private void HandlePlatformCollision(Collision2D collision)
{
// Make player child of moving platform
if (collision.gameObject.CompareTag("MovingPlatform"))
{
transform.SetParent(collision.transform);
}
}
private void OnCollisionExit2D(Collision2D collision)
{
// Unparent from moving platforms
if (collision.gameObject.CompareTag("MovingPlatform"))
{
transform.SetParent(null);
}
}
private IEnumerator InvincibilityFrames()
{
// Enable invincibility
Physics2D.IgnoreLayerCollision(gameObject, LayerMask.GetMask("Enemy"));
// Visual feedback
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
Color originalColor = spriteRenderer.color;
spriteRenderer.color = new Color(1f, 1f, 1f, 0.3f);
yield return new WaitForSeconds(1f);
spriteRenderer.color = originalColor;
}
// Disable invincibility
Physics2D.IgnoreLayerCollision(gameObject, LayerMask.GetMask("Enemy"));
}
// Public methods
public void SetVelocity(Vector2 newVelocity)
{
velocity = newVelocity;
}
public Vector2 GetVelocity()
{
return velocity;
}
public bool IsFacingRight()
{
return facingRight;
}
public bool IsGrounded()
{
return isGrounded;
}
}
// Enemy Controller
public class EnemyController : MonoBehaviour
{
[Header("Enemy Settings")]
[SerializeField] private int maxHealth = 100;
[SerializeField] private int damage = 10;
[SerializeField] private float moveSpeed = 2f;
[SerializeField] private float detectionRange = 5f;
[SerializeField] private float attackRange = 1f;
[SerializeField] private LayerMask playerLayer;
[Header("Animation")]
[SerializeField] private Animator animator;
[SerializeField] private GameObject deathEffect;
private int currentHealth;
private Transform player;
private bool isDead = false;
private bool canAttack = true;
private void Awake()
{
currentHealth = maxHealth;
if (animator == null)
animator = GetComponent<Animator>();
}
private void Start()
{
// Find player
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
{
player = playerObj.transform;
}
}
private void Update()
{
if (isDead) return;
// Check if player is in range
if (player != null)
{
float distanceToPlayer = Vector2.Distance(transform.position, player.position);
if (distanceToPlayer <= detectionRange)
{
if (distanceToPlayer <= attackRange && canAttack)
{
Attack();
}
else
{
MoveTowardsPlayer();
}
}
else
{
Patrol();
}
}
UpdateAnimations();
}
private void MoveTowardsPlayer()
{
if (player == null) return;
Vector2 direction = (player.position - transform.position).normalized;
transform.position = Vector2.MoveTowards(
transform.position,
player.position,
moveSpeed * Time.deltaTime
);
// Flip enemy based on direction
if (direction.x > 0 && transform.localScale.x < 0)
{
Flip();
}
else if (direction.x < 0 && transform.localScale.x > 0)
{
Flip();
}
}
private void Patrol()
{
// Simple patrol behavior
// This would be expanded with waypoints or random movement
transform.position += Vector2.right * moveSpeed * Time.deltaTime * Mathf.Sign(Mathf.Sin(Time.time));
}
private void Attack()
{
// Trigger attack animation
if (animator != null)
{
animator.SetTrigger("Attack");
}
// Start attack cooldown
StartCoroutine(AttackCooldown());
// Deal damage to player if in range
float distanceToPlayer = Vector2.Distance(transform.position, player.position);
if (distanceToPlayer <= attackRange)
{
PlayerController playerController = player.GetComponent<PlayerController>();
if (playerController != null)
{
playerController.TakeDamage(damage);
}
}
}
private IEnumerator AttackCooldown()
{
canAttack = false;
yield return new WaitForSeconds(1.5f);
canAttack = true;
}
private void UpdateAnimations()
{
if (animator == null) return;
animator.SetBool("IsDead", isDead);
animator.SetBool("IsMoving", player != null && Vector2.Distance(transform.position, player.position) > attackRange);
}
private void Flip()
{
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
public void TakeDamage(int damageAmount)
{
if (isDead) return;
currentHealth -= damageAmount;
// Trigger hurt animation
if (animator != null)
{
animator.SetTrigger("Hurt");
}
// Visual feedback
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
StartCoroutine(FlashRed());
}
// Check if dead
if (currentHealth <= 0)
{
Die();
}
}
private IEnumerator FlashRed()
{
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
Color originalColor = spriteRenderer.color;
spriteRenderer.color = Color.red;
yield return new WaitForSeconds(0.1f);
spriteRenderer.color = originalColor;
}
}
private void Die()
{
isDead = true;
// Trigger death animation
if (animator != null)
{
animator.SetTrigger("Die");
}
// Disable collision
GetComponent<Collider2D>().enabled = false;
// Disable movement
enabled = false;
// Spawn death effect
if (deathEffect != null)
{
Instantiate(deathEffect, transform.position, Quaternion.identity);
}
// Remove enemy after delay
StartCoroutine(DestroyAfterDelay());
}
private IEnumerator DestroyAfterDelay()
{
yield return new WaitForSeconds(2f);
Destroy(gameObject);
}
}
// Collectible Item
public class CollectibleItem : MonoBehaviour
{
[Header("Item Settings")]
[SerializeField] private string itemName = "Coin";
[SerializeField] private int value = 1;
[SerializeField] private GameObject collectEffect;
[SerializeField] private AudioClip collectSound;
private bool isCollected = false;
private void OnTriggerEnter2D(Collider2D other)
{
if (isCollected) return;
if (other.CompareTag("Player"))
{
Collect();
}
}
public void Collect()
{
if (isCollected) return;
isCollected = true;
// Add to player inventory
PlayerController player = FindObjectOfType<PlayerController>();
if (player != null)
{
// This would interface with an inventory system
Debug.Log($"Collected {itemName} worth {value} points");
}
// Spawn collect effect
if (collectEffect != null)
{
Instantiate(collectEffect, transform.position, Quaternion.identity);
}
// Play collect sound
if (collectSound != null)
{
AudioSource.PlayClipAtPoint(collectSound, transform.position);
}
// Destroy item
Destroy(gameObject);
}
public string GetItemName()
{
return itemName;
}
public int GetValue()
{
return value;
}
}
// AudioManager Singleton
public class AudioManager : MonoBehaviour
{
public static AudioManager Instance { get; private set; }
[Header("Audio Settings")]
[SerializeField] private AudioSource musicSource;
[SerializeField] private AudioSource sfxSource;
[SerializeField] private AudioClip[] musicTracks;
[SerializeField] private AudioClip[] soundEffects;
private Dictionary<string, AudioClip> soundDictionary;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
// Initialize sound dictionary
soundDictionary = new Dictionary<string, AudioClip>();
foreach (AudioClip clip in soundEffects)
{
soundDictionary[clip.name] = clip;
}
// Play background music
PlayMusic("BackgroundMusic");
}
public void PlayMusic(string musicName)
{
AudioClip musicClip = Array.Find(musicTracks, clip => clip.name == musicName);
if (musicClip != null && musicSource != null)
{
musicSource.clip = musicClip;
musicSource.loop = true;
musicSource.Play();
}
}
public void PlaySound(string soundName)
{
if (soundDictionary.ContainsKey(soundName) && sfxSource != null)
{
sfxSource.PlayOneShot(soundDictionary[soundName]);
}
}
public void StopMusic()
{
if (musicSource != null)
{
musicSource.Stop();
}
}
public void SetMusicVolume(float volume)
{
if (musicSource != null)
{
musicSource.volume = volume;
}
}
public void SetSFXVolume(float volume)
{
if (sfxSource != null)
{
sfxSource.volume = volume;
}
}
}
2. Unreal Engine Game mit C++ - 3D Shooter
// PlayerCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CameraComponent.h"
#include "Components/HealthComponent.h"
#include "Components/StaminaComponent.h"
#include "Components/WeaponComponent.h"
#include "PlayerCharacter.generated.h"
UCLASS()
class APlayerCharacter : public ACharacter
{
GENERATED_BODY()
public:
APlayerCharacter();
virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
virtual void BeginPlay() override;
// Movement
UFUNCTION(BlueprintCallable, Category = "Player")
void MoveForward(float Value);
UFUNCTION(BlueprintCallable, Category = "Player")
void MoveRight(float Value);
UFUNCTION(BlueprintCallable, Category = "Player")
void Turn(float Value);
UFUNCTION(BlueprintCallable, Category = "Player")
void StartJump();
UFUNCTION(BlueprintCallable, Category = "Player")
void StopJumping();
// Combat
UFUNCTION(BlueprintCallable, Category = "Player")
void StartFire();
UFUNCTION(BlueprintCallable, Category = "Player")
void StopFire();
UFUNCTION(BlueprintCallable, Category = "Player")
void Reload();
UFUNCTION(BlueprintCallable, Category = "Player")
void Aim();
UFUNCTION(BlueprintCallable, Category = "Player")
void StopAiming();
// Camera
UFUNCTION(BlueprintCallable, Category = "Player")
void ToggleCameraMode();
// Health and Stamina
UFUNCTION(BlueprintCallable, Category = "Player")
float GetHealth() const;
UFUNCTION(BlueprintCallable, Category = "Player")
float GetMaxHealth() const;
UFUNCTION(BlueprintCallable, Category = "Player")
float GetStamina() const;
UFUNCTION(BlueprintCallable, Category = "Player")
float GetMaxStamina() const;
// Weapon
UFUNCTION(BlueprintCallable, Category = "Player")
AWeaponComponent* GetCurrentWeapon() const;
UFUNCTION(BlueprintCallable, Category = "Player")
void EquipWeapon(class AWeapon* Weapon);
// Movement
UFUNCTION(BlueprintCallable, Category = "Player")
bool IsMoving() const;
UFUNCTION(BlueprintCallable, Category = "Player")
bool IsJumping() const;
UFUNCTION(BlueprintCallable, Category = "Player")
bool IsAiming() const;
UFUNCTION(BlueprintCallable, Category = "Player")
bool IsSprinting() const;
protected:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
private:
// Movement
void MoveForward(float Value);
void MoveRight(float Value);
void Turn(float Value);
void StartJump();
void StopJumping();
// Combat
void StartFire();
void StopFire();
void Reload();
void Aim();
void StopAiming();
// Camera
void ToggleCameraMode();
// Movement states
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player")
bool bIsMoving;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player")
bool bIsJumping;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player")
bool bIsAiming;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Player")
bool bIsSprinting;
// Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USpringArmComponent* SpringArmComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UCameraComponent* CameraComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UHealthComponent* HealthComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UStaminaComponent* StaminaComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UWeaponComponent* WeaponComponent;
// Movement variables
UPROPERTY(EditDefaults, Category = "Player|Movement")
float BaseTurnRate;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float LookUpRate;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float JumpHeight;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float AirControl;
// Combat variables
UPROPERTY(EditDefaults, Category = "Player|Combat")
float BaseTurnRate;
UPROPERTY(EditDefaults, Category = "Player|Combat")
float AimSensitivity;
// Camera variables
UPROPERTY(EditDefaults, Category = "Player|Camera")
bool bFirstPerson;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float BaseFOV;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float AimFOV;
// Movement
UPROPERTY(EditDefaults, Category = "Player|Movement")
float MaxWalkSpeed;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float MaxRunSpeed;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float MaxSprintSpeed;
UPROPERTY(EditDefaults, Category = "Player|Movement")
float SprintStaminaCost;
// Combat
UPROPERTY(EditDefaults, Category = "Player|Combat")
float BaseTurnRate;
UPROPERTY(EditDefaults, Category = "Player|Combat")
float AimSensitivity;
// Camera
UPROPERTY(EditDefaults, Category = "Player|Camera")
bool bFirstPerson;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float BaseFOV;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float AimFOV;
};
// PlayerCharacter.cpp
#include "PlayerCharacter.h"
#include "Engine/World.h"
#include "EnhancedInputComponent.h"
#include "Components/EnhancedInputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
APlayerCharacter::APlayerCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create components
SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("HealthComponent"));
StaminaComponent = CreateDefaultSubobject<UStaminaComponent>(TEXT("StaminaComponent"));
WeaponComponent = CreateDefaultSubobject<UWeaponComponent>(TEXT("WeaponComponent"));
// Set default values
BaseTurnRate = 45.0f;
LookUpRate = 45.0f;
JumpHeight = 300.0f;
AirControl = 0.05f;
AimSensitivity = 1.0f;
bFirstPerson = true;
BaseFOV = 90.0f;
AimFOV = 60.0f;
MaxWalkSpeed = 600.0f;
MaxRunSpeed = 900.0f;
MaxSprintSpeed = 1200.0f;
SprintStaminaCost = 10.0f;
bIsMoving = false;
bIsJumping = false;
bIsAiming = false;
bIsSprinting = false;
}
void APlayerCharacter::BeginPlay()
{
Super::BeginPlay();
// Setup input
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetupPlayerInput(this);
}
// Initialize components
if (HealthComponent)
{
HealthComponent->OnDeath.AddDynamic(this, &APlayerCharacter::OnDeath);
}
// Equip default weapon
if (WeaponComponent)
{
// This would be set up based on game rules
// WeaponComponent->SpawnWeapon(DefaultWeaponClass);
}
}
void APlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Update movement states
UpdateMovementStates();
// Update camera
UpdateCamera(DeltaTime);
// Handle stamina regeneration
if (StaminaComponent && !bIsSprinting)
{
StaminaComponent->RegenerateStamina(DeltaTime);
}
}
void APlayerCharacter::SetupPlayerInput(UInputComponent* PlayerInputComponent)
{
// Bind movement actions
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_MoveForward, this, &APlayerCharacter::MoveForward);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_MoveRight, this, &APlayerCharacter::MoveRight);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Turn, this, &APlayerCharacter::Turn);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Jump, this, &APlayerCharacter::StartJump);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_StopJump, this, &APlayerCharacter::StopJumping);
// Bind combat actions
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Fire, this, &APlayerCharacter::StartFire);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_StopFire, this, &APlayerCharacter::StopFire);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Reload, this, &APlayerCharacter::Reload);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Aim, this, &APlayerCharacter::Aim);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_StopAim, this, &APlayerCharacter::StopAiming);
// Bind utility actions
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_ToggleCamera, this, &APlayerCharacter::ToggleCameraMode);
// Bind sprint action
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_Sprint, this, &APlayerCharacter::StartSprint);
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_StopSprint, this, &APlayerCharacter::StopSprinting);
}
void APlayerCharacter::MoveForward(float Value)
{
if (Controller != nullptr)
{
const FRotator = GetControlRotation();
const FRotation = FRotator.Pitch;
// Calculate movement direction
const FVector Direction = FRotation.Vector();
const FVector MovementVector = Direction * Value;
// Apply movement
AddMovementInput(MovementVector);
// Set moving state
bIsMoving = FMath::Abs(Value) > 0.1f;
}
}
void APlayerCharacter::MoveRight(float Value)
{
if (Controller != nullptr)
{
const FRotator = GetControlRotation();
const FRotation = FRotator.Yaw;
// Calculate movement direction
const FVector Direction = FRotation.RightVector();
const FVector MovementVector = Direction * Value;
// Apply movement
AddMovementInput(MovementVector);
// Set moving state
bIsMoving = FMath::Abs(Value) > 0.1f;
}
}
void APlayerCharacter::Turn(float Value)
{
if (Controller != nullptr)
{
// Apply turn
AddControllerYawInput(Value * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
}
void APlayerCharacter::StartJump()
{
if (bIsJumping) return;
// Check if can jump
if (CanJump())
{
// Jump
bIsJumping = true;
// Play jump montage
if (UAnimInstance* JumpMontage = GetMesh()->GetAnimInstance())
{
JumpMontage->Montage_Play("Jump");
}
// Apply jump force
LaunchCharacter(FVector(0.0f, 0.0f, JumpHeight));
// Play jump sound
if (USoundBase* JumpSound = GEngine->SoundBase)
{
JumpSound->PlaySound2D(GetActorLocation());
}
}
}
void APlayerCharacter::StopJumping()
{
// Stop jumping
bIsJumping = false;
// Stop jump montage
if (UAnimInstance* JumpMontage = GetMesh()->GetAnimInstance())
{
JumpMontage->Montage_Stop("Jump");
}
}
void APlayerCharacter::StartFire()
{
if (WeaponComponent)
{
WeaponComponent->StartFire();
// Set aiming state
bIsAiming = true;
// Update camera for aiming
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(AimFOV);
}
}
}
void APlayerCharacter::StopFire()
{
if (WeaponComponent)
{
WeaponComponent->StopFire();
}
// Clear aiming state
bIsAiming = false;
// Reset camera
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(BaseFOV);
}
}
void APlayerCharacter::Reload()
{
if (WeaponComponent)
{
WeaponComponent->Reload();
}
}
void APlayerCharacter::Aim()
{
// Start aiming
bIsAiming = true;
// Update camera for aiming
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(AimFOV);
// Reduce mouse sensitivity for precision aiming
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetAimSensitivity(AimSensitivity * 0.5f);
}
}
// Play aim montage
if (UAnimInstance* AimMontage = GetMesh()->GetAnimInstance())
{
AimMontage->Montage_Play("Aim");
}
}
void APlayerCharacter::StopAiming()
{
// Stop aiming
bIsAiming = false;
// Reset camera
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(BaseFOV);
}
// Reset mouse sensitivity
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetAimSensitivity(AimSensitivity);
}
// Stop aim montage
if (UAnimInstance* AimMontage = GetMesh()->GetAnimInstance())
{
AimMontage->Montage_Stop("Aim");
}
}
void APlayerCharacter::ToggleCameraMode()
{
// Toggle between first and third person
bFirstPerson = !bFirstPerson;
// Update camera
if (CameraComponent)
{
if (bFirstPerson)
{
CameraComponent->AttachToComponent(SpringArmComponent, USpringArmComponent::SocketName);
CameraComponent->SetFieldOfView(bIsAiming ? AimFOV : BaseFOV);
}
else
{
CameraComponent->DetachFromController();
CameraComponent->SetFieldOfView(BaseFOV);
}
}
}
void APlayerCharacter::StartSprint()
{
if (bIsSprinting || !CanSprint()) return;
// Start sprinting
bIsSprinting = true;
// Update movement speed
if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement())
{
MovementComponent->MaxWalkSpeed = MaxSprintSpeed;
}
// Play sprint montage
if (UAnimInstance* SprintMontage = GetMesh()->GetAnimInstance())
{
SprintMontage->Montage_Play("Sprint");
}
// Play sprint sound
if (USoundBase* SprintSound = GEngine->SoundBase)
{
SprintSound->PlaySound2D(GetActorLocation());
}
}
void APlayerCharacter::StopSprinting()
{
if (!bIsSprinting) return;
// Stop sprinting
bIsSprinting = false;
// Reset movement speed
if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement())
{
MovementComponent->MaxWalkSpeed = MaxRunSpeed;
}
// Stop sprint montage
if (UAnimInstance* SprintMontage = GetMesh()->GetAnimInstance())
{
SprintMontage->Montage_Stop("Sprint");
}
}
void APlayerCharacter::UpdateMovementStates()
{
// Update jumping state
if (GetCharacterMovement())
{
bIsJumping = !GetCharacterMovement()->IsMovingOnGround();
}
// Update moving state
bIsMoving = GetVelocity().Size2D() > 0.1f;
// Handle sprint stamina
if (bIsSprinting && StaminaComponent)
{
StaminaComponent->ConsumeStamina(SprintStaminaCost * GetWorld()->GetDeltaSeconds());
// Stop sprinting if out of stamina
if (StaminaComponent->GetStamina() <= 0.0f)
{
StopSprinting();
}
}
}
void APlayerCharacter::UpdateCamera(float DeltaTime)
{
if (CameraComponent)
{
// Handle camera shake when firing
if (WeaponComponent && WeaponComponent->IsFiring())
{
float CameraShakeIntensity = WeaponComponent->GetCameraShakeIntensity();
if (CameraShakeIntensity > 0.0f)
{
// Apply camera shake
FVector CameraLocation = CameraComponent->GetComponentLocation();
FRotator CameraRotation = CameraComponent->GetComponentRotation();
// Add random offset
float RandomX = FMath::RandRange(-CameraShakeIntensity, CameraShakeIntensity);
float RandomY = FMath::RandRange(-CameraShakeIntensity, CameraShakeIntensity);
float RandomZ = FMath::RandRange(-CameraShakeIntensity, CameraShakeIntensity);
FVector CameraOffset = FVector(RandomX, RandomY, RandomZ);
FRotator CameraOffsetRotation = FRotator(RandomX, RandomY, RandomZ);
CameraComponent->SetWorldLocationAndRotation(CameraLocation + CameraOffset, CameraRotation + CameraOffsetRotation);
}
}
}
}
float APlayerCharacter::GetHealth() const
{
return HealthComponent ? HealthComponent->GetHealth() : 0.0f;
}
float APlayerCharacter::GetMaxHealth() const
{
return HealthComponent ? HealthComponent->GetMaxHealth() : 0.0f;
}
float APlayerCharacter::GetStamina() const
{
return StaminaComponent ? StaminaComponent->GetStamina() : 0.0f;
}
float APlayerCharacter::GetMaxStamina() const
{
return StaminaComponent ? StaminaComponent->MaxStamina : 0.0f;
}
AWeaponComponent* APlayerCharacter::GetCurrentWeapon() const
{
return WeaponComponent;
}
void APlayerCharacter::EquipWeapon(AWeapon* Weapon)
{
if (WeaponComponent)
{
WeaponComponent->EquipWeapon(Weapon);
}
}
bool APlayerCharacter::IsMoving() const
{
return bIsMoving;
}
bool APlayerCharacter::IsJumping() const
{
return bIsJumping;
}
bool APlayerCharacter::IsAiming() const
{
return bIsAiming;
}
bool APlayerCharacter::IsSprinting() const
{
return bIsSprinting;
}
bool APlayerCharacter::CanSprint() const
{
return !bIsJumping && StaminaComponent && StaminaComponent->GetStamina() > SprintStaminaCost;
}
void APlayerCharacter::OnDeath()
{
// Handle death
DisableInput();
// Play death montage
if (UAnimInstance* DeathMontage = GetMesh()->GetAnimInstance())
{
DeathMontage->Montage_Play("Death");
}
// Disable collision
SetActorEnableCollision(false);
// Hide weapon
if (WeaponComponent)
{
WeaponComponent->SetVisibility(false);
}
// Ragdoll physics
if (GetCapsuleComponent())
{
GetCapsuleComponent->SetSimulatePhysics(true);
}
// Game over handling
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->OnPlayerDeath();
}
}
// Enhanced Input Component
UCLASS()
class AEnhancedInputComponent : public UInputComponent
{
GENERATED_BODY()
public:
AEnhancedInputComponent();
virtual void SetupInputBinding(UInputComponent* PlayerInputComponent) override;
protected:
virtual void BeginPlay() override;
private:
UEnhancedInputAction CurrentAction;
float ActionValue;
void HandleMovement(float Value);
void HandleCombat(float Value);
void HandleUtility(float Value);
};
// Enhanced Input Component Implementation
AEnhancedInputComponent::AEnhancedInputComponent()
{
CurrentAction = EEnhancedInputAction::IA_None;
ActionValue = 0.0f;
}
void AEnhancedInputComponent::BeginPlay()
{
Super::BeginPlay();
// Setup input binding
SetupInputBinding(this);
}
void AEnhancedInputComponent::SetupInputBinding(UInputComponent* PlayerInputComponent)
{
// Movement bindings
PlayerInputComponent->BindAxis("MoveForward", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("MoveRight", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("Turn", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("LookUp", this, &AEnhancedInputComponent::HandleMovement);
// Combat bindings
PlayerInputComponent->BindAction("Fire", this, &AEnhancedInputComponent::HandleCombat);
PlayerInputComponent->BindAction("Reload", this, &AEnhancedInputComponent::HandleCombat);
PlayerInputComponent->BindAction("Aim", this, &AEnhancedInputComponent::HandleCombat);
// Utility bindings
PlayerInputComponent->BindAction("Jump", this, &AEnhancedInputComponent::HandleUtility);
PlayerInputComponent->BindAction("Sprint", this, &AEnhancedInputComponent::HandleUtility);
PlayerInputComponent->BindAction("ToggleCamera", this, &AEnhancedInputComponent::HandleUtility);
}
void AEnhancedInputComponent::HandleMovement(float Value)
{
CurrentAction = EEnhancedInputAction::IA_MoveForward;
ActionValue = Value;
if (APlayerCharacter* Player = Cast<APlayerCharacter>(GetOwner()))
{
switch (CurrentAction)
{
case EEnhancedInputAction::IA_MoveForward:
Player->MoveForward(Value);
break;
case EEnhancedInputAction::IA_MoveRight:
Player->MoveRight(Value);
break;
case EEnhancedInputAction::IA_Turn:
Player->Turn(Value);
break;
case EEnhancedInputAction::IA_LookUp:
// Handle look up/down
break;
}
}
}
void AEnhancedInputComponent::HandleCombat(float Value)
{
CurrentAction = EEnhancedInputAction::IA_Fire;
ActionValue = Value;
if (APlayerCharacter* Player = Cast<APlayerCharacter>(GetOwner()))
{
switch (CurrentAction)
{
case EEnhancedInputAction::IA_Fire:
if (Value > 0.5f)
{
Player->StartFire();
}
else
{
Player->StopFire();
}
break;
case EEnhancedInputAction::IA_Reload:
if (Value > 0.5f)
{
Player->Reload();
}
break;
case EEnhancedInputAction::IA_Aim:
if (Value > 0.5f)
{
Player->Aim();
}
else
{
Player->StopAiming();
}
break;
}
}
}
void AEnhancedInputComponent::HandleUtility(float Value)
{
CurrentAction = EEnhancedInputAction::IA_Jump;
ActionValue = Value;
if (APlayerCharacter* Player = Cast<APlayerCharacter>(GetOwner()))
{
switch (CurrentAction)
{
case EEnhancedInputAction::IA_Jump:
if (Value > 0.5f)
{
Player->StartJump();
}
else
{
Player->StopJumping();
}
break;
case EEnhancedInputAction::IA_Sprint:
if (Value > 0.5f)
{
Player->StartSprint();
}
else
{
Player->StopSprinting();
}
break;
case EEnhancedInputAction::IA_ToggleCamera:
if (Value > 0.5f)
{
Player->ToggleCameraMode();
}
break;
}
}
}
3. Custom Physics Engine mit C++
// PhysicsEngine.h
#pragma once
#include "CoreMinimal.h"
#include "Math/Vector2D.h"
#include "Math/Vector3D.h"
#include "Containers/Array.h"
#include "Containers/Map.h"
class PhysicsBody;
class Collider;
class Rigidbody;
// Physics Engine class
class PHYSICS_API PhysicsEngine
{
public:
PhysicsEngine();
~PhysicsEngine();
// World management
void SetGravity(const FVector2D& Gravity);
void SetTimeStep(float TimeStep);
// Body management
PhysicsBody* CreateBody(const FVector2D& Position, float Mass);
void DestroyBody(PhysicsBody* Body);
// Collider management
void AddCollider(PhysicsBody* Body, TSharedPtr<Collider> Collider);
void RemoveCollider(PhysicsBody* Body, Collider* Collider);
// Simulation
void Step(float DeltaTime);
// Query methods
TArray<PhysicsBody*> GetBodiesInArea(const FVector2D& Min, const FVector2D& Max);
bool IsOverlapping(const Collider* ColliderA, const Collider* ColliderB) const;
// Debug rendering
void DebugRender();
private:
void UpdateBodies(float DeltaTime);
void UpdateCollisions();
void ResolveCollisions();
void BroadPhaseCollisionDetection();
void NarrowPhaseCollisionDetection();
void CollisionResolution();
void IntegrateForces(float DeltaTime);
void ApplyGravity(float DeltaTime);
TArray<PhysicsBody*> Bodies;
TArray<TSharedPtr<Collider>> Colliders;
TArray<CollisionPair> CollisionPairs;
FVector2D Gravity;
float TimeStep;
bool bIsDebugRendering;
};
// Collision Pair structure
struct CollisionPair
{
PhysicsBody* BodyA;
PhysicsBody* BodyB;
FVector2D ContactNormal;
float PenetrationDepth;
CollisionPair(PhysicsBody* InBodyA, PhysicsBody* InBodyB, const FVector2D& InNormal, float InPenetration)
: BodyA(InBodyA), BodyB(InBodyB), ContactNormal(InNormal), PenetrationDepth(InPenetration) {}
};
// Physics Body class
class PHYSICS_API PhysicsBody
{
public:
PhysicsBody(const FVector2D& Position, float Mass);
~PhysicsBody();
// Position and movement
void SetPosition(const FVector2D& Position);
FVector2D GetPosition() const { return Position; }
void SetVelocity(const FVector2D& Velocity);
FVector2D GetVelocity() const { return Velocity; }
void AddForce(const FVector2D& Force);
void AddImpulse(const FVector2D& Impulse);
// Properties
void SetMass(float Mass);
float GetMass() const { return Mass; }
void SetStatic(bool bStatic);
bool IsStatic() const { return bStatic; }
void SetGravityScale(float Scale);
float GetGravityScale() const { return GravityScale; }
// Collision
void SetCollisionEnabled(bool bEnabled);
bool IsCollisionEnabled() const { return bCollisionEnabled; }
// Components
void AddCollider(TSharedPtr<Collider> Collider);
void RemoveCollider(Collider* Collider);
TArray<TSharedPtr<Collider>> GetColliders() const { return Colliders; }
// Material properties
void SetRestitution(float Restitution);
float GetRestitution() const { return Restitution; }
void SetFriction(float Friction);
float GetFriction() const { return Friction; }
private:
FVector2D Position;
FVector2D Velocity;
FVector2D Force;
float Mass;
float InverseMass;
bool bStatic;
float GravityScale;
bool bCollisionEnabled;
TArray<TSharedPtr<Collider>> Colliders;
float Restitution;
float Friction;
friend class PhysicsEngine;
};
// Collider base class
class PHYSICS_API Collider
{
public:
Collider();
virtual ~Collider();
// Type identification
enum class EType
{
Circle,
Rectangle,
Polygon,
Edge,
Point
};
virtual EType GetType() const = 0;
// Collision detection
virtual bool Overlaps(const Collider* Other) const = 0;
virtual bool ContainsPoint(const FVector2D& Point) const = 0;
virtual bool IntersectsLine(const FVector2D& Start, const FVector2D& End) const = 0;
// Collision response
virtual void ComputeCollisionData(const Collider* Other, FVector2D& OutNormal, float& OutPenetration) const = 0;
// Bounds
virtual FVector2D GetCenter() const = 0;
virtual FVector2D GetExtents() const = 0;
// Material properties
void SetRestitution(float Restitution);
float GetRestitution() const { return Restitution; }
void SetFriction(float Friction);
float GetFriction() const { return Friction; }
protected:
float Restitution;
float Friction;
friend class PhysicsEngine;
};
// Circle collider
class PHYSICS_API CircleCollider : public Collider
{
public:
CircleCollider(float Radius);
virtual EType GetType() const override { return Circle; }
virtual bool Overlaps(const Collider* Other) const override;
virtual bool ContainsPoint(const FVector2D& Point) const override;
virtual bool IntersectsLine(const FVector2D& Start, const FVector2D& End) const override;
virtual void ComputeCollisionData(const Collider* Other, FVector2D& OutNormal, float& OutPenetration) const override;
virtual FVector2D GetCenter() const override;
virtual FVector2D GetExtents() const override;
void SetRadius(float NewRadius);
float GetRadius() const { return Radius; }
private:
float Radius;
};
// Rectangle collider
class PHYSICS_API RectangleCollider : public Collider
{
public:
RectangleCollider(const FVector2D& Size);
virtual EType GetType() const override { return Rectangle; }
virtual bool Overlaps(const Collider* Other) const override;
virtual bool ContainsPoint(const FVector2D& Point) const override;
virtual bool IntersectsLine(const FVector2D& Start, const FVector2D& End) const override;
virtual void ComputeCollisionData(const Collider* Other, FVector2D& OutNormal, float& OutPenetration) const override;
virtual FVector2D GetCenter() const override;
virtual FVector2D GetExtents() const override;
void SetSize(const FVector2D& NewSize);
FVector2D GetSize() const { return Size; }
private:
FVector2D Size;
};
// Physics Engine Implementation
PhysicsEngine::PhysicsEngine()
{
Gravity = FVector2D(0.0f, 9.81f);
TimeStep = 1.0f / 60.0f;
bIsDebugRendering = false;
}
PhysicsEngine::~PhysicsEngine()
{
// Clean up bodies
for (PhysicsBody* Body : Bodies)
{
delete Body;
}
Bodies.Empty();
// Clean up colliders
Colliders.Empty();
}
void PhysicsEngine::SetGravity(const FVector2D& InGravity)
{
Gravity = InGravity;
}
void PhysicsEngine::SetTimeStep(float InTimeStep)
{
TimeStep = InTimeStep;
}
PhysicsBody* PhysicsEngine::CreateBody(const FVector2D& Position, float Mass)
{
PhysicsBody* Body = new PhysicsBody(Position, Mass);
Bodies.Add(Body);
return Body;
}
void PhysicsEngine::DestroyBody(PhysicsBody* Body)
{
if (Bodies.Contains(Body))
{
Bodies.Remove(Body);
delete Body;
}
}
void PhysicsEngine::AddCollider(PhysicsBody* Body, TSharedPtr<Collider> Collider)
{
if (Body && Collider)
{
Body->AddCollider(Collider);
Colliders.Add(Collider);
}
}
void PhysicsEngine::RemoveCollider(PhysicsBody* Body, Collider* Collider)
{
if (Body && Body->GetColliders().Contains(Collider))
{
Body->RemoveCollider(Collider);
}
// Remove from global list
for (int32 i = 0; i < Colliders.Num(); ++i)
{
if (Colliders[i].Get() == Collider)
{
Colliders.RemoveAt(i);
break;
}
}
}
void PhysicsEngine::Step(float DeltaTime)
{
// Update physics simulation
UpdateBodies(DeltaTime);
// Update collisions
UpdateCollisions();
// Resolve collisions
ResolveCollisions();
}
void PhysicsEngine::UpdateBodies(float DeltaTime)
{
for (PhysicsBody* Body : Bodies)
{
if (!Body->IsStatic())
{
// Apply gravity
ApplyGravity(DeltaTime);
// Integrate forces
IntegrateForces(DeltaTime);
// Update position
FVector2D NewPosition = Body->GetPosition() + Body->GetVelocity() * DeltaTime;
Body->SetPosition(NewPosition);
}
}
}
void PhysicsEngine::UpdateCollisions()
{
CollisionPairs.Empty();
// Broad phase collision detection
BroadPhaseCollisionDetection();
// Narrow phase collision detection
NarrowPhaseCollisionDetection();
}
void PhysicsEngine::BroadPhaseCollisionDetection()
{
// Simple spatial hashing for broad phase
const int32 GridSize = 100;
TMap<FVector2D, TArray<PhysicsBody*>> SpatialGrid;
// Add bodies to spatial grid
for (PhysicsBody* Body : Bodies)
{
if (!Body->IsCollisionEnabled()) continue;
FVector2D GridPos = FVector2D(
FMath::Floor(Body->GetPosition().X / GridSize),
FMath::Floor(Body->GetPosition().Y / GridSize)
);
SpatialGrid.FindOrAdd(GridPos).Add(Body);
}
// Check potential collisions
for (auto& GridCell : SpatialGrid)
{
TArray<PhysicsBody*>& CellBodies = GridCell.Value;
for (int32 i = 0; i < CellBodies.Num(); ++i)
{
for (int32 j = i + 1; j < CellBodies.Num(); ++j)
{
PhysicsBody* BodyA = CellBodies[i];
PhysicsBody* BodyB = CellBodies[j];
// Quick AABB check
if (CheckAABBOverlap(BodyA, BodyB))
{
CollisionPairs.Add(CollisionPair(BodyA, BodyB));
}
}
}
}
}
void PhysicsEngine::NarrowPhaseCollisionDetection()
{
for (CollisionPair& Pair : CollisionPairs)
{
PhysicsBody* BodyA = Pair.BodyA;
PhysicsBody* BodyB = Pair.BodyB;
// Get colliders for each body
TArray<TSharedPtr<Collider>> CollidersA = BodyA->GetColliders();
TArray<TSharedPtr<Collider>> CollidersB = BodyB->GetColliders();
// Check each collider pair
for (const TSharedPtr<Collider>& ColliderA : CollidersA)
{
for (const TSharedPtr<Collider>& ColliderB : CollidersB)
{
if (ColliderA->Overlaps(ColliderB.Get()))
{
// Compute collision data
FVector2D Normal;
float Penetration;
ColliderA->ComputeCollisionData(ColliderB.Get(), Normal, Penetration);
// Add collision pair with computed data
Pair.ContactNormal = Normal;
Pair.PenetrationDepth = Penetration;
break; // Only one collision per pair
}
}
}
}
}
void PhysicsEngine::ResolveCollisions()
{
for (CollisionPair& Pair : CollisionPairs)
{
PhysicsBody* BodyA = Pair.BodyA;
PhysicsBody* BodyB = Pair.BodyB;
// Skip if either body is static
if (BodyA->IsStatic() && BodyB->IsStatic())
{
continue;
}
// Calculate relative masses
float MassA = BodyA->GetMass();
float MassB = BodyB->GetMass();
float TotalMass = MassA + MassB;
float InverseMassA = MassA > 0.0f ? 1.0f / MassA : 0.0f;
float InverseMassB = MassB > 0.0f ? 1.0f / MassB : 0.0f;
// Calculate impulse
float Restitution = FMath::Min(BodyA->GetRestitution(), BodyB->GetRestitution());
float Impulse = (1 + Restitution) * FVector2D::Dot(Pair.ContactNormal, BodyB->GetVelocity() - BodyA->GetVelocity()) / TotalMass;
// Apply impulse
if (!BodyA->IsStatic())
{
FVector2D VelocityA = BodyA->GetVelocity();
VelocityA += Impulse * InverseMassA;
BodyA->SetVelocity(VelocityA);
}
if (!BodyB->IsStatic())
{
FVector2D VelocityB = BodyB->GetVelocity();
VelocityB -= Impulse * InverseMassB;
BodyB->SetVelocity(VelocityB);
}
// Position correction
const float Percent = 0.8f; // Position correction percentage
const float Slop = 0.2f; // Position correction slop
FVector2D CorrectionMagnitude = Pair.PenetrationDepth * Percent;
FVector2D Correction = Pair.ContactNormal * CorrectionMagnitude;
if (!BodyA->IsStatic())
{
FVector2D PositionA = BodyA->GetPosition();
PositionA -= Correction * (InverseMassA * Slop);
BodyA->SetPosition(PositionA);
}
if (!BodyB->IsStatic())
{
FVector2D PositionB = BodyB->GetPosition();
PositionB += Correction * (InverseMassB * Slop);
BodyB->SetPosition(PositionB);
}
}
}
void PhysicsEngine::IntegrateForces(float DeltaTime)
{
for (PhysicsBody* Body : Bodies)
{
if (!Body->IsStatic())
{
// Semi-implicit Euler integration
FVector2D Acceleration = Body->GetForce() * Body->GetInverseMass();
FVector2D Velocity = Body->GetVelocity() + Acceleration * DeltaTime;
Body->SetVelocity(Velocity);
Body->SetForce(FVector2D::ZeroVector); // Reset force accumulator
}
}
}
void PhysicsEngine::ApplyGravity(float DeltaTime)
{
for (PhysicsBody* Body : Bodies)
{
if (!Body->IsStatic())
{
FVector2D GravityForce = Gravity * Body->GetGravityScale() * Body->GetMass();
Body->AddForce(GravityForce);
}
}
}
bool PhysicsEngine::CheckAABBOverlap(const PhysicsBody* BodyA, const PhysicsBody* BodyB)
{
if (!BodyA || !BodyB) return false;
// Get AABB bounds for both bodies
FVector2D MinA, MaxA;
FVector2D MinB, MaxB;
GetBodyBounds(BodyA, MinA, MaxA);
GetBodyBounds(BodyB, MinB, MaxB);
// Check AABB overlap
return (MinA.X <= MaxB.X && MaxA.X >= MinB.X &&
MinA.Y <= MaxB.Y && MaxA.Y >= MinB.Y);
}
void PhysicsEngine::GetBodyBounds(const PhysicsBody* Body, FVector2D& Min, FVector2D& Max)
{
if (!Body) return;
Min = FVector2D(FLT_MAX, FLT_MAX);
Max = FVector2D(-FLT_MAX, -FLT_MAX);
for (const TSharedPtr<Collider>& Collider : Body->GetColliders())
{
FVector2D Center = Collider->GetCenter();
FVector2D Extents = Collider->GetExtents();
FVector2D ColliderMin = Center - Extents;
FVector2D ColliderMax = Center + Extents;
Min.X = FMath::Min(Min.X, ColliderMin.X);
Min.Y = FMath::Min(Min.Y, ColliderMin.Y);
Max.X = FMath::Max(Max.X, ColliderMax.X);
Max.Y = FMath::Max(Max.Y, ColliderMax.Y);
}
}
TArray<PhysicsBody*> PhysicsEngine::GetBodiesInArea(const FVector2D& Min, const FVector2D& Max)
{
TArray<PhysicsBody*> BodiesInArea;
for (PhysicsBody* Body : Bodies)
{
FVector2D BodyMin, BodyMax;
GetBodyBounds(Body, BodyMin, BodyMax);
if (BodyMin.X <= Max.X && BodyMax.X >= Min.X &&
BodyMin.Y <= Max.Y && BodyMax.Y >= Min.Y)
{
BodiesInArea.Add(Body);
}
}
return BodiesInArea;
}
bool PhysicsEngine::IsOverlapping(const Collider* ColliderA, const Collider* ColliderB) const
{
if (!ColliderA || !ColliderB) return false;
return ColliderA->Overlaps(ColliderB);
}
void PhysicsEngine::DebugRender()
{
if (!bIsDebugRendering) return;
// Render bodies
for (const PhysicsBody* Body : Bodies)
{
FVector2D Position = Body->GetPosition();
FVector2D Min, Max;
GetBodyBounds(Body, Min, Max);
// Draw body outline
DrawDebugBox(Min, Max, FColor::Green);
// Draw velocity vector
FVector2D VelocityEnd = Position + Body->GetVelocity();
DrawDebugLine(Position, VelocityEnd, FColor::Blue);
}
// Render colliders
for (const TSharedPtr<Collider>& Collider : Colliders)
{
FVector2D Center = Collider->GetCenter();
FVector2D Extents = Collider->GetExtents();
DrawDebugBox(Center - Extents, Center + Extents, FColor::Red);
}
// Render spatial grid
if (bIsDebugRendering)
{
const int32 GridSize = 100;
const FColor GridColor = FColor(0, 0, 1, 0.1f);
for (int32 X = 0; X < 10; X++)
{
for (int32 Y = 0; Y < 10; Y++)
{
FVector2D GridMin(X * GridSize, Y * GridSize);
FVector2D GridMax((X + 1) * GridSize, (Y + 1) * GridSize);
DrawDebugBox(GridMin, GridMax, GridColor);
}
}
}
}
// Circle Collider Implementation
CircleCollider::CircleCollider(float InRadius)
: Radius(InRadius)
{
}
bool CircleCollider::Overlaps(const Collider* Other) const
{
if (!Other) return false;
switch (Other->GetType())
{
case Circle:
{
const CircleCollider* OtherCircle = static_cast<const CircleCollider*>(Other);
float Distance = FVector2D::Dist(GetCenter(), OtherCircle->GetCenter());
return Distance < (Radius + OtherCircle->GetRadius());
}
case Rectangle:
{
const RectangleCollider* OtherRect = static_cast<const RectangleCollider*>(Other);
return OverlapsRectangle(OtherRect);
}
default:
return false;
}
}
bool CircleCollider::ContainsPoint(const FVector2D& Point) const
{
return FVector2D::Dist(GetCenter(), Point) <= Radius;
}
bool CircleCollider::IntersectsLine(const FVector2D& Start, const FVector2D& End) const
{
// Line-circle intersection test
FVector2D D = End - Start;
float A = D.Dot(D);
float B = 2.0f * D.Dot(Start);
float C = Start.SizeSquared() - Radius * Radius;
float Discriminant = B * B - 4.0f * A * C;
if (Discriminant < 0.0f)
{
return false; // No intersection
}
float T1 = (-B - FMath::Sqrt(Discriminant)) / (2.0f * A);
float T2 = (-B + FMath::Sqrt(Discriminant)) / (2.0f * A);
if (T1 >= 0.0f && T1 <= 1.0f)
{
return ContainsPoint(Start + D * T1);
}
if (T2 >= 0.0f && T2 <= 1.0f)
{
return ContainsPoint(Start + D * T2);
}
return false;
}
void CircleCollider::ComputeCollisionData(const Collider* Other, FVector2D& OutNormal, float& OutPenetration) const
{
if (!Other || Other->GetType() != Circle)
{
OutNormal = FVector2D::ZeroVector;
OutPenetration = 0.0f;
return;
}
const CircleCollider* OtherCircle = static_cast<const CircleCollider*>(Other);
// Calculate collision normal
FVector2D Direction = OtherCircle->GetCenter() - GetCenter();
float Distance = Direction.Size();
if (Distance > 0.0f)
{
OutNormal = Direction / Distance;
}
else
{
OutNormal = FVector2D::ZeroVector;
}
// Calculate penetration depth
OutPenetration = (Radius + OtherCircle->GetRadius()) - Distance;
}
FVector2D CircleCollider::GetCenter() const
{
return Position;
}
FVector2D CircleCollider::GetExtents() const
{
return FVector2D(Radius, Radius);
}
void CircleCollider::SetRadius(float NewRadius)
{
Radius = NewRadius;
}
// Rectangle Collider Implementation
RectangleCollider::RectangleCollider(const FVector2D& InSize)
: Size(InSize)
{
}
bool RectangleCollider::Overlaps(const Collider* Other) const
{
if (!Other) return false;
switch (Other->GetType())
{
case Rectangle:
{
const RectangleCollider* OtherRect = static_cast<const RectangleCollider*>(Other);
return OverlapsRectangle(OtherRect);
}
case Circle:
{
const CircleCollider* OtherCircle = static_cast<const CircleCollider*>(Other);
return OtherCircle->Overlaps(this);
}
default:
return false;
}
}
bool RectangleCollider::OverlapsRectangle(const RectangleCollider* Other) const
{
if (!Other) return false;
FVector2A = GetCenter() - GetExtents();
FVector2B = GetCenter() + GetExtents();
FVector2C = Other->GetCenter() - Other->GetExtents();
FVector2D = Other->GetCenter() + Other->GetExtents();
return (A.X <= C.X && B.X >= C.X &&
A.Y <= C.Y && B.Y >= C.Y);
}
bool RectangleCollider::ContainsPoint(const FVector2D& Point) const
{
FVector2A = GetCenter() - GetExtents();
FVector2B = GetCenter() + GetExtents();
return Point.X >= A.X && Point.X <= B.X &&
Point.Y >= A.Y && Point.Y <= B.Y;
}
bool RectangleCollider::IntersectsLine(const FVector2D& Start, const FVector2D& End) const
{
// Line-rectangle intersection test
FVector2A = GetCenter() - GetExtents();
FVector2B = GetCenter() + GetExtents();
// Check if line intersects rectangle
return LineIntersectsRect(Start, End, A, B);
}
void RectangleCollider::ComputeCollisionData(const Collider* Other, FVector2D& OutNormal, float& OutPenetration) const
{
if (!Other || Other->GetType() != Rectangle)
{
OutNormal = FVector2D::ZeroVector;
OutPenetration = 0.0f;
return;
}
const RectangleCollider* OtherRect = static_cast<const RectangleCollider*>(Other);
// Calculate overlap
FVector2A = GetCenter() - GetExtents();
FVector2B = GetCenter() + GetExtents();
FVector2C = OtherRect->GetCenter() - OtherRect->GetExtents();
FVector2D = OtherRect->GetCenter() + OtherRect->GetExtents();
// Calculate overlap
float OverlapX = FMath::Min(B.X, C.X + OtherRect->GetExtents().X) - FMath::Max(A.X, C.X - OtherRect->GetExtents().X);
float OverlapY = FMath::Min(B.Y, C.Y + OtherRect->GetExtents().Y) - FMath::Max(A.Y, C.Y - OtherRect->GetExtents().Y);
// Calculate collision normal and penetration
if (OverlapX < OverlapY)
{
OutNormal = FVector2D(1.0f, 0.0f);
OutPenetration = OverlapX;
}
else
{
OutNormal = FVector2D(0.0f, 1.0f);
OutPenetration = OverlapY;
}
// Flip normal if needed
if (FVector2D::Dot((GetCenter() - OtherRect->GetCenter()), OutNormal) < 0.0f)
{
OutNormal = -OutNormal;
}
}
FVector2D RectangleCollider::GetCenter() const
{
return Position;
}
FVector2D RectangleCollider::GetExtents() const
{
return Size * 0.5f;
}
void RectangleCollider::SetSize(const FVector2D& NewSize)
{
Size = NewSize;
}
// Line-Rectangle intersection helper
bool LineIntersectsRect(const FVector2D& Start, const FVector2D& End, const FVector2D& RectMin, const FVector2D& RectMax)
{
// Check if line segment intersects rectangle
// This is a simplified implementation
return Start.X <= RectMax.X && End.X >= RectMin.X &&
Start.Y <= RectMax.Y && End.Y >= RectMin.Y &&
Start.X <= End.X && Start.Y <= End.Y;
}
Game Engine Vergleich
| Engine | Sprachen | Plattformen | Stärken | Lizenz | Typ |
|---|---|---|---|---|---|
| Unity | C# | Cross-Platform | Mittel | Kostenlos | All |
| Unreal | C++ | Cross-Platform | Hoch | Lizenzgebühr | AAA |
| Godot | GDScript/C# | Cross-Platform | Mittel | Kostenlos | Indie |
| CryEngine | C++ | Cross-Platform | Hoch | Lizenzgebühr | AAA |
| Amazon Lumberyard | Lua | Cross-Platform | Mittel | Kostenlos | Cloud |
3D-Graphics Pipeline
Rendering Pipeline Stages
graph TD
A[Application] --> B[Vertex Processing]
B --> C[Primitive Assembly]
C --> D[Vertex Shader]
D --> E[Fragment Shader]
E --> F[Per-Fragment Operations]
F --> G[Blending]
G --> H[Frame Buffer]
H --> I[Display]
Shader Types
| Typ | Zweck | Sprache | Komplexität |
|---|---|---|---|
| Vertex Shader | Vertex-Transformation | GLSL/HLSL | Mittel |
| Fragment Shader | Pixel-Farbe | GLSL/HLSL | Hoch |
| Geometry Shader | Primitive-Erzeugung | GLSL/HLSL | Sehr Hoch |
| Compute Shader | General-Purpose | GLSL/HLSL | Hoch |
Physics Engine Konzepte
Collision Detection Algorithmen
| Algorithm | Typ | Komplexität | Genauigkeit | Anwendung |
|---|---|---|---|---|
| AABB | Broad Phase | O(1) | Niedrig | Schnelle Filterung |
| OBB | Narrow Phase | O(n) | Mittel | Genau |
| SAT | Narrow Phase | O(log n) | Hoch | Präzise |
| GJK | Narrow Phase | O(log n) | Sehr Hoch | Komplexe |
Integration Methods
| Methode | Stabilität | Energie-Effizienz | Anwendung | |--------|-----------|-----------------|-------------|---------| | Euler | Bedingt | Gering | Einfach | General Purpose | | Verlet | Bedingt | Gering | Stabil | Präzise | | RK4 | Bedingt | Gering | Stabil | Präzise |
Animation Systeme
Skeletal Animation
// Bone structure
struct Bone
{
FString Name;
int32 ParentIndex;
FVector3D Position;
FQuat4 Rotation;
TArray<Bone> Children;
// Transform matrix
FMatrix44 Transform;
// Animation data
TArray<FTransform> Keyframes;
int32 CurrentKeyframe;
float AnimationSpeed;
bool bLooping;
};
// Animation system
class AnimationSystem
{
public:
void UpdateAnimation(float DeltaTime);
void PlayAnimation(const FString& AnimationName);
void StopAnimation();
void SetAnimationSpeed(float Speed);
void SetLooping(bool bLoop);
private:
TArray<SkeletalAnimation*> Animations;
SkeletalAnimation* CurrentAnimation;
float CurrentTime;
};
Animation Blending
// Animation blending states
enum class EAnimationBlendState
{
Idle,
Moving,
Jumping,
Attacking,
Dead
};
// Blend tree structure
struct FAnimationBlendState
{
EAnimationState State;
float BlendTime;
float BlendDuration;
UAnimationAsset* Animation;
};
Game Loop Implementierung
Fixed Time Step Game Loop
void Game::Tick(float DeltaTime)
{
// Accumulate time
AccumulatedTime += DeltaTime;
// Fixed timestep update
while (AccumulatedTime >= FixedTimeStep)
{
// Update game logic
UpdateGameLogic(FixedTimeStep);
// Handle input
HandleInput(FixedTimeStep);
// Update physics
UpdatePhysics(FixedTimeStep);
// Accumulate time
AccumulatedTime -= FixedTimeStep;
}
// Render
Render();
}
Variable Time Step Game Loop
void Game::Tick(float DeltaTime)
{
// Update game logic
UpdateGameLogic(DeltaTime);
// Handle input
HandleInput(DeltaTime);
// Update physics
UpdatePhysics(DeltaTime);
// Render
Render();
}
Mobile Game Optimierung
Performance Techniques
- Level of Detail (LOD): Dynamische Qualitätsanpassung
- Occlusion Culling: Unsichtbare Objekte ausblenden
- Frustum Culling: Sichtbaren Bereich begrenzen
- Object Pooling: Objekte wiederverwenden
- Texture Compression: Grafik-Kompression
Memory Management
// Object pooling for game objects
template<typename T>
class ObjectPool
{
public:
ObjectPool(int32 PoolSize = 100)
{
for (int32 i = 0; i < PoolSize; ++i)
{
AvailableObjects.Add(new T());
}
}
T* GetObject()
{
if (AvailableObjects.Num() > 0)
{
T* Object = AvailableObjects.Pop();
ActiveObjects.Add(Object);
return Object;
}
return new T();
}
void ReturnObject(T* Object)
{
if (ActiveObjects.Contains(Object))
{
ActiveObjects.Remove(Object);
AvailableObjects.Add(Object);
}
}
private:
TArray<T*> AvailableObjects;
TArray<T*> ActiveObjects;
};
Vorteile und Nachteile
Vorteile von Game Engines
- Rapid Development: Visuelle Editoren, Drag-and-Drop
- Cross-Platform: Einmal für alle Plattformen entwickeln
- Built-in Systems: Physik, Audio, Animation, Networking
- Asset Pipeline: Asset-Management und -Import
- Community Support: Große Entwickler-Communities
Nachteile
- Performance: Nicht immer optimal für AAA-Spiele
- Flexibilität: Engine-spezifische Einschränkungen
- Lizenzkosten: Kommerzielle Lizenzmodelle
- Dateigröße: Große Build-Größen
- Debugging: Engine-spezifische Debugging-Werkzeuge
Häufige Prüfungsfragen
-
Was ist der Unterschied zwischen Unity und Unreal Engine? Unity verwendet C# und ist für Indie- und Mobile-Spiele optimiert, Unreal Engine verwendet C++ und für AAA-Spiele mit High-End-Grafik.
-
Erklären Sie die Game Loop! Die Game Loop ist der zentrale Zyklus aus Input-Handling, Logic-Update, Physics-Update und Rendering mit festem oder variablem Zeitintervall.
-
Wann verwendet man welche Kollisionserkennung? AABB für schnelle Filterung, OBB für präzise Kollisionen, SAT für komplexe Geometrien, GJK für komplexe Szenarien.
-
Was ist der Zweck von Shadern? Shaders sind kleine Programme, die auf der GPU laufen und die visuelle Darstellung von 3D-Objekten steuern.
Wichtigste Quellen
- https://unity.com/
- https://unrealengine.com/
- https://docs.unity3d.com/
- https://docs.unrealengine.com/