fix(config): 3 units, smartphone aspect ratio, denser map, 5s respawn
Changes based on playtesting feedback: - UnitsPerTeam: 5 -> 3 (more manageable for demo) - RespawnDelay: 3s -> 5s (tagged units out longer) - Map aspect ratio: 40x30 -> 18x38 (9:19 smartphone portrait) - Houses: 6 -> 19 (dense layout forcing route choices) - Bases: moved to top/bottom center for vertical play All gameplay numbers now organized as public constants for easy tuning. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,17 +15,39 @@ public class Game : MonoBehaviour
|
|||||||
gameGO.AddComponent<Game>();
|
gameGO.AddComponent<Game>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants - tune later
|
// ===========================================
|
||||||
|
// CONFIGURATION - All tunable constants here
|
||||||
|
// ===========================================
|
||||||
|
|
||||||
|
// Gameplay
|
||||||
|
public const int UnitsPerTeam = 3;
|
||||||
public const float UnitSpeed = 5f;
|
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 VisionRadius = 4f;
|
||||||
public const float TagRadius = 0.75f;
|
public const float TagRadius = 0.75f;
|
||||||
public const float RespawnDelay = 3f;
|
public const float FlagPickupRadius = 0.75f;
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
public const float RespawnDelay = 5f;
|
||||||
public const float FlagReturnDelay = 5f;
|
public const float FlagReturnDelay = 5f;
|
||||||
|
|
||||||
|
// Scoring
|
||||||
public const int WinScore = 3;
|
public const int WinScore = 3;
|
||||||
|
|
||||||
// Map dimensions
|
// Map - 9:19 aspect ratio for smartphone (portrait)
|
||||||
const float MapWidth = 40f;
|
public const float MapWidth = 18f;
|
||||||
const float MapHeight = 30f;
|
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
|
// References set during setup
|
||||||
public static Game Instance { get; private set; }
|
public static Game Instance { get; private set; }
|
||||||
@@ -82,33 +104,79 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
void CreateObstacles()
|
void CreateObstacles()
|
||||||
{
|
{
|
||||||
// Houses as obstacles - asymmetric backyard layout
|
// 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 = {
|
Vector2[] housePositions = {
|
||||||
new(-12f, 8f),
|
// Bottom section (near player base) - create 3 exit routes
|
||||||
new(-8f, -5f),
|
new(-4f, -14f),
|
||||||
new(0f, 3f),
|
new(4f, -12f),
|
||||||
new(5f, -8f),
|
new(0f, -10f),
|
||||||
new(10f, 6f),
|
|
||||||
new(14f, -3f),
|
// 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 = {
|
Vector2[] houseSizes = {
|
||||||
new(4f, 3f),
|
// Bottom section
|
||||||
new(3f, 4f),
|
new(3f, 2.5f),
|
||||||
new(5f, 2.5f),
|
new(2.5f, 3f),
|
||||||
new(3f, 3f),
|
new(2f, 2f),
|
||||||
new(4f, 4f),
|
|
||||||
new(3.5f, 3f),
|
// 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++)
|
for (int i = 0; i < housePositions.Length; i++)
|
||||||
{
|
{
|
||||||
var house = CreateSprite($"House_{i}", new Color(0.4f, 0.4f, 0.4f), houseSizes[i].x, houseSizes[i].y);
|
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);
|
house.transform.position = new Vector3(housePositions[i].x, housePositions[i].y, 0);
|
||||||
|
|
||||||
var collider = house.AddComponent<BoxCollider2D>();
|
var collider = house.AddComponent<BoxCollider2D>();
|
||||||
// Size is (1,1) in local space - scale handles actual size
|
collider.size = Vector2.one; // Local space - scale handles actual size
|
||||||
collider.size = Vector2.one;
|
|
||||||
|
|
||||||
var sr = house.GetComponent<SpriteRenderer>();
|
var sr = house.GetComponent<SpriteRenderer>();
|
||||||
sr.sortingOrder = -5;
|
sr.sortingOrder = -5;
|
||||||
@@ -117,30 +185,30 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
void CreateBases()
|
void CreateBases()
|
||||||
{
|
{
|
||||||
// Player base - bottom left
|
// Player base - bottom center
|
||||||
var playerBaseGO = CreateSprite("PlayerBase", new Color(0.2f, 0.3f, 0.8f, 0.5f), 6f, 6f);
|
var playerBaseGO = CreateSprite("PlayerBase", new Color(0.2f, 0.3f, 0.8f, 0.5f), BaseSize, BaseSize);
|
||||||
playerBaseGO.transform.position = new Vector3(-MapWidth / 2f + 5f, -MapHeight / 2f + 5f, 0);
|
playerBaseGO.transform.position = new Vector3(0, -MapHeight / 2f + BaseInset, 0);
|
||||||
playerBase = playerBaseGO.transform;
|
playerBase = playerBaseGO.transform;
|
||||||
var sr1 = playerBaseGO.GetComponent<SpriteRenderer>();
|
var sr1 = playerBaseGO.GetComponent<SpriteRenderer>();
|
||||||
sr1.sortingOrder = -8;
|
sr1.sortingOrder = -8;
|
||||||
|
|
||||||
var playerBaseTrigger = playerBaseGO.AddComponent<BoxCollider2D>();
|
var playerBaseTrigger = playerBaseGO.AddComponent<BoxCollider2D>();
|
||||||
playerBaseTrigger.isTrigger = true;
|
playerBaseTrigger.isTrigger = true;
|
||||||
playerBaseTrigger.size = Vector2.one; // Local space - scale handles actual size
|
playerBaseTrigger.size = Vector2.one;
|
||||||
|
|
||||||
var playerBaseZone = playerBaseGO.AddComponent<BaseZone>();
|
var playerBaseZone = playerBaseGO.AddComponent<BaseZone>();
|
||||||
playerBaseZone.team = Unit.Team.Player;
|
playerBaseZone.team = Unit.Team.Player;
|
||||||
|
|
||||||
// Enemy base - top right
|
// Enemy base - top center
|
||||||
var enemyBaseGO = CreateSprite("EnemyBase", new Color(0.8f, 0.2f, 0.2f, 0.5f), 6f, 6f);
|
var enemyBaseGO = CreateSprite("EnemyBase", new Color(0.8f, 0.2f, 0.2f, 0.5f), BaseSize, BaseSize);
|
||||||
enemyBaseGO.transform.position = new Vector3(MapWidth / 2f - 5f, MapHeight / 2f - 5f, 0);
|
enemyBaseGO.transform.position = new Vector3(0, MapHeight / 2f - BaseInset, 0);
|
||||||
enemyBase = enemyBaseGO.transform;
|
enemyBase = enemyBaseGO.transform;
|
||||||
var sr2 = enemyBaseGO.GetComponent<SpriteRenderer>();
|
var sr2 = enemyBaseGO.GetComponent<SpriteRenderer>();
|
||||||
sr2.sortingOrder = -8;
|
sr2.sortingOrder = -8;
|
||||||
|
|
||||||
var enemyBaseTrigger = enemyBaseGO.AddComponent<BoxCollider2D>();
|
var enemyBaseTrigger = enemyBaseGO.AddComponent<BoxCollider2D>();
|
||||||
enemyBaseTrigger.isTrigger = true;
|
enemyBaseTrigger.isTrigger = true;
|
||||||
enemyBaseTrigger.size = Vector2.one; // Local space - scale handles actual size
|
enemyBaseTrigger.size = Vector2.one;
|
||||||
|
|
||||||
var enemyBaseZone = enemyBaseGO.AddComponent<BaseZone>();
|
var enemyBaseZone = enemyBaseGO.AddComponent<BaseZone>();
|
||||||
enemyBaseZone.team = Unit.Team.Enemy;
|
enemyBaseZone.team = Unit.Team.Enemy;
|
||||||
@@ -157,7 +225,7 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
var flagCollider1 = playerFlagGO.AddComponent<CircleCollider2D>();
|
var flagCollider1 = playerFlagGO.AddComponent<CircleCollider2D>();
|
||||||
flagCollider1.isTrigger = true;
|
flagCollider1.isTrigger = true;
|
||||||
flagCollider1.radius = 0.75f;
|
flagCollider1.radius = FlagPickupRadius;
|
||||||
|
|
||||||
var rb1 = playerFlagGO.AddComponent<Rigidbody2D>();
|
var rb1 = playerFlagGO.AddComponent<Rigidbody2D>();
|
||||||
rb1.bodyType = RigidbodyType2D.Kinematic;
|
rb1.bodyType = RigidbodyType2D.Kinematic;
|
||||||
@@ -174,7 +242,7 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
var flagCollider2 = enemyFlagGO.AddComponent<CircleCollider2D>();
|
var flagCollider2 = enemyFlagGO.AddComponent<CircleCollider2D>();
|
||||||
flagCollider2.isTrigger = true;
|
flagCollider2.isTrigger = true;
|
||||||
flagCollider2.radius = 0.75f;
|
flagCollider2.radius = FlagPickupRadius;
|
||||||
|
|
||||||
var rb2 = enemyFlagGO.AddComponent<Rigidbody2D>();
|
var rb2 = enemyFlagGO.AddComponent<Rigidbody2D>();
|
||||||
rb2.bodyType = RigidbodyType2D.Kinematic;
|
rb2.bodyType = RigidbodyType2D.Kinematic;
|
||||||
@@ -185,27 +253,35 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
void SpawnUnits()
|
void SpawnUnits()
|
||||||
{
|
{
|
||||||
// Spawn 5 player units near player base
|
// Spawn player units near player base
|
||||||
for (int i = 0; i < 5; i++)
|
for (int i = 0; i < UnitsPerTeam; i++)
|
||||||
{
|
{
|
||||||
var offset = new Vector3((i - 2) * 1.5f, -2f, 0);
|
var offset = GetUnitSpawnOffset(i, UnitsPerTeam, isPlayer: true);
|
||||||
var unit = CreateUnit($"PlayerUnit_{i}", Unit.Team.Player, playerBase.position + offset);
|
var unit = CreateUnit($"PlayerUnit_{i}", Unit.Team.Player, playerBase.position + offset);
|
||||||
playerUnits.Add(unit);
|
playerUnits.Add(unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn 5 enemy units near enemy base
|
// Spawn enemy units near enemy base
|
||||||
for (int i = 0; i < 5; i++)
|
for (int i = 0; i < UnitsPerTeam; i++)
|
||||||
{
|
{
|
||||||
var offset = new Vector3((i - 2) * 1.5f, 2f, 0);
|
var offset = GetUnitSpawnOffset(i, UnitsPerTeam, isPlayer: false);
|
||||||
var unit = CreateUnit($"EnemyUnit_{i}", Unit.Team.Enemy, enemyBase.position + offset);
|
var unit = CreateUnit($"EnemyUnit_{i}", Unit.Team.Enemy, enemyBase.position + offset);
|
||||||
enemyUnits.Add(unit);
|
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)
|
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);
|
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, 1f, 1f);
|
var unitGO = CreateSprite(name, color, UnitSize, UnitSize);
|
||||||
unitGO.transform.position = position;
|
unitGO.transform.position = position;
|
||||||
|
|
||||||
var unit = unitGO.AddComponent<Unit>();
|
var unit = unitGO.AddComponent<Unit>();
|
||||||
@@ -213,7 +289,7 @@ public class Game : MonoBehaviour
|
|||||||
|
|
||||||
var collider = unitGO.AddComponent<CircleCollider2D>();
|
var collider = unitGO.AddComponent<CircleCollider2D>();
|
||||||
collider.isTrigger = true;
|
collider.isTrigger = true;
|
||||||
collider.radius = 0.5f;
|
collider.radius = UnitColliderRadius;
|
||||||
|
|
||||||
var rb = unitGO.AddComponent<Rigidbody2D>();
|
var rb = unitGO.AddComponent<Rigidbody2D>();
|
||||||
rb.bodyType = RigidbodyType2D.Kinematic;
|
rb.bodyType = RigidbodyType2D.Kinematic;
|
||||||
@@ -320,13 +396,13 @@ public class Game : MonoBehaviour
|
|||||||
// Respawn all units at bases
|
// Respawn all units at bases
|
||||||
for (int i = 0; i < playerUnits.Count; i++)
|
for (int i = 0; i < playerUnits.Count; i++)
|
||||||
{
|
{
|
||||||
var offset = new Vector3((i - 2) * 1.5f, -2f, 0);
|
var offset = GetUnitSpawnOffset(i, playerUnits.Count, isPlayer: true);
|
||||||
playerUnits[i].ForceRespawn(playerBase.position + offset);
|
playerUnits[i].ForceRespawn(playerBase.position + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < enemyUnits.Count; i++)
|
for (int i = 0; i < enemyUnits.Count; i++)
|
||||||
{
|
{
|
||||||
var offset = new Vector3((i - 2) * 1.5f, 2f, 0);
|
var offset = GetUnitSpawnOffset(i, enemyUnits.Count, isPlayer: false);
|
||||||
enemyUnits[i].ForceRespawn(enemyBase.position + offset);
|
enemyUnits[i].ForceRespawn(enemyBase.position + offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user