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 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;
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; }
}
entonces, los scripts de los edificios se activan cuando todavia nisiquera es colocado el edificio. Como lo puedo solucionar?
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:
Produccion
, Almacén
, GeneraDinero
, etc.).enabled = false
).Start()
por primera vez.csharppublic 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:
csharpm.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.
Prefab Preview
Prefab Edificio (el de siempre).
En el modo construcción únicamente instancias el prefab Preview.
Cuando el jugador pulse confirmar:
Esto evita por completo el problema porque los scripts no existen hasta que creas el segundo prefab.
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:
csharppublic 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()
.
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!