tengo un problema en el juego tycoon que estoy des...

Creado el: 10 de junio de 2025

Respondido usando O3 por Chat01

Pregunta

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;

text
public 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

text
private 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

text
IsPlacingBuilding = 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;

text
public 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

text
public 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?

Pensamiento

Considering script flow fix for building placement

The user is developing a Unity tycoon game and has an issue with two scripts activating Start() functions prematurely during placement preview. The challenge is preventing the building's Start() from executing before it’s actually placed. A possible fix could involve using flags like isFixed or adjusting initialization flow with functions like Awake or Start(). Alternatively, separating preview logic into a different object with reduced scripts could help by preventing unwanted executions during the preview stage.

Exploring options to handle building placement logic

The user needs a solution where building scripts only run after placement to avoid preview scripts activating prematurely. A possible approach is to disable components or check isFixed before running Start() by using OnEnable. Alternatively, they could instantiate the building with inactive prefab objects or use separate preview and final prefabs to prevent undesired script behavior. Another idea is restructuring scripts to only activate logic once isFixed is true, or storing start logic in Update.

Thought for 32 seconds

Respuesta

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.


Estrategias para evitar que los scripts empiecen antes de tiempo

EstrategiaCuándo usarlaVentajas / 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.

Ejemplo práctico (estrategia B – deshabilitar/rehabilitar scripts)

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:

  • El preview enseña sprites y colisiones, pero no corre lógica.
  • Al confirmar, simplemente vuelves a habilitar todo y el Start() no se vuelve a ejecutar (ya pasó), pero los scripts empiezan a funcionar porque su Update, Coroutine, etc. vuelven a estar habilitados.

Ejemplo con evento 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 ... }

Recomendación

  • Para producción rápida: estrategia B (deshabilitar / habilitar componentes) suele ser la más cómoda: un solo prefab y cambios mínimos.
  • Para proyectos grandes o edificaciones complejas: combinar A y C puede dar mejores separaciones (un prefab preview liviano + evento 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!

Compartir este Q&A