using UnityEngine; using Photon.Pun; using System.Collections; // Local presentation only. Gravity is evaluated by each ship owner, then normal ship sync replicates it. public class JellyArenaVisuals : MonoBehaviour { private static JellyArenaVisuals active; private static readonly Vector2[] WellPoints = { new Vector2(-17, 10), new Vector2(17,-10), new Vector2(19,16), new Vector2(-19,-16), new Vector2(-27,0), new Vector2(27,0) }; private static readonly Vector2[] JellyfishPoints = { new Vector2(-16,18), new Vector2(18,-17), new Vector2(22,14) }; private readonly Vector2[] wells = WellPoints; private readonly System.Collections.Generic.List ornaments = new System.Collections.Generic.List(); private readonly System.Collections.Generic.List origins = new System.Collections.Generic.List(); private readonly System.Collections.Generic.List debris = new System.Collections.Generic.List(); private SpriteRenderer Add(Sprite sprite, Vector2 point, float size, string label, int order) => Add(transform, sprite, point, size, label, order); private static SpriteRenderer Add(Transform parent, Sprite sprite, Vector2 point, float size, string label, int order) { var item = new GameObject(label); #if UNITY_EDITOR if (!Application.isPlaying) UnityEditor.Undo.RegisterCreatedObjectUndo(item, "Build Editable Map Layouts"); #endif item.transform.SetParent(parent, false); item.transform.localPosition = point; var art = item.AddComponent(); art.sprite = sprite; art.sortingOrder = order; item.transform.localScale = Vector3.one * (size / Mathf.Max(sprite.bounds.size.x, sprite.bounds.size.y)); return art; } // ของประดับที่บันทึกลง Scene ได้ (แก้ขนาด/ตำแหน่งแกนกลางและแมงกะพรุนได้) // GravityWell ผูกกับแรงดึงจริงในเกม จึงถูกวางกลับตำแหน่งเดิมทุกครั้งที่เริ่มเกม private static bool IsEditableOrnament(string name) => name == "EnergyCoreArtwork" || name == "GravityWell" || name == "FloatingJellyfish"; void Awake() { var sheet = SkillSheetVisual.Load("Props_Jellyfish"); var jelly = System.Array.Find(sheet, s => s.name == "Props_Jellyfish_1"); var orb = System.Array.Find(sheet, s => s.name == "Props_Jellyfish_2"); if (jelly == null || orb == null) { enabled = false; return; } bool authored = transform.Find("EnergyCoreArtwork") != null; foreach (Transform child in transform) if (child.name != "Background" && !(authored && IsEditableOrnament(child.name))) child.gameObject.SetActive(false); active = this; if (!authored || !BindAuthoredOrnaments()) { // A visible core anchors the map visually and matches the EnergyCore zone. var core = Add(orb, Vector2.zero, 10, "EnergyCoreArtwork", -1); ornaments.Add(core); origins.Add(core.transform.localPosition); foreach (Vector2 point in wells) { var art = Add(orb, point, 5, "GravityWell", -1); ornaments.Add(art); origins.Add(art.transform.localPosition); } foreach (Vector2 point in JellyfishPoints) { var art = Add(jelly, point, 10, "FloatingJellyfish", -2); ornaments.Add(art); origins.Add(art.transform.localPosition); } } var rocks = SkillSheetVisual.Load("Obs_Asteroids"); var rock = System.Array.Find(rocks, s => s.name == "Obs_Asteroids_1"); if (rock != null) for (int i=0; i<24; i++) debris.Add(Add(rock, Vector2.zero, 1, "VortexDecoration", -6)); } // ใช้ของประดับที่บันทึกไว้ใน Scene; ลำดับต้องเป็น แกนกลาง → หลุมแรงโน้มถ่วง → แมงกะพรุน (Update ใช้ลำดับนี้) private bool BindAuthoredOrnaments() { var core = transform.Find("EnergyCoreArtwork").GetComponent(); var wellArt = new System.Collections.Generic.List(); var jellyArt = new System.Collections.Generic.List(); foreach (Transform child in transform) { var art = child.GetComponent(); if (art == null || art.sprite == null) continue; if (child.name == "GravityWell") wellArt.Add(art); else if (child.name == "FloatingJellyfish") jellyArt.Add(art); } if (core == null || core.sprite == null || wellArt.Count != wells.Length) { // ไม่ครบ: ซ่อนของที่บันทึกไว้แล้วกลับไปสร้างด้วยโค้ดแบบเดิม Debug.LogWarning("Map0_Layout ornaments are incomplete (need EnergyCoreArtwork and " + wells.Length + " GravityWell); using the code-built ornaments.", this); foreach (Transform child in transform) if (IsEditableOrnament(child.name)) child.gameObject.SetActive(false); return false; } ornaments.Add(core); origins.Add(core.transform.localPosition); for (int i = 0; i < wellArt.Count; i++) { wellArt[i].transform.localPosition = wells[i]; ornaments.Add(wellArt[i]); origins.Add(wellArt[i].transform.localPosition); } foreach (var art in jellyArt) { ornaments.Add(art); origins.Add(art.transform.localPosition); } return true; } #if UNITY_EDITOR // ใช้จากเมนู Editable/2 บน GameplayManager: สร้างของประดับแม็พแมงกะพรุนลง Scene ให้แก้ได้ // (เศษหินที่หมุนวนเป็นแอนิเมชันล้วน จึงยังสร้างตอนรัน) public static bool BuildEditableOrnaments(Transform layout) { if (layout == null || layout.Find("EnergyCoreArtwork") != null) return false; var sheet = SkillSheetVisual.Load("Props_Jellyfish"); var jelly = System.Array.Find(sheet, s => s.name == "Props_Jellyfish_1"); var orb = System.Array.Find(sheet, s => s.name == "Props_Jellyfish_2"); if (jelly == null || orb == null) { Debug.LogWarning("Props_Jellyfish sprites are missing."); return false; } UnityEditor.Undo.RegisterFullObjectHierarchyUndo(layout.gameObject, "Build Editable Map Layouts"); foreach (Transform child in layout) if (child.name != "Background") child.gameObject.SetActive(false); Add(layout, orb, Vector2.zero, 10, "EnergyCoreArtwork", -1); foreach (Vector2 point in WellPoints) Add(layout, orb, point, 5, "GravityWell", -1); foreach (Vector2 point in JellyfishPoints) Add(layout, jelly, point, 10, "FloatingJellyfish", -2); return true; } #endif public static Vector2 PullAt(Vector2 position) { if (active == null || !active.isActiveAndEnabled) return Vector2.zero; Vector2 pull = Vector2.zero; foreach (Vector2 point in active.wells) { Vector2 delta = (Vector2)active.transform.TransformPoint(point) - position; float distance = delta.magnitude; if (distance > .15f && distance < 7) pull += delta.normalized * (2.8f * (1-distance/7) * Mathf.Min(1,distance)); } return Vector2.ClampMagnitude(pull, 2.8f); } void Update() { float time = (float)(PhotonNetwork.Time % 10000); for (int i=0;i(FindObjectsSortMode.None); var active = System.Array.FindAll(players, p => !p.isDead); if (active.Length > 0) { Vector2 target = active[Random.Range(0, active.Length)].transform.position; target += Random.insideUnitCircle * 2f; Vector2 min = GameplayManager.GetArenaMin(mapIndex); Vector2 max = GameplayManager.GetArenaMax(mapIndex); spawnPos = new Vector3(Mathf.Clamp(target.x, min.x + 2, max.x - 2), Mathf.Clamp(target.y, min.y + 2, max.y - 2), 0); } hazardPrefabName = "Hazard_Lightning"; waitTime = Random.Range(3f, 8f); } else if (mapIndex == 1) // Obelisk Plains { SpawnPrismSwamp(); hazardPrefabName = ""; // Permanent authored swamp regions are created once above. waitTime = Random.Range(4f, 6f); } else if (mapIndex == 2) // Abandoned Mech Warzone { hazardPrefabName = "Hazard_MoltenAsteroid"; Vector2 min = GameplayManager.GetArenaMin(mapIndex); Vector2 max = GameplayManager.GetArenaMax(mapIndex); spawnPos = new Vector3(Random.Range(min.x + 1f, max.x - 1f), max.y + 3f, 0); waitTime = Random.Range(3f, 6f); // เกิดถี่หน่อย } if (!string.IsNullOrEmpty(hazardPrefabName)) { PhotonNetwork.InstantiateRoomObject(hazardPrefabName, spawnPos, Quaternion.identity); } yield return new WaitForSeconds(waitTime); } } private void SpawnPrismSwamp() { int count = 0; foreach (var hazard in FindObjectsByType(FindObjectsSortMode.None)) if (hazard.type == HazardController.HazardType.SlowZone) count++; if (count >= 6) return; Vector2 min = GameplayManager.GetArenaMin(1), max = GameplayManager.GetArenaMax(1); for (int attempt = 0; attempt < 60; attempt++) { // เกิดเป็นคู่สมมาตร 180 องศา (เหมือนเลย์เอาต์) เดิมเกิดแค่ครึ่งล่าง = ฝั่ง Host เสียเปรียบ Vector2 point = new Vector2(Random.Range(min.x + 6, max.x - 6), Random.Range(min.y + 6, -9)); Vector2 mirror = -point; if (Vector2.Distance(point, new Vector2(0,-16)) < 9) continue; if (Physics2D.OverlapCircleAll(point, 3).Length > 0 || Physics2D.OverlapCircleAll(mirror, 3).Length > 0) continue; int variant = Random.Range(85,88); PhotonNetwork.InstantiateRoomObject("Hazard_SlowZone", point, Quaternion.identity, 0, new object[] { false, variant }); PhotonNetwork.InstantiateRoomObject("Hazard_SlowZone", mirror, Quaternion.identity, 0, new object[] { false, variant }); break; } } }