Skip to content
Open

Dz10 #1470

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Assets/Resources/PlayerUnits/PlayerUnit3.prefab
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ MonoBehaviour:
_maxHealth: 400
_brainUpdateDelay: 0.25
_moveDelay: 0.25
_attackDelay: 0.75
_attackDelay: 0.15
_attackRange: 3.5
_shotsPerTarget: 1
_targetsInVolley: 1
Expand Down
1 change: 1 addition & 0 deletions Assets/Scripts/Controller/LevelController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public void StartLevel(int level)
var density = Random.Range(_settings.MapMinDensity, _settings.MapMaxDensity);
var map = MapGenerator.Generate(_settings.MapWidth, _settings.MapHeight, density, level);
_runtimeModel.Clear();
UnitBrains.ArmyBrain.Reset();
_runtimeModel.Map = new Map(map, Settings.PlayersCount);
_runtimeModel.Stage = RuntimeModel.GameStage.ChooseUnit;
_runtimeModel.Bases[RuntimeModel.PlayerId] = new MainBase(_settings.MainBaseMaxHp);
Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/EnterPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class EnterPoint : MonoBehaviour
{
[SerializeField] private Settings _settings;
[SerializeField] private Canvas _targetCanvas;
private float _timeScale = 1;
private float _timeScale = 5;

void Start()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ protected override void UpdateImpl(float deltaTime, float time)
// Insert you code here
///////////////////////////////////////

float maxHeight = totalDistance * 0.6f;
localHeight = maxHeight * (((t * 2 - 1) * (t * 2 - 1) * -1) + 1);

///////////////////////////////////////
// End of the code to insert
///////////////////////////////////////

Height = localHeight;
if (time > StartTime + _timeToTarget)
Hit(_target);
Expand Down
103 changes: 103 additions & 0 deletions Assets/Scripts/UnitBrains/ArmyBrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using Model;
using Model.Runtime.ReadOnly;
using UnityEngine;
using Utilities;

namespace UnitBrains
{
public class ArmyBrain
{
private readonly TimeUtil _timeUtil;
private readonly IReadOnlyRuntimeModel _runtimeModel;

private static ArmyBrain _instance;

private ArmyBrain()
{
_timeUtil = ServiceLocator.Get<TimeUtil>();
_runtimeModel = ServiceLocator.Get<IReadOnlyRuntimeModel>();
}

public static ArmyBrain GetInstance()
{
if (_instance == null)
_instance = new ArmyBrain();
return _instance;
}

public static void Reset() => _instance = null;

// Ближайший к нашей базе враг, если враги на нашей половине;
// иначе — враг с наименьшим HP.
public IReadOnlyUnit GetRecommendedTarget()
{
var enemies = _runtimeModel.RoBotUnits.ToList();
if (!enemies.Any()) return null;

var onOurHalf = EnemiesOnOurHalf(enemies);
if (onOurHalf.Any())
return onOurHalf.OrderBy(e => DistanceToPlayerBase(e.Pos)).First();

return enemies.OrderBy(e => e.Health).First();
}

// Перед нашей базой, если враги на нашей половине;
// иначе — на расстоянии выстрела от ближайшего к базе врага.
public Vector2Int GetRecommendedPoint()
{
var enemies = _runtimeModel.RoBotUnits.ToList();
var playerBase = _runtimeModel.RoMap.Bases[RuntimeModel.PlayerId];

if (!enemies.Any())
return playerBase;

var onOurHalf = EnemiesOnOurHalf(enemies);
if (onOurHalf.Any())
{
var enemyBase = _runtimeModel.RoMap.Bases[RuntimeModel.BotPlayerId];
var step = DirectionStep(playerBase, enemyBase);
return playerBase + step * 3;
}

var nearest = enemies.OrderBy(e => DistanceToPlayerBase(e.Pos)).First();
return PointAtRangeFrom(nearest.Pos, playerBase, attackRange: 3);
}

private List<IReadOnlyUnit> EnemiesOnOurHalf(List<IReadOnlyUnit> enemies)
{
var playerBase = _runtimeModel.RoMap.Bases[RuntimeModel.PlayerId];
int half = _runtimeModel.RoMap.Width / 2;
bool baseOnLeft = playerBase.x <= half;
return baseOnLeft
? enemies.Where(e => e.Pos.x <= half).ToList()
: enemies.Where(e => e.Pos.x > half).ToList();
}

private float DistanceToPlayerBase(Vector2Int pos)
{
return Vector2Int.Distance(pos, _runtimeModel.RoMap.Bases[RuntimeModel.PlayerId]);
}

private static Vector2Int DirectionStep(Vector2Int from, Vector2Int to)
{
var d = to - from;
return new Vector2Int(
d.x == 0 ? 0 : (d.x > 0 ? 1 : -1),
d.y == 0 ? 0 : (d.y > 0 ? 1 : -1)
);
}

private static Vector2Int PointAtRangeFrom(Vector2Int origin, Vector2Int toward, int attackRange)
{
var delta = toward - origin;
float dist = delta.magnitude;
if (dist < 1f) return origin;
return origin + new Vector2Int(
Mathf.RoundToInt(delta.x / dist * attackRange),
Mathf.RoundToInt(delta.y / dist * attackRange)
);
}
}
}
3 changes: 3 additions & 0 deletions Assets/Scripts/UnitBrains/ArmyBrain.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/Scripts/UnitBrains/BaseUnitBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public virtual Vector2Int GetNextStep()
var target = runtimeModel.RoMap.Bases[
IsPlayerUnitBrain ? RuntimeModel.BotPlayerId : RuntimeModel.PlayerId];

_activePath = new DummyUnitPath(runtimeModel, unit.Pos, target);
_activePath = new AStarUnitPath(runtimeModel, unit.Pos, target);
return _activePath.GetNextStepFrom(unit.Pos);
}

