GUÍA COMPLETA: Sistema de Menús y Guardado en Unity para
Juego Top-Down
ÍNDICE
1. Estructura del Proyecto
2. Arquitectura de Managers
3. Pantalla de Inicio (Main Menu)
4. Sistema de Guardado con Slots
5. Pantalla Continuar (Load Game)
6. Menú In-Game (Pause Menu)
7. Menú de Ajustes (Settings)
8. Integración Completa
9. Build Settings y Escenas
10. Mejores Prácticas
1. ESTRUCTURA DEL PROYECTO
Jerarquía de Carpetas Recomendada
Assets/
├── _Project/
│ ├── Animations/
│ ├── Audio/
│ │ ├── Music/
│ │ └── SFX/
│ ├── Prefabs/
│ │ ├── UI/
│ │ └── Gameplay/
│ ├── Scenes/
│ │ ├── [Link]
│ │ └── [Link]
│ ├── ScriptableObjects/
│ ├── Scripts/
│ │ ├── Managers/
│ │ ├── UI/
│ │ ├── Gameplay/
│ │ └── Data/
│ ├── Sprites/
│ │ ├── UI/
Generated by [Link]
│ │ ├── Characters/
│ │ └── Environment/
│ └── Fonts/
├── Plugins/
└── Resources/
Escenas del Proyecto
Escena Build Index Descripción
MainMenu 0 Menú principal con
título y botones
Gameplay 1 Escena principal del
juego top-down
2. ARQUITECTURA DE MANAGERS
Patrón Singleton + DontDestroyOnLoad
Los managers que deben persistir entre escenas (GameManager,
SaveManager, AudioManager) usan el patrón Singleton con
DontDestroyOnLoad.
[Link]
using UnityEngine;
using [Link];
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
[Header("Game State")]
public bool IsPaused = false;
public int CurrentSaveSlot = -1; // -1 = ninguno, 0-2 = slots
[Header("References")]
public GameObject pauseMenuCanvas;
private void Awake()
{
// Patrón Singleton
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
Generated by [Link]
}
else
{
Destroy(gameObject);
return;
}
}
private void Update()
{
// Abrir/cerrar menú de pausa con ESC
if ([Link]([Link]))
{
if ([Link]().name == "Gameplay")
{
TogglePause();
}
}
}
public void TogglePause()
{
IsPaused = !IsPaused;
if (IsPaused)
{
[Link] = 0f;
pauseMenuCanvas?.SetActive(true);
}
else
{
[Link] = 1f;
pauseMenuCanvas?.SetActive(false);
// Asegurar que submenús también se cierren
[Link]?.CloseAllSubMenus();
}
}
public void ResumeGame()
{
IsPaused = false;
[Link] = 1f;
pauseMenuCanvas?.SetActive(false);
}
public void StartNewGame()
{
CurrentSaveSlot = -1;
[Link] = 1f;
[Link]("Gameplay");
Generated by [Link]
}
public void ContinueGame(int slotIndex)
{
if ([Link](slotIndex))
{
CurrentSaveSlot = slotIndex;
[Link] = 1f;
[Link]("Gameplay");
}
}
public void ReturnToMainMenu()
{
IsPaused = false;
[Link] = 1f;
CurrentSaveSlot = -1;
[Link]("MainMenu");
}
public void QuitGame()
{
#if UNITY_EDITOR
[Link] = false;
#else
[Link]();
#endif
}
}
[Link] - Sistema de Guardado con Slots
using UnityEngine;
using System;
using [Link];
using [Link];
[Serializable]
public class GameSaveData
{
public int saveVersion = 1;
public string saveDate;
public string playerName;
public int playerLevel = 1;
public float playerHealth = 100f;
public float playerMaxHealth = 100f;
public float playerPositionX;
public float playerPositionY;
public float playerPositionZ;
public int currentSceneIndex = 1;
Generated by [Link]
public int enemiesDefeated = 0;
public float playTime = 0f;
// Agrega más datos según tu juego
}
[Serializable]
public class SlotMetadata
{
public bool isUsed = false;
public string lastSaveDate = "";
public string playerName = "Nueva Partida";
public int playerLevel = 1;
public float playTime = 0f;
}
public class SaveManager : MonoBehaviour
{
public static SaveManager Instance { get; private set; }
private const int TOTAL_SLOTS = 3;
private const string SAVE_FOLDER = "SaveData";
private const string SAVE_FILE_PREFIX = "save_slot_";
private const string METADATA_FILE = "slots_metadata";
private string SaveFolderPath =>
[Link]([Link], SAVE_FOLDER);
[Header("Current Save")]
public GameSaveData CurrentSave = new GameSaveData();
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
EnsureSaveDirectory();
}
else
{
Destroy(gameObject);
}
}
private void EnsureSaveDirectory()
{
if ()
{
[Link](SaveFolderPath);
Generated by [Link]
}
}
private string GetSlotPath(int slotIndex)
{
return [Link](SaveFolderPath, $"{SAVE_FILE_PREFIX}
{slotIndex}.json");
}
private string GetMetadataPath()
{
return [Link](SaveFolderPath,
$"{METADATA_FILE}.json");
}
/// <summary>
/// Verifica si un slot tiene datos guardados
/// </summary>
public bool HasSaveData(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= TOTAL_SLOTS) return
false;
return [Link](GetSlotPath(slotIndex));
}
/// <summary>
/// Obtiene los metadatos de todos los slots
/// </summary>
public List<SlotMetadata> GetAllSlotsMetadata()
{
var metadataList = new List<SlotMetadata>();
for (int i = 0; i < TOTAL_SLOTS; i++)
{
if (HasSaveData(i))
{
string json = [Link](GetSlotPath(i));
var saveData =
[Link]<GameSaveData>(json);
[Link](new SlotMetadata
{
isUsed = true,
lastSaveDate = [Link],
playerName = [Link],
playerLevel = [Link],
playTime = [Link]
});
}
else
{
Generated by [Link]
[Link](new SlotMetadata { isUsed =
false });
}
}
return metadataList;
}
/// <summary>
/// Guarda la partida actual en el slot especificado
/// </summary>
public void SaveGame(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= TOTAL_SLOTS) return;
// Actualizar datos del save
[Link] = [Link]("dd/MM/yyyy
HH:mm");
[Link] = 1;
// Serializar a JSON
string json = [Link](CurrentSave, true);
[Link](GetSlotPath(slotIndex), json);
[Link]($"Partida guardada en Slot {slotIndex + 1}");
}
/// <summary>
/// Carga una partida del slot especificado
/// </summary>
public bool LoadGame(int slotIndex)
{
if (!HasSaveData(slotIndex)) return false;
string json = [Link](GetSlotPath(slotIndex));
CurrentSave = [Link]<GameSaveData>(json);
[Link]($"Partida cargada desde Slot {slotIndex + 1}");
return true;
}
/// <summary>
/// Elimina los datos de un slot
/// </summary>
public void DeleteSave(int slotIndex)
{
if (HasSaveData(slotIndex))
{
[Link](GetSlotPath(slotIndex));
Generated by [Link]
[Link]($"Partida eliminada del Slot {slotIndex +
1}");
}
}
/// <summary>
/// Actualiza los datos del jugador desde la escena de
gameplay
/// </summary>
public void UpdateSaveData(Vector3 playerPos, float health,
int level, float playTime)
{
[Link] = playerPos.x;
[Link] = playerPos.y;
[Link] = playerPos.z;
[Link] = health;
[Link] = level;
[Link] = playTime;
}
}
[Link] - Gestor de Navegación entre Pantallas
using UnityEngine;
using [Link];
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
[Header("Main Menu Screens")]
public GameObject mainMenuPanel;
public GameObject continuePanel;
public GameObject settingsPanel;
[Header("In-Game Menus")]
public GameObject pauseMenuPanel;
public GameObject pauseSettingsPanel;
public GameObject pauseLoadPanel;
public GameObject pauseSavePanel;
[Header("Slot UI Prefabs")]
public Transform continueSlotsContainer;
public Transform pauseLoadSlotsContainer;
public Transform pauseSaveSlotsContainer;
public GameObject slotPrefab;
private Stack<GameObject> menuHistory = new
Stack<GameObject>();
private GameObject currentPanel;
Generated by [Link]
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
// Configurar pantalla inicial según la escena
string sceneName =
[Link]().name;
if (sceneName == "MainMenu")
{
ShowPanel(mainMenuPanel);
}
}
public void ShowPanel(GameObject panel)
{
// Ocultar panel actual
if (currentPanel != null)
{
[Link](false);
[Link](currentPanel);
}
// Mostrar nuevo panel
[Link](true);
currentPanel = panel;
}
public void ShowMainMenu()
{
[Link]();
ShowPanel(mainMenuPanel);
}
public void ShowContinueScreen()
{
ShowPanel(continuePanel);
RefreshSlotDisplay(continueSlotsContainer,
Generated by [Link]
[Link]);
}
public void ShowSettings()
{
ShowPanel(settingsPanel);
}
public void ShowPauseMenu()
{
ShowPanel(pauseMenuPanel);
}
public void ShowPauseLoad()
{
ShowPanel(pauseLoadPanel);
RefreshSlotDisplay(pauseLoadSlotsContainer,
[Link]);
}
public void ShowPauseSave()
{
ShowPanel(pauseSavePanel);
RefreshSlotDisplay(pauseSaveSlotsContainer,
[Link]);
}
public void ShowPauseSettings()
{
ShowPanel(pauseSettingsPanel);
}
public void GoBack()
{
if ([Link] > 0)
{
if (currentPanel != null)
[Link](false);
currentPanel = [Link]();
[Link](true);
}
}
public void CloseAllSubMenus()
{
pauseSettingsPanel?.SetActive(false);
pauseLoadPanel?.SetActive(false);
pauseSavePanel?.SetActive(false);
Generated by [Link]
}
// ============ SLOTS UI ============
public enum SlotMode { Load, Save }
private void RefreshSlotDisplay(Transform container, SlotMode
mode)
{
// Limpiar slots existentes
foreach (Transform child in container)
{
Destroy([Link]);
}
// Obtener metadatos
var slots = [Link]();
for (int i = 0; i < [Link]; i++)
{
int slotIndex = i; // Capturar para lambda
var metadata = slots[i];
// Instanciar prefab de slot
GameObject slotObj = Instantiate(slotPrefab,
container);
SlotUI slotUI = [Link]<SlotUI>();
if (slotUI != null)
{
[Link](
slotIndex,
metadata,
mode == [Link],
() => OnSlotSelected(slotIndex, mode)
);
}
}
}
private void OnSlotSelected(int slotIndex, SlotMode mode)
{
if (mode == [Link])
{
if ([Link](slotIndex))
{
[Link](slotIndex);
}
else
Generated by [Link]
{
[Link]("No hay datos en este slot");
}
}
else // Save
{
[Link](slotIndex);
[Link] = slotIndex;
// Refrescar display
if (currentPanel == pauseSavePanel)
{
RefreshSlotDisplay(pauseSaveSlotsContainer,
[Link]);
}
}
}
}
3. PANTALLA DE INICIO (MAIN MENU)
Estructura del Canvas en la Escena MainMenu
Canvas (Screen Space - Overlay)
├── MainMenuPanel
│ ├── GameTitle (TextMeshPro)
│ ├── ButtonContainer (Vertical Layout Group)
│ │ ├── StartButton
│ │ ├── ContinueButton
│ │ └── ExitButton
│ └── VersionText (TextMeshPro)
├── ContinuePanel (desactivado por defecto)
│ ├── TitleText ("Continuar Partida")
│ ├── SlotsContainer (Vertical Layout Group)
│ │ └── (Slots generados dinámicamente)
│ └── BackButton
└── SettingsPanel (desactivado por defecto)
├── TitleText ("Ajustes")
├── VolumeSlider
├── FullscreenToggle
└── BackButton
[Link]
using UnityEngine;
using [Link];
using TMPro;
public class MainMenuController : MonoBehaviour
Generated by [Link]
{
[Header("Buttons")]
public Button startButton;
public Button continueButton;
public Button exitButton;
[Header("Panels")]
public GameObject mainMenuPanel;
public GameObject continuePanel;
public GameObject settingsPanel;
private void Start()
{
// Asignar listeners a botones
[Link](OnStartClicked);
[Link](OnContinueClicked);
[Link](OnExitClicked);
// Verificar si hay partidas guardadas para
habilitar/deshabilitar botón
UpdateContinueButtonState();
}
private void OnDestroy()
{
// Remover listeners para evitar memory leaks
[Link](OnStartClicked);
[Link](OnContinueClicked);
[Link](OnExitClicked);
}
private void UpdateContinueButtonState()
{
// Verificar si existe alguna partida guardada
bool hasSaves = false;
for (int i = 0; i < 3; i++)
{
if ([Link](i))
{
hasSaves = true;
break;
}
}
[Link] = hasSaves;
// Cambiar color si está deshabilitado
var colors = [Link];
[Link] = new Color(0.5f, 0.5f, 0.5f, 0.5f);
Generated by [Link]
[Link] = colors;
}
private void OnStartClicked()
{
[Link]("Iniciando nueva partida...");
[Link]();
}
private void OnContinueClicked()
{
[Link]("Abriendo pantalla de continuar...");
[Link]();
}
private void OnExitClicked()
{
[Link]("Saliendo del juego...");
[Link]();
}
}
4. SISTEMA DE GUARDADO CON SLOTS
[Link] - Componente visual de cada slot
using UnityEngine;
using [Link];
using TMPro;
using System;
public class SlotUI : MonoBehaviour
{
[Header("UI Elements")]
public TextMeshProUGUI slotNumberText;
public TextMeshProUGUI playerNameText;
public TextMeshProUGUI levelText;
public TextMeshProUGUI dateText;
public TextMeshProUGUI playTimeText;
public Button actionButton;
public Image backgroundImage;
public GameObject emptySlotOverlay;
[Header("Colors")]
public Color emptyColor = new Color(0.3f, 0.3f, 0.3f, 0.5f);
public Color occupiedColor = new Color(0.2f, 0.4f, 0.8f,
0.8f);
public Color hoverColor = new Color(0.3f, 0.6f, 1f, 0.9f);
Generated by [Link]
public void SetupSlot(int slotIndex, SlotMetadata metadata,
bool isLoadMode, Action onClick)
{
// Configurar número de slot
[Link] = $"SLOT {slotIndex + 1}";
if ([Link])
{
// Slot ocupado - mostrar datos
[Link](false);
[Link] = occupiedColor;
[Link] = [Link];
[Link] = $"Nv. {[Link]}";
[Link] = [Link];
[Link] =
FormatPlayTime([Link]);
[Link] = true;
}
else
{
// Slot vacío
[Link](true);
[Link] = emptyColor;
[Link] = "Vacío";
[Link] = "";
[Link] = "Sin datos";
[Link] = "";
// En modo carga, deshabilitar slot vacío
[Link] = isLoadMode ? false :
true;
}
// Configurar botón
[Link]();
[Link](() =>
onClick?.Invoke());
}
private string FormatPlayTime(float hours)
{
int h = [Link](hours);
int m = [Link]((hours - h) * 60);
return $"{h}h {m}m";
}
}
Generated by [Link]
Prefab de Slot (Estructura)
SlotPrefab (GameObject con [Link])
├── Background (Image)
├── SlotNumberText (TextMeshPro)
├── PlayerNameText (TextMeshPro)
├── LevelText (TextMeshPro)
├── DateText (TextMeshPro)
├── PlayTimeText (TextMeshPro)
├── ActionButton (Button)
│ └── ButtonText (TextMeshPro) - "Cargar" o "Guardar"
└── EmptyOverlay (Image semi-transparente)
└── EmptyText (TextMeshPro) - "VACÍO"
5. PANTALLA CONTINUAR (LOAD GAME)
[Link]
using UnityEngine;
using [Link];
public class ContinuePanelController : MonoBehaviour
{
[Header("UI References")]
public Button backButton;
public Transform slotsContainer;
public GameObject slotPrefab;
private void OnEnable()
{
[Link](OnBackClicked);
RefreshSlots();
}
private void OnDisable()
{
[Link](OnBackClicked);
}
private void RefreshSlots()
{
// Limpiar slots existentes
foreach (Transform child in slotsContainer)
{
Destroy([Link]);
}
// Obtener metadatos
var slots = [Link]();
Generated by [Link]
for (int i = 0; i < [Link]; i++)
{
int slotIndex = i;
var metadata = slots[i];
GameObject slotObj = Instantiate(slotPrefab,
slotsContainer);
SlotUI slotUI = [Link]<SlotUI>();
if (slotUI != null)
{
[Link](
slotIndex,
metadata,
true, // Modo carga
() => OnSlotLoadClicked(slotIndex)
);
}
}
}
private void OnSlotLoadClicked(int slotIndex)
{
if ([Link](slotIndex))
{
// Cargar partida
[Link](slotIndex);
[Link] = slotIndex;
[Link]("Gameplay");
}
}
private void OnBackClicked()
{
[Link]();
}
}
6. MENÚ IN-GAME (PAUSE MENU)
Estructura del Canvas de Pausa en Gameplay
El menú de pausa se coloca en la escena Gameplay como un Canvas separado
o hijo del GameManager (DontDestroyOnLoad).
Generated by [Link]
Canvas_PauseMenu (Screen Space - Overlay, desactivado)
├── PauseMenuPanel
│ ├── TitleText ("PAUSA")
│ ├── ButtonContainer
│ │ ├── ResumeButton ("Reanudar")
│ │ ├── SettingsButton ("Ajustes")
│ │ ├── SaveButton ("Guardar Partida")
│ │ ├── LoadButton ("Cargar Partida")
│ │ └── MainMenuButton ("Menú Principal")
│ └── PlayTimeText (TextMeshPro)
├── PauseSettingsPanel (desactivado)
│ ├── TitleText ("AJUSTES")
│ ├── VolumeSection
│ │ ├── VolumeSlider
│ │ └── VolumeLabel
│ ├── FullscreenToggle
│ └── BackButton
├── PauseSavePanel (desactivado)
│ ├── TitleText ("GUARDAR PARTIDA")
│ ├── SlotsContainer
│ └── BackButton
└── PauseLoadPanel (desactivado)
├── TitleText ("CARGAR PARTIDA")
├── SlotsContainer
└── BackButton
[Link]
using UnityEngine;
using [Link];
using TMPro;
public class PauseMenuController : MonoBehaviour
{
[Header("Main Pause Menu")]
public GameObject pauseMenuPanel;
public Button resumeButton;
public Button settingsButton;
public Button saveButton;
public Button loadButton;
public Button mainMenuButton;
public TextMeshProUGUI playTimeText;
[Header("Sub-Panels")]
public GameObject settingsPanel;
public GameObject savePanel;
public GameObject loadPanel;
[Header("Slot System")]
public Transform saveSlotsContainer;
Generated by [Link]
public Transform loadSlotsContainer;
public GameObject slotPrefab;
private GameObject currentSubPanel;
private float sessionPlayTime = 0f;
private void Start()
{
// Asignar listeners
[Link](OnResume);
[Link](OnSettings);
[Link](OnSave);
[Link](OnLoad);
[Link](OnMainMenu);
// Asegurar que el menú está oculto al inicio
[Link](false);
[Link](false);
[Link](false);
[Link](false);
}
private void Update()
{
if (![Link])
{
sessionPlayTime += [Link];
}
}
private void OnDestroy()
{
[Link](OnResume);
[Link](OnSettings);
[Link](OnSave);
[Link](OnLoad);
[Link](OnMainMenu);
}
private void OnResume()
{
[Link]();
}
private void OnSettings()
{
ShowSubPanel(settingsPanel);
}
Generated by [Link]
private void OnSave()
{
ShowSubPanel(savePanel);
RefreshSlots(saveSlotsContainer, false);
}
private void OnLoad()
{
ShowSubPanel(loadPanel);
RefreshSlots(loadSlotsContainer, true);
}
private void OnMainMenu()
{
// Confirmar si hay progreso sin guardar (opcional)
[Link]();
}
private void ShowSubPanel(GameObject panel)
{
[Link](false);
[Link](true);
currentSubPanel = panel;
}
public void BackToPauseMenu()
{
if (currentSubPanel != null)
{
[Link](false);
currentSubPanel = null;
}
[Link](true);
}
private void RefreshSlots(Transform container, bool
isLoadMode)
{
// Limpiar
foreach (Transform child in container)
{
Destroy([Link]);
}
var slots = [Link]();
for (int i = 0; i < [Link]; i++)
{
int slotIndex = i;
Generated by [Link]
var metadata = slots[i];
GameObject slotObj = Instantiate(slotPrefab,
container);
SlotUI slotUI = [Link]<SlotUI>();
if (slotUI != null)
{
if (isLoadMode)
{
[Link](slotIndex, metadata, true,
() => OnLoadSlotSelected(slotIndex));
}
else
{
[Link](slotIndex, metadata, false,
() => OnSaveSlotSelected(slotIndex));
}
}
}
}
private void OnSaveSlotSelected(int slotIndex)
{
// Actualizar datos de guardado con estado actual del
juego
var player = FindObjectOfType<PlayerController>();
if (player != null)
{
[Link](
[Link],
[Link],
[Link],
sessionPlayTime
);
}
[Link](slotIndex);
[Link] = slotIndex;
// Feedback visual
[Link]($"Partida guardada en Slot {slotIndex + 1}");
// Refrescar slots
RefreshSlots(saveSlotsContainer, false);
}
private void OnLoadSlotSelected(int slotIndex)
{
Generated by [Link]
if ([Link](slotIndex))
{
[Link](slotIndex);
[Link] = slotIndex;
// Recargar escena con datos cargados
[Link]("Gameplay");
}
}
}
7. MENÚ DE AJUSTES (SETTINGS)
[Link] - Gestor de Configuraciones
using UnityEngine;
using [Link];
using [Link];
using TMPro;
public class SettingsManager : MonoBehaviour
{
public static SettingsManager Instance { get; private set; }
[Header("Audio")]
public AudioMixer audioMixer;
public Slider masterVolumeSlider;
public Slider musicVolumeSlider;
public Slider sfxVolumeSlider;
[Header("Graphics")]
public Toggle fullscreenToggle;
public TMP_Dropdown resolutionDropdown;
public TMP_Dropdown qualityDropdown;
[Header("Data Keys")]
private const string MASTER_VOL_KEY = "MasterVolume";
private const string MUSIC_VOL_KEY = "MusicVolume";
private const string SFX_VOL_KEY = "SFXVolume";
private const string FULLSCREEN_KEY = "Fullscreen";
private const string RESOLUTION_KEY = "Resolution";
private const string QUALITY_KEY = "Quality";
private Resolution[] availableResolutions;
private void Awake()
{
Generated by [Link]
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
LoadSettings();
SetupUIListeners();
PopulateResolutionDropdown();
}
private void SetupUIListeners()
{
// Volumen con conversión logarítmica
[Link](SetMasterVolume);
[Link](SetMusicVolume);
[Link](SetSFXVolume);
[Link](SetFullscreen);
[Link](SetResolution);
[Link](SetQuality);
}
private void PopulateResolutionDropdown()
{
availableResolutions = [Link];
[Link]();
int currentResolutionIndex = 0;
var options = new
[Link]<string>();
for (int i = 0; i < [Link]; i++)
{
string option = $"{availableResolutions[i].width} x
{availableResolutions[i].height}";
[Link](option);
if (availableResolutions[i].width ==
Generated by [Link]
[Link] &&
availableResolutions[i].height ==
[Link])
{
currentResolutionIndex = i;
}
}
[Link](options);
[Link] = currentResolutionIndex;
[Link]();
}
// ============ VOLUMEN (con conversión logarítmica)
============
public void SetMasterVolume(float value)
{
// Conversión logarítmica para percepción auditiva
correcta
float dB = value > 0.0001f ? Mathf.Log10(value) * 20 : -
80f;
[Link]("MasterVol", dB);
[Link](MASTER_VOL_KEY, value);
}
public void SetMusicVolume(float value)
{
float dB = value > 0.0001f ? Mathf.Log10(value) * 20 : -
80f;
[Link]("MusicVol", dB);
[Link](MUSIC_VOL_KEY, value);
}
public void SetSFXVolume(float value)
{
float dB = value > 0.0001f ? Mathf.Log10(value) * 20 : -
80f;
[Link]("SFXVol", dB);
[Link](SFX_VOL_KEY, value);
}
// ============ GRÁFICOS ============
public void SetFullscreen(bool isFullscreen)
{
[Link] = isFullscreen;
[Link](FULLSCREEN_KEY, isFullscreen ? 1 : 0);
}
Generated by [Link]
public void SetResolution(int resolutionIndex)
{
Resolution resolution =
availableResolutions[resolutionIndex];
[Link]([Link], [Link],
[Link]);
[Link](RESOLUTION_KEY, resolutionIndex);
}
public void SetQuality(int qualityIndex)
{
[Link](qualityIndex);
[Link](QUALITY_KEY, qualityIndex);
}
// ============ CARGAR/GUARDAR ============
public void LoadSettings()
{
// Cargar volumen (con valor por defecto 0.75)
float masterVol = [Link](MASTER_VOL_KEY,
0.75f);
float musicVol = [Link](MUSIC_VOL_KEY,
0.75f);
float sfxVol = [Link](SFX_VOL_KEY, 0.75f);
[Link] = masterVol;
[Link] = musicVol;
[Link] = sfxVol;
// Aplicar valores al mixer
SetMasterVolume(masterVol);
SetMusicVolume(musicVol);
SetSFXVolume(sfxVol);
// Pantalla completa
bool isFullscreen = [Link](FULLSCREEN_KEY, 1)
== 1;
[Link] = isFullscreen;
[Link] = isFullscreen;
// Calidad
int quality = [Link](QUALITY_KEY,
[Link]());
[Link] = quality;
[Link](quality);
}
Generated by [Link]
public void SaveSettings()
{
[Link]();
[Link]("Ajustes guardados");
}
private void OnDestroy()
{
masterVolumeSlider?.[Link](SetMasterVolume
);
musicVolumeSlider?.[Link](SetMusicVolume);
sfxVolumeSlider?.[Link](SetSFXVolume);
fullscreenToggle?.[Link](SetFullscreen);
resolutionDropdown?.[Link](SetResolution);
qualityDropdown?.[Link](SetQuality);
}
}
Configuración del AudioMixer
1. Crea un Audio Mixer (Assets > Create > Audio Mixer)
2. Crea tres grupos: Master, Music, SFX
3. Expón los parámetros de volumen:
– En cada grupo, haz clic derecho en “Volume” → “Expose ‘Volume’
to script”
– Renombra los parámetros expuestos a: MasterVol, MusicVol,
SFXVol
8. INTEGRACIÓN COMPLETA
[Link] (Ejemplo básico para top-down)
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Movement")]
public float moveSpeed = 5f;
Generated by [Link]
[Header("Stats")]
public float MaxHealth = 100f;
public float CurrentHealth = 100f;
public int Level = 1;
private Rigidbody2D rb;
private Vector2 movement;
private Vector2 lastPosition;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
// Si hay una partida cargada, aplicar datos
if ([Link] != null &&
[Link] != null)
{
var save = [Link];
[Link] = new Vector3(
[Link],
[Link],
[Link]
);
CurrentHealth = [Link];
Level = [Link];
}
}
private void Update()
{
// Input
movement.x = [Link]("Horizontal");
movement.y = [Link]("Vertical");
movement = [Link];
}
private void FixedUpdate()
{
[Link] = movement * moveSpeed;
}
private void OnDestroy()
{
// Auto-guardar posición al salir (opcional)
if ([Link] != null &&
[Link] >= 0)
{
[Link](
[Link],
CurrentHealth,
Generated by [Link]
Level,
0f
);
}
}
}
9. BUILD SETTINGS Y ESCENAS
Configuración de Escenas en Build
1. Ve a File > Build Settings
2. Agrega las escenas en este orden:
Orden Escena
0 MainMenu
1 Gameplay
// [Link](0) → MainMenu
// [Link](1) → Gameplay
10. MEJORES PRÁCTICAS
Optimización de UI
1. Un solo Canvas por escena con múltiples paneles (mejor rendimiento
que múltiples Canvas)
2. Desactivar paneles inactivos con SetActive(false) en vez de solo
cambiar alpha
3. Usar [Link] para animaciones en lugar de
[Link]
4. Minimizar el uso de Layout Groups en paneles que se actualizan
frecuentemente
Persistencia de Datos
Método Uso Recomendado
PlayerPrefs Settings, preferencias (volumen, pantalla completa)
JSON + Partidas guardadas, progreso del jugador
Generated by [Link]
Método Uso Recomendado
persistentDataPath
ScriptableObjects Datos estáticos (stats de items, configuraciones de
niveles)
Seguridad del Guardado
// Para evitar modificaciones simples del archivo JSON:
// 1. Agregar un checksum simple
// 2. Ofuscar el JSON con Base64
public string ObfuscateJson(string json)
{
byte[] bytes = [Link](json);
return [Link].ToBase64String(bytes);
}
public string DeobfuscateJson(string obfuscated)
{
byte[] bytes = [Link].FromBase64String(obfuscated);
return [Link](bytes);
}
Jerarquía de GameObjects en Escena (Gameplay)
Gameplay Scene
├── === MANAGERS ===
│ ├── GameManager ([Link], DontDestroyOnLoad)
│ ├── SaveManager ([Link], DontDestroyOnLoad)
│ ├── AudioManager (DontDestroyOnLoad)
│ └── UIManager ([Link])
│
├── === ENVIRONMENT ===
│ ├── Tilemap_Ground
│ ├── Tilemap_Collisions
│ └── Props
│
├── === ENTITIES ===
│ ├── Player ([Link], Rigidbody2D)
│ ├── Enemies/
│ └── NPCs/
│
├── === UI ===
│ ├── Canvas_GameplayHUD (activo)
│ └── Canvas_PauseMenu (desactivado inicialmente)
│ └── [Link]
│
Generated by [Link]
└── === CAMERA ===
└── Main Camera (Follow Player)
RESUMEN DE FLUJO DEL JUEGO
[INICIO] → MainMenu Scene
├── Botón "Iniciar" → Gameplay Scene (nueva partida)
├── Botón "Continuar" → ContinuePanel (seleccionar slot) →
Gameplay Scene
└── Botón "Salir" → [Link]()
[EN JUEGO] → Gameplay Scene
├── Tecla ESC → Pause Menu
│ ├── "Reanudar" → Cierra menú, [Link] = 1
│ ├── "Ajustes" → SettingsPanel (volumen, pantalla, etc.)
│ ├── "Guardar" → SavePanel (3 slots) → Guarda JSON
│ ├── "Cargar" → LoadPanel (3 slots) → Carga JSON + recarga
escena
│ └── "Menú Principal" → MainMenu Scene
└── Juego continúa...
ARCHIVOS CLAVE DEL SISTEMA
Archivo Propósito
[Link] Singleton central, control de pausa,
cambio de escenas
[Link] Guardado/carga JSON con 3 slots
[Link] Navegación entre paneles de menú
[Link] Lógica de botones del menú principal
[Link] Lógica del menú de pausa in-game
[Link] Ajustes de audio y gráficos
[Link] Visualización de cada slot de guardado
[Link] Estructura de datos serializable
Esta guía proporciona una base completa para tu sistema de menús. Puedes
extender cada componente según las necesidades específicas de tu juego top-
down.
Generated by [Link]