Unity技能范围检测 --

 Unity 攻击范围检测_unity技能攻击范围判定-优快云博客

众所周知moba中的每个英雄都有一套自己的技能的攻击范围方式,有如廉颇一样的圆形范围,有火舞一样的直线范围,吕布的扇形方天戟范围,还有上图的牛魔大招时的矩形范围等等。

    一些技能是通过物理的碰撞检测来判断的,一些则是通过这样的范围来检测的。物理检测的诟病就在于开销过大,在能考虑不用物理来检测的情况下更倾向来自己通过算法模拟实现。

小菜的学习研究中,将这些自己算法检测的攻击范围划分了几种类型,并做了几个demo的演示。

如上演示,小菜简单的讲这些类型划分成了如下几类:

1). Circle                        圆形

2). Triangle                    三角形

3). Fanshaped                扇形

4). Rectangle                 矩形

5). Sector                       扇面

6). Ring                          环形

[Circle 圆形]

这应该是最简单的类型,只要去判断self和target的distance就可以做到了。

我们希望能直观看到范围的情况,故使用Debug.DrawLine做了调试的绘制。

绘制编码:

小菜不想由于各个对象高度的不同带来的检测差异,故用NormalizePosition将位置的y信息都归置成了0。

范围检测编码:

[Triangle 三角形]

三角形范围的判定,实际就是点在三角形内的判定。

数学上检测点在三角形内有三种推论方法。内角和法/同向法/重心法。

对数学感兴趣的可以参考:https://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html

小菜这里直接套用了重心法检测。

检测编码:

绘制编码:

[Fanshaped 扇形]

扇形的范围检测我们实际可以抽象成两个步骤。

1).判断self和target的distance.

2).由于点乘,使用点乘dot来计算self到target的单位向量,与self的forward向量(本身也是单位向量)来计算得夹角cos值。使用Mathf.Acos将其转化为弧度,再转换成角度做一次判断就好了。

绘制编码:

范围检测编码:

[Rectangle 矩形]

矩形的检测小菜大概是有两种方法:

1).通过点在矩形内的数学推导公式来计算。

2).通过点乘和distance来计算。

还是先将矩形绘制出来吧。

绘制编码:

范围检测:

通过点在矩形内的数学推导公式来计算矩形范围

判断一个点是否在两条线段之间夹着就转化成,判断一个点是否在某条线段的一边上,就可以利用叉乘的方向性,来判断夹角是否超过了180度 。

只要判断(AB X AE ) * (CDX CE)  >= 0 就说明E在AB,CD中间夹着,同理计算另两边DA和BC就可以了。

最后就是只需要判断

(AB X AE ) * (CD X CE)  >= 0 && (DA X DE ) * (BC X BE) >= 0 。

范围检测编码:

通过点乘和distance来计算矩形范围

还是先上一张图辅助理解吧。

两次点乘的结果在于判断target的前后和左右关系。

编码看似比上面的少很多,实际关联的理解可一点都不简单

[Sector 扇面]

扇面和扇形的检测很相似,不同的只是多了一层距离的检测。

绘制编码:

范围检测编码:

[Ring 环形]

环形和圆形的检测很相似,也只是多了一层距离的检测。

绘制编码:

范围检测编码:

