Fundamentos de Desarrollo de Juegos: Unity, Unreal Engine, Gráficos 3D, Physics y Animación
Este artículo es una introducción completa a los fundamentos del desarrollo de juegos incluyendo Unity, Unreal Engine, gráficos 3D, Physics Engines y animación con ejemplos prácticos.
En resumen
El desarrollo de juegos combina programación, gráficos 3D, física y animación. Unity y Unreal Engine son los motores líderes, C# y C++ son los lenguajes de programación principales, OpenGL/Vulkan son las APIs de gráficos.
Con proyectos prácticos y casos de estudio reales, el curso te proporcionará el conocimiento necesario para iniciar o avanzar tu carrera en desarrollo de juegos.
Obtén más información aquí: Game Development E-Degree.
Descripción técnica concisa
Desarrollo de juegos es la creación de software interactivo de entretenimiento mediante la combinación de programación, gráficos, sonido y mecánicas de juego.
Componentes clave:
Game Engines
- Unity: Motor C# para juegos 2D/3D, multiplataforma
- Unreal Engine: Motor C++ para juegos AAA, gráficos de alta gama
- Godot: Motor de código abierto con GDScript/C#
- CryEngine: Motor C++ para gráficos realistas
Gráficos 3D
- Rendering Pipeline: Vertex → Fragment → Display
- Shaders: GLSL/HLSL para efectos visuales
- Lighting: Modelos de iluminación (Phong, PBR)
- Texturing: Materiales 2D/3D y 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: Animación basada en timeline
- Procedural Animation: Generada algorítmicamente
- Morph Targets: Interpolación de formas
Puntos clave para examen
- Desarrollo de juegos: Creación de juegos interactivos
- Unity: Motor C# para juegos multiplataforma
- Unreal Engine: Motor C++ para juegos AAA
- Gráficos 3D: Rendering Pipeline, Shaders, Lighting
- Physics: Detección de colisiones, simulación física
- Animation: Animación Skeletal, Keyframe, Procedural
- Game Loop: Ciclo Update-Render, Fixed Time Step
- Relevancia profesional: Desarrollo y tecnologías de juegos modernos
Componentes clave
- Game Engine: Base para el desarrollo de juegos
- Rendering Pipeline: Procesamiento y visualización de gráficos
- Physics Engine: Simulación física y detección de colisiones
- Animation System: Movimiento y animación de personajes
- Input System: Interacción del usuario y controles
- Audio System: Efectos de sonido y música
- UI System: Interfaz de usuario y HUD
- Networking: Funcionalidad multijugador y en línea
Ejemplos prácticos
1. Juego Unity con C# - Plataformas 2D
using UnityEngine;
using System.Collections;
// Player Controller para plataformas 2D
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()
{
// Movimiento horizontal
horizontalInput = Input.GetAxisRaw("Horizontal");
// Input de salto
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
}
// Input de dash
if (Input.GetButtonDown("Dash"))
{
Dash();
}
// Input de ataque
if (Input.GetButtonDown("Attack"))
{
Attack();
}
}
private void HandleMovement()
{
// Movimiento horizontal con aceleración suave
float targetVelocityX = horizontalInput * moveSpeed;
velocity.x = Mathf.SmoothDamp(velocity.x, targetVelocityX, 0.1f);
// Aplicar movimiento
rb.velocity = new Vector2(velocity.x, rb.velocity.y);
// Voltear personaje según dirección
if (horizontalInput != 0 && horizontalInput != transform.localScale.x)
{
Flip();
}
}
private void HandlePhysics()
{
// Verificar contacto con suelo
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// Limitar velocidad de caída
if (rb.velocity.y < -MAX_FALL_SPEED)
{
rb.velocity = new Vector2(rb.velocity.x, -MAX_FALL_SPEED);
}
// Aplicar gravedad
if (!isGrounded)
{
rb.velocity += Vector2.up * Physics2D.gravity * Time.fixedDeltaTime * 2f;
}
}
private void Jump()
{
// Resetear velocidad vertical
rb.velocity = new Vector2(rb.velocity.x, 0f);
// Aplicar fuerza de salto
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
// Disparar animación de salto
if (animator != null)
{
animator.SetTrigger("Jump");
}
// Reproducir sonido de salto
PlaySound("Jump");
}
private void Dash()
{
// Aplicar fuerza de dash
float dashForce = 15f;
float dashDirection = facingRight ? 1f : -1f;
rb.velocity = new Vector2(dashForce * dashDirection, rb.velocity.y);
// Disparar animación de dash
if (animator != null)
{
animator.SetTrigger("Dash");
}
// Reproducir sonido de dash
PlaySound("Dash");
// Iniciar cooldown de dash
StartCoroutine(DashCooldown());
}
private void Attack()
{
// Disparar animación de ataque
if (animator != null)
{
animator.SetTrigger("Attack");
}
// Reproducir sonido de ataque
PlaySound("Attack");
// Detectar y dañar enemigos
DetectAndDamageEnemies();
}
private void Flip()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
private void UpdateAnimations()
{
if (animator == null) return;
// Configurar parámetros de movimiento
animator.SetFloat("Speed", Mathf.Abs(rb.velocity.x));
animator.SetBool("IsGrounded", isGrounded);
animator.SetFloat("VerticalSpeed", rb.velocity.y);
// Configurar estados de animación
if (Mathf.Abs(rb.velocity.x) > 0.1f)
{
animator.SetBool("IsMoving", true);
}
else
{
animator.SetBool("IsMoving", false);
}
}
private void DetectAndDamageEnemies()
{
// Detectar enemigos en rango de ataque
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)
{
// Reproducir sonido a través de AudioManager
AudioManager.Instance.PlaySound(soundName);
}
private IEnumerator DashCooldown()
{
// Deshabilitar dash durante el cooldown
float cooldownTime = 1f;
// Retroalimentación visual
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;
}
}
// Manejo de colisiones
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"))
{
// Lógica específica de plataforma
HandlePlatformCollision(collision);
}
}
private void TakeDamage(int damage)
{
// Aplicar daño al jugador
// Esto sería manejado por un sistema de salud separado
// Disparar animación de daño
if (animator != null)
{
animator.SetTrigger("Hurt");
}
// Reproducir sonido de daño
PlaySound("Hurt");
// Knockback
Vector2 knockbackDirection = (transform.position - collision.transform.position).normalized;
rb.AddForce(knockbackDirection * 5f, ForceMode2D.Impulse);
// Iniciar frames de invulnerabilidad
StartCoroutine(InvincibilityFrames());
}
private void CollectItem(GameObject item)
{
// Lógica de recolección de item
CollectibleItem collectible = item.GetComponent<CollectibleItem>();
if (collectible != null)
{
collectible.Collect();
}
// Reproducir sonido de recolección
PlaySound("Collect");
// Disparar animación de recolección
if (animator != null)
{
animator.SetTrigger("Collect");
}
}
private void HandlePlatformCollision(Collision2D collision)
{
// Hacer al jugador hijo de la plataforma móvil
if (collision.gameObject.CompareTag("MovingPlatform"))
{
transform.SetParent(collision.transform);
}
}
private void OnCollisionExit2D(Collision2D collision)
{
// Desvincularse de plataformas móviles
if (collision.gameObject.CompareTag("MovingPlatform"))
{
transform.SetParent(null);
}
}
private IEnumerator InvincibilityFrames()
{
// Habilitar invulnerabilidad
Physics2D.IgnoreLayerCollision(gameObject, LayerMask.GetMask("Enemy"));
// Retroalimentación visual
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;
}
// Deshabilitar invulnerabilidad
Physics2D.IgnoreLayerCollision(gameObject, LayerMask.GetMask("Enemy"));
}
// Métodos públicos
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()
{
// Encontrar jugador
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
if (playerObj != null)
{
player = playerObj.transform;
}
}
private void Update()
{
if (isDead) return;
// Verificar si el jugador está en rango
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
);
// Voltear enemigo según dirección
if (direction.x > 0 && transform.localScale.x < 0)
{
Flip();
}
else if (direction.x < 0 && transform.localScale.x > 0)
{
Flip();
}
}
private void Patrol()
{
// Comportamiento de patrulla simple
// Esto se expandiría con waypoints o movimiento aleatorio
transform.position += Vector2.right * moveSpeed * Time.deltaTime * Mathf.Sign(Mathf.Sin(Time.time));
}
private void Attack()
{
// Disparar animación de ataque
if (animator != null)
{
animator.SetTrigger("Attack");
}
// Iniciar cooldown de ataque
StartCoroutine(AttackCooldown());
// Infligir daño al jugador si está en rango
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;
// Disparar animación de daño
if (animator != null)
{
animator.SetTrigger("Hurt");
}
// Retroalimentación visual
SpriteRenderer spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
StartCoroutine(FlashRed());
}
// Verificar si está muerto
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;
// Disparar animación de muerte
if (animator != null)
{
animator.SetTrigger("Die");
}
// Deshabilitar colisión
GetComponent<Collider2D>().enabled = false;
// Deshabilitar movimiento
enabled = false;
// Generar efecto de muerte
if (deathEffect != null)
{
Instantiate(deathEffect, transform.position, Quaternion.identity);
}
// Eliminar enemigo tras retraso
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;
// Agregar al inventario del jugador
PlayerController player = FindObjectOfType<PlayerController>();
if (player != null)
{
// Esto interactuaría con un sistema de inventario
Debug.Log($"Collected {itemName} worth {value} points");
}
// Generar efecto de recolección
if (collectEffect != null)
{
Instantiate(collectEffect, transform.position, Quaternion.identity);
}
// Reproducir sonido de recolección
if (collectSound != null)
{
AudioSource.PlayClipAtPoint(collectSound, transform.position);
}
// Destruir 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()
{
// Inicializar diccionario de sonidos
soundDictionary = new Dictionary<string, AudioClip>();
foreach (AudioClip clip in soundEffects)
{
soundDictionary[clip.name] = clip;
}
// Reproducir música de fondo
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. Juego de Unreal Engine con C++ - Shooter 3D
// 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;
// Movimiento
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();
// Combate
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();
// Cámara
UFUNCTION(BlueprintCallable, Category = "Player")
void ToggleCameraMode();
// Salud y resistencia
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;
// Arma
UFUNCTION(BlueprintCallable, Category = "Player")
AWeaponComponent* GetCurrentWeapon() const;
UFUNCTION(BlueprintCallable, Category = "Player")
void EquipWeapon(class AWeapon* Weapon);
// Movimiento
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:
// Movimiento
void MoveForward(float Value);
void MoveRight(float Value);
void Turn(float Value);
void StartJump();
void StopJumping();
// Combate
void StartFire();
void StopFire();
void Reload();
void Aim();
void StopAiming();
// Cámara
void ToggleCameraMode();
// Estados de movimiento
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;
// Componentes
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;
// Variables de movimiento
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;
// Variables de combate
UPROPERTY(EditDefaults, Category = "Player|Combat")
float BaseTurnRate;
UPROPERTY(EditDefaults, Category = "Player|Combat")
float AimSensitivity;
// Variables de cámara
UPROPERTY(EditDefaults, Category = "Player|Camera")
bool bFirstPerson;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float BaseFOV;
UPROPERTY(EditDefaults, Category = "Player|Camera")
float AimFOV;
// Movimiento
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;
// Combate
UPROPERTY(EditDefaults, Category = "Player|Combat")
float BaseTurnRate;
UPROPERTY(EditDefaults, Category = "Player|Combat")
float AimSensitivity;
// Cámara
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;
// Crear componentes
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"));
// Establecer valores por defecto
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();
// Configurar entrada
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetupPlayerInput(this);
}
// Inicializar componentes
if (HealthComponent)
{
HealthComponent->OnDeath.AddDynamic(this, &APlayerCharacter::OnDeath);
}
// Equipar arma por defecto
if (WeaponComponent)
{
// This would be set up based on game rules
// WeaponComponent->SpawnWeapon(DefaultWeaponClass);
}
}
void APlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Actualizar estados de movimiento
UpdateMovementStates();
// Actualizar cámara
UpdateCamera(DeltaTime);
// Manejar regeneración de resistencia
if (StaminaComponent && !bIsSprinting)
{
StaminaComponent->RegenerateStamina(DeltaTime);
}
}
void APlayerCharacter::SetupPlayerInput(UInputComponent* PlayerInputComponent)
{
// Vincular acciones de movimiento
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);
// Vincular acciones de combate
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);
// Vincular acciones de utilidad
PlayerInputComponent->BindAction(EEnhancedInputAction::IA_ToggleCamera, this, &APlayerCharacter::ToggleCameraMode);
// Vincular acción de sprint
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;
// Calcular dirección del movimiento
const FVector Direction = FRotation.Vector();
const FVector MovementVector = Direction * Value;
// Aplicar movimiento
AddMovementInput(MovementVector);
// Establecer estado de movimiento
bIsMoving = FMath::Abs(Value) > 0.1f;
}
}
void APlayerCharacter::MoveRight(float Value)
{
if (Controller != nullptr)
{
const FRotator = GetControlRotation();
const FRotation = FRotator.Yaw;
// Calcular dirección del movimiento
const FVector Direction = FRotation.RightVector();
const FVector MovementVector = Direction * Value;
// Aplicar movimiento
AddMovementInput(MovementVector);
// Establecer estado de movimiento
bIsMoving = FMath::Abs(Value) > 0.1f;
}
}
void APlayerCharacter::Turn(float Value)
{
if (Controller != nullptr)
{
// Aplicar giro
AddControllerYawInput(Value * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}
}
void APlayerCharacter::StartJump()
{
if (bIsJumping) return;
// Verificar si se puede saltar
if (CanJump())
{
// Saltar
bIsJumping = true;
// Reproducir montaje de salto
if (UAnimInstance* JumpMontage = GetMesh()->GetAnimInstance())
{
JumpMontage->Montage_Play("Jump");
}
// Aplicar fuerza de salto
LaunchCharacter(FVector(0.0f, 0.0f, JumpHeight));
// Reproducir sonido de salto
if (USoundBase* JumpSound = GEngine->SoundBase)
{
JumpSound->PlaySound2D(GetActorLocation());
}
}
}
void APlayerCharacter::StopJumping()
{
// Detener salto
bIsJumping = false;
// Detener montaje de salto
if (UAnimInstance* JumpMontage = GetMesh()->GetAnimInstance())
{
JumpMontage->Montage_Stop("Jump");
}
}
void APlayerCharacter::StartFire()
{
if (WeaponComponent)
{
WeaponComponent->StartFire();
// Establecer estado de apuntado
bIsAiming = true;
// Actualizar cámara para apuntar
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(AimFOV);
}
}
}
void APlayerCharacter::StopFire()
{
if (WeaponComponent)
{
WeaponComponent->StopFire();
}
// Limpiar estado de apuntado
bIsAiming = false;
// Restablecer cámara
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(BaseFOV);
}
}
void APlayerCharacter::Reload()
{
if (WeaponComponent)
{
WeaponComponent->Reload();
}
}
void APlayerCharacter::Aim()
{
// Comenzar a apuntar
bIsAiming = true;
// Actualizar cámara para apuntar
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(AimFOV);
// Reducir sensibilidad del ratón para apuntado preciso
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetAimSensitivity(AimSensitivity * 0.5f);
}
}
// Reproducir montaje de apuntado
if (UAnimInstance* AimMontage = GetMesh()->GetAnimInstance())
{
AimMontage->Montage_Play("Aim");
}
}
void APlayerCharacter::StopAiming()
{
// Detener apuntado
bIsAiming = false;
// Restablecer cámara
if (CameraComponent && bFirstPerson)
{
CameraComponent->SetFieldOfView(BaseFOV);
}
// Restablecer sensibilidad del ratón
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->SetAimSensitivity(AimSensitivity);
}
// Detener montaje de apuntado
if (UAnimInstance* AimMontage = GetMesh()->GetAnimInstance())
{
AimMontage->Montage_Stop("Aim");
}
}
void APlayerCharacter::ToggleCameraMode()
{
// Alternar entre primera y tercera persona
bFirstPerson = !bFirstPerson;
// Actualizar cámara
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;
// Comenzar a correr a toda velocidad
bIsSprinting = true;
// Actualizar velocidad de movimiento
if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement())
{
MovementComponent->MaxWalkSpeed = MaxSprintSpeed;
}
// Reproducir montaje de sprint
if (UAnimInstance* SprintMontage = GetMesh()->GetAnimInstance())
{
SprintMontage->Montage_Play("Sprint");
}
// Reproducir sonido de sprint
if (USoundBase* SprintSound = GEngine->SoundBase)
{
SprintSound->PlaySound2D(GetActorLocation());
}
}
void APlayerCharacter::StopSprinting()
{
if (!bIsSprinting) return;
// Detener sprint
bIsSprinting = false;
// Restablecer velocidad de movimiento
if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement())
{
MovementComponent->MaxWalkSpeed = MaxRunSpeed;
}
// Detener montaje de sprint
if (UAnimInstance* SprintMontage = GetMesh()->GetAnimInstance())
{
SprintMontage->Montage_Stop("Sprint");
}
}
void APlayerCharacter::UpdateMovementStates()
{
// Actualizar estado de salto
if (GetCharacterMovement())
{
bIsJumping = !GetCharacterMovement()->IsMovingOnGround();
}
// Actualizar estado de movimiento
bIsMoving = GetVelocity().Size2D() > 0.1f;
// Manejar resistencia de sprint
if (bIsSprinting && StaminaComponent)
{
StaminaComponent->ConsumeStamina(SprintStaminaCost * GetWorld()->GetDeltaSeconds());
// Detener sprint si se agota la resistencia
if (StaminaComponent->GetStamina() <= 0.0f)
{
StopSprinting();
}
}
}
void APlayerCharacter::UpdateCamera(float DeltaTime)
{
if (CameraComponent)
{
// Manejar sacudida de cámara al disparar
if (WeaponComponent && WeaponComponent->IsFiring())
{
float CameraShakeIntensity = WeaponComponent->GetCameraShakeIntensity();
if (CameraShakeIntensity > 0.0f)
{
// Aplicar sacudida de cámara
FVector CameraLocation = CameraComponent->GetComponentLocation();
FRotator CameraRotation = CameraComponent->GetComponentRotation();
// Agregar desplazamiento aleatorio
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()
{
// Manejar muerte
DisableInput();
// Reproducir montaje de muerte
if (UAnimInstance* DeathMontage = GetMesh()->GetAnimInstance())
{
DeathMontage->Montage_Play("Death");
}
// Desactivar colisión
SetActorEnableCollision(false);
// Ocultar arma
if (WeaponComponent)
{
WeaponComponent->SetVisibility(false);
}
// Física de muñeca de trapo
if (GetCapsuleComponent())
{
GetCapsuleComponent->SetSimulatePhysics(true);
}
// Manejo de game over
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
PC->OnPlayerDeath();
}
}
// Componente de entrada mejorada
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);
};
// Implementación del componente de entrada mejorada
AEnhancedInputComponent::AEnhancedInputComponent()
{
CurrentAction = EEnhancedInputAction::IA_None;
ActionValue = 0.0f;
}
void AEnhancedInputComponent::BeginPlay()
{
Super::BeginPlay();
// Configurar vinculación de entrada
SetupInputBinding(this);
}
void AEnhancedInputComponent::SetupInputBinding(UInputComponent* PlayerInputComponent)
{
// Vinculaciones de movimiento
PlayerInputComponent->BindAxis("MoveForward", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("MoveRight", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("Turn", this, &AEnhancedInputComponent::HandleMovement);
PlayerInputComponent->BindAxis("LookUp", this, &AEnhancedInputComponent::HandleMovement);
// Vinculaciones de combate
PlayerInputComponent->BindAction("Fire", this, &AEnhancedInputComponent::HandleCombat);
PlayerInputComponent->BindAction("Reload", this, &AEnhancedInputComponent::HandleCombat);
PlayerInputComponent->BindAction("Aim", this, &AEnhancedInputComponent::HandleCombat);
// Vinculaciones de utilidad
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. Motor de Física Personalizado con 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 &&
## Comparación de Motores de Juego
| Motor | Lenguajes | Plataformas | Fortalezas | Licencia | Tipo |
|--------|---------|------------|---------|-------|------|
| **Unity** | C# | Multiplataforma | Media | Gratuita | Multipropósito |
| **Unreal** | C++ | Multiplataforma | Alta | Royalty | AAA |
| **Godot** | GDScript/C# | Multiplataforma | Media | Gratuita | Indie |
| **CryEngine** | C++ | Multiplataforma | Alta | Royalty | AAA |
| **Amazon Lumberyard** | Lua | Multiplataforma | Media | Gratuita | Cloud |
## Pipeline de Gráficos 3D
### Etapas del Pipeline de Rendering
```mermaid
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]
Tipos de Shaders
| Tipo | Propósito | Lenguaje | Complejidad |
|---|---|---|---|
| Vertex Shader | Transformación de vértices | GLSL/HLSL | Media |
| Fragment Shader | Color de píxeles | GLSL/HLSL | Alta |
| Geometry Shader | Generación de primitivas | GLSL/HLSL | Muy Alta |
| Compute Shader | Propósito general | GLSL/HLSL | Alta |
Conceptos de Motor de Física
Algoritmos de Detección de Colisiones
| Algoritmo | Tipo | Complejidad | Precisión | Aplicación |
|---|---|---|---|---|
| AABB | Broad Phase | O(1) | Baja | Filtrado rápido |
| OBB | Narrow Phase | O(n) | Media | Preciso |
| SAT | Narrow Phase | O(log n) | Alta | Preciso |
| GJK | Narrow Phase | O(log n) | Muy Alta | Complejo |
Métodos de Integración
| Método | Estabilidad | Eficiencia Energética | Aplicación |
|---|---|---|---|
| Euler | Condicional | Baja | Propósito general |
| Verlet | Condicional | Baja | Preciso |
| RK4 | Condicional | Baja | Preciso |
Sistemas de Animación
Animación Esquelética
// 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;
};
Mezcla de Animaciones
// Animation blending states
enum class EAnimationBlendState
{
Idle,
Moving,
Jumping,
Attacking,
Dead
};
// Blend tree structure
struct FAnimationBlendState
{
EAnimationState State;
float BlendTime;
float BlendDuration;
UAnimationAsset* Animation;
};
Implementación del Game Loop
Game Loop con Tiempo Fijo
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();
}
Game Loop con Tiempo Variable
void Game::Tick(float DeltaTime)
{
// Update game logic
UpdateGameLogic(DeltaTime);
// Handle input
HandleInput(DeltaTime);
// Update physics
UpdatePhysics(DeltaTime);
// Render
Render();
}
Optimización para Juegos Móviles
Técnicas de Rendimiento
- Level of Detail (LOD): Ajuste dinámico de calidad
- Occlusion Culling: Ocultar objetos invisibles
- Frustum Culling: Limitar el área visible
- Object Pooling: Reutilizar objetos
- Texture Compression: Compresión de gráficos
Gestión de Memoria
// 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;
};
Ventajas e Inconvenientes
Ventajas de los Motores de Juego
- Desarrollo Rápido: Editores visuales, drag-and-drop
- Multiplataforma: Desarrolla una sola vez para todas las plataformas
- Sistemas Integrados: Física, audio, animación, networking
- Pipeline de Recursos: Gestión e importación de assets
- Apoyo Comunitario: Grandes comunidades de desarrolladores
Inconvenientes
- Rendimiento: No siempre óptimo para juegos AAA
- Flexibilidad: Limitaciones específicas del motor
- Costos de Licencia: Modelos comerciales de licencias
- Tamaño de Compilación: Builds de gran tamaño
- Debugging: Herramientas de debugging específicas del motor
Preguntas Frecuentes de Examen
-
¿Cuál es la diferencia entre Unity y Unreal Engine? Unity utiliza C# y está optimizado para juegos indie y móviles. Unreal Engine usa C++ y está enfocado en juegos AAA con gráficos de alta gama.
-
Explica el Game Loop. El Game Loop es el ciclo central que consiste en manejo de entrada, actualización de lógica, actualización de física y renderizado, con intervalos de tiempo fijo o variable.
-
¿Cuándo se usa cada tipo de detección de colisiones? AABB para filtrado rápido, OBB para colisiones precisas, SAT para geometrías complejas, GJK para escenarios muy complejos.
-
¿Cuál es el propósito de los shaders? Los shaders son pequeños programas que se ejecutan en la GPU y controlan la representación visual de objetos 3D.
Fuentes Principales
- https://unity.com/
- https://unrealengine.com/
- https://docs.unity3d.com/
- https://docs.unrealengine.com/


