// -------------------------------------------------------------------------------------------- public override void Execute(Game game, Action OnComplete) { Card card = Card.GetCard(cardId); BoardTile boardTile = game.board.GetTile(coord); card.Owner.PlayCard(card, boardTile); }
// -------------------------------------------------------------------------------------------- public void OnDragFromUnitView(UnitView unitView, Vector2 prevDragPosition, Vector2 dragDelta) { if (_selectedUnit != null && unitView.Unit != _selectedUnit) { return; } if (_game.board.RaycastToPlane(_game.gameCamera.ScreenPointToRay(prevDragPosition + dragDelta), out Vector3 worldPos)) { BoardTile boardTile = _game.board.GetBoardTileAtPosition(worldPos); if (boardTile != null && boardTile != _prevBoardTile) { _prevBoardTile = boardTile; } } if (_pathSelectionView.IsBuilt) { _pathSelectionView.PathTo(_prevBoardTile); } if (_useSkillView.IsBuilt) { _useSkillView.TargetTowardTile(_prevBoardTile); } }
// -------------------------------------------------------------------------------------------- public static List <BoardTile> GetPlayableTiles(Game game, Player owner, CardData cardData) { List <BoardTile> toReturn = new List <BoardTile>(); if (cardData.energyRequired > owner.Energy) { // return empty list because this card is too expensive to be palyed. return(toReturn); } if (!string.IsNullOrEmpty(cardData.useSkillId)) { return(Skill.GetTargetableTiles(AppManager.Config.GetSkillData(cardData.useSkillId), game, owner.Hero)); } if (!string.IsNullOrEmpty(cardData.spawnUnitId)) { UnitData unitData = AppManager.Config.GetUnitData(cardData.spawnUnitId); for (int x = 0; x < game.board.width; x++) { for (int y = 0; y < game.board.height; y++) { BoardTile boardTile = game.board.GetTile(x, y); if (Unit.CanSpawnOnTile(unitData, boardTile, owner.Hero)) { toReturn.Add(boardTile); } } } return(toReturn); } return(toReturn); }
// -------------------------------------------------------------------------------------------- public static List <BoardTile> GetTargetableTiles(SkillData skillData, Game game, Unit user) { List <BoardTile> toReturn = new List <BoardTile>(); for (int x = 0; x < game.board.width; x++) { for (int y = 0; y < game.board.height; y++) { BoardTile boardTile = game.board.GetTile(x, y); if (boardTile == null) { continue; } if (!IsTileValidTarget(skillData, user, boardTile)) { continue; } if ((boardTile.Coord - user.BoardTile.Coord).ManhattanDistance > skillData.range) { continue; } toReturn.Add(boardTile); } } return(toReturn); }
// -------------------------------------------------------------------------------------------- public void HighlightBoardTile(IntVector2 coord) { if (BoardTileView.TryGetView(this.GetTile(coord), out BoardTileView view)) { view.SetHighlight(BoardTileView.EHighlight.Neutral); HighlightedTile = view.BoardTile; } }
// -------------------------------------------------------------------------------------------- public override void Destroy() { base.Destroy(); _owner.PlayerTurnStarted -= Player_PlayerTurnStarted; _owner.PlayerTurnEnded -= Player_PlayerTurnEnded; BoardTile?.SetOccupant(null); BoardTile = null; }
// -------------------------------------------------------------------------------------------- public void ClearAllBoardTileHighlights() { foreach (BoardTile boardTile in _tiles) { if (BoardTileView.TryGetView(boardTile, out BoardTileView boardTileView)) { boardTileView.SetHighlight(BoardTileView.EHighlight.None); } } HighlightedTile = null; }
// -------------------------------------------------------------------------------------------- public static bool CanSpawnOnTile(UnitData unitData, BoardTile boardTile, Unit spawnFrom) { if (boardTile.Occupant != null) { return(false); } if ((boardTile.Coord - spawnFrom.BoardTile.Coord).ManhattanDistance > 1) { return(false); } return(true); }
// -------------------------------------------------------------------------------------------- public void PathTo(BoardTile boardTile) { // return when no unit has been set if (unit == null) { return; } // return it is not the selected unit's owner's turn if (unit.Owner.playerIndex != _game.CurrentPlayer.playerIndex) { CurrentPath = null; return; } if (CurrentPath == null || CurrentPath.Length == 0) { CurrentPath = new[] { unit.BoardTile.Coord }; } int alreadyContainsTileAtIndex = -1; for (int i = 0; i < CurrentPath.Length; i++) { if (CurrentPath[i].Equals(boardTile.Coord)) { alreadyContainsTileAtIndex = i; break; } } if (alreadyContainsTileAtIndex >= 0) { // if the path already contains this tile, backtrack to it and set the currentPath to that IntVector2[] backtrackedPath = new IntVector2[alreadyContainsTileAtIndex + 1]; for (int i = 0; i <= alreadyContainsTileAtIndex; i++) { backtrackedPath[i] = CurrentPath[i]; } CurrentPath = Board.RemoveDuplicates(backtrackedPath); } else { // the path does not contain this tile, so lets try to find the best path to it for (int backtrackIndex = CurrentPath.Length - 1; backtrackIndex >= 0; backtrackIndex--) { // backtrack along our CurrentPath until we find a coord that has a valid path to the target IntVector2[] upToBacktrackIndex = new List <IntVector2>(CurrentPath).GetRange(0, backtrackIndex + 1).ToArray(); int cost = _game.board.CalculateCostForPath(unit, upToBacktrackIndex); if (_game.board.TryGetBestPathForUnit(unit, CurrentPath[backtrackIndex], boardTile.Coord, cost, out IntVector2[] backtrackAppend))
// -------------------------------------------------------------------------------------------- private Unit PlaceUnit(UnitData unitData, BoardTile boardTile) { Unit unit = new Unit(unitData, _game, this); unit.OccupyBoardTile(boardTile, true); // when placed, face the center of the board // TODO: is this the default behavior? should the player be able to choose the initial facing direction? unit.SetFacing(Unit.VectorToFacing(_game.board.CenterPos - unit.Transform.position), false); unit.OnTookDamage += OnUnitTookDamage; _units.Add(unit); return(unit); }
// -------------------------------------------------------------------------------------------- public List <BoardTile> GetAffectedTiles(Unit.EFacing facing, IntVector2 startCoord) { BoardTile startTile = _game.board.GetTile(startCoord); List <BoardTile> toReturn = new List <BoardTile>(); switch (_skillData.areaType) { case SkillData.EAreaType.Single: toReturn.Add(startTile); break; default: throw new NotImplementedException($"GetTargetTiles not implemented for areaType {_skillData.areaType}"); } return(toReturn); }
// -------------------------------------------------------------------------------------------- public void OnPointerDownOverUnitView(UnitView unitView) { _selectedUnit = unitView.Unit; _pathSelectionView.unit = _selectedUnit; _useSkillView.unit = _selectedUnit; _prevBoardTile = unitView.Unit.BoardTile; if (_selectedUnit.CanMove && !_pathSelectionView.IsBuilt) { // only show the UnitPathSelectionView if the unit can move _pathSelectionView.Render(AppManager.Transform); } if (_selectedUnit.CanUseSkill && !_useSkillView.IsBuilt) { // only show the UnitUseSkillView if the unit can use its skill _useSkillView.Render(AppManager.Transform); } }
// -------------------------------------------------------------------------------------------- public Board(Game game, int width, int height) : base("Board") { _game = game; this.width = width; this.height = height; _tiles = new BoardTile[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { BoardTile boardTile = new BoardTile(x, y); AddChild(boardTile); _tiles[x, y] = boardTile; } } _groundPlane = new Plane(Vector3.up, Vector3.zero); }
// -------------------------------------------------------------------------------------------- public void OccupyBoardTile(BoardTile newTile, bool asChild) { // do this check so that OccupyBoardTile can be called arbitrarily without re-occupying the same boardtile if (BoardTile != newTile) { // leave the current tile, if it exists BoardTile?.SetOccupant(null); // set the new tile, then add this unit as an occupant BoardTile = newTile; newTile.SetOccupant(this); } if (asChild) { // parent the unit to the tile and zero out its local position newTile.AddChild(this); LocalPosition = Vector3.zero; } }
// -------------------------------------------------------------------------------------------- public static void Create(BoardTile boardTile, InstantiateDelegate callback) { AppManager.AssetManager.Load(AssetPaths.Prefabs.BoardTileView, (bool successful, GameObject payload) => { if (successful) { GameObject viewGo = Instantiate(payload, boardTile.Transform, false); BoardTileView view = viewGo.GetComponent <BoardTileView>(); view.BoardTile = boardTile; view._highlight = EHighlight.None; view.SetMaterial(); _boardTileToView.Add(boardTile, view); callback(view); } }); }
// -------------------------------------------------------------------------------------------- public override bool IsValid(Game game) { Card card = Card.GetCard(cardId); BoardTile boardTile = game.board.GetTile(coord); if (boardTile == null) { return(false); } if (!card.CanPlayOnTile(boardTile)) { return(false); } if (card.energyRequired > card.Owner.Energy) { return(false); } return(true); }
// --------------------------------------------------------------------------------------------s public void PlayCard(Card card, BoardTile targetTile) { _energy = Mathf.Clamp(_energy - card.energyRequired, 0, _energyCap); if (!string.IsNullOrEmpty(card.cardData.spawnUnitId)) { PlaceUnit(AppManager.Config.GetUnitData(card.cardData.spawnUnitId), targetTile); } if (!string.IsNullOrEmpty(card.cardData.useSkillId)) { throw new NotImplementedException("haven't implemented playing cards with useSkillId"); } if (_hand.ContainsCard(card)) { _hand.DiscardCard(card); } _discardPile.Add(card); PlayerPlayedCard?.Invoke(this, new PlayerEventArgs(this)); }
// -------------------------------------------------------------------------------------------- public void TargetTowardTile(BoardTile boardTile) { // return if it is not the unit's owner's turn if (unit.Owner.playerIndex != _game.CurrentPlayer.playerIndex) { return; } // set CurrentlyTargeting to null and try to set it CurrentlyTargeting = null; int closestDistance = int.MaxValue; foreach (BoardTile targetableTile in _targetableTiles) { int distance = (boardTile.Coord - targetableTile.Coord).ManhattanDistance; if (distance > 1) { continue; } if (distance < closestDistance) { closestDistance = distance; CurrentlyTargeting = targetableTile; } } GameObject.SetActive(CurrentlyTargeting != null); if (CurrentlyTargeting == null) { return; } Unit.EFacing facing = Unit.VectorToFacing(CurrentlyTargeting.LocalPosition - unit.BoardTile.LocalPosition); LocalRotation = Unit.FacingToRotation(facing); LocalPosition = unit.BoardTile.LocalPosition + (LocalRotation * (Vector3.right * Offset.x)) + new Vector3(0f, Offset.y, 0f); }
// -------------------------------------------------------------------------------------------- public static bool IsTileValidTarget(SkillData skillData, Unit user, BoardTile tile) { switch (skillData.target) { case SkillData.ETarget.Ally: return(tile.Occupant?.IsAllyOf(user) ?? false); case SkillData.ETarget.Enemy: return(tile.Occupant?.IsEnemyOf(user) ?? false); case SkillData.ETarget.Tile: return(true); case SkillData.ETarget.Self: return(tile == user.BoardTile); case SkillData.ETarget.None: return(false); default: Debug.LogError($"CanTargetTile not implemented for {skillData.target}"); return(false); } }
// -------------------------------------------------------------------------------------------- public void Move(IntVector2[] path, bool animate, Action onComplete) { if (!CanMove) { Debug.LogError($"Unit {id} cannot move"); return; } _onMoveComplete = () => { onComplete?.Invoke(); _onMoveComplete = null; }; HasMoved = true; if (animate) { if (_moveAnim != null) { Debug.LogError("move anim in progress!"); return; } _moveAnim = new TofuAnimation(); Parent.RemoveChild(this, false); for (int i = 1; i < path.Length; i++) { Vector3 fromPos = _game.board.GetTile(path[i - 1]).Transform.position; Vector3 toPos = _game.board.GetTile(path[i]).Transform.position; float time = (toPos - fromPos).magnitude / _data.travelSpeed; if (i != 1) { // we don't need to call Then() on the first loop _moveAnim.Then(); } _moveAnim.Execute(() => { SetFacing(VectorToFacing(toPos - fromPos), false); }) .Value01(time, EEaseType.Linear, (float newValue) => { Transform.position = Vector3.LerpUnclamped(fromPos, toPos, newValue); Ray ray = new Ray(Transform.position + Vector3.up, Vector3.down); RaycastHit[] hits = Physics.RaycastAll(ray, 2f); for (int j = 0; j < hits.Length; j++) { BoardTileView boardTileView = hits[j].collider.GetComponentInParent <BoardTileView>(); if (boardTileView == null) { continue; } if (boardTileView.BoardTile != BoardTile) { OccupyBoardTile(boardTileView.BoardTile, false); } break; } }); } _moveAnim.Then() .Execute(() => { StopOnTile(); }) .Play(); } else { for (int i = 0; i < path.Length; i++) { BoardTile boardTile = _game.board.GetTile(path[i]); StopOnTile(); } _onMoveComplete?.Invoke(); } }
// -------------------------------------------------------------------------------------------- private void UseSkillOnBoardTile(BoardTile boardTile, Action onComplete) { throw new NotImplementedException(); }
// -------------------------------------------------------------------------------------------- /// <summary> /// Finds the best path for a unit to a target coordinate using A* /// </summary> public bool TryGetBestPathForUnit(Unit unit, IntVector2 startCoord, IntVector2 targetCoord, int movementExhausted, out IntVector2[] bestPath) { bestPath = new IntVector2[0]; if (movementExhausted >= unit.MoveRange) { // return empty array if we've already exahusted the list return(false); } // A* implementation for best path HashSet <IntVector2AsAStarNode> open = new HashSet <IntVector2AsAStarNode>(); HashSet <IntVector2AsAStarNode> closed = new HashSet <IntVector2AsAStarNode>(); open.Add(new IntVector2AsAStarNode { coord = startCoord, previous = null, f = 0, g = 0, h = 0, }); int moveRange = unit.MoveRange - movementExhausted; bool succeeded = false; while (open.Count > 0) { List <IntVector2AsAStarNode> openAsList = new List <IntVector2AsAStarNode>(open); // sort the open list ascending by f value openAsList.Sort((IntVector2AsAStarNode a, IntVector2AsAStarNode b) => { return(a.f.CompareTo(b.f)); }); // set the current node to the node with the least f IntVector2AsAStarNode currentNode = openAsList[0]; closed.Add(openAsList[0]); open.Remove(currentNode); if (currentNode.coord.Equals(targetCoord)) // remember to use .Equals() instead of == becuase these are not the same object { bestPath = currentNode.ToPath(); succeeded = true; break; } List <BoardTile> nextBoardTiles = new List <BoardTile>(); BoardTile northTile = GetTile(currentNode.coord.x, currentNode.coord.y + 1); if (northTile != null) { nextBoardTiles.Add(northTile); } BoardTile southTile = GetTile(currentNode.coord.x, currentNode.coord.y - 1); if (southTile != null) { nextBoardTiles.Add(southTile); } BoardTile eastTile = GetTile(currentNode.coord.x + 1, currentNode.coord.y); if (eastTile != null) { nextBoardTiles.Add(eastTile); } BoardTile westTile = GetTile(currentNode.coord.x - 1, currentNode.coord.y); if (westTile != null) { nextBoardTiles.Add(westTile); } foreach (BoardTile boardTile in nextBoardTiles) { // check if we've already visited this coord bool isClosed = false; foreach (IntVector2AsAStarNode closedNode in closed) { isClosed |= closedNode.coord.Equals(boardTile.Coord); } if (isClosed) { continue; } // create a new node to add to the open list IntVector2AsAStarNode childNode = new IntVector2AsAStarNode(); childNode.coord = boardTile.Coord; childNode.previous = currentNode; childNode.g = currentNode.g + boardTile.GetMoveCostForUnit(unit); childNode.h = (boardTile.Coord - targetCoord).ManhattanDistance; childNode.f = childNode.g + childNode.h; // we can't look at tiles that are beyond a unit's move range or that are occupied by some other unit if (childNode.g > moveRange || (boardTile.Occupant != null && boardTile.Occupant != unit)) { continue; } // check if we've visited this coord but now we have a better path to it bool foundBetterPath = false; bool haveVisited = false; foreach (IntVector2AsAStarNode openNode in open) { if (!openNode.coord.Equals(childNode.coord)) { continue; } haveVisited = true; if (openNode.f < childNode.f) { continue; } // we've found a better node! openNode.g = childNode.g; openNode.h = childNode.h; openNode.f = childNode.f; openNode.previous = childNode.previous; foundBetterPath = true; } if (!haveVisited || !foundBetterPath) { open.Add(childNode); } } } if (bestPath.Length == 0) { // if we haven't found a best path, then there's no way the unit could move to the target // instead, return the best path to the node closest to the target List <IntVector2AsAStarNode> toCullFromClosed = new List <IntVector2AsAStarNode>(); foreach (IntVector2AsAStarNode closedNode in closed) { if (closedNode.g != moveRange) { toCullFromClosed.Remove(closedNode); } } foreach (IntVector2AsAStarNode toRemove in toCullFromClosed) { closed.Remove(toRemove); } List <IntVector2AsAStarNode> closedAsList = new List <IntVector2AsAStarNode>(closed); closedAsList.Sort((IntVector2AsAStarNode a, IntVector2AsAStarNode b) => { return(a.f.CompareTo(b.f)); }); bestPath = closedAsList[0].ToPath(); } return(succeeded); }
// -------------------------------------------------------------------------------------------- private void HighlightBoardTilesForUnitMoveRecursive(Unit unit, Dictionary <BoardTile, int> visitedToCost, BoardTile current, int cost) { if (current == null) { return; } if (current.Occupant != null && current.Occupant != unit) { return; } if (cost > unit.MoveRange) { return; } if (visitedToCost.ContainsKey(current)) { int previousCost = visitedToCost[current]; if (cost < previousCost) { visitedToCost[current] = cost; } else { // we've alredy visited this tile and it was cheaper then, so we're done return; } } else { visitedToCost.Add(current, cost); } if (BoardTileView.TryGetView(current, out BoardTileView view)) { view.SetHighlight(BoardTileView.EHighlight.Move); } BoardTile northTile = GetTile(current.xCoord, current.yCoord + 1); if (northTile != null) { HighlightBoardTilesForUnitMoveRecursive(unit, visitedToCost, northTile, cost + northTile.GetMoveCostForUnit(unit)); } BoardTile southTile = GetTile(current.xCoord, current.yCoord - 1); if (southTile != null) { HighlightBoardTilesForUnitMoveRecursive(unit, visitedToCost, southTile, cost + southTile.GetMoveCostForUnit(unit)); } BoardTile eastTile = GetTile(current.xCoord + 1, current.yCoord); if (eastTile != null) { HighlightBoardTilesForUnitMoveRecursive(unit, visitedToCost, eastTile, cost + eastTile.GetMoveCostForUnit(unit)); } BoardTile westTile = GetTile(current.xCoord - 1, current.yCoord); if (westTile != null) { HighlightBoardTilesForUnitMoveRecursive(unit, visitedToCost, westTile, cost + westTile.GetMoveCostForUnit(unit)); } }
public void OnSkillTargetSelected(Unit unit, Unit.EFacing facing, BoardTile target) { QueueAction(new UseSkillAction(CurrentPlayer.playerIndex, unit.id, facing, target.Coord), () => { }); }
// -------------------------------------------------------------------------------------------- public bool IsTileValidTarget(BoardTile tile) { return(IsTileValidTarget(_skillData, _user, tile)); }
// -------------------------------------------------------------------------------------------- public static bool CanPlayOnTile(Game game, Player owner, CardData cardData, BoardTile boardTile) { return(GetPlayableTiles(game, owner, cardData).Contains(boardTile)); }
// -------------------------------------------------------------------------------------------- public bool CanPlayOnTile(BoardTile boardTile) { return(GetPlayableTiles().Contains(boardTile)); }
// -------------------------------------------------------------------------------------------- public bool IsAdjacentTo(BoardTile other) { return((Coord - other.Coord).ManhattanDistance == 1); }
// -------------------------------------------------------------------------------------------- public static bool TryGetView(BoardTile boardTile, out BoardTileView view) { return(_boardTileToView.TryGetValue(boardTile, out view)); }