using Core.Health; using Core.Input; using Core.Utilities; using DG.Tweening; using JetBrains.Annotations; using System; using System.Collections; using System.Collections.Generic; using TMPro; using TowerDefense.Level; using TowerDefense.Towers; using TowerDefense.Towers.Placement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using TowerDefense.Nodes; using TowerDefense.Affectors; using KTGMGemClient; namespace TowerDefense.UI.HUD { /// /// An object that manages user interaction with the game. Its responsibilities deal with /// /// /// Building towers /// /// /// Selecting towers and units /// /// /// [RequireComponent(typeof(Camera))] public class EndlessGameUI : Singleton { /// /// The states the UI can be in /// public enum State { /// /// The game is in its normal state. Here the player can pan the camera, select units and towers /// Normal, /// /// The game is in 'build mode'. Here the player can pan the camera, confirm or deny placement /// Building, /// /// The game is Paused. Here, the player can restart the level, or quit to the main menu /// Paused, /// /// The game is over and the level was failed/completed /// GameOver, /// /// The game is in 'build mode' and the player is dragging the ghost tower /// BuildingWithDrag } /// /// Gets the current UI state /// public State state { get; private set; } /// /// The currently selected tower /// public LayerMask placementAreaMask; /// /// 兵线层对应的Mask. /// public LayerMask waveLineMask; /// /// 战场区域Mask. /// public LayerMask battleAreaMask; /// /// The layer for tower selection /// public LayerMask towerSelectionLayer; /// /// The physics layer for moving the ghost around the world /// when the placement is not valid /// public LayerMask ghostWorldPlacementMask; public readonly int MAX_TOWERNUM = 20; /// /// The radius of the sphere cast /// for checking ghost placement /// public float sphereCastRadius = 1; /// /// 随机购买塔防的按钮. /// public Button randomTowerBtn; /// /// 飘血数字对应的prefab. /// public TextMoveDoTween bloodText; /// /// 中毒飘字. /// public TextMoveDoTween bloodPoison; /// /// 暴击飘字. /// public TextMoveDoTween bloodCrit; /// /// 购买塔防按钮上的Text. /// protected TextMeshProUGUI towerPriceText; protected bool tdBuyDisable = false; /// /// 鼠标在移动一个Tower之前,要隐藏的Tower数据和指针。 /// protected Tower towerToMove = null; /// /// Fires when the changes /// should only allow firing when TouchUI is used /// public event Action stateChanged; /// /// Fires off when the ghost was previously not valid but now is due to currency amount change /// public event Action ghostBecameValid; /// /// Fires when a tower is selected/deselected /// public event Action selectionChanged; /// /// 成长骰子对应的Timer. /// protected List listTowerTimer = new List(); protected Dictionary towerTimeDic = new Dictionary(); /// /// Placement area ghost tower is currently on /// IPlacementArea m_CurrentArea; /// /// Grid position ghost tower in on /// IntVector2 m_GridPosition; /// /// Our cached camera reference /// Camera m_Camera; /// /// Current tower placeholder. Will be null if not in the state. /// TowerPlacementGhost m_CurrentTower; // TowerList用于简单记录相关的数据 protected List m_listTower = new List(); /// /// Tracks if the ghost is in a valid location and the player can afford it /// bool m_GhostPlacementPossible; /// /// Gets the current selected tower /// public Tower currentSelectedTower { get; private set; } /// /// 拖动塔防时,原塔防的等级。 /// protected int dragTowerLevel = 0; /// /// 选中Tower时候的鼠标点击坐标与Tower中心所在的屏幕坐标之间的偏移值. /// protected Vector2 selTowerOffset = new Vector2(); // 测试屏幕显示相关的倒计时. protected bool bLoaded = false; private Timer overTimer; /// /// 总兵线数 /// public int TotalWaveLines { get; } = 5; public IntVector2 currentGrid { get { return m_GridPosition; } } /// /// Gets whether a tower has been selected /// public bool isTowerSelected { get { return currentSelectedTower != null; } } /// /// 攻击塔位占的行数 /// public int AttackRowNumbers { get; set; } = 2; /// /// 所有攻击塔位的摧毁信息,是否被摧毁,默认全部没有被摧毁 /// private bool[,] TowerDestroyArr; public event Action GameOverEvent; public IPlacementArea selfTowerPlaceArea { get { if (m_CurrentArea == null) { GameObject placeObj = GameObject.FindGameObjectWithTag("PlaceTower"); if (placeObj != null) m_CurrentArea = placeObj.GetComponent(); } return m_CurrentArea; } } /// /// 增加一个防塔的数据结构,测试数据 /// /// public void addTower(Tower t) { m_listTower.Add(t); // 当前所在的位置是否攻击位置,随手瞎写:2 或者 3也就是前两排是上阵的 if (t.gridPosition.y >= 2) t.bInAttackMode = true; // ATTENTION TO FIX: 先写死,有一个简单的逻辑再说: if (m_listTower.Count >= MAX_TOWERNUM) disableRandomTowerBtn(); } /// /// 根据塔位索引位置,查找位置上是否有对应的塔防数据。 /// /// /// /// public Tower FindTowerWithGridIdx(int x, int y) { foreach (Tower lt in m_listTower) { if ((lt.gridPosition.x == x) && (lt.gridPosition.y == y)) return lt; } return null; } public bool towerInList(Tower t) { return m_listTower.Contains(t); } /// /// 设置已经上阵的所有塔的攻击状态,是否可以攻击 /// /// public void SetAttackingTowerState(bool canAttack) { foreach (Tower tower in m_listTower) { tower.bInAttackMode = canAttack; } } public void delTower(Tower t) { // 删除Tower有可能对应的Timer. foreach (var tdata in towerTimeDic) { if (tdata.Value == t) { // 先删除对应的timer数据: foreach (var timer in listTowerTimer) { if (tdata.Key == timer.timerID) { listTowerTimer.Remove(timer); break; } } // 删除字典并退出: towerTimeDic.Remove(tdata.Key); break; } } int tcnt = m_listTower.Count; m_listTower.Remove(t); if (tcnt == MAX_TOWERNUM) enableRandomTowerBtn(); } /// /// 清空缓存数据 /// public void restartLevel() { m_listTower.Clear(); listTowerTimer.Clear(); towerTimeDic.Clear(); } /// /// 查找相同类型,相同段位的Tower /// /// public Tower findSameLvlTower(Tower t) { Tower rest = null; foreach (Tower lt in m_listTower) { if (lt == t) continue; if ((lt.currentLevel == t.currentLevel) && (lt.towerName == t.towerName)) { return lt; } } return rest; } /// /// Gets whether certain build operations are valid /// public bool isBuilding { get { return state == State.Building || state == State.BuildingWithDrag; } } /// /// Cancel placing the ghost /// public void CancelGhostPlacement() { if (m_CurrentTower) Destroy(m_CurrentTower.gameObject); m_CurrentTower = null; SetState(State.Normal); DeselectTower(); } /// /// Returns the GameUI to dragging mode with the curent tower /// /// /// /// Throws exception when not in build mode /// public void ChangeToDragMode() { if (!isBuilding) { throw new InvalidOperationException("Trying to return to Build With Dragging Mode when not in Build Mode"); } SetState(State.BuildingWithDrag); } /// /// Returns the GameUI to BuildMode with the current tower /// /// /// Throws exception when not in Drag mode /// public void ReturnToBuildMode() { if (!isBuilding) { throw new InvalidOperationException("Trying to return to Build Mode when not in Drag Mode"); } SetState(State.Building); } /// /// Changes the state and fires /// /// The state to change to /// thrown on an invalid state void SetState(State newState) { if (state == newState) { return; } State oldState = state; if (oldState == State.Paused || oldState == State.GameOver) { Time.timeScale = 1f; } switch (newState) { case State.Normal: break; case State.Building: break; case State.BuildingWithDrag: break; case State.Paused: case State.GameOver: if (oldState == State.Building) CancelGhostPlacement(); Time.timeScale = 1f; break; default: throw new ArgumentOutOfRangeException("newState", newState, null); } state = newState; if (stateChanged != null) { stateChanged(oldState, state); } } /// /// Called when the game is over /// public void GameOver() { SetState(State.GameOver); } /// /// Pause the game and display the pause menu /// public void Pause() { SetState(State.Paused); } /// /// Resume the game and close the pause menu /// public void Unpause() { SetState(State.Normal); } /// /// Changes the mode to drag /// /// /// The tower to build /// /// /// Throws exception when trying to change to Drag mode when not in Normal Mode /// public void SetToDragMode([NotNull] Tower towerToBuild) { if (state != State.Normal) { throw new InvalidOperationException("Trying to enter drag mode when not in Normal mode"); } if (m_CurrentTower != null) { // Destroy current ghost CancelGhostPlacement(); } SetUpGhostTower(towerToBuild); SetState(State.BuildingWithDrag); } /// /// 点击某一个塔防之后,开启拖动塔防的模式。 /// River: 修改为暂不删除原来的Tower,把原来的Tower隐藏掉. /// /// public void startDragTower(Tower towerOld) { Tower newT = null; foreach (Tower tower in EndlessLevelManager.instance.TowerLibrary) { if (tower.towerName != towerOld.towerName) continue; newT = Instantiate(tower, transform); break; } // 从列表中删除Tower.并破坏Tower的外形。 dragTowerLevel = towerOld.currentLevel; // 尝试不再删除原来的Tower,而是尝试在合成成功后再删除原来的Tower towerOld.showTower(false); towerToMove = towerOld; // 先删除,再设置移动相关。 SetToDragMode(newT); if (towerOld.towerFeature == EFeatureTower.Skill_Bomb) m_CurrentTower.SetAttackArea(dragTowerLevel, towerOld.attributeId); } /// /// Sets the UI into a build state for a given tower /// River: 当界面上某一个塔防的按钮被按下后,需要调用的这个函数进入建造模式。 /// /// /// The tower to build /// /// /// Throws exception trying to enter Build Mode when not in Normal Mode /// public void SetToBuildMode([NotNull] Tower towerToBuild) { if (state != State.Normal) { throw new InvalidOperationException("Trying to enter Build mode when not in Normal mode"); } if (m_CurrentTower != null) { // Destroy current ghost CancelGhostPlacement(); } SetUpGhostTower(towerToBuild); SetState(State.Building); } /// /// 目标位置是否是可攻击属性的空塔位 /// /// /// protected bool isFreeAttackGrid(PointerInfo pinfo) { // 判断格子上的塔防: UIPointer pointer = WrapPointer(pinfo); Tower sTower = PickTowerInGrid(pointer); if (sTower != null) return false; if ((m_GridPosition.x >= 0) && (m_GridPosition.y >= 0)) { if (m_CurrentArea.isFreeAtackPos(m_GridPosition.x, m_GridPosition.y)) return true; } return false; } /// /// 是否是可开启的AttackGrid塔位. /// /// /// protected bool isWaitBuyAttackGrid(PointerInfo pinfo) { // 判断格子上的塔防: UIPointer pointer = WrapPointer(pinfo); Tower sTower = PickTowerInGrid(pointer); if (sTower != null) return false; if ((m_GridPosition.x >= 0) && (m_GridPosition.y >= 0)) { if (m_CurrentArea.isWaitBuyGrid(m_GridPosition.x, m_GridPosition.y)) return true; } return false; } /// /// 目标位置是否有同等级同类型的Tower. /// /// /// protected bool isValidateCombineTarget(PointerInfo pinfo) { // 判断是否格子上重复放置塔防 if (!m_CurrentTower || !IsGhostAtValidPosition()) { // 最大级别的Tower不能再合并了. if (towerToMove.isAtMaxLevel) return false; // 判断格子上的塔防: UIPointer pointer = WrapPointer(pinfo); Tower sTower = PickTowerInGrid(pointer); if (sTower && sTower != towerToMove) { int testLvl = dragTowerLevel; if (towerToMove && sTower.currentLevel == testLvl && sTower.towerName == towerToMove.towerName) return true; } } return false; } /// /// 获取目标格子上的塔防的名字数据 /// /// /// protected string getTargetTowerName(PointerInfo pinfo) { if (m_CurrentTower == null || !IsGhostAtValidPosition()) { // 获取目标格子上的塔防指针: UIPointer pointer = WrapPointer(pinfo); Tower sTower = PickTowerInGrid(pointer); if ((sTower != null) && (sTower != towerToMove)) { return sTower.towerName; } } return ""; } /// /// 成长骰子升级为高一级别的随机骰子. /// /// protected void growUpTower(Tower tower) { Tower newTower = EndlessRandomTower.instance.GetRandomTower(false); // 所有的Tower不能升级成为FeatureTower. int maxLoop = 20; while (newTower.towerFeature != EFeatureTower.NULL) { newTower = EndlessRandomTower.instance.GetRandomTower(false); maxLoop--; if (maxLoop <= 0) { newTower = EndlessRandomTower.instance.getRTower(); Debug.LogError("超级循环发生了."); break; } } // 成长为高一级别的骰子 DisplaceTower(newTower, tower, 1); } /// /// 破坏某一列的塔位数据 /// /// /// public void DestroyTowerGrid(int xidx) { for (int i = 0; i < AttackRowNumbers; ++i) { if (TowerDestroyArr[xidx, i]) continue; Node newNode = EndlessLevelManager.instance.SwitchHomeBase(xidx); WaveLineAgentInsMgr[] agentInsMgrs = AgentInsManager.instance.GetWaveLineList(); for (int j = 0; j < agentInsMgrs[xidx].listAgent.Count; ++j) { agentInsMgrs[xidx].listAgent[j].ChangeNextNode(newNode); } TowerDestroyArr[xidx, i] = true; Tower tower = FindTowerWithGridIdx(xidx, 3 - i); if (tower) { delTower(tower); tower.Sell(); } if (m_CurrentArea != null) // 3 -> dimensions.y - 1 m_CurrentArea.SetDestroyedGrid(xidx, 3 - i); break; } if (GameConfig.IsNewbie && EndlessUIStart.instance.beginSkillStep) { EndlessUIStart.instance.beginSkillStep = true; EndlessLevelManager.instance.StopSecondWave(); } bool isAllDestroyed = true; for (int i = 0; i < AttackRowNumbers; ++i) { if (!TowerDestroyArr[xidx, i]) { isAllDestroyed = false; break; } } // 该兵线的所有基地都被摧毁完毕,目前的逻辑只要有一条兵线两个基地都被摧毁就算游戏结束 if (isAllDestroyed) { // 停止所有兵线的出兵 for (int i = 0; i < TotalWaveLines; ++i) { EndlessLevelManager.instance.StopWaveLine(i); } // 让所有兵线上已经生成的所有agent播放一个死亡动画然后销毁 WaveLineAgentInsMgr[] waveLineAgentIns = AgentInsManager.instance.GetWaveLineList(); for (int i = 0; i < waveLineAgentIns.Length; ++i) { while (waveLineAgentIns[i].listAgent.Count > 0) { waveLineAgentIns[i].listAgent[0].PlayDeath(); } } // 红心减少逻辑 // HealthHeartState.instance.killHeart(false); GameOver(); overTimer = new Timer(1.2f, SafelyCallGameOverEvent); } } private void SafelyCallGameOverEvent() { if (GameOverEvent != null) GameOverEvent(); overTimer = null; } /// /// 拖动一个Tower之后,松开鼠标或者EndDrag. /// 1: 目标点可合成,则直接合成。 /// 2: 目标点不管是空白,还是不能放置Tower,都要让当前的Tower返回到原来的TowerPlace /// /// public void onEndTowerDrag(PointerInfo pointerInfo) { bool bSkill = false; if (!m_CurrentTower || !m_CurrentTower.controller) { CancelPlaceTower(pointerInfo); return; } if (m_CurrentTower.controller.towerFeature != EFeatureTower.NULL) bSkill = true; // 判断目标位置是否有Tower且类型和等级一致,如果没有,则GhostTower删除,原Tower显示。 if (isValidateCombineTarget(pointerInfo)) TryPlaceTower(pointerInfo); else if (isFreeAttackGrid(pointerInfo) && !bSkill) { if (!TryPlaceTower(pointerInfo, false, true)) return; // 删除towerToMove,确保塔防数据不再出现多个 if (towerToMove != null) { delTower(towerToMove); towerToMove.showTower(true); towerToMove.Sell(); towerToMove = null; } } // 当前是Skill塔位的状态. else if (bSkill) { if (SkillPlayEndDrag(pointerInfo)) { // 先释放掉当前的Ghost塔防. CancelGhostPlacement(); // 删除towerToMove,确保塔防数据不再出现多个 if (towerToMove != null) { delTower(towerToMove); towerToMove.showTower(true); towerToMove.Sell(); towerToMove = null; } } else CancelPlaceTower(pointerInfo); } else CancelPlaceTower(pointerInfo); } /// /// 强制放置塔,主要是用于新手 /// /// /// /// 塔的等级 public void PlaceTowerForce(Tower newTower, IntVector2 pos, int level) { TowerPlacementGhost currentTower = Instantiate(newTower.towerGhostPrefab); currentTower.Initialize(newTower); Tower controller = currentTower.controller; Tower createdTower = Instantiate(controller); createdTower.Initialize(m_CurrentArea, pos); createdTower.SetLevel(level - 1); addTower(createdTower); Destroy(currentTower.gameObject); } protected bool SkillPlayEndDrag(PointerInfo pointer) { // 我操,终于可以了!ATTENTION TO OPP: PointerInfo tp = new PointerActionInfo(); tp.currentPosition = pointer.currentPosition; tp.previousPosition = pointer.previousPosition; tp.delta = pointer.delta; tp.startedOverUI = pointer.startedOverUI; // River: 调整偏移值,用于鼠标移动塔防的时候,更加平滑。 tp.currentPosition.x += selTowerOffset.x; tp.currentPosition.y += selTowerOffset.y; UIPointer npt = new UIPointer { overUI = false, pointer = tp, overWaveLine = false, ray = m_Camera.ScreenPointToRay(tp.currentPosition) }; int sId = towerToMove.attributeId; int sLevel = towerToMove.currentLevel; // 火是列攻击: if (m_CurrentTower.controller.towerFeature == EFeatureTower.Skill_Fire) { WavelineAreaRaycast(ref npt); if (npt.overWaveLine) { WaveLineSelEffect selEff = npt.wavelineHit.Value.collider.GetComponent(); if (selEff) { // 播放特效,并处理伤害. EndlessWaveLineManager.instance.PlayWaveLineEffect(selEff.waveLineId); AgentInsManager.instance.ExecWavelineAttack(selEff.waveLineId, sId, sLevel, false); return true; } } } // 炸弹是区域攻击显示: if (m_CurrentTower.controller.towerFeature == EFeatureTower.Skill_Bomb) { // 测试代码与战场区域碰撞,碰撞后显示攻击区域: BattleAreaRaycast(ref npt); if (npt.overWaveLine) { EndlessWaveLineManager.instance.PlayBattleAreaBombEffect(npt.wavelineHit.Value.point); AgentInsManager.instance.ExecBombAttack(npt.wavelineHit.Value.point, sId, sLevel, false); return true; } } return false; } protected void CancelPlaceTower(PointerInfo pointerInfo) { CancelGhostPlacement(); if (towerToMove) { towerToMove.showTower(true); // 处理复制骰子: if (towerToMove.towerFeature == EFeatureTower.CopyCat) { CopyTargetTower(pointerInfo); } else { // 升级目标位置的塔防. currentSelectedTower = towerToMove; } towerToMove = null; } SetState(State.Normal); } /// /// 处理复制宝石塔防 /// /// protected void CopyTargetTower(PointerInfo pointerInfo) { string tstr = getTargetTowerName(pointerInfo); Tower targetTower = null; if (tstr.Length > 0) targetTower = EndlessRandomTower.instance.getTowerByName(tstr); currentSelectedTower = towerToMove; if (targetTower) { // 1:记录当前Tower的Level,删除当前的Tower. int towerLvl = currentSelectedTower.currentLevel + 1; int posx = currentSelectedTower.gridPosition.x; int posy = currentSelectedTower.gridPosition.y; currentSelectedTower.Sell(); delTower(currentSelectedTower); // 在目标位置放置新的Tower类型,只有零级。 RandomPlaceTower(targetTower, posx, posy, 0); } } /// /// Attempt to position a tower at the given location /// /// The pointer we're using to position the tower public bool TryPlaceTower(PointerInfo pointerInfo, bool force = false, bool zeroCost = false) { UIPointer pointer = WrapPointer(pointerInfo); // Do nothing if we're over UI if (pointer.overUI && !force) { Debug.Log("在UI上了,直接返回"); return false; } BuyTower(pointer, force, zeroCost); return true; } /// /// 根据鼠标的点击信息来确认当前点击的位置是否有塔防模型。 /// /// /// protected Tower PickTowerInGrid(UIPointer pointer) { RaycastHit output; bool hasHit = Physics.Raycast(pointer.ray, out output, float.MaxValue, towerSelectionLayer); if (!hasHit || pointer.overUI) { return null; } var controller = output.collider.GetComponent(); return controller; } /// /// Position the ghost tower at the given pointer /// /// The pointer we're using to position the tower /// Optional parameter for configuring if the ghost is hidden when in an invalid location public void TryMoveGhost(PointerInfo pointerInfo, bool hideWhenInvalid = true) { if (m_CurrentTower == null) { return; //throw new InvalidOperationException("Trying to move the tower ghost when we don't have one"); } UIPointer pointer = WrapPointer(pointerInfo); // Do nothing if we're over UI if (pointer.overUI && hideWhenInvalid) { m_CurrentTower.Hide(); return; } MoveGhost(pointer, hideWhenInvalid); } /// /// Activates the tower controller UI with the specific information /// /// /// The tower controller information to use /// /// /// Throws exception when selecting tower when does not equal /// public void SelectTower(Tower tower) { if (state != State.Normal) { throw new InvalidOperationException("Trying to select whilst not in a normal state"); } DeselectTower(); currentSelectedTower = tower; if (currentSelectedTower != null) { currentSelectedTower.removed += OnTowerDied; } startDragTower(tower); } /// /// 在当前的PlaceArea内删除有相同LvL相同类型的防塔. /// /// public bool deleteSameLvlTower(Tower tower) { Tower delTower = findSameLvlTower(tower); if (delTower == null) return false; Tower backT = currentSelectedTower; currentSelectedTower = delTower; SellSelectedTower(); currentSelectedTower = backT; return true; } /// /// Upgrades , if possible /// /// /// Throws exception when selecting tower when does not equal /// or is null /// public void UpgradeSelectedTower() { if (state != State.Normal) { throw new InvalidOperationException("Trying to upgrade whilst not in Normal state"); } if (currentSelectedTower == null) { throw new InvalidOperationException("Selected Tower is null"); } if (currentSelectedTower.isAtMaxLevel) { return; } int upgradeCost = currentSelectedTower.GetCostForNextLevel(); bool successfulUpgrade = EndlessLevelManager.instance.Currency.TryPurchase(upgradeCost); if (successfulUpgrade) { currentSelectedTower.UpgradeTower(); } DeselectTower(); } /// /// 当前的塔防随机升级为另一种塔防的种类。 /// protected bool randomUpgradeTower() { if (state != State.Normal) { throw new InvalidOperationException("Trying to upgrade whilst not in Normal state"); } if (currentSelectedTower == null) { throw new InvalidOperationException("Selected Tower is null"); } if (currentSelectedTower.isAtMaxLevel) return false; // 直接随机升级,零成本。 // 1:记录当前Tower的Level,删除当前的Tower. 2:从TowerLib中随机找一个高一级别的Tower. string towerName = currentSelectedTower.towerName; if (currentSelectedTower.towerFeature == EFeatureTower.NULL) towerName = ""; int towerLvl = currentSelectedTower.currentLevel + 1; int posx = currentSelectedTower.gridPosition.x; int posy = currentSelectedTower.gridPosition.y; currentSelectedTower.Sell(); delTower(currentSelectedTower); // 随机放置一个新的Tower EndlessRandomTower.instance.randomTower(posx, posy, towerLvl, towerName); DeselectTower(); return true; } /// /// Sells if possible /// /// /// Throws exception when selecting tower when does not equal /// or is null /// public void SellSelectedTower() { if (state != State.Normal) { throw new InvalidOperationException("Trying to sell tower whilst not in Normal state"); } if (currentSelectedTower == null) { throw new InvalidOperationException("Selected Tower is null"); } int sellValue = currentSelectedTower.GetSellLevel(); if (EndlessLevelManager.instanceExists && sellValue > 0) { EndlessLevelManager.instance.Currency.AddCurrency(sellValue); currentSelectedTower.Sell(); // 从列表中删除Tower. delTower(currentSelectedTower); } DeselectTower(); } /// /// Buys the tower and places it in the place that it currently is /// /// /// Throws exception if trying to buy towers in Build Mode /// public void BuyTower() { if (!isBuilding) { throw new InvalidOperationException("Trying to buy towers when not in Build Mode"); } if (m_CurrentTower == null || !IsGhostAtValidPosition()) { return; } int cost = m_CurrentTower.controller.purchaseCost; bool successfulPurchase = EndlessLevelManager.instance.Currency.TryPurchase(cost); if (successfulPurchase) { PlaceTower(); } } /// /// Used to buy the tower during the build phase /// Checks currency and calls /// /// Throws exception when not in a build mode or when tower is not a valid position /// /// public void BuyTower(UIPointer pointer, bool force = false, bool zeroCost = false) { if (!isBuilding) return; // 判断是否格子上重复放置塔防 if (!m_CurrentTower || !IsGhostAtValidPosition()) { // 判断格子上的塔防: Tower sTower = PickTowerInGrid(pointer); if (sTower) { Tower curTower = m_CurrentTower.controller; int testLvl = dragTowerLevel; if ((sTower.currentLevel == testLvl) && (sTower.towerName == curTower.towerName)) { // 先释放掉当前的Ghost塔防. CancelGhostPlacement(); // 升级目标位置的塔防. currentSelectedTower = sTower; SetState(State.Normal); // 新的代码,合并升级为随机塔防类型. randomUpgradeTower(); } } else { // 放置攻击塔位 PlacementAreaRaycast(ref pointer); if (!pointer.raycast.HasValue || pointer.raycast.Value.collider == null) { CancelGhostPlacement(); return; } PlaceGhost(pointer); } return; } // 这是判断是否超出了格子 PlacementAreaRaycast(ref pointer, force); if (!pointer.raycast.HasValue || pointer.raycast.Value.collider == null) { CancelGhostPlacement(); return; } int cost = m_CurrentTower.controller.purchaseCost; if (zeroCost) cost = 0; bool successfulPurchase = EndlessLevelManager.instance.Currency.TryPurchase(cost); if (successfulPurchase) { PlaceGhost(pointer); } } /// /// Deselect the current tower and hides the UI /// public void DeselectTower() { if (currentSelectedTower != null) { currentSelectedTower.removed -= OnTowerDied; } currentSelectedTower = null; if (selectionChanged != null) { selectionChanged(null); } } /// /// Checks the position of the /// on the /// /// /// True if the placement is valid /// /// /// Throws exception if the check is done in state /// public bool IsGhostAtValidPosition(bool opponent = false) { if (m_CurrentTower == null || m_CurrentArea == null) return false; TowerFitStatus fits = m_CurrentArea.Fits(m_GridPosition, m_CurrentTower.controller.dimensions); return fits == TowerFitStatus.Fits; } /// /// Checks if buying the ghost tower is possible /// /// /// True if can purchase /// /// /// Throws exception if not in Build Mode or Build With Dragging mode /// public bool IsValidPurchase() { if (!isBuilding) { throw new InvalidOperationException("Trying to check ghost position when not in a build mode"); } if (m_CurrentTower == null || m_CurrentArea == null) return false; return EndlessLevelManager.instance.Currency.CanAfford(m_CurrentTower.controller.purchaseCost); } /// /// Places a tower where the ghost tower is /// /// /// Throws exception if not in Build State or is not at a valid position /// public void PlaceTower(int lvl = 0, bool opponent = false) { if (!isBuilding) throw new InvalidOperationException("Trying to place tower when not in a Build Mode"); if (lvl <= 0) { if (!IsGhostAtValidPosition(opponent)) throw new InvalidOperationException("Trying to place tower on an invalid area"); } if (m_CurrentArea == null) return; Tower createdTower = Instantiate(m_CurrentTower.controller); createdTower.opponentSide = opponent; createdTower.Initialize(m_CurrentArea, m_GridPosition, lvl); // River: 内部缓存数据,用于后期容易找到数据. addTower(createdTower); CancelGhostPlacement(); // 处理成长骰子,复制骰子等等功能. if (lvl == 0) { ProcessFeatureTower(createdTower); } } protected void ProcessFeatureTower(Tower ctower) { if (ctower.towerFeature == EFeatureTower.GrowUp) { Timer timer = new Timer(10.0f, () => { growUpTower(ctower); }); listTowerTimer.Add(timer); towerTimeDic.Add(timer.timerID, ctower); } else if (ctower.towerFeature == EFeatureTower.CopyCat) { } return; } /// /// 成功购买塔防后,更新价格,更新按钮,更新按钮上的塔防价格等等. /// protected void OnSuccessBuyTower() { var tpMgr = TowerPrice.instance; if (!tpMgr) return; // 价格更新 tpMgr.onTowerBuySuccess(); if (!towerPriceText) { towerPriceText = randomTowerBtn.transform.Find("cashText").GetComponent(); if (towerPriceText) towerPriceText.text = tpMgr.currentTowerPrice.ToString(); } else { towerPriceText.text = tpMgr.currentTowerPrice.ToString(); } // 无法支付新的塔防价格,按钮变灰. if (tpMgr.currentTowerPrice > EndlessLevelManager.instance.Currency.currentCurrency) { disableRandomTowerBtn(); } } /// /// 随机放置Tower按钮禁止使用,灰掉. /// protected void disableRandomTowerBtn() { randomTowerBtn.interactable = false; if (towerPriceText) { towerPriceText.color = new Color(0.5f, 0.5f, 0.5f); } tdBuyDisable = true; } /// /// 随机购买Tower的按钮重设置为有效. /// protected void enableRandomTowerBtn() { // ATTENTION TO FIX: 再次判断是因为有的地方是直接调用 if ((TowerPrice.instance.currentTowerPrice > EndlessLevelManager.instance.Currency.currentCurrency) || (m_listTower.Count >= MAX_TOWERNUM)) return; if (towerPriceText) towerPriceText.color = new Color(1.0f, 1.0f, 1.0f); if (randomTowerBtn) randomTowerBtn.interactable = true; tdBuyDisable = false; } /// /// 游戏内向上飘血 /// /// /// /// public void generateBloodText(Vector3 wpos, float val, bool crit = false, bool doubleHit = false, bool poison = false) { Vector3 spos = m_Camera.WorldToScreenPoint(wpos); TextMoveDoTween tm; if (crit) { tm = Poolable.TryGetPoolable(bloodCrit.gameObject); } else if (poison) { tm = Poolable.TryGetPoolable(bloodPoison.gameObject); } else { tm = Poolable.TryGetPoolable(bloodText.gameObject); } if (tm) { tm.GetComponent().SetParent(GameObject.Find("MainUI").GetComponent(), true); string bloodStr = ""; if (doubleHit) bloodStr += "Dble!"; if (crit) bloodStr += "Crt!"; bloodStr += "-"; bloodStr += val.ToString(); tm.moveBloodText(spos.x, spos.y, bloodStr, crit); } } private void Start() { // 获取相应的放置区域。 GameObject placeObj = GameObject.FindGameObjectWithTag("PlaceTower"); if (placeObj != null) m_CurrentArea = placeObj.GetComponent(); placeObj = GameObject.FindGameObjectWithTag("PlaceTowerOpponent"); } /// /// 替换towerToMove的目标Tower,如果是使用towerToMove,则必须提前处理相应的指针。 /// /// public void DisplaceTower(Tower newTower, Tower oldTower, int lvl = 0) { // 先记录oldTower的位置信息: int posx = oldTower.gridPosition.x; int posy = oldTower.gridPosition.y; bool opponent = oldTower.opponentSide; int newLvl = oldTower.currentLevel + 1; if (newLvl > 4) newLvl = 4; delTower(oldTower); oldTower.showTower(true); oldTower.Sell(); // 减化成长塔位对应的代码,用于不再影响目前正处于建设状态的塔位. if (m_CurrentArea == null) return; IntVector2 ivec; ivec.x = posx; ivec.y = posy; Tower createdTower = Instantiate(newTower); createdTower.opponentSide = opponent; createdTower.Initialize(m_CurrentArea, ivec, newLvl); // River: 内部缓存数据,用于后期容易找到数据. addTower(createdTower); } /// /// 直接在IPlaceArea上随机放置一个Tower。这是随机放置塔防的入口类。这是入口的塔防类。 /// /// public bool RandomPlaceTower(Tower tow, int posx = -1, int posy = -1, int lvl = 0, int forceCost = -1) { // 获取IPlaceArea. if (m_CurrentArea == null) { GameObject placeObj = GameObject.FindGameObjectWithTag("PlaceTower"); if (placeObj == null) return false; // 获取相应的放置区域。 m_CurrentArea = placeObj.GetComponent(); if (m_CurrentArea == null) return false; } bool zeroCost = false; if (posx < 0 && posy < 0) { IntVector2 tvec = m_CurrentArea.getFreePos(1, 1); if (tvec.x < 0 && tvec.y < 0) return false; m_GridPosition.x = tvec.x; m_GridPosition.y = tvec.y; } else { m_GridPosition.x = posx; m_GridPosition.y = posy; zeroCost = true; } // 购买成功,才会继续放置塔防: if (tow.towerGhostPrefab == null) { return false; } // River: 修改Cost为全局统一维护的数据 int cost = TowerPrice.instance.currentTowerPrice; if (zeroCost) cost = 0; if (forceCost != -1) cost = forceCost; bool successfulPurchase = EndlessLevelManager.instance.Currency.TryPurchase(cost); if (!successfulPurchase) return false; SetUpGhostTower(tow); //Debug.Log("设置影子塔防."); m_CurrentTower.Show(); if (successfulPurchase) { // 删除towerToMove,确保塔防数据不再出现多个 if (zeroCost && (towerToMove != null)) { delTower(towerToMove); towerToMove.showTower(true); towerToMove.Sell(); towerToMove = null; } // 塔防购买成功后的相关更新: if (!zeroCost) OnSuccessBuyTower(); SetState(State.Building); PlaceTower(lvl); } return true; } /// /// Calculates whether the given pointer is over the current tower ghost /// /// /// The information used to check against the /// /// /// Throws an exception if not in Build Mode /// public bool IsPointerOverGhost(PointerInfo pointerInfo) { if (state != State.Building) { throw new InvalidOperationException("Trying to tap on ghost tower when not in Build Mode"); } UIPointer uiPointer = WrapPointer(pointerInfo); RaycastHit hit; return m_CurrentTower.ghostCollider.Raycast(uiPointer.ray, out hit, float.MaxValue); } /// /// Selects a tower beneath the given pointer if there is one /// 选择塔防 /// /// /// The pointer information concerning the selector of the pointer /// /// /// Throws an exception when not in /// public void TrySelectTower(PointerInfo info) { if (state != State.Normal) { //throw new InvalidOperationException("Trying to select towers outside of Normal state"); return; } UIPointer uiPointer = WrapPointer(info); RaycastHit output; // River: Raycast的碰撞是游戏内物品的Collider进行碰撞检测的 bool hasHit = Physics.Raycast(uiPointer.ray, out output, float.MaxValue, towerSelectionLayer); if (!hasHit)//|| uiPointer.overUI) { //Debug.Log("没有点中或者点中了UI:" + hasHit.ToString() + "," + uiPointer.overUI.ToString()); return; } var controller = output.collider.GetComponent(); if (controller != null) { SelectTower(controller); } // 计算偏移值. CalSelTowerScreenOffset(info, controller); } /// /// 鼠标选中一个Tower的时候,计算当前鼠标位置与当前Tower位置在屏幕上坐标位置的偏移量。 /// /// /// protected void CalSelTowerScreenOffset(PointerInfo info, Tower tower) { Vector3 towerCentPos = m_Camera.WorldToScreenPoint(tower.transform.position); selTowerOffset.x = towerCentPos.x - info.currentPosition.x; selTowerOffset.y = towerCentPos.y - info.currentPosition.y; } /// /// Gets the world position of the ghost tower /// /// /// Throws an exception when not in the Build Mode or /// When a ghost tower does not exist /// public Vector3 GetGhostPosition() { if (!isBuilding) { throw new InvalidOperationException("Trying to get ghost position when not in a Build Mode"); } if (m_CurrentTower == null) { throw new InvalidOperationException("Trying to get ghost position for an object that does not exist"); } return m_CurrentTower.transform.position; } /// /// Moves the ghost to the center of the screen /// /// /// Throws exception when not in build mode /// public void MoveGhostToCenter() { if (state != State.Building) { throw new InvalidOperationException("Trying to move ghost when not in Build Mode"); } // try to find a valid placement Ray ray = m_Camera.ScreenPointToRay(new Vector2(Screen.width * 0.5f, Screen.height * 0.5f)); RaycastHit placementHit; if (Physics.SphereCast(ray, sphereCastRadius, out placementHit, float.MaxValue, placementAreaMask)) { MoveGhostWithRaycastHit(placementHit); } else { MoveGhostOntoWorld(ray, false); } } /// /// Set initial values, cache attached components /// and configure the controls /// protected override void Awake() { base.Awake(); DOTween.Init(true, true, LogBehaviour.Verbose).SetCapacity(200, 10); state = State.Normal; m_Camera = GetComponent(); TowerDestroyArr = new bool[5, AttackRowNumbers]; } /// /// 战场内所有的Tower实例都需要升级相关的数据. /// 找到相同类型的所有Tower,然后进行局内升级的修改。 /// /// protected void towerUpgradeInBattle(TowerLevelUp tlu) { foreach (Tower tower in m_listTower) { if (tlu.towerName != tower.towerName) continue; tower.upGradeInSceneTL(); } return; } /// /// Reset TimeScale if game is paused /// protected override void OnDestroy() { base.OnDestroy(); Time.timeScale = 1f; } /// /// Subscribe to the level manager /// protected virtual void OnEnable() { if (EndlessLevelManager.instanceExists) EndlessLevelManager.instance.Currency.currencyChanged += OnCurrencyChanged; } /// /// Unsubscribe from the level manager /// protected virtual void OnDisable() { if (EndlessLevelManager.instanceExists) EndlessLevelManager.instance.Currency.currencyChanged -= OnCurrencyChanged; } /// /// Creates a new UIPointer holding data object for the given pointer position /// protected UIPointer WrapPointer(PointerInfo pointerInfo) { return new UIPointer { overUI = IsOverUI(pointerInfo), pointer = pointerInfo, overWaveLine = false, ray = m_Camera.ScreenPointToRay(pointerInfo.currentPosition) }; } /// /// Checks whether a given pointer is over any UI /// /// The pointer to test /// True if the event system reports pointer being over UI protected bool IsOverUI(PointerInfo pointerInfo) { int pointerId; EventSystem currentEventSystem = EventSystem.current; // Pointer id is negative for mouse, positive for touch var cursorInfo = pointerInfo as MouseCursorInfo; var mbInfo = pointerInfo as MouseButtonInfo; var touchInfo = pointerInfo as TouchInfo; if (cursorInfo != null) { pointerId = PointerInputModule.kMouseLeftId; } else if (mbInfo != null) { // LMB is 0, but kMouseLeftID = -1; pointerId = -mbInfo.mouseButtonId - 1; } else if (touchInfo != null) { pointerId = touchInfo.touchId; } else { throw new ArgumentException("Passed pointerInfo is not a TouchInfo or MouseCursorInfo", "pointerInfo"); } return currentEventSystem.IsPointerOverGameObject(pointerId); } /// /// Move the ghost to the pointer's position /// /// The pointer to place the ghost at /// Optional parameter for whether the ghost should be hidden or not /// If we're not in the correct state protected void MoveGhost(UIPointer pointer, bool hideWhenInvalid = true) { // 我操,终于可以了!ATTENTION TO OPP: PointerInfo tp = new PointerActionInfo(); tp.currentPosition = pointer.pointer.currentPosition; tp.previousPosition = pointer.pointer.previousPosition; tp.delta = pointer.pointer.delta; tp.startedOverUI = pointer.pointer.startedOverUI; // River: 调整偏移值,用于鼠标移动塔防的时候,更加平滑。 tp.currentPosition.x += selTowerOffset.x; tp.currentPosition.y += selTowerOffset.y; UIPointer npt = new UIPointer { overUI = false, pointer = tp, overWaveLine = false, ray = m_Camera.ScreenPointToRay(tp.currentPosition) }; m_GridPosition.x = -1; m_GridPosition.y = -1; // // WORK START: 从这里开始,移动的时候与场景内的WaveSelEffect射线做碰撞。 // Raycast onto placement layer PlacementAreaRaycast(ref npt); if (npt.raycast != null) { MoveGhostWithRaycastHit(npt.raycast.Value); } else { // 根据技能塔的类型,来碰撞不同的数据: // 火是列攻击: if (m_CurrentTower.controller.towerFeature == EFeatureTower.Skill_Fire) { WavelineAreaRaycast(ref npt); if (npt.overWaveLine) { WaveLineSelEffect selEff = npt.wavelineHit.Value.collider.GetComponent(); if (selEff) selEff.SetWaveLineSel(true); } } // 炸弹是区域攻击显示: if (m_CurrentTower.controller.towerFeature == EFeatureTower.Skill_Bomb) { // 测试代码与战场区域碰撞,碰撞后显示攻击区域: BattleAreaRaycast(ref npt); if (npt.overWaveLine) m_CurrentTower.fadeOutAttackArea(); } MoveGhostOntoWorld(npt.ray, hideWhenInvalid); } } /// /// Move ghost with successful raycastHit onto m_PlacementAreaMask /// protected virtual void MoveGhostWithRaycastHit(RaycastHit raycast) { // We successfully hit one of our placement areas // Try and get a placement area on the object we hit m_CurrentArea = raycast.collider.GetComponent(); if (m_CurrentArea == null) { Debug.LogError("There is not an IPlacementArea attached to the collider found on the m_PlacementAreaMask"); return; } m_GridPosition = m_CurrentArea.WorldToGrid(raycast.point, m_CurrentTower.controller.dimensions); TowerFitStatus fits = m_CurrentArea.Fits(m_GridPosition, m_CurrentTower.controller.dimensions); m_CurrentTower.Show(); m_GhostPlacementPossible = fits == TowerFitStatus.Fits && IsValidPurchase(); m_CurrentTower.Move(raycast.point, //m_CurrentArea.GridToWorld(m_GridPosition, m_CurrentTower.controller.dimensions), m_CurrentArea.transform.rotation, m_GhostPlacementPossible); } /// /// Move ghost with the given ray /// protected virtual void MoveGhostOntoWorld(Ray ray, bool hideWhenInvalid) { m_CurrentArea = null; if (!hideWhenInvalid) { RaycastHit hit; // check against all layers that the ghost can be on Physics.SphereCast(ray, sphereCastRadius, out hit, float.MaxValue, ghostWorldPlacementMask); if (hit.collider == null) { return; } m_CurrentTower.Show(); // ATTENTION TO FIX:什么情况下会导致Y值过高,然后闪烁出现? Vector3 tvec3 = hit.point; tvec3.y = 0.0f; hit.point = tvec3; m_CurrentTower.Move(hit.point, hit.collider.transform.rotation, false); } else { m_CurrentTower.Hide(); } } /// /// Place the ghost at the pointer's position /// /// The pointer to place the ghost at /// If we're not in the correct state protected void PlaceGhost(UIPointer pointer) { MoveGhost(pointer); if (m_CurrentArea != null) { TowerFitStatus fits = m_CurrentArea.Fits(m_GridPosition, m_CurrentTower.controller.dimensions); if (fits == TowerFitStatus.Fits) { // Place the ghost Tower controller = m_CurrentTower.controller; Tower createdTower = Instantiate(controller); createdTower.Initialize(m_CurrentArea, m_GridPosition); createdTower.SetLevel(dragTowerLevel); // ATTENTION TO FIX:是否应该加入List: addTower(createdTower); dragTowerLevel = 0; CancelGhostPlacement(); } } } /// /// Raycast onto tower placement areas /// /// The pointer we're testing protected void PlacementAreaRaycast(ref UIPointer pointer, bool force = false) { pointer.raycast = null; if (pointer.overUI && (!force)) { // Pointer is over UI, so no valid position return; } // Raycast onto placement area layer RaycastHit hit; if (Physics.Raycast(pointer.ray, out hit, float.MaxValue, placementAreaMask)) { pointer.raycast = hit; } } /// /// 是否会跟兵线层数据有碰撞发生. /// /// protected void WavelineAreaRaycast(ref UIPointer pointer) { RaycastHit hit; if (Physics.Raycast(pointer.ray, out hit, float.MaxValue, waveLineMask)) { pointer.wavelineHit = hit; pointer.overWaveLine = true; } return; } /// /// /// /// protected void BattleAreaRaycast(ref UIPointer pointer) { RaycastHit hit; if (Physics.Raycast(pointer.ray, out hit, float.MaxValue, battleAreaMask)) { pointer.wavelineHit = hit; pointer.overWaveLine = true; } return; } /// /// 更新每一个 /// protected void updateSceneTowerUpgradeStatus() { bool zeroTower = m_listTower.Count == 0; } /// /// Modifies the valid rendering of the ghost tower once there is enough currency /// protected virtual void OnCurrencyChanged() { // 如果当前处于TowerBuy Disable的状态,根据Currency的值来判断是否应该重新开启RandomTowerBuy按钮. if (tdBuyDisable) { enableRandomTowerBtn(); } // 无法支付新的塔防价格,按钮变灰. var tpMgr = TowerPrice.instance; if (tpMgr.currentTowerPrice > EndlessLevelManager.instance.Currency.currentCurrency) disableRandomTowerBtn(); // 处理场景内升级相关的内容 updateSceneTowerUpgradeStatus(); if (!isBuilding || m_CurrentTower == null || m_CurrentArea == null) { return; } TowerFitStatus fits = m_CurrentArea.Fits(m_GridPosition, m_CurrentTower.controller.dimensions); bool valid = fits == TowerFitStatus.Fits && IsValidPurchase(); m_CurrentTower.Move(m_CurrentArea.GridToWorld(m_GridPosition, m_CurrentTower.controller.dimensions), m_CurrentArea.transform.rotation, valid); if (valid && !m_GhostPlacementPossible && ghostBecameValid != null) { m_GhostPlacementPossible = true; ghostBecameValid(); } } /// /// Closes the Tower UI on death of tower /// protected void OnTowerDied(DamageableBehaviour targetable) { DeselectTower(); } /// /// Creates and hides the tower and shows the buildInfoUI /// /// /// Throws exception if the is null /// void SetUpGhostTower([NotNull] Tower towerToBuild) { if (towerToBuild == null) { throw new ArgumentNullException("towerToBuild"); } m_CurrentTower = Instantiate(towerToBuild.towerGhostPrefab); m_CurrentTower.Initialize(towerToBuild); } /// /// 处理GameUI的Update相关内容,目前的主要工作是处理成长骰子. /// private void Update() { if (overTimer != null) overTimer.Tick(Time.deltaTime); for (int ti = listTowerTimer.Count - 1; ti >= 0; ti--) { // 如果执行到,会在DelTower函数内删除对应的listTowerTimer. listTowerTimer[ti].Tick(Time.deltaTime); } // 更新处理自己方光塔对应的攻击增加. updateLightTowerAttRise(); } /// /// 更新火属性Tower对左右塔的攻击增加. /// protected void updateLightTowerAttRise() { int yRow = 2; int maxTower = MAX_TOWERNUM / 3; for (int ti = 0; ti < maxTower; ti++) { Tower tw = FindTowerWithGridIdx(ti, yRow); if (tw) tw.attackRise = 0.0f; } for (int ti = 0; ti < maxTower; ti++) { Tower tw = FindTowerWithGridIdx(ti, yRow); if (tw == null) continue; if (tw.towerFeature == EFeatureTower.LightTower) { int nidx = ti - 1; if (nidx >= 0) { tw = FindTowerWithGridIdx(nidx, yRow); if (tw) tw.attackRise = 0.1f; } nidx = ti + 1; if (nidx < maxTower) { tw = FindTowerWithGridIdx(nidx, yRow); if (tw) tw.attackRise = 0.1f; } } } } } }