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.
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/