附上完整代码:

 
  1. using System.Collections;

  2. using System.Collections.Generic;

  3. using UnityEngine;

  4. public enum CheckType

  5. {

  6. None,

  7. /// <summary> 圆形 </summary>

  8. Circle,

  9. /// <summary> 三角形 </summary>

  10. Triangle,

  11. /// <summary> 扇形 </summary>

  12. Fanshaped,

  13. /// <summary> 矩形 </summary>

  14. Rectangle,

  15. /// <summary> 扇面 </summary>

  16. Sector,

  17. /// <summary> 环形 </summary>

  18. Ring,

  19. }

  20. [ExecuteInEditMode]

  21. public class RangeCheckScript : MonoBehaviour

  22. {

  23. public CheckType currType = CheckType.None;

  24. public Transform mPalyer;

  25. public Transform mTarget;

  26. public bool mCheckOpen = true;

  27. void Update()

  28. {

  29. if (!mCheckOpen)

  30. return;

  31. if (null != mPalyer)

  32. Debug.DrawLine(mPalyer.position, mPalyer.position + mPalyer.forward * 8,Color.yellow);

  33. bool bResult = false;

  34. switch (currType)

  35. {

  36. case CheckType.None:

  37. break;

  38. case CheckType.Circle:

  39. bResult = CircleCheck(mPalyer, mTarget, 6);

  40. break;

  41. case CheckType.Triangle:

  42. bResult = TriangleCheck(mPalyer, mTarget, 1, 10);

  43. break;

  44. case CheckType.Fanshaped:

  45. bResult = FanshapedCheck(mPalyer, mTarget, 45, 5);

  46. break;

  47. case CheckType.Rectangle:

  48. //bResult = SimulateRectangleCheck(mPalyer, mTarget, 2, 8);

  49. bResult = RectangleCheck(mPalyer, mTarget, 2, 8);

  50. break;

  51. case CheckType.Sector:

  52. bResult = SectorCheck(mPalyer, mTarget, 45, 5, 8);

  53. break;

  54. case CheckType.Ring:

  55. bResult = RingCheck(mPalyer, mTarget, 4, 8);

  56. break;

  57. default:

  58. break;

  59. }

  60. if (bResult)

  61. Debug.LogError("检测到目标");

  62. }

  63. /// <summary>

  64. /// 圆形范围检测

  65. /// </summary>

  66. private bool CircleCheck(Transform self, Transform target, float distance)

  67. {

  68. if (null == self || null == target)

  69. return false;

  70. //---------------------绘制图形-----------------------------------

  71. Vector3 selfPosition = NormalizePosition(self.position);

  72. Vector3 targetPosition = NormalizePosition(target.position);

  73. int nCircleDentity = 360;

  74. Vector3 beginPoint = selfPosition;

  75. Vector3 endPoint = Vector3.zero;

  76. float tempStep = 2 * Mathf.PI / nCircleDentity;

  77. bool bFirst = true;

  78. for (float step = 0; step < 2 * Mathf.PI; step += tempStep)

  79. {

  80. float x = distance * Mathf.Cos(step);

  81. float z = distance * Mathf.Sin(step);

  82. endPoint.x = selfPosition.x + x;

  83. endPoint.z = selfPosition.z + z;

  84. if (bFirst)

  85. bFirst = false;

  86. else

  87. Debug.DrawLine(beginPoint, endPoint, Color.red);

  88. beginPoint = endPoint;

  89. }

  90. //---------------------范围检测-----------------------------------

  91. float currDistance = Vector3.Distance(selfPosition, targetPosition);

  92. if (currDistance <= distance)

  93. return true;

  94. return false;

  95. }

  96. /// <summary>

  97. /// 三角形范围检测

  98. /// </summary>

  99. private bool TriangleCheck(Transform self, Transform target, float halfWidth,float distance)

  100. {

  101. if (null == self || null == target)

  102. return false;

  103. //---------------------绘制图形-----------------------------------

  104. Vector3 selfPosition = NormalizePosition(self.position);

  105. Vector3 targetPosition = NormalizePosition(target.position);

  106. Quaternion tempQuat = self.rotation;

  107. //三角形的三个点

  108. Vector3 leftPoint = selfPosition + (tempQuat * Vector3.left) * halfWidth;

  109. Vector3 rightPoint = selfPosition + (tempQuat * Vector3.right) * halfWidth;

  110. Vector3 forwardPoint = selfPosition + (tempQuat * Vector3.forward) * distance;

  111. Debug.DrawLine(leftPoint,rightPoint,Color.red);

  112. Debug.DrawLine(rightPoint, forwardPoint, Color.red);

  113. Debug.DrawLine(forwardPoint, leftPoint, Color.red);

  114. //---------------------范围检测-----------------------------------

  115. bool bResult = IsPointInTriangle(leftPoint, forwardPoint, rightPoint, targetPosition);

  116. return bResult;

  117. }

  118. /// <summary>

  119. /// 扇形范围检测

  120. /// </summary>

  121. private bool FanshapedCheck(Transform self, Transform target, float halfAngle, float distance)

  122. {

  123. if (null == self || null == target)

  124. return false;

  125. //---------------------绘制图形-----------------------------------

  126. Vector3 selfPosition = NormalizePosition(self.position);

  127. Vector3 targetPosition = NormalizePosition(target.position);

  128. Quaternion selfQuat = self.rotation;

  129. int nCircleDentity = 360;

  130. Vector3 firstPoint = Vector3.zero;

  131. Vector3 beginPoint = selfPosition;

  132. Vector3 endPoint = Vector3.zero;

  133. float tempStep = 2 * Mathf.PI / nCircleDentity;

  134. float leftRadian = Mathf.PI / 2 + Mathf.Deg2Rad * halfAngle;

  135. float rightRadian = Mathf.PI / 2 - Mathf.Deg2Rad * halfAngle;

  136. bool bFirst = true;

  137. for (float step = 0; step < 2 * Mathf.PI; step += tempStep)

  138. {

  139. float x = distance * Mathf.Cos(step);

  140. float z = distance * Mathf.Sin(step);

  141. endPoint.x = selfPosition.x + x;

  142. endPoint.z = selfPosition.z + z;

  143. if (step >= rightRadian && step <= leftRadian)

  144. {

  145. if (bFirst)

  146. {

  147. firstPoint = endPoint;

  148. bFirst = false;

  149. }

  150. Debug.DrawLine(beginPoint, endPoint, Color.red);

  151. beginPoint = endPoint;

  152. }

  153. }

  154. Debug.DrawLine(selfPosition, firstPoint, Color.red);

  155. Debug.DrawLine(selfPosition, beginPoint, Color.red);

  156. //---------------------范围检测-----------------------------------

  157. //计算距离

  158. float currDis = Vector3.Distance(selfPosition, targetPosition);

  159. if (currDis > distance)

  160. return false;

  161. //计算self到target的向量

  162. Vector3 dir = targetPosition - selfPosition;

  163. //点乘dir向量和自身的forward向量 cosq

  164. float dotForward = Vector3.Dot(dir.normalized, (selfQuat * Vector3.forward).normalized);

  165. //得到夹角弧度并转换成角度

  166. float radian = Mathf.Acos(dotForward);

  167. float currAngle = Mathf.Rad2Deg * radian;

  168. if (Mathf.Abs(currAngle) <= halfAngle)

  169. return true;

  170. return false;

  171. }

  172. /// <summary>

  173. /// 矩形范围检测(数学点和矩形关系判断)

  174. /// </summary>

  175. private bool SimulateRectangleCheck(Transform self, Transform target, float halfWidth, float distance)

  176. {

  177. if (null == self || null == target)

  178. return false;

  179. //---------------------绘制图形-----------------------------------

  180. Vector3 selfPosition = NormalizePosition(self.position);

  181. Vector3 targetPosition = NormalizePosition(target.position);

  182. Vector3 selfEulerAngles = self.rotation.eulerAngles;

  183. Quaternion selfQuat = self.rotation;

  184. //矩形的四个点

  185. Vector3 leftPoint = selfPosition + (selfQuat * Vector3.left) * halfWidth;

  186. Vector3 rightPoint = selfPosition + (selfQuat * Vector3.right) * halfWidth;

  187. Vector3 leftUpPoint = leftPoint + (selfQuat * Vector3.forward) * distance;

  188. Vector3 rightUpPoint = rightPoint + (selfQuat * Vector3.forward) * distance;

  189. Debug.DrawLine(selfPosition, leftPoint, Color.red);

  190. Debug.DrawLine(selfPosition, rightPoint, Color.red);

  191. Debug.DrawLine(leftPoint, leftUpPoint, Color.red);

  192. Debug.DrawLine(rightPoint, rightUpPoint, Color.red);

  193. Debug.DrawLine(leftUpPoint, rightUpPoint, Color.red);

  194. //---------------------范围检测-----------------------------------

  195. Vector2 point = Vector2.zero;

  196. point.x = targetPosition.x;

  197. point.y = targetPosition.z;

  198. Vector2 point1 = Vector2.zero;

  199. point1.x = leftUpPoint.x;

  200. point1.y = leftUpPoint.z;

  201. Vector2 point2 = Vector2.zero;

  202. point2.x = rightUpPoint.x;

  203. point2.y = rightUpPoint.z;

  204. Vector2 point3 = Vector2.zero;

  205. point3.x = rightPoint.x;

  206. point3.y = rightPoint.z;

  207. Vector2 point4 = Vector2.zero;

  208. point4.x = leftPoint.x;

  209. point4.y = leftPoint.z;

  210. bool bResult = IsPointInRectangle(point1, point2, point3, point4, point);

  211. return bResult;

  212. }

  213. /// <summary>

  214. /// 矩形范围检测(点乘方式)

  215. /// </summary>

  216. private bool RectangleCheck(Transform self, Transform target, float halfWidth, float distance)

  217. {

  218. if (null == self || null == target)

  219. return false;

  220. //---------------------绘制图形-----------------------------------

  221. Vector3 selfPosition = NormalizePosition(self.position);

  222. Vector3 targetPosition = NormalizePosition(target.position);

  223. Vector3 selfEulerAngles = self.rotation.eulerAngles;

  224. Quaternion selfQuat = self.rotation;

  225. //矩形的四个点

  226. Vector3 leftPoint = selfPosition + (selfQuat * Vector3.left) * halfWidth;

  227. Vector3 rightPoint = selfPosition + (selfQuat * Vector3.right) * halfWidth;

  228. Vector3 leftUpPoint = leftPoint + (selfQuat * Vector3.forward) * distance;

  229. Vector3 rightUpPoint = rightPoint + (selfQuat * Vector3.forward) * distance;

  230. Debug.DrawLine(selfPosition, leftPoint, Color.red);

  231. Debug.DrawLine(selfPosition, rightPoint, Color.red);

  232. Debug.DrawLine(leftPoint, leftUpPoint, Color.red);

  233. Debug.DrawLine(rightPoint, rightUpPoint, Color.red);

  234. Debug.DrawLine(leftUpPoint, rightUpPoint, Color.red);

  235. //---------------------范围检测-----------------------------------

  236. //计算self到target的向量

  237. Vector3 dir = targetPosition - selfPosition;

  238. //点乘dir向量和自身的forward向量

  239. float dotForward = Vector3.Dot(dir, (selfQuat * Vector3.forward).normalized);

  240. //target处于self的前方的height范围

  241. if (dotForward > 0 && dotForward <= distance)

  242. {

  243. float dotRight = Vector3.Dot(dir, (selfQuat * Vector3.right).normalized);

  244. //target处于self的左右halfWidth的范围

  245. if (Mathf.Abs(dotRight) <= halfWidth)

  246. return true;

  247. }

  248. return false;

  249. }

  250. /// <summary>

  251. /// 扇面范围检测

  252. /// </summary>

  253. private bool SectorCheck(Transform self, Transform target, float halfAngle, float nearDis, float farDis)

  254. {

  255. if (null == self || null == target)

  256. return false;

  257. if (nearDis > farDis)

  258. {

  259. float tempDis = nearDis;

  260. nearDis = farDis;

  261. farDis = tempDis;

  262. }

  263. //---------------------绘制图形-----------------------------------

  264. Vector3 selfPosition = NormalizePosition(self.position);

  265. Vector3 targetPosition = NormalizePosition(target.position);

  266. Quaternion selfQuat = self.rotation;

  267. int nCircleDentity = 360;

  268. Vector3 nearFirstPoint = Vector3.zero;

  269. Vector3 nearBeginPoint = selfPosition;

  270. Vector3 nearEndPoint = Vector3.zero;

  271. Vector3 farFirstPoint = Vector3.zero;

  272. Vector3 farBeginPoint = selfPosition;

  273. Vector3 farEndPoint = Vector3.zero;

  274. float tempStep = 2 * Mathf.PI / nCircleDentity;

  275. float leftRadian = Mathf.PI / 2 + Mathf.Deg2Rad * halfAngle;

  276. float rightRadian = Mathf.PI / 2 - Mathf.Deg2Rad * halfAngle;

  277. bool bFirst = true;

  278. for (float step = 0; step < 2 * Mathf.PI; step += tempStep)

  279. {

  280. float nearX = nearDis * Mathf.Cos(step);

  281. float nearZ = nearDis * Mathf.Sin(step);

  282. float farX = farDis * Mathf.Cos(step);

  283. float farZ = farDis * Mathf.Sin(step);

  284. if (step >= rightRadian && step <= leftRadian)

  285. {

  286. //-------绘制近扇面

  287. nearEndPoint.x = selfPosition.x + nearX;

  288. nearEndPoint.z = selfPosition.z + nearZ;

  289. //-------绘制远扇面

  290. farEndPoint.x = selfPosition.x + farX;

  291. farEndPoint.z = selfPosition.z + farZ;

  292. if (bFirst)

  293. {

  294. nearFirstPoint = nearEndPoint;

  295. farFirstPoint = farEndPoint;

  296. bFirst = false;

  297. }

  298. else

  299. {

  300. Debug.DrawLine(nearBeginPoint, nearEndPoint, Color.red);

  301. Debug.DrawLine(farBeginPoint, farEndPoint, Color.red);

  302. }

  303. nearBeginPoint = nearEndPoint;

  304. farBeginPoint = farEndPoint;

  305. }

  306. }

  307. Debug.DrawLine(nearFirstPoint, farFirstPoint, Color.red);

  308. Debug.DrawLine(nearEndPoint, farEndPoint, Color.red);

  309. Debug.DrawLine(selfPosition, nearFirstPoint, Color.blue);

  310. Debug.DrawLine(selfPosition, nearEndPoint, Color.blue);

  311. //---------------------范围检测-----------------------------------

  312. //计算距离

  313. float currDis = Vector3.Distance(selfPosition, targetPosition);

  314. if (currDis < nearDis || currDis > farDis)

  315. return false;

  316. //计算self到target的向量

  317. Vector3 dir = targetPosition - selfPosition;

  318. //点乘dir向量和自身的forward向量 cosq

  319. float dotForward = Vector3.Dot(dir.normalized, (selfQuat * Vector3.forward).normalized);

  320. //得到夹角弧度并转换成角度

  321. float radian = Mathf.Acos(dotForward);

  322. float currAngle = Mathf.Rad2Deg * radian;

  323. if (Mathf.Abs(currAngle) <= halfAngle)

  324. return true;

  325. return false;

  326. }

  327. /// <summary>

  328. /// 双圆范围检测

  329. /// </summary>

  330. private bool RingCheck(Transform self, Transform target, float nearDis, float farDis)

  331. {

  332. if (null == self || null == target)

  333. return false;

  334. if (nearDis > farDis)

  335. {

  336. float tempDis = nearDis;

  337. nearDis = farDis;

  338. farDis = tempDis;

  339. }

  340. //---------------------绘制图形-----------------------------------

  341. Vector3 selfPosition = NormalizePosition(self.position);

  342. Vector3 targetPosition = NormalizePosition(target.position);

  343. int nCircleDentity = 360;

  344. Vector3 nearBeginPoint = selfPosition;

  345. Vector3 nearEndPoint = Vector3.zero;

  346. Vector3 farBeginPoint = selfPosition;

  347. Vector3 farEndPoint = Vector3.zero;

  348. float tempStep = 2 * Mathf.PI / nCircleDentity;

  349. bool bFirst = true;

  350. for (float step = 0; step < 2 * Mathf.PI; step += tempStep)

  351. {

  352. float nearX = nearDis * Mathf.Cos(step);

  353. float nearZ = nearDis * Mathf.Sin(step);

  354. nearEndPoint.x = selfPosition.x + nearX;

  355. nearEndPoint.z = selfPosition.z + nearZ;

  356. float farX = farDis * Mathf.Cos(step);

  357. float farZ = farDis * Mathf.Sin(step);

  358. farEndPoint.x = selfPosition.x + farX;

  359. farEndPoint.z = selfPosition.z + farZ;

  360. if (bFirst)

  361. bFirst = false;

  362. else

  363. {

  364. Debug.DrawLine(nearBeginPoint, nearEndPoint, Color.red);

  365. Debug.DrawLine(farBeginPoint, farEndPoint, Color.red);

  366. }

  367. nearBeginPoint = nearEndPoint;

  368. farBeginPoint = farEndPoint;

  369. }

  370. //---------------------范围检测-----------------------------------

  371. float currDistance = Vector3.Distance(selfPosition, targetPosition);

  372. if (currDistance >= nearDis && currDistance <= farDis )

  373. return true;

  374. return false;

  375. }

  376. /// <summary>

  377. /// 规范位置(去除高度带来的影响)

  378. /// </summary>

  379. private Vector3 NormalizePosition(Vector3 position,float hight = 0.0f)

  380. {

  381. Vector3 tempPosition = Vector3.zero;

  382. tempPosition.x = position.x;

  383. tempPosition.y = hight;

  384. tempPosition.z = position.z;

  385. return tempPosition;

  386. }

  387. /// <summary>

  388. /// 三角形检查

  389. /// </summary>

  390. private bool IsPointInTriangle(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 targetPoint)

  391. {

  392. Vector3 v0 = point2 - point1;

  393. Vector3 v1 = point3 - point1;

  394. Vector3 v2 = targetPoint - point1;

  395. float dot00 = Vector3.Dot(v0, v0);

  396. float dot01 = Vector3.Dot(v0, v1);

  397. float dot02 = Vector3.Dot(v0, v2);

  398. float dot11 = Vector3.Dot(v1, v1);

  399. float dot12 = Vector3.Dot(v1, v2);

  400. float inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);

  401. float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;

  402. if (u < 0 || u > 1)

  403. return false;

  404. float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;

  405. if (v < 0 || v > 1)

  406. return false;

  407. return u + v <= 1;

  408. }

  409. /// <summary>

  410. /// 判断点p是否在p1 p2 p3 p4构成的矩形内

  411. /// </summary>

  412. private bool IsPointInRectangle(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, Vector2 point)

  413. {

  414. return GetCross(point1, point2, point) * GetCross(point3, point4, point) >= 0

  415. && GetCross(point2, point3, point) * GetCross(point4, point1, point) >= 0;

  416. }

  417. /// <summary>

  418. /// 计算 |p1 p2| X |p1 p|

  419. /// </summary>

  420. private float GetCross(Vector2 point1, Vector2 point2, Vector2 point)

  421. {

  422. return ((point2.x - point1.x) * (point.y - point1.y) - (point.x - point1.x) * (point2.y - point1.y));

  423. }

  424. }

