返回总目录

第七章 寻路与地图对象(Pathfinding and Map Object)

这一章主要进行寻路与地图对象的部分工作。


文章目录

  • 第七章 寻路与地图对象(Pathfinding and Map Object)
    • 五 搜索移动范围与路径(Search Move Range and Path)
      • 1 修改寻路(Extend Pathfinding)
      • 2 搜索移动范围(Search Move Range)
      • 3 搜索路径(Search Path)
      • 4 测试寻路与移动(Test Pathfinding and Moving)
      • 5 测试完整代码(Testing Full Code)

五 搜索移动范围与路径(Search Move Range and Path)

就像我们之前说的,移动范围与攻击范围的搜索算法十分的类似,只需要修改少部分内容:

  • 计算消耗G;

  • 在判断是否能加入开放集时,需要判断格子是否有效(是否可移动);

  • 对外调用的函数,加入移动消耗。


1 修改寻路(Extend Pathfinding)

这个方法,我们在PathFinding类中添加。

  • 首先,修改对外调用函数:

            // 修改后寻找移动范围public bool SearchMoveRange(IHowToFind howToFind, CellData start, float movePoint, MoveConsumption consumption){if (howToFind == null || start == null || movePoint < 0){return false;}Reset();m_HowToFind = howToFind;m_MoveConsumption = consumption;m_StartCell = start;m_StartCell.ResetAStar();m_Range.y = movePoint;m_Reachable.Add(m_StartCell);return SearchRangeInternal();}// 修改后搜寻路径public bool SearchPath(IHowToFind howToFind, CellData start, CellData end, MoveConsumption consumption){if (howToFind == null || start == null || end == null){return false;}Reset();m_HowToFind = howToFind;m_MoveConsumption = consumption;m_StartCell = start;m_StartCell.ResetAStar();m_EndCell = end;m_EndCell.ResetAStar();m_Reachable.Add(m_StartCell);m_StartCell.h = m_HowToFind.CalcH(this, m_StartCell);return SearchRangeInternal();}/// 修改后寻找攻击范围/// 这里添加的参数 `useEndCell` ,/// 是在 `移动范围` 与 `攻击范围` 一起显示时,/// 不破坏 `起始节点`。public bool SearchAttackRange(IHowToFind howToFind, CellData start, int minRange, int maxRange, bool useEndCell = false){if (howToFind == null || start == null || minRange < 1 || maxRange < minRange){return false;}Reset();m_HowToFind = howToFind;m_Range = new Vector2(minRange, maxRange);// 在重置时,不重置 `父亲节点` ,// 其一:没有用到// 其二:二次查找时不破坏路径,否则路径将被破坏if (useEndCell){m_EndCell = start;m_EndCell.g = 0f;m_Reachable.Add(m_EndCell);}else{m_StartCell = start;m_StartCell.g = 0f;m_Reachable.Add(m_StartCell);}return SearchRangeInternal();}

    当然,你还要记得修改MapGraph里的对应函数。

  • 其次,修改Reset函数:

    在寻路过程中,我们会不断的重新计算A星的G和H,还有父亲节点。

    Reset时,我们不必每个都重置,只需要之前做的修改那样,将关键点重置(例如起始结束节点,而攻击范围没有用到父亲节点,所以只重置G)。

            /// <summary>/// 重置/// </summary>public void Reset(){m_Reachable.Clear();m_Explored.Clear();m_Result.Clear();m_Range = Vector2.zero;m_StartCell = null;m_EndCell = null;m_CurrentCell = null;m_Finished = false;m_HowToFind = null;m_MoveConsumption = null;m_SearchCount = 0;}
  • 再次,添加获取消耗方法:

            /// <summary>/// 获取移动消耗/// </summary>/// <param name="terrainType"></param>/// <returns></returns>public float GetMoveConsumption(TerrainType terrainType){if (m_MoveConsumption == null){return 1f;}return m_MoveConsumption[terrainType];}
  • 最后,添加建立路径方法:

            /// <summary>/// 建立路径List/// </summary>/// <param name="endCell"></param>/// <param name="useResult"></param>/// <returns></returns>public List<CellData> BuildPath(CellData endCell, bool useResult){if (endCell == null){Debug.LogError("PathFinding -> Argument named `endCell` is null.");return null;}List<CellData> path = useResult ? m_Result : new List<CellData>();CellData current = endCell;path.Add(current);while (current.previous != null){current = current.previous;path.Insert(0, current);}return path;}/// <summary>/// 建立路径Stack/// </summary>/// <param name="endCell"></param>/// <returns></returns>public Stack<CellData> BuildPath(CellData endCell){if (endCell == null){Debug.LogError("PathFinding -> Argument named `endCell` is null.");return null;}Stack<CellData> path = new Stack<CellData>();CellData current = endCell;path.Push(current);while (current.previous != null){current = current.previous;path.Push(current);}return path;}

