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 el 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; }

}


entonces, los scripts de los edificios se activan cuando todavia nisiquera es colocado el edificio. Como lo puedo solucionar?

Pensamiento

Proposing a solution for the problem

The user mentioned a problem where the Start() function in the building script is activated during preview instantiation, which happens before final placement. To fix this, I could suggest adding a PlacementMode (as an enum) with states like Preview and Fixed. The main logic in the script would only execute in the Fixed state, preventing premature execution during placement. This way, the initialization happens only after the building is placed.

Proposing building script modifications

The user is dealing with issues where building scripts are activated during preview placement. I could suggest a few solutions: one approach involves using Awake for preview-related functions and deferring Start() until placement. Another solution is to instantiate the preview with behavior scripts disabled, then enable them after placement. Alternatively, they could modify the building script to check for PlacementMode.Fixed before running the main logic. This could involve creating separate prefabs for preview and real buildings to streamline the process.

Proposing building script management

I’m considering two solutions for managing building scripts before placement. One approach involves creating separate prefabs: one for preview and another for the real building. Alternatively, I propose gating building scripts based on a PlacementMode flag. By disabling components in preview mode and enabling them only when placed, I can prevent premature execution. The BuildingManager can manage this, adjusting behavior based on the mode. This method allows us to delay initialization until the building is fully placed.

Thought for 40 seconds

Respuesta

Cuando instancias el prefab para el preview Unity ya lo considera “vivo”, por eso todos los Start() de los componentes del edificio se ejecutan en la primera actualización del frame. Para impedir que la lógica “real” se dispare antes de colocar el edificio tienes básicamente dos caminos habituales:


1. Desactivar los scripts mientras el objeto está en modo preview

Pasos

  1. Enumera qué componentes deben esperar (por ejemplo Produccion, Almacén, GeneraDinero, etc.).
  2. Al crear el preview, desactívalos (enabled = false).
  3. Cuando el jugador confirme la colocación, vuelve a habilitarlos antes de salir del modo construcción; en ese momento se ejecutará su Start() por primera vez.
csharp
public enum PlacementMode { Preview, Fixed } public class BuildingManager : MonoBehaviour { public PlacementMode Mode { get; private set; } // Arrastra aquí todos los scripts que no deben correr en preview [SerializeField] MonoBehaviour[] runtimeBehaviours; void Awake() { // El prefab ya llega en modo Preview SetPlacementMode(PlacementMode.Preview); } public void SetPlacementMode(PlacementMode newMode) { Mode = newMode; bool active = Mode == PlacementMode.Fixed; foreach (var b in runtimeBehaviours) b.enabled = active; // Start() se lanzará la primera vez que pase de false→true } }

En tu BuildingGridPlacer._InstanceBuilding() ya estás llamando:

csharp
m.SetPlacementMode(PlacementMode.Fixed);

Con el cambio anterior los componentes se activan ahí mismo y sus Start() se ejecutan solo una vez el edificio está fijado.


2. Usar dos prefabs (Preview / Definitivo)

  1. Prefab Preview

    • Solo contiene los SpriteRenderer y, si quieres, un material semitransparente.
    • Ningún script de lógica.
  2. Prefab Edificio (el de siempre).

  3. En el modo construcción únicamente instancias el prefab Preview.

  4. Cuando el jugador pulse confirmar:

    • Instancias el prefab Edificio en la misma posición.
    • Destruyes el preview.

Esto evita por completo el problema porque los scripts no existen hasta que creas el segundo prefab.


3. Mover la inicialización a un método explícito

Si prefieres no tocar el sistema de activación, quita la lógica costosa de Start() y muévela a un método como Init() o OnPlaced() que llamas manualmente justo después de fijar el edificio. Ejemplo:

csharp
public class Generador : MonoBehaviour { bool _initialized; public void OnPlaced() { if (_initialized) return; _initialized = true; // Toda la lógica que antes estaba en Start() // ... } }

Luego, tras SetPlacementMode(PlacementMode.Fixed) recorres todos los scripts que deban inicializarse y llamas a OnPlaced().


¿Cuál elegir?

  • Opción 1 (activar/desactivar scripts) es la que menos ensucia tu jerarquía y no duplica prefabs.
  • Opción 2 da más libertad visual (material translúcido, outline, etc.) y mantiene tu lógica de juego 100 % separada del preview.
  • Opción 3 sólo merece la pena si ya tienes mucho código en Start() y no quieres tocar la activación de componentes.

Con cualquiera de estas soluciones tus edificios no “arrancarán” hasta que el jugador los coloque definitivamente. ¡Así evitas cobros, producción o animaciones antes de tiempo!

Compartir este Q&A