using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; public class Game : MonoBehaviour { // Auto-bootstrap when game starts - no Editor setup needed [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static void Bootstrap() { if (Instance != null) return; var gameGO = new GameObject("Game"); gameGO.AddComponent(); } // =========================================== // CONFIGURATION - All tunable constants here // =========================================== // Gameplay public const int UnitsPerTeam = 3; public const float UnitSpeed = 5f; public const float UnitSize = 1f; public const float UnitColliderRadius = 0.5f; // Vision & Detection public const float VisionRadius = 4f; public const float TagRadius = 0.75f; public const float FlagPickupRadius = 0.75f; // Timing public const float RespawnDelay = 5f; public const float FlagReturnDelay = 5f; // Scoring public const int WinScore = 3; // Map - 9:19 aspect ratio for smartphone (portrait) public const float MapWidth = 18f; public const float MapHeight = 38f; // Bases public const float BaseSize = 5f; public const float BaseInset = 4f; // Distance from map edge // Houses public const float HouseMinSize = 2f; public const float HouseMaxSize = 4f; // References set during setup public static Game Instance { get; private set; } public List playerUnits = new(); public List enemyUnits = new(); public Transform playerBase; public Transform enemyBase; public Flag playerFlag; public Flag enemyFlag; int playerScore; int enemyScore; bool gameOver; TextMeshProUGUI scoreText; TextMeshProUGUI gameOverText; void Awake() { Instance = this; } void Start() { SetupCamera(); CreateGround(); CreateObstacles(); CreateBases(); CreateFlags(); SpawnUnits(); CreateUI(); SetupSystems(); } void SetupCamera() { var cam = Camera.main; if (cam != null) { cam.orthographic = true; cam.orthographicSize = MapHeight / 2f + 2f; cam.transform.position = new Vector3(0, 0, -10); } } void CreateGround() { var ground = CreateSprite("Ground", new Color(0.2f, 0.4f, 0.2f), MapWidth, MapHeight); ground.transform.position = Vector3.zero; var sr = ground.GetComponent(); sr.sortingOrder = -10; } void CreateObstacles() { // Dense house layout creating multiple route choices // Layout designed for 18x38 map with bases at corners // Houses create lanes, chokepoints, and flanking routes Vector2[] housePositions = { // Bottom section (near player base) - create 3 exit routes new(-4f, -14f), new(4f, -12f), new(0f, -10f), // Lower-mid section - force route decisions new(-6f, -6f), new(-2f, -4f), new(3f, -7f), new(6f, -3f), // Center section - dense, creates cat-and-mouse area new(-5f, 2f), new(-1f, 0f), new(2f, 3f), new(5f, -1f), new(0f, 5f), // Upper-mid section - mirror complexity new(-6f, 8f), new(-2f, 10f), new(4f, 7f), new(6f, 11f), // Top section (near enemy base) - create 3 approach routes new(-4f, 14f), new(0f, 12f), new(5f, 15f), }; Vector2[] houseSizes = { // Bottom section new(3f, 2.5f), new(2.5f, 3f), new(2f, 2f), // Lower-mid new(2.5f, 3f), new(3f, 2f), new(2f, 2.5f), new(2.5f, 2.5f), // Center - varied sizes for interesting gaps new(3f, 2f), new(2f, 3f), new(2.5f, 2f), new(2f, 2.5f), new(2f, 2f), // Upper-mid new(2.5f, 2.5f), new(2f, 3f), new(3f, 2.5f), new(2f, 2f), // Top section new(2.5f, 3f), new(3f, 2f), new(2f, 2.5f), }; for (int i = 0; i < housePositions.Length; i++) { var house = CreateSprite($"House_{i}", new Color(0.35f, 0.35f, 0.4f), houseSizes[i].x, houseSizes[i].y); house.transform.position = new Vector3(housePositions[i].x, housePositions[i].y, 0); var collider = house.AddComponent(); collider.size = Vector2.one; // Local space - scale handles actual size var sr = house.GetComponent(); sr.sortingOrder = -5; } } void CreateBases() { // Player base - bottom center var playerBaseGO = CreateSprite("PlayerBase", new Color(0.2f, 0.3f, 0.8f, 0.5f), BaseSize, BaseSize); playerBaseGO.transform.position = new Vector3(0, -MapHeight / 2f + BaseInset, 0); playerBase = playerBaseGO.transform; var sr1 = playerBaseGO.GetComponent(); sr1.sortingOrder = -8; var playerBaseTrigger = playerBaseGO.AddComponent(); playerBaseTrigger.isTrigger = true; playerBaseTrigger.size = Vector2.one; var playerBaseZone = playerBaseGO.AddComponent(); playerBaseZone.team = Unit.Team.Player; // Enemy base - top center var enemyBaseGO = CreateSprite("EnemyBase", new Color(0.8f, 0.2f, 0.2f, 0.5f), BaseSize, BaseSize); enemyBaseGO.transform.position = new Vector3(0, MapHeight / 2f - BaseInset, 0); enemyBase = enemyBaseGO.transform; var sr2 = enemyBaseGO.GetComponent(); sr2.sortingOrder = -8; var enemyBaseTrigger = enemyBaseGO.AddComponent(); enemyBaseTrigger.isTrigger = true; enemyBaseTrigger.size = Vector2.one; var enemyBaseZone = enemyBaseGO.AddComponent(); enemyBaseZone.team = Unit.Team.Enemy; } void CreateFlags() { // Player flag at player base var playerFlagGO = CreateSprite("PlayerFlag", new Color(0.3f, 0.5f, 1f), 1f, 1.5f); playerFlagGO.transform.position = playerBase.position; playerFlag = playerFlagGO.AddComponent(); playerFlag.team = Unit.Team.Player; playerFlag.homePosition = playerBase; var flagCollider1 = playerFlagGO.AddComponent(); flagCollider1.isTrigger = true; flagCollider1.radius = FlagPickupRadius; var rb1 = playerFlagGO.AddComponent(); rb1.bodyType = RigidbodyType2D.Kinematic; var sr1 = playerFlagGO.GetComponent(); sr1.sortingOrder = 5; // Enemy flag at enemy base var enemyFlagGO = CreateSprite("EnemyFlag", new Color(1f, 0.3f, 0.3f), 1f, 1.5f); enemyFlagGO.transform.position = enemyBase.position; enemyFlag = enemyFlagGO.AddComponent(); enemyFlag.team = Unit.Team.Enemy; enemyFlag.homePosition = enemyBase; var flagCollider2 = enemyFlagGO.AddComponent(); flagCollider2.isTrigger = true; flagCollider2.radius = FlagPickupRadius; var rb2 = enemyFlagGO.AddComponent(); rb2.bodyType = RigidbodyType2D.Kinematic; var sr2 = enemyFlagGO.GetComponent(); sr2.sortingOrder = 5; } void SpawnUnits() { // Spawn player units near player base for (int i = 0; i < UnitsPerTeam; i++) { var offset = GetUnitSpawnOffset(i, UnitsPerTeam, isPlayer: true); var unit = CreateUnit($"PlayerUnit_{i}", Unit.Team.Player, playerBase.position + offset); playerUnits.Add(unit); } // Spawn enemy units near enemy base for (int i = 0; i < UnitsPerTeam; i++) { var offset = GetUnitSpawnOffset(i, UnitsPerTeam, isPlayer: false); var unit = CreateUnit($"EnemyUnit_{i}", Unit.Team.Enemy, enemyBase.position + offset); enemyUnits.Add(unit); } } Vector3 GetUnitSpawnOffset(int index, int total, bool isPlayer) { float spacing = 1.8f; float xOffset = (index - (total - 1) / 2f) * spacing; float yOffset = isPlayer ? -2f : 2f; return new Vector3(xOffset, yOffset, 0); } Unit CreateUnit(string name, Unit.Team team, Vector3 position) { Color color = team == Unit.Team.Player ? new Color(0.3f, 0.5f, 1f) : new Color(1f, 0.3f, 0.3f); var unitGO = CreateSprite(name, color, UnitSize, UnitSize); unitGO.transform.position = position; var unit = unitGO.AddComponent(); unit.team = team; var collider = unitGO.AddComponent(); collider.isTrigger = true; collider.radius = UnitColliderRadius; var rb = unitGO.AddComponent(); rb.bodyType = RigidbodyType2D.Kinematic; var sr = unitGO.GetComponent(); sr.sortingOrder = 10; return unit; } void CreateUI() { // Create Canvas var canvasGO = new GameObject("Canvas"); var canvas = canvasGO.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvasGO.AddComponent(); canvasGO.AddComponent(); // Score text var scoreGO = new GameObject("ScoreText"); scoreGO.transform.SetParent(canvasGO.transform, false); scoreText = scoreGO.AddComponent(); scoreText.text = "0 - 0"; scoreText.fontSize = 48; scoreText.alignment = TextAlignmentOptions.Center; scoreText.color = Color.white; var scoreRect = scoreGO.GetComponent(); scoreRect.anchorMin = new Vector2(0.5f, 1f); scoreRect.anchorMax = new Vector2(0.5f, 1f); scoreRect.pivot = new Vector2(0.5f, 1f); scoreRect.anchoredPosition = new Vector2(0, -20); scoreRect.sizeDelta = new Vector2(200, 60); // Game over text (hidden initially) var gameOverGO = new GameObject("GameOverText"); gameOverGO.transform.SetParent(canvasGO.transform, false); gameOverText = gameOverGO.AddComponent(); gameOverText.text = ""; gameOverText.fontSize = 72; gameOverText.alignment = TextAlignmentOptions.Center; gameOverText.color = Color.white; gameOverText.gameObject.SetActive(false); var gameOverRect = gameOverGO.GetComponent(); gameOverRect.anchorMin = new Vector2(0.5f, 0.5f); gameOverRect.anchorMax = new Vector2(0.5f, 0.5f); gameOverRect.pivot = new Vector2(0.5f, 0.5f); gameOverRect.anchoredPosition = Vector2.zero; gameOverRect.sizeDelta = new Vector2(400, 100); } void SetupSystems() { // Add RouteDrawer gameObject.AddComponent(); // Add Visibility system var visibility = gameObject.AddComponent(); visibility.playerUnits = playerUnits.ToArray(); visibility.enemyUnits = enemyUnits.ToArray(); visibility.enemyFlag = enemyFlag; // Add SimpleAI var ai = gameObject.AddComponent(); ai.aiUnits = enemyUnits.ToArray(); ai.playerFlag = playerFlag; ai.aiBase = enemyBase; } public void Score(Unit.Team team) { if (gameOver) return; if (team == Unit.Team.Player) playerScore++; else enemyScore++; UpdateScoreUI(); Debug.Log($"Score: {playerScore} - {enemyScore}"); if (playerScore >= WinScore) EndGame(true); else if (enemyScore >= WinScore) EndGame(false); else ResetRound(); } void UpdateScoreUI() { if (scoreText != null) scoreText.text = $"{playerScore} - {enemyScore}"; } void ResetRound() { // Return flags to home playerFlag.ReturnHome(); enemyFlag.ReturnHome(); // Respawn all units at bases for (int i = 0; i < playerUnits.Count; i++) { var offset = GetUnitSpawnOffset(i, playerUnits.Count, isPlayer: true); playerUnits[i].ForceRespawn(playerBase.position + offset); } for (int i = 0; i < enemyUnits.Count; i++) { var offset = GetUnitSpawnOffset(i, enemyUnits.Count, isPlayer: false); enemyUnits[i].ForceRespawn(enemyBase.position + offset); } } void EndGame(bool playerWon) { gameOver = true; if (gameOverText != null) { gameOverText.text = playerWon ? "YOU WIN!" : "YOU LOSE!"; gameOverText.color = playerWon ? Color.green : Color.red; gameOverText.gameObject.SetActive(true); } Debug.Log(playerWon ? "Player wins!" : "Enemy wins!"); } public Transform GetBase(Unit.Team team) { return team == Unit.Team.Player ? playerBase : enemyBase; } public Flag GetFlag(Unit.Team team) { return team == Unit.Team.Player ? playerFlag : enemyFlag; } // Helper to create a colored sprite GameObject CreateSprite(string name, Color color, float width, float height) { var go = new GameObject(name); var sr = go.AddComponent(); sr.sprite = CreateRectSprite(); sr.color = color; go.transform.localScale = new Vector3(width, height, 1); return go; } // Create a simple white 1x1 sprite static Sprite cachedSprite; static Sprite CreateRectSprite() { if (cachedSprite != null) return cachedSprite; var tex = new Texture2D(1, 1); tex.SetPixel(0, 0, Color.white); tex.Apply(); cachedSprite = Sprite.Create(tex, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f), 1); return cachedSprite; } } // Simple component to identify base zones public class BaseZone : MonoBehaviour { public Unit.Team team; }