### 如何在 Unity 中实现攻击范围检测 #### 使用触发器进行攻击范围检测 为了有效地执行攻击范围检测,在 Unity 中通常采用带有 `IsTrigger` 属性的碰撞体组件。当希望检测特定对象是否进入某个单位的攻击半径内时,可以通过创建一个围绕攻击者的不可见碰撞体并将其设为触发器来完成这一目标[^2]。 对于攻击者而言,应该在其周围附加适当形状和尺寸的 Collider 组件(例如 SphereCollider 或 BoxCollider),并将该 Collider 的 Is Trigger 属性勾选启用。这样做的目的是让此 Collider 不再参与物理模拟中的刚体交互而是仅用于事件触发。 接着编写脚本监听这些触发事件: ```csharp using UnityEngine; public class AttackRangeDetector : MonoBehaviour { private void OnTriggerEnter(Collider other) { // 当有物体进入触发区域时调用的方法 Debug.Log("进入了攻击范围:" + other.name); // 这里可以根据需求添加更多逻辑处理, // 比如判断是否为目标敌人以及发起攻击等操作。 } private void OnTriggerExit(Collider other) { // 当有物体离开触发区域时调用的方法 Debug.Log("离开了攻击范围:" + other.name); } } ``` 上述代码展示了如何利用 C# 脚本来响应进入 (`OnTriggerEnter`) 和退出 (`OnTriggerExit`) 触发区间的事件。每当有一个合适的 GameObject 接触到设定好的触发器边界时就会触发相应的函数,从而允许开发者定义具体的反应机制,比如启动一次攻击动作或者更新UI提示玩家当前处于危险状态等等。 需要注意的是,在实际项目中可能还需要考虑一些额外的因素,例如排除不必要的碰撞检测项(像地面或其他环境物件)、确保只有预期的目标能够激活触发条件等。这往往涉及到对 LayerMask 参数的应用以及其他高级技巧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值