Expand Down
222 changes: 222 additions & 0 deletions Assets/Scripts/UnitBrains/Pathfinding/AStarUnitPath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Model;
using UnityEngine;

namespace UnitBrains.Pathfinding
{
public class AStarUnitPath : BaseUnitPath
{
private static readonly Vector2Int[] Directions =
{
new Vector2Int( 1, 0),
new Vector2Int(-1, 0),
new Vector2Int( 0, 1),
new Vector2Int( 0,-1),
};

public AStarUnitPath(IReadOnlyRuntimeModel runtimeModel, Vector2Int startPoint, Vector2Int endPoint)
: base(runtimeModel, startPoint, endPoint)
{
}

protected override void Calculate()
{
var goal = ResolveGoal(endPoint);

if (startPoint == goal)
{
path = new[] { startPoint };
return;
}

if (startPoint == goal)
{
path = new[] { startPoint };
return;
}

var open = new List<Vector2Int> { startPoint };
var openSet = new HashSet<Vector2Int> { startPoint };
var closed = new HashSet<Vector2Int>();

var cameFrom = new Dictionary<Vector2Int, Vector2Int>();

var gScore = new Dictionary<Vector2Int, int> { [startPoint] = 0 };
var fScore = new Dictionary<Vector2Int, int> { [startPoint] = Heuristic(startPoint, goal) };

const int MAX_ITERS = 20000;
var iters = 0;
var bestSoFar = startPoint;
var bestSoFarH = Heuristic(startPoint, goal);

while (open.Count > 0)
{
if (++iters > MAX_ITERS)
{
Debug.LogWarning($"AStar exceeded MAX_ITERS={MAX_ITERS}. Returning fallback path.");
path = new[] { startPoint };
return;
}

var current = SelectBest(open, fScore, goal);
var currentH = Heuristic(current, goal);
if (currentH < bestSoFarH)
{
bestSoFarH = currentH;
bestSoFar = current;
}

if (current == goal)
{
path = ReconstructPath(cameFrom, current).ToArray();
return;
}

open.Remove(current);
openSet.Remove(current);
closed.Add(current);

foreach (var dir in Directions)
{
var neighbor = current + dir;

if (closed.Contains(neighbor))
continue;

if (!IsWalkableOrGoal(neighbor, goal))
continue;

var tentativeG = GetScore(gScore, current) + 1;

if (!openSet.Contains(neighbor))
{
open.Add(neighbor);
openSet.Add(neighbor);
}
else
{
if (tentativeG >= GetScore(gScore, neighbor))
continue;
}

cameFrom[neighbor] = current;
gScore[neighbor] = tentativeG;
fScore[neighbor] = tentativeG + Heuristic(neighbor, goal);
}
}

if (bestSoFar != startPoint)
path = ReconstructPath(cameFrom, bestSoFar).ToArray();
else
path = new[] { startPoint };
}

private bool IsOccupiedByUnit(Vector2Int cell)
{
foreach (var u in runtimeModel.RoUnits)
{
if (u.Pos == cell)
return true;
}
return false;
}

private Vector2Int ResolveGoal(Vector2Int desired)
{
if (runtimeModel.IsTileWalkable(desired))
return desired;

var visited = new HashSet<Vector2Int>();
var q = new Queue<Vector2Int>();

// стартуем не с desired (он непроходим), а с его соседей
foreach (var dir in Directions)
{
var n = desired + dir;
if (visited.Add(n))
q.Enqueue(n);
}

const int MAX_NODES = 5000; // защита
var processed = 0;

while (q.Count > 0 && processed++ < MAX_NODES)
{
var cur = q.Dequeue();
if (runtimeModel.IsTileWalkable(cur))
return cur;

foreach (var dir in Directions)
{
var n = cur + dir;
if (visited.Add(n))
q.Enqueue(n);
}
}

// если совсем нет проходимых — возвращаем старт (пути нет)
return startPoint;
}

private bool IsWalkableOrGoal(Vector2Int cell, Vector2Int goal)
{
if (cell == startPoint) return true;
if (cell == goal) return true;

if (!runtimeModel.IsTileWalkable(cell))
return false;

if (IsOccupiedByUnit(cell))
return false;

return true;
}

private static int Heuristic(Vector2Int a, Vector2Int b)
=> Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y); // Manhattan

private static int GetScore(Dictionary<Vector2Int, int> scores, Vector2Int key)
=> scores.TryGetValue(key, out var v) ? v : int.MaxValue / 4;

private static Vector2Int SelectBest(List<Vector2Int> open, Dictionary<Vector2Int, int> fScore, Vector2Int goal)
{
var best = open[0];
var bestF = GetScore(fScore, best);
var bestH = Heuristic(best, goal);

for (int i = 1; i < open.Count; i++)
{
var v = open[i];
var f = GetScore(fScore, v);
if (f > bestF) continue;

var h = Heuristic(v, goal);
if (f < bestF || h < bestH)
{
best = v;
bestF = f;
bestH = h;
}
}

return best;
}

private static IEnumerable<Vector2Int> ReconstructPath(Dictionary<Vector2Int, Vector2Int> cameFrom, Vector2Int current)
{
var stack = new Stack<Vector2Int>();
stack.Push(current);

while (cameFrom.TryGetValue(current, out var prev))
{
current = prev;
stack.Push(current);
}

while (stack.Count > 0)
yield return stack.Pop();
}
}
}
11 changes: 11 additions & 0 deletions Assets/Scripts/UnitBrains/Pathfinding/AStarUnitPath.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading