<html lang='es' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'></html> Generador Procedural de Torres y Seguimiento de Cámara Profesional - Videojuegos con Unity

Generador Procedural de Torres y Seguimiento de Cámara Profesional

 

 ## Tutorial 5: Generador Procedural de Torres (`HelixManager`)
### ¿Para qué sirve?

El cerebro procedural del juego. Rota la torre basándose en el arrastre (*swipe*) del usuario, lee los ScriptableObjects de etapa, instancia los niveles.

 

### Código del Script (`HelixManager.cs`)
```csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class HelixManager : MonoBehaviour
{
    [Header("Referencias de Posicionamiento")]
    [SerializeField] private Transform topTransform;
    [SerializeField] private Transform goalTransform;
    [SerializeField] private GameObject helixLevelPrefab;

    [Header("Configuración de Etapas")]
    [SerializeField] private List<Stage> allStages = new List<Stage>();
    [SerializeField] private GameObject splashPrefab;

    private Vector2 lastTapPosition;
    private Vector3 startRotation;
    private float helixDistance;
    private List<GameObject> spawnedLevels = new List<GameObject>();

    private void Awake()
    {
        startRotation = transform.localEulerAngles;
        if (topTransform != null && goalTransform != null)
        {
            helixDistance = topTransform.localPosition.y - (goalTransform.localPosition.y + 0.1f);
        }
        LoadStage(0);
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            lastTapPosition = Input.mousePosition;
        }
        else if (Input.GetMouseButton(0))
        {
            Vector2 currentTapPosition = Input.mousePosition;
            if (lastTapPosition == Vector2.zero) lastTapPosition = currentTapPosition;

            float distance = (lastTapPosition.x - currentTapPosition.x) / 4f;
            lastTapPosition = currentTapPosition;
            transform.Rotate(Vector3.up * distance);
        }
        else if (Input.GetMouseButtonUp(0))
        {
            lastTapPosition = Vector2.zero;
        }
    }

    public void LoadStage(int stageNumber)
    {
        if (stageNumber >= allStages.Count)
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
            return;
        }

        Stage stage = allStages[Mathf.Clamp(stageNumber, 0, allStages.Count - 1)];
        if (stage == null) return;

        Camera.main.backgroundColor = stage.stageBackgroundColor;

        if (splashPrefab != null)
        {
            SpriteRenderer splashRend = splashPrefab.GetComponent<SpriteRenderer>();
            if (splashRend != null) splashRend.color = stage.stageSplashColor;
        }

        BallManager ball = FindFirstObjectByType<BallManager>();
        if (ball != null)
        {
            Renderer ballRend = ball.GetComponent<Renderer>();
            if (ballRend != null) ballRend.material.color = stage.stageBallColor;
        }

        Renderer helixRend = GetComponent<Renderer>();
        if (helixRend != null) helixRend.material.color = stage.stageHelixColor;

        transform.localEulerAngles = startRotation;

        foreach (GameObject go in spawnedLevels)
        {
            if (go != null) Destroy(go);
        }
        spawnedLevels.Clear();

        if (stage.levels.Count == 0 || helixLevelPrefab == null) return;

        float levelDistance = helixDistance / stage.levels.Count;
        float spawnPosY = topTransform.localPosition.y;

        for (int i = 0; i < stage.levels.Count; i++)
        {
            spawnPosY -= levelDistance;
            GameObject levelInstance = Instantiate(helixLevelPrefab, transform);
            levelInstance.transform.localPosition = new Vector3(0, spawnPosY, 0);
            spawnedLevels.Add(levelInstance);

            int childCount = levelInstance.transform.childCount;
            int partsToDisable = Mathf.Clamp(12 - stage.levels[i].parCount, 0, childCount);
           
            List<GameObject> disableParts = new List<GameObject>();
            int safetyCounter = 0;

            while (disableParts.Count < partsToDisable && safetyCounter < 100)
            {
                safetyCounter++;
                int randomIndex = Random.Range(0, childCount);
                GameObject randomPart = levelInstance.transform.GetChild(randomIndex).gameObject;

                if (!disableParts.Contains(randomPart))
                {
                    randomPart.SetActive(false);
                    disableParts.Add(randomPart);
                }
            }

            List<GameObject> leftParts = new List<GameObject>();
            foreach (Transform t in levelInstance.transform)
            {
                Renderer partRend = t.GetComponent<Renderer>();
                if (partRend != null) partRend.material.color = stage.stageLevelPartColor;

                if (t.gameObject.activeInHierarchy) leftParts.Add(t.gameObject);
            }

            int deathPartsCount = Mathf.Clamp(stage.levels[i].deathPartCount, 0, leftParts.Count);
            List<GameObject> deathParts = new List<GameObject>();
            safetyCounter = 0;

            while (deathParts.Count < deathPartsCount && leftParts.Count > 0 && safetyCounter < 100)
            {
                safetyCounter++;
                int randomIndex = Random.Range(0, leftParts.Count);
                GameObject randomPart = leftParts[randomIndex];

                if (!deathParts.Contains(randomPart))
                {
                    if (randomPart.GetComponent<DeathZone>() == null) randomPart.AddComponent<DeathZone>();
                    deathParts.Add(randomPart);
                }
            }
        }
    }
}
```

### Implementación
Se asigna al objeto raíz de la torre o cilindro central en la jerarquía de la escena.

---

 

## Tutorial 6: Seguimiento de Cámara Profesional (`CameraAdministrador`)
### ¿Para qué sirve?

Sigue verticalmente a la bola a lo largo de su descenso por la torre. Se ejecuta en `LateUpdate` para evitar  tirones (*jitter*) derivados de las físicas y cuenta con búsqueda automática de referencias. verticalmente y distribuye de forma aleatoria.

### Código del Script (`CameraAdministrador.cs`)
```csharp
using UnityEngine;

public class CameraAdministrador : MonoBehaviour
{
    [Header("Configuración de Seguimiento")]
    [SerializeField] private BallManager ball;
    [SerializeField] private bool smoothFollow = false;
    [SerializeField] private float smoothSpeed = 10f;

    private float offset;

    void Start()
    {
        if (ball == null) ball = FindFirstObjectByType<BallManager>();

        if (ball != null)
        {
            offset = transform.position.y - ball.transform.position.y;
        }
    }

    void LateUpdate()
    {
        if (ball == null) return;

        Vector3 targetPos = transform.position;
        float targetY = ball.transform.position.y + offset;

        if (smoothFollow)
        {
            targetPos.y = Mathf.Lerp(transform.position.y, targetY, smoothSpeed * Time.deltaTime);
        }
        else
        {
            targetPos.y = targetY;
        }

        transform.position = targetPos;
    }
}
```

### Implementación
Agrégalo al componente Main Camera de tu escena de juego.

---



No hay comentarios

Imágenes del tema: merrymoonmary. Con la tecnología de Blogger.