2 搜索移动范围(Search Move Range)

我们先新建类:

using UnityEngine;namespace DR.Book.SRPG_Dev.Maps.FindPath
{[CreateAssetMenu(fileName = "FindMoveRange.asset", menuName = "SRPG/How to find move range")]public class FindMoveRange : FindRange {public override CellData ChoseCell(PathFinding search){// TODOreturn base.ChoseCell(search);}public override float CalcGPerCell(PathFinding search, CellData adjacent){// TODOreturn base.CalcGPerCell(search, adjacent);}public override bool CanAddAdjacentToReachable(PathFinding search, CellData adjacent){// TODOreturn base.CanAddAdjacentToReachable(search, adjacent);}}
}
  • 首先,选择节点,根据A星需要选择F最小的节点:

            public override CellData ChoseCell(PathFinding search){if (search.reachable.Count == 0){return null;}/// 取得f最小的节点(因为我们没有计算h,这里就是g)/// 当你在寻找路径有卡顿时,请一定使用更好的查找方式,/// 例如可以改用二叉树的方式,/// 也可将PathFinding里面reachable.Add(adjacent)的方法改成边排序边加入的方法search.reachable.Sort((cell1, cell2) => -cell1.f.CompareTo(cell2.f));int index = search.reachable.Count - 1;CellData chose = search.reachable[index];search.reachable.RemoveAt(index);return chose;}
  • 其次, 是判断格子是否可移动,还需要判断是否更新新的损失与父亲节点:

            public override bool CanAddAdjacentToReachable(PathFinding search, CellData adjacent){// 没有Tileif (!adjacent.hasTile){return false;}// 已经有对象了if (adjacent.hasMapObject){return false;}// 如果已经在关闭集if (search.IsCellInExpored(adjacent)){return false;}// 计算消耗 = 当前cell的消耗 + 邻居cell的消耗float g = search.currentCell.g + CalcGPerCell(search, adjacent);// 已经加入过开放集if (search.IsCellInReachable(adjacent)){// 如果新消耗更低if (g < adjacent.g){adjacent.g = g;adjacent.previous = search.currentCell;}return false;}// 不在范围内if (g < 0f || g > search.range.y){return false;}adjacent.g = g;adjacent.previous = search.currentCell;return true;}
  • 最后,我们填充计算G的函数:

            public override float CalcGPerCell(PathFinding search, CellData adjacent){// 获取邻居的TileSrpgTile tile = search.map.GetTile(adjacent.position);// 返回本格子的消耗return search.GetMoveConsumption(tile.terrainType);}

3 搜索路径(Search Path)

直接搜索路径可以说是最标准的A星了,以前我们已经写了很多方法,所以不用再重新写了。

由于多了目标节点,所以在结束搜索的判断上要进行更改。

我们需要做的工作如下:

  • 更改结束搜索的判断;

  • 计算H值;

  • 更改加入是否开放集的判断;

  • 更改建立结果。

我们一个一个来解决。

  • 第一,还是新建一个类:

    using System.Linq;
    using UnityEngine;namespace DR.Book.SRPG_Dev.Maps.FindPath
    {[CreateAssetMenu(fileName = "FindPathDirect.asset", menuName = "SRPG/How to find path")]public class FindPathDirect : FindMoveRange{public override bool IsFinishedOnChose(PathFinding search){// TODOreturn true;}public override float CalcH(PathFinding search, CellData adjacent){// TODOreturn 0f;}public override bool CanAddAdjacentToReachable(PathFinding search, CellData adjacent){// TODOreturn true;}public override void BuildResult(PathFinding search){// TODO}}
    }
  • 第二,结束搜索:

    要注意结束搜索的条件,当开放集中没有节点时说明没有找到到达目标点的路径,这就需要我们寻找离目标点最近的节点作为路径目标。

            public override bool IsFinishedOnChose(PathFinding search){// 如果开放集中已经空了,则说明没有达到目标点if (search.currentCell == null){// 使用h最小值建立结果CellData minHCell = search.explored.First(cell => cell.h == search.explored.Min(c => c.h));search.BuildPath(minHCell, true);return true;}// 找到了目标点if (search.currentCell == search.endCell){return true;}if (!search.IsCellInExpored(search.currentCell)){search.explored.Add(search.currentCell);}return false;}
  • 第三,判断加入开放集的条件:

    这里和移动范围相差不多,只是不用再判断范围了,还需要计算H值。

            public override bool CanAddAdjacentToReachable(PathFinding search, CellData adjacent){// 没有Tileif (!adjacent.hasTile){return false;}// 已经有对象了if (adjacent.hasMapObject){return false;}// 如果已经在关闭集if (search.IsCellInExpored(adjacent)){return false;}// 计算消耗 = 当前cell的消耗 + 邻居cell的消耗float g = search.currentCell.g + CalcGPerCell(search, adjacent);// 已经加入过开放集if (search.IsCellInReachable(adjacent)){// 如果新消耗更低if (g < adjacent.g){adjacent.g = g;adjacent.previous = search.currentCell;}return false;}adjacent.g = g;adjacent.h = CalcH(search, adjacent);adjacent.previous = search.currentCell;return true;}
  • 第四,计算H值:

    H值是预计消耗,我们规定这里每个格子的预计消耗为1,即到达目标点的预计消耗为距离。

            public override float CalcH(PathFinding search, CellData adjacent){Vector2 hVec;hVec.x = Mathf.Abs(adjacent.position.x - search.endCell.position.x);hVec.y = Mathf.Abs(adjacent.position.y - search.endCell.position.y);return hVec.x + hVec.y;}
  • 第五,建立结果:

            public override void BuildResult(PathFinding search){// 当没有达到目标点时,已经建立过结果if (search.result.Count > 0){return;}search.BuildPath(search.endCell, true);}

4 测试寻路与移动(Test Pathfinding and Moving)

要测试寻路,遵循以下步骤(修改后的测试代码见下一节):

  • 1 建立新的寻路资源:点击菜单Assets/Create/SRPG/How to find move rangeAssets/Create/SRPG/How to find path

  • 2 将资源拖入到MapGraph

  • 3 修改EditorTestPathFinding.cs测试代码,加入移动范围和直接寻路的代码;

  • 4 在地图中查看测试效果;

  • 5 在Console面板查看输出。

看起来还不错。


5 测试完整代码(Testing Full Code)

#region ---------- File Info ----------
/// **********************************************************************
/// Copyright (C) 2018 DarkRabbit(ZhangHan)
///
/// File Name:              EditorTestPathFinding.cs
/// Author:                 DarkRabbit
/// Create Time:            Sun, 02 Sep 2018 01:49:51 GMT
/// Modifier:
/// Module Description:
/// Version:                V1.0.0
/// **********************************************************************
#endregion ---------- File Info ----------using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace DR.Book.SRPG_Dev.Maps.Testing
{using DR.Book.SRPG_Dev.Maps.FindPath;using DR.Book.SRPG_Dev.Models;public class EditorTestPathFinding : MonoBehaviour{[Serializable]public enum TestPathfindingType{Attack,Move,MoveAndAttack,Path}public MapGraph m_Map;public MapClass m_TestClassPrefab;public GameObject m_TestCursorPrefab;/// <summary>/// 选择测试的寻路类型/// </summary>public TestPathfindingType m_PathfindingType;/// <summary>/// 移动点数/// </summary>public float m_MovePoint = 9f;/// <summary>/// 攻击范围/// </summary>public Vector2Int m_AttackRange = new Vector2Int(2, 3);/// <summary>/// 是否打印不关键的信息/// </summary>public bool m_DebugInfo = true;/// <summary>/// 是否打印寻路的每一步/// </summary>public bool m_DebugStep = false;private List<GameObject> m_TestCursors;private MapClass m_TestClass;private MoveConsumption m_MoveConsumption;private List<CellData> m_CursorCells;private CellData m_StartCell;private CellData m_EndCell;#if UNITY_EDITORprivate void Awake(){if (m_Map == null){m_Map = GameObject.FindObjectOfType<MapGraph>();}if (m_Map == null){Debug.LogError("EditorTestPathFinding -> Map was not found.");return;}m_Map.InitMap();m_Map.searchPath.onStep += MapPathfinding_OnStep;if (m_TestCursorPrefab == null){Debug.LogError("EditorTestPathFinding -> Cursor Prefab is null.");return;}m_TestCursors = new List<GameObject>();if (m_TestClassPrefab == null){Debug.LogError("EditorTestPathFinding -> Class Prefab is null.");return;}m_TestClass = GameObject.Instantiate<MapClass>(m_TestClassPrefab,m_Map.transform.Find("MapObject"),false);m_TestClass.map = m_Map;m_TestClass.UpdatePosition(new Vector3Int(-1, -1, 0));m_TestClass.onMovingEnd += M_TestClass_onMovingEnd;RecreateMoveConsumption();m_CursorCells = new List<CellData>();}private void M_TestClass_onMovingEnd(CellData endCell){m_TestClass.animatorController.StopMove();}private void MapPathfinding_OnStep(PathFinding searchPath){if (m_DebugInfo && m_DebugStep){Debug.LogFormat("{0}: The G value of Cell {1} is {2}.",searchPath.searchCount,searchPath.currentCell.position.ToString(),searchPath.currentCell.g.ToString());}}private void Update(){if (m_Map == null || m_TestCursorPrefab == null){return;}if (Input.anyKeyDown && m_TestClass.moving){Debug.Log("Wait for moving.");return;}// 左键建立范围if (Input.GetMouseButtonDown(0)){if (m_AttackRange.x < 1 || m_AttackRange.y < m_AttackRange.x){Debug.LogError("EditorTestPathFinding -> Check the attack range.");return;}Vector3 mousePos = Input.mousePosition;Vector3 world = Camera.main.ScreenToWorldPoint(mousePos);Vector3Int cellPosition = m_Map.grid.WorldToCell(world);CellData selectedCell = m_Map.GetCellData(cellPosition);if (selectedCell != null){ClearTestCursors();switch (m_PathfindingType){case TestPathfindingType.Move:case TestPathfindingType.MoveAndAttack:ShowMoveRangeCells(selectedCell);break;case TestPathfindingType.Path:if (!ShowPathCells(selectedCell)){return;}break;default:ShowAttackRangeCells(selectedCell);break;}}}// 右键删除建立的cursorif (Input.GetMouseButtonDown(1)){ClearTestCursors();ClearMoveRangeAndPath();}// 中建重新生成移动消耗if (Input.GetMouseButtonDown(2)){RecreateMoveConsumption();}}private void OnDestroy(){if (m_Map != null){m_Map.searchPath.onStep -= MapPathfinding_OnStep;m_Map = null;}}/// <summary>/// 生成Cursors/// </summary>/// <param name="cells"></param>/// <param name="atk"></param>public void CreateTestCursors(List<CellData> cells, bool atk){if (cells != null && cells.Count > 0){Color color = Color.clear;if (atk){color = Color.red;color.a = 0.5f;}foreach (var cell in cells){CreateTestCursor(cell, color);}}}/// <summary>/// 生成单个Cursor/// </summary>/// <param name="cell"></param>/// <param name="color"></param>public void CreateTestCursor(CellData cell, Color color){Vector3 pos = m_Map.GetCellPosition(cell.position);GameObject find = GameObject.Instantiate(m_TestCursorPrefab, transform, false);find.name = cell.position.ToString();find.transform.position = pos;if (color != Color.clear){find.GetComponent<SpriteRenderer>().color = color;find.name += " atk";}m_TestCursors.Add(find);}public void ClearTestCursors(){if (m_TestCursors != null && m_TestCursors.Count > 0){foreach (var find in m_TestCursors){GameObject.Destroy(find);}m_TestCursors.Clear();}}public void ClearMoveRangeAndPath(){m_StartCell = null;m_EndCell = null;m_TestClass.UpdatePosition(new Vector3Int(-1, -1, 0));m_CursorCells.Clear();}/// <summary>/// 重新生成移动消耗/// </summary>public void RecreateMoveConsumption(){// TODO 在构造函数内部用的Random初始化,// 有了数据后,这个地方将进行修改m_MoveConsumption = new MoveConsumption(ClassType.Knight1);Debug.LogFormat("{0}={1}, {2}={3}",TerrainType.Plain.ToString(),m_MoveConsumption[TerrainType.Plain].ToString(),TerrainType.Road.ToString(),m_MoveConsumption[TerrainType.Road].ToString());}/// <summary>/// 当左键按下时,Move类型的活动/// </summary>/// <param name="selectedCell"></param>/// <returns></returns>public List<CellData> ShowMoveRangeCells(CellData selectedCell){List<CellData> cells;if (m_CursorCells.Count == 0 || !m_CursorCells.Contains(selectedCell)){m_CursorCells.Clear();if (m_DebugInfo){Debug.LogFormat("MoveRange: start position is {0}, move point is {1}",selectedCell.position.ToString(),m_MovePoint.ToString());}m_TestClass.UpdatePosition(selectedCell.position);cells = new List<CellData>(m_Map.SearchMoveRange(selectedCell, m_MovePoint, m_MoveConsumption));m_CursorCells.AddRange(cells);if (m_PathfindingType == TestPathfindingType.MoveAndAttack){// 移动范围后,进行查找攻击范围List<CellData> attackCells = new List<CellData>();foreach (var cell in m_CursorCells){List<CellData> atks = m_Map.SearchAttackRange(cell, m_AttackRange.x, m_AttackRange.y, true);foreach (var c in atks){if (!cells.Contains(c) && !attackCells.Contains(c)){attackCells.Add(c);}}}CreateTestCursors(attackCells, true);}}else{if (m_DebugInfo){Debug.LogFormat("Selected end position {0}", selectedCell.position);}m_CursorCells.Clear();Stack<CellData> pathCells = m_Map.searchPath.BuildPath(selectedCell);cells = new List<CellData>(pathCells);m_TestClass.animatorController.PlayMove();m_TestClass.StartMove(pathCells);}CreateTestCursors(cells, false);return cells;}/// <summary>/// 当左键按下时,Attack类型的活动/// </summary>/// <param name="selectedCell"></param>/// <returns></returns>public List<CellData> ShowAttackRangeCells(CellData selectedCell){if (m_DebugInfo){Debug.LogFormat("AttackRange: start position is {0}, range is {1}",selectedCell.position.ToString(),m_AttackRange.ToString());}List<CellData> cells = m_Map.SearchAttackRange(selectedCell, m_AttackRange.x, m_AttackRange.y);CreateTestCursors(cells, true);return cells;}/// <summary>/// 当左键按下时,Path类型的活动/// </summary>/// <param name="selectedCell"></param>/// <returns></returns>public bool ShowPathCells(CellData selectedCell){if (m_StartCell == null){m_StartCell = selectedCell;if (m_DebugInfo){Debug.LogFormat("Selected start position {0}", m_StartCell.position);}m_TestClass.UpdatePosition(selectedCell.position);return false;}m_EndCell = selectedCell;if (m_DebugInfo){Debug.LogFormat("Selected end position {0}", m_EndCell.position);}List<CellData> cells = m_Map.SearchPath(m_StartCell, m_EndCell, m_MoveConsumption);m_TestClass.animatorController.PlayMove();m_TestClass.StartMove(new Stack<CellData>(cells));m_StartCell = null;m_EndCell = null;CreateTestCursors(cells, false);return true;}#endif}
}

SRPG游戏开发(二十七)第七章 寻路与地图对象 - 五 搜索移动范围与路径(Search Move Range and Path)相关推荐

  1. SRPG游戏开发(二十六)第七章 寻路与地图对象 - 四 地图对象(Map Object)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...

  2. SRPG游戏开发(二十三)第七章 寻路与地图对象 - 一 A*寻路算法(A* Search Algorithm)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...

  3. SRPG游戏开发(三十)第七章 寻路与地图对象 - 八 优化地图(Optimize MapGraph)

    返回总目录 第七章 寻路与地图对象(Pathfinding and Map Object) 这一章主要进行寻路与地图对象的部分工作. 第七章 寻路与地图对象(Pathfinding and Map O ...

  4. 【Android游戏开发二十七】讲解游戏开发与项目下的hdpi 、mdpi与ldpi资源文件夹以及游戏高清版本的设置...

    今天一个开发者问到我为什么游戏开发要删除项目下的hdpi.mdpi和ldpi文件夹:下面详细给大家解答一下: 首先童鞋们如果看过我写的<[Android游戏开发二十一]Android os设备谎 ...

  5. SRPG游戏开发(三)第二章 创建项目

    返回目录 第二章 创建项目 本章开始我们来创建我们的项目,导入用到的素材. 一 创建项目 打开Unity3D,点击New按钮.这时我们看到创建项目的设置界面. 图 2 - 1创建项目 Project ...

  6. RTS游戏开发:基于Gird的Theta*寻路算法,以及利用视线算法优化A*路径的思路【附视线算法的代码、A*、Theta*的伪代码】

    为什么要有Theta*寻路,Theta*寻路相对A*寻路的优缺点. 这是一个A*的寻路找出的路径,可以看出,A*的路径是附着于网格的.这导致了A*的路径不是最短路,而且不自然,在RTS游戏中这是不被允 ...

  7. 《SRPG游戏开发》导航(2019.03.04更新)

    <SRPG游戏开发>导航 第一章到第五章并没有使用Markdown,且经过CSDN几次改版和取消目录,这几章排版有些怪怪的. 2019.03.04 第十一章(十 - 十二) ,间章 第十一 ...

  8. SRPG游戏开发(六十三)第十一章 地图动作与地图事件 - 十二 完善地图信息与测试(Perfect MapEventInfo and Testing)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

  9. SRPG游戏开发(六十)第十一章 地图动作与地图事件 - 九 触发事件与切换回合(Trigger Events and Change Turn)

    返回<SRPG游戏开发>导航 第十一章 地图动作与地图事件(Map Action and Map Event) 我们已经有了剧本,而且可以运行剧本,但我们还缺少对地图的操作控制. 我们这一 ...

最新文章

  1. mysql nextval同步锁_mysql中实现类似oracle中的nextval函数
  2. 算法导论之图的最小生成树
  3. vmdk文件怎么安装到虚拟机_【技术分享】虚拟机镜像解析
  4. Java小结(二)——打印矩形和九九乘法表
  5. samba 实现不同操作系统之间的文件共享
  6. 初探奥尔良(Orleans)
  7. linux下查看进程的线程数,linux查看进程的线程数
  8. 学习结构[记录]类型(7) - 结构也可以有构造函数
  9. c语言第四作业答案,C语言第一次作业及答案
  10. 在java中使用solrj对solr进行CRUD
  11. php面向对象链,php面向对象之链式操作
  12. PyQt5将ui文件编译为py文件的方法
  13. Audio播放流程(四)---MediaPlayerService流程之AudioTrack的创建
  14. 计算机 工程伦理论文题目,北京工业大学研究生院
  15. C语言实验报告册中级进步,C语言实验报告册
  16. 计算机公式与函数有何区别,Excel中公式和函数的区别/excle中的函数公式
  17. C盘空间莫名丢失20G?
  18. 固态硬盘系统迁移踩过的坑
  19. STM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)
  20. 使用nvm管理node

热门文章

  1. IMX6Q基于yocto 4.9.88系统交叉编译nss-3.73
  2. 模拟电子技术实验作业(10)
  3. ijkplayer 音频解码线程
  4. CSS样式让图片旋转
  5. 控制台应用程序-打印图形
  6. 今天工作有用到,记录一下cloudberry 的基础用法吧
  7. 计算机系统I 综合实验-四路抢答器的设计
  8. 电脑为什么丢失msvcp140.dll?msvcp140.dll丢失修复详细教程步骤
  9. linux 下 飞鸽传书 最新原版的简单安装过程
  10. 放弃高考连续3次创业80后,今天IPO敲钟市值2400亿