tengo un problema en el juego tycoon que estoy des...
Creado el: 10 de junio de 2025
Creado el: 10 de junio de 2025
tengo un problema en el juego tycoon que estoy desarrollando (unity). Los scripts ligados a cada edificio activan todas las funciones en Start(), pero el problema es que los dos script que uso para colocar los edificios los instancia al mostrar el preview:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class BuildingGridPlacer : BuildingPlacer
{
public static bool IsPlacingBuilding = false;
textpublic globalControl scriptGlobalUI; public ap0Control script0UI; public Vector2 cellSize = new Vector2(1, 1); public Vector2 gridOffset; public Renderer gridRenderer; public bool isIsometric = false; private bool isMobile; private bool isDragging = false; private Vector2 lastTouchPosition;
#if UNITY_EDITOR
private void OnValidate()
{
_UpdateGridVisual();
}
#endif
textprivate void Start() { isMobile = Application.isMobilePlatform; _UpdateGridVisual(); _EnableGridVisual(false); } private void Update() { if (_buildingPrefab != null) { if (isMobile) { _HandleMobileInput(); } else { _HandlePCInput(); } } } private void _HandlePCInput() { if (_buildingPrefab != null) { if (Input.GetMouseButtonDown(1)) { _CancelBuildingMode(); return; } if (EventSystem.current.IsPointerOverGameObject()) { if (Input.GetMouseButtonDown(0)) { _CancelBuildingMode(); return; } if (_toBuild.activeSelf) _toBuild.SetActive(false); return; } else if (!_toBuild.activeSelf) _toBuild.SetActive(true); if (Input.GetKeyDown(KeyCode.Space)) { // esta madre no hace nada } Vector3 mouseWorldPos3D = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector2 mouseWorldPos = new Vector2(mouseWorldPos3D.x, mouseWorldPos3D.y); Vector2 snappedPos = isIsometric ? _ClampToNearestIsometric(mouseWorldPos, cellSize) : _ClampToNearest(mouseWorldPos, cellSize); _toBuild.transform.position = new Vector3(snappedPos.x, snappedPos.y, _toBuild.transform.position.z); foreach (SpriteRenderer sr in _toBuild.GetComponent<BuildingManager>().spriteRenderers) { sr.sortingOrder = _CalculateSortingOrder(sr.transform.position) + 1000; } if (Input.GetMouseButtonDown(0)) { _InstanceBuilding(); return; } } } private void _HandleMobileInput() { if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); // Si el usuario toca (tap) sobre la UI, cancelar modo construcción if (touch.phase == TouchPhase.Began && IsPointerOverUI(touch.position)) { _CancelBuildingMode(); return; } if (touch.phase == TouchPhase.Began && IsPointerOverBuildUI(touch.position)) { // Mostrar preview sin moverlo if (!_toBuild.activeSelf) _toBuild.SetActive(true); isDragging = false; return; } // Siempre mostrar el preview mientras esté en modo construcción if (!_toBuild.activeSelf) _toBuild.SetActive(true); Vector3 touchWorldPos3D = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, Camera.main.nearClipPlane)); Vector2 touchWorldPos = new Vector2(touchWorldPos3D.x, touchWorldPos3D.y); switch (touch.phase) { case TouchPhase.Began: isDragging = true; lastTouchPosition = touchWorldPos; Vector2 snappedStartPos = isIsometric ? _ClampToNearestIsometric(touchWorldPos, cellSize) : _ClampToNearest(touchWorldPos, cellSize); _toBuild.transform.position = new Vector3(snappedStartPos.x, snappedStartPos.y, _toBuild.transform.position.z); break; case TouchPhase.Moved: if (isDragging) { Vector2 snappedPos = isIsometric ? _ClampToNearestIsometric(touchWorldPos, cellSize) : _ClampToNearest(touchWorldPos, cellSize); _toBuild.transform.position = new Vector3(snappedPos.x, snappedPos.y, _toBuild.transform.position.z); } break; case TouchPhase.Ended: case TouchPhase.Canceled: isDragging = false; break; } } if (_toBuild != null) { foreach (SpriteRenderer sr in _toBuild.GetComponent<BuildingManager>().spriteRenderers) { sr.sortingOrder = _CalculateSortingOrder(sr.transform.position) + 1000; } } } private void _InstanceBuilding() { if (_toBuild != null) { BuildingManager m = _toBuild.GetComponent<BuildingManager>(); if (m.hasValidPlacement) { m.SetPlacementMode(PlacementMode.Fixed); foreach (SpriteRenderer sr in m.spriteRenderers) { sr.sortingOrder = _CalculateSortingOrder(sr.transform.position); } _buildingPrefab = null; _toBuild = null; _EnableGridVisual(false); script0UI.OcultarIndicaciones(); script0UI.BotonesConsOff(); IsPlacingBuilding = false; } } } private void _CancelBuildingMode() { if (_toBuild != null) Destroy(_toBuild); _toBuild = null; _buildingPrefab = null; _EnableGridVisual(false); script0UI.OcultarIndicaciones(); script0UI.BotonesConsOff(); IsPlacingBuilding = false; } public void InstanceBuilding() { _InstanceBuilding(); } public void CancelBuilding() { _CancelBuildingMode(); } protected override void _PrepareBuilding() { base._PrepareBuilding(); _EnableGridVisual(true); scriptGlobalUI.CloseGlobalPanels(); script0UI.MostrarIndicaciones();
#if UNITY_ANDROID
script0UI.BotonesConsOn();
if (_toBuild != null && Camera.main != null)
{
// centro de la pantalla en píxeles
Vector3 screenCenter = new Vector3(Screen.width * 0.5f,
Screen.height * 0.5f,
Mathf.Abs(Camera.main.transform.position.z));
text// pasar a coordenadas mundo Vector2 worldCenter = Camera.main.ScreenToWorldPoint(screenCenter); // ajustar a la rejilla (cartesiana o isométrica) Vector2 snapped = isIsometric ? _ClampToNearestIsometric(worldCenter, cellSize) : _ClampToNearest(worldCenter, cellSize); // posicionar el preview _toBuild.transform.position = new Vector3(snapped.x, snapped.y, _toBuild.transform.position.z); }
#endif
textIsPlacingBuilding = true; } private Vector2 _ClampToNearest(Vector2 pos, Vector2 cell) { Vector2 snapped = new Vector2( Mathf.Floor(pos.x / cell.x) * cell.x + (cell.x * 0.5f) + gridOffset.x, Mathf.Floor(pos.y / cell.y) * cell.y + (cell.y * 0.5f) + gridOffset.y ); return snapped; } private Vector2 _ClampToNearestIsometric(Vector2 pos, Vector2 cell) { // Medidas de medio tile float halfW = cell.x * 0.5f; float halfH = cell.y * 0.5f; // Convertir posición mundo a "coordenadas de rejilla" float isoX = (pos.x / halfW + pos.y / halfH) * 0.5f; float isoY = (pos.y / halfH - pos.x / halfW) * 0.5f; // Snap a entero int gridX = Mathf.RoundToInt(isoX); int gridY = Mathf.RoundToInt(isoY); // Volver a convertir a coordenadas mundo float worldX = (gridX - gridY) * halfW; float worldY = (gridX + gridY) * halfH; return new Vector2(worldX + gridOffset.x, worldY + gridOffset.y); } private void _EnableGridVisual(bool on) { if (gridRenderer == null) return; gridRenderer.gameObject.SetActive(on); } private void _UpdateGridVisual() { if (gridRenderer == null) return; gridRenderer.sharedMaterial.SetVector("_Cell_Size", new Vector4(cellSize.x, cellSize.y, 0, 0)); } private int _CalculateSortingOrder(Vector3 position) { return Mathf.RoundToInt(-position.y * 100); } private bool IsPointerOverUI(Vector2 screenPos) { PointerEventData data = new PointerEventData(EventSystem.current) { position = screenPos }; List<RaycastResult> results = new List<RaycastResult>(); EventSystem.current.RaycastAll(data, results); foreach (var res in results) { Transform t = res.gameObject.transform; bool isBuildUI = false; while (t != null) { if (t.CompareTag("BuildUI")) { isBuildUI = true; break; } t = t.parent; } if (isBuildUI) continue; return true; } return false; } private bool IsPointerOverBuildUI(Vector2 screenPos) { PointerEventData data = new PointerEventData(EventSystem.current) { position = screenPos }; List<RaycastResult> results = new List<RaycastResult>(); EventSystem.current.RaycastAll(data, results); foreach (var res in results) { Transform t = res.gameObject.transform; while (t != null) { if (t.CompareTag("BuildUI")) return true; t = t.parent; } } return false; }
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public enum PlacementMode
{
Fixed,
Valid,
Invalid
}
public class BuildingManager : MonoBehaviour
{
public Color validPlacementColor = Color.green;
public Color invalidPlacementColor = Color.red;
textpublic SpriteRenderer[] spriteRenderers; private Dictionary<SpriteRenderer, Color> initialColors; [HideInInspector] public bool hasValidPlacement; [HideInInspector] public bool isFixed; private int _nObstacles; private void Awake() { hasValidPlacement = true; isFixed = true; _nObstacles = 0; _InitializeColors(); } private void OnTriggerEnter2D(Collider2D other) { if (isFixed) return; // ignore ground objects if (_IsGround(other.gameObject)) return; _nObstacles++; SetPlacementMode(PlacementMode.Invalid); } private void OnTriggerExit2D(Collider2D other) { if (isFixed) return; // ignore ground objects if (_IsGround(other.gameObject)) return; _nObstacles--; if (_nObstacles == 0) SetPlacementMode(PlacementMode.Valid); }
#if UNITY_EDITOR
private void OnValidate()
{
_InitializeColors();
}
#endif
textpublic void SetPlacementMode(PlacementMode mode) { if (mode == PlacementMode.Fixed) { isFixed = true; hasValidPlacement = true; } else if (mode == PlacementMode.Valid) { hasValidPlacement = true; } else { hasValidPlacement = false; } SetColor(mode); } public void SetColor(PlacementMode mode) { Color colorToApply; if (mode == PlacementMode.Fixed) { foreach (SpriteRenderer sr in spriteRenderers) sr.color = initialColors[sr]; return; } colorToApply = (mode == PlacementMode.Valid) ? validPlacementColor : invalidPlacementColor; foreach (SpriteRenderer sr in spriteRenderers) sr.color = colorToApply; } private void _InitializeColors() { if (initialColors == null) initialColors = new Dictionary<SpriteRenderer, Color>(); else initialColors.Clear(); foreach (SpriteRenderer sr in spriteRenderers) initialColors[sr] = sr.color; } private bool _IsGround(GameObject o) { return ((1 << o.layer) & BuildingPlacer.instance.groundLayerMask.value) != 0; }
}
entonces, los scripts de los edificios se activan cuando todavia nisiquera es colocado el edificio. Como lo puedo solucionar?
Por qué pasa
En Unity Awake()
y Start()
se ejecutan en cuanto el GameObject
y el MonoBehaviour
están activos y habilitados.
Cuando creas el “preview” con Instantiate()
lo estás instanciando ya activo (SetActive(true)
por defecto), así que todos los scripts de ese prefab entran en Awake/Start
como si el edificio ya estuviera colocado.
Estrategia | Cuándo usarla | Ventajas / Inconvenientes |
---|---|---|
A. Prefab doble<br>– Un prefab ligero solo-preview (sin lógica, solo los SpriteRenderer ).<br>– Al confirmar, instancias el prefab “real”. | Proyecto pequeño o pocos tipos de edificio. | Sencillo, no hay Start() fantasma.<br>Necesitas mantener dos prefabs por edificio. |
B. Deshabilitar componentes hasta el “place”<br>– Instancias el prefab normal.<br>– Tras Instantiate , recorres todos los MonoBehaviour y los desactivas (component.enabled = false ).<br>– Cuando el jugador confirma, vuelves a habilitarlos. | Muchos edificios diferentes pero con scripts comunes. | Solo un prefab.<br>Código centralizado. |
C. Comprobar estado en tus scripts<br>En cada script productivo:<br>csharp<br>void Start() { if (!GetComponent<BuildingManager>().isFixed) return; ... }<br> <br>y suscribirte a un evento OnPlaced para iniciar la lógica. | Cuando cambiar todos los scripts es aceptable. | No necesitas tocar el sistema de colocación.<br>Start() solo se ejecuta una vez, así que la lógica debe moverse a otro método llamado al colocar. |
csharp// -------- BuildingGridPlacer -------- protected override void _PrepareBuilding() { base._PrepareBuilding(); // Instanciamos el preview _toBuild = Instantiate(_buildingPrefab); // 1. Deshabilitamos todos los MonoBehaviour foreach (var mb in _toBuild.GetComponentsInChildren<MonoBehaviour>()) { // Dejamos activos los que SÍ necesitamos durante el preview if (mb is BuildingManager) continue; // queremos el color verde/rojo mb.enabled = false; } _EnableGridVisual(true); ... } private void _InstanceBuilding() { if (_toBuild == null) return; var bm = _toBuild.GetComponent<BuildingManager>(); if (!bm.hasValidPlacement) return; // 2. Rehabilitamos los componentes foreach (var mb in _toBuild.GetComponentsInChildren<MonoBehaviour>()) mb.enabled = true; bm.SetPlacementMode(PlacementMode.Fixed); ... }
Con esto:
Start()
no se vuelve a ejecutar (ya pasó), pero los scripts empiezan a funcionar porque su Update
, Coroutine
, etc. vuelven a estar habilitados.OnPlaced
(estrategia C)csharp// -------- BuildingManager -------- public UnityEvent OnPlaced = new UnityEvent(); public void SetPlacementMode(PlacementMode mode) { ... if (mode == PlacementMode.Fixed) OnPlaced.Invoke(); // avisamos a los otros scripts } // -------- Cualquier otro script del edificio -------- void Start() { GetComponent<BuildingManager>().OnPlaced.AddListener(Init); } void Init() { // Aquí arranca la lógica productiva ... }
OnPlaced
).Con cualquiera de estos métodos los Start()
de tus scripts ya no se ejecutarán mientras el jugador esté simplemente moviendo el edificio en modo construcción. ¡Éxitos con tu tycoon!