Unity的50个使用技巧(2016 Edition)



Unity更好用

例如,我现在信赖FPS计数器。使用property drawer的功能可以降低编写customeditors的必要性。同时Prefab的工作方式也降低了显式嵌套Prefab或替代件的需求。Scriptable objects更为友好。 


VisualStudio集成度更佳

从而使调试操作更简便,同时减少了对于大量Gorilla调试操作的需求。


第三方工具和库更优化

AssetStore里现有许多可用的对于可视化调试和更佳日志记录等事物的辅助工具。我们自带(免费)的扩展插件有大量初始发布中所述的代码(以及本次版本发布所述的许多代码)。


版本控制更好

(或者可以说,我现在知道如何更有效地使用它)。比方说,无需对Prefab执行多个副本或者备份。


个人经验的积累

在过去四年里,我参与了许多Unity项目;包括大量的游戏原型制作,等的游戏制作,以及我们的旗舰工具Unity asset 

本文是在考虑上述所有内容的基础上对初始版本进行的修订版本。


在继续讨论技巧前,本人现发布以下免责声明(与初始版本基本相同):

这些技巧并不适用于每个Unity项目。

这些技巧是基于本人与3到20人的小型团队协作参与项目所获得的经验。在结构性,可重用性,清晰度等方面存在费用——根据团队规模,项目规模和项目目标来确定是否此费用。比方说,你可能不会在游戏制作环节使用所有内容。

许多技巧涉及个人喜好问题(虽然这里列出的技巧之间可能有竞争,但都是很好的技巧)。

此外,Unity也在其官网上发布了一些最佳操作实例(虽然它们中大多数是从效能角度出发):


1http://unity3d.com/learn/tutorials/topics/best-practices2基于物理的内容创建的最佳操作实例:https://youtu.be/OeEYEUCa4tI3Unity中的最佳2D操作实例:https://youtu.be/HM17mAmLd7k4Unity内部技巧和技巧: https://youtu.be/Ozc_hXzp_KU5Unity提示和技巧:https://youtu.be/2S6Ygq58QF86

http://docs.unity3d.com/Manual/HOWTO-ArtAssetBestPracticeGuide.html








开发流程


1 确定开始的缩放比例,并以相同缩放比例构建所有原型。

否则,你可能需要后续重做assets(例如,无法总是正确地缩放动画)。对于3D游戏,采用1 Unity单位= 1m通常是最佳的。对于不使用照明或物理的2D游戏,采用1 Unity单位 = 1 像素(在“设计”分辨率阶段)通常是较好的。对于UI(以及2D游戏),选择设计分辨率(我们使用HD2xHD,并将所有assets设计为以此分辨率缩放。


2  使每个场景都可以运行。

这样可以避免为了运行游戏而必须转换场景,从而加快了测试速度。如果要在所有场景中必需的场景加载之间持续存在对象,这可能需要技巧。一种方法是当持续对象不存在于场景中时,使它们作为可自行加载的单例模式。另一个技巧中将详述单例模式。


3  使用源代码控制,并学习如何有效地使用它。

assets序列化为文本。实际上,它并不会提高场景和Prefab的可合并性,但它会使变化更容易观测。

采用场景和Prefab共享策略。一般来说,多个人不应在同一场景或Prefab工作。对于小型制作团队,只要在开始工作前确保没有人制作场景或Prefab即可。交换表示场景所有权的物理标记可能很有用(如果桌面上有场景标记,你仅可以在某一场景中工作)。


将标签作为书签。

确定并坚持采用分支策略。由于场景和Prefab不能平滑地合并,分支稍显复杂。然而当你决定使用分支时,它应该结合场景和Prefab共享策略使用。


使用子模块时要小心 。子模型可能是维护可重用代码的最佳途径。但需注意几个警告事项:

元数据文件通常在多个项目中不一致。对于非Monobehaviour或非Scriptable object代码而言,这通常不是问题,但对于MonoBehaviours和Scriptable objects使用子模块可能会导致代码丢失。

如果你参与许多项目(包括一个或多个子模块项目),倘若你必须对几次迭代中的多个项目执行获取—合并—提交—推送操作以稳定所有项目的代码,有时会发生更新崩溃(并且如果其他人同时进行变更,它可能会转变为持续崩溃)。一种最大程度上降低此效应的方法是在项目初始阶段对子模块进行更改。如此一来,总是需要推送仅使用子模块的项目;它们从来无需推回。


4 保持测试场景和代码分离。

向存储库提交临时资源和脚本,并在完成后将它们移出项目。


5 如果你要更新工具(尤其是Unity),必须同时进行。

当你使用一个与先前不同的版本打开项目时,Unity能够更好地保留链接,但倘若人们使用不同的版本,有时仍然会丢失链接。


6 在一个干净的项目中导入第三方assets

并从中导出一个可供自己使用的新的资源包。当你直接向项目导入这些资源,它们有时会导致问题:

可能存在冲突(文件或文件名),尤其对于在插件目录根中存在文件或者在实例中使用StandardAssets中assets的资源。

这些资源可能被无序地放入到自有项目的文件中。如果你决定不使用或者想要移除这些assets,这可能成为一个重要问题。

请按照下述步骤使assets导入更安全:


1创建一个新项目,然后导入asset。2运行实例并确保它们能够工作。3将asset排列为一个更合适的目录结构。(我通常不对一个资源强制排列自有的目录结构。但是我确保所有文件均在一个目录中,同时在重要位置不存在任何可能会覆盖项目中现有文件的文件。4运行实例并确保它们仍可以工作。(有时,当我移动事物时会导致assets损坏,但这通常不应该是一个问题)。5现要移除所有无需的事物(如实例)。6确保asset仍可编译,并且Prefab仍然拥有所有自身的链接。若留下任何需运行的事项,则对它进行测试。7现选定所有assets,并导出一个资源包。8导入到你的项目中。


7 自动构建进程。

甚至对于小型项目,这步很有用,但对于以下情况尤为适用:

你需要构建许多不同的游戏版本。

其他拥有不同程度技术知识的团队成员需要进行构建,或者

你需要对项目进行小幅调整后才能进行构建。

详见Unity构建编译:对于如何执行的较好指导的基本和高级可能性。


8 为你的设置建立文档

大部分记录应在代码中,但是某些事项应记录在代码外。制作设计师通过耗时的设置来筛选代码。文档化的设置可以提高效率(若文档是最新的)。

对下述内容建立文档:

标签使用。

图层使用(对于碰撞,剔除和光线投射—从本质上来说,每个图层对应的使用)。

图层的GUI深度(每个图层对应的显示)

场景设置。

复杂PrefabPrefab结构。

常用语偏好。

构建设置。

通用编码


9 将所有代码放入一个命名空间中。

这避免了自有库和第三方代码之间可能发生的代码冲突。但不要依赖于命名空间以避免与重要类冲突。即使你会使用不同的命名空间,也不要将“对象”、“动作”或“事件”作为类名称。


10  使用断言。

断言对于代码中不变量的测试非常有用,它能够辅助清除逻辑错误。类提供了可用的断言。它们都可以测试一些条件,但如果不符合条件,则在控制台中写入错误信息。如果你不熟悉如何有效地使用断言,请参考使用断言编程的优点(a.k.a.断言语句)。


11  切勿对显示文本以外的任何事项使用字符串。

尤其应注意,不要使用字符串来标识对象或Prefab。但存在一些例外情形(仍然有一些内容只能通过Unity中的名称访问)。在这种情形下,将这些字符串定义为“AnimationNames”或 “AudioModuleNames”等文件中的常量。倘若这些类变为不可管理,使用嵌套类后便可类似命名AnimationNames.Player.Run。


12  不要使用“Invoke”和“SendMessage”。

这些MonoBehaviour方法通过名称调用其他方法。通过名称调用的方法难以在代码中追踪(无法找到“Usages”,而“发SendMessage”的范围更宽,因此更难以追踪)。

较简便的方法是使用Coroutines和C#操作推出“Invoke”:


你可以参考monoBehaviour模式:

this.Invoke(ShootEnemy);   //其中ShootEnemy是一个无参数的void法。

如果你实现自己的基础MonoBehaviour,你可以向其中添加自己的“Invoke”。

另一种较安全的“SendMessage”方法更难以实施。与之相反,我通常使用“GetComponent”变量以获取父对象,当前游戏对象或子对象的组件,并直接执行调用。


13  当游戏运行时,不要让派生对象混乱层次结构。

将它们的父对象设为场景对象,以便在游戏运行时更容易找到内容。你可以使用一个空游戏对象,或者甚至使用一个无行为的单例模式(详见本文后面的部分),从而更容易地从代码进行访问。将此对象命名为“DynamicObjects”。


14  明确是否要将空值(null)作为一个合法值,并尽量避免这么做

空值可辅助检测错误代码。但是,如果你使“if”默默地通过空值成为一种习惯,错误代码将很快运行,同时你只能在很久之后才会注意到错误。此外,随着每个图层通过空变量,它可以在代码深度暴露。我尝试避免将空值整体作为一个合法值。

我优先采用的常用语不是进行任何空检查,倘若它是一个问题,让代码失败。有时,在“可重用”方法中,我将检查出一个值为空的变量,并抛出一个异常,而不是将它传递至其它可能失败的方法。

在某些情形下,值可以合法为空,并且需要采取不同的方式处理。在此类情况下,添加注释来解释什么时候某些内容可能为空,并说明为什么可能为空。

常见场景通常用于inspector配置的值。用户可以指定一个值,但如果未指定任何值,则使用一个默认值。最好结合包含T值的可选类。(这有点像“可为空”)。你可以使用一个特殊的属性渲染器来渲染一个勾选框,若勾选,则仅显示数值框。

(但切勿直接使用泛型类,你必须扩展特定T值的类)。


在你的代码中,你可以采取这种使用途径:

health= healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax;


15  如果你使用“协程”,学习如何有效地使用它。

“协程”是解决许多问题的一种最有效的方法。但是难以对“协程”进行调式,同时你可以很容易地对它进行混乱的编码,从而使其他人,甚至包括你自己也无法理解其意义。

你应该知道:

 1)如何并发执行协程。

2)如何按序执行协程。

3)如何从现有程序中创建新的协程。

4)如何使用“CustomYieldInstruction”创建自定义协程。





16  利用扩展法来协同共享接口的组件。

有时可以方便地获取实施某个接口的组件,或者找到这些组件相应的对象。

下述实例使用typeof而不是这些函数的通用版本。通用版本无法协同接口使用,但typeof却可以。下面的方法将其整洁地套入通用方法之中。



17  利用扩展法使语法更简洁。

例如:


18  使用另一种防御性GetComponent方法。

有时通过RequiredComponent强制组件关系可能难以操作,但是这总是可能和可取的,特别是当你调用其它类上的GetComponent。作为一种替代方法,但需要某个组件打印找到的错误信息时,可以使用下述GameObject扩展。



19  避免对相同的事项使用不同的常用语。

在许多情况下,有多种常用法。此时,对整个项目选择一种常用法。其原因在于:

1)某些常用语不能一起工作。在某个方向中使用一种常用语强行设 计可能不适合另一种常用语。

2)对于整个项目使用相同的常用语能够使团队成员更容易理解进展。它使结构和代码更容易理解。这样就更难犯错。


常用语组示例:

协程与状态机。

 嵌套的Prefab,互相链接的Prefab和超级Prefab

数据分离策略。

对2D游戏中状态使用sprites的方法。

Prefab结构。

派生策略。

定位对象的方法:按类型,按名称,按标签,按图层和按引用关系(“链接”)。

分组对象的方法:按类型,按名称,按标签,按图层和按引用数组(“链接”)。

调用其他组件方法的途径。

查找对象组和自注册。

控制执行次序(使用Unity的执行次序设置,还是使用yield逻辑,利用Awake / StartUpdate / Late Update依赖,还是使用纯手动的方法,或者采用次序无关的架构)。

在游戏中使用鼠标选择对象/位置/目标:SelectionManager或者对象自主管理。

在场景变换时保存数据:通过,或者是在新场景加载时未毁损的对象。

组合(混合、添加和分层)动画的方法。

输入处理(中央和本地)


20  维护一个自有的Time类,这可以更容易实现游戏暂停。

包装一个“Time.DeltaTime”和“Time.TimeSinceLevelLoad”来实现暂停和游戏速度的缩放。它使用时有点麻烦,但是当对象运行在不同的时钟速率下就容易多了(例如界面动画和游戏动画)。

Unity更好用

例如,我现在信赖FPS计数器。使用property drawer的功能可以降低编写customeditors的必要性。同时Prefab的工作方式也降低了显式嵌套Prefab或替代件的需求。Scriptable objects更为友好。 


VisualStudio集成度更佳

从而使调试操作更简便,同时减少了对于大量Gorilla调试操作的需求。


第三方工具和库更优化

AssetStore里现有许多可用的对于可视化调试和更佳日志记录等事物的辅助工具。我们自带(免费)的扩展插件有大量初始发布中所述的代码(以及本次版本发布所述的许多代码)。


版本控制更好

(或者可以说,我现在知道如何更有效地使用它)。比方说,无需对Prefab执行多个副本或者备份。


个人经验的积累

在过去四年里,我参与了许多Unity项目;包括大量的游戏原型制作,等的游戏制作,以及我们的旗舰工具Unity asset 

本文是在考虑上述所有内容的基础上对初始版本进行的修订版本。


在继续讨论技巧前,本人现发布以下免责声明(与初始版本基本相同):

这些技巧并不适用于每个Unity项目。

这些技巧是基于本人与3到20人的小型团队协作参与项目所获得的经验。在结构性,可重用性,清晰度等方面存在费用——根据团队规模,项目规模和项目目标来确定是否此费用。比方说,你可能不会在游戏制作环节使用所有内容。

许多技巧涉及个人喜好问题(虽然这里列出的技巧之间可能有竞争,但都是很好的技巧)。

此外,Unity也在其官网上发布了一些最佳操作实例(虽然它们中大多数是从效能角度出发):


1http://unity3d.com/learn/tutorials/topics/best-practices2基于物理的内容创建的最佳操作实例:https://youtu.be/OeEYEUCa4tI3Unity中的最佳2D操作实例:https://youtu.be/HM17mAmLd7k4Unity内部技巧和技巧: https://youtu.be/Ozc_hXzp_KU5Unity提示和技巧:https://youtu.be/2S6Ygq58QF86

http://docs.unity3d.com/Manual/HOWTO-ArtAssetBestPracticeGuide.html








开发流程


1 确定开始的缩放比例,并以相同缩放比例构建所有原型。

否则,你可能需要后续重做assets(例如,无法总是正确地缩放动画)。对于3D游戏,采用1 Unity单位= 1m通常是最佳的。对于不使用照明或物理的2D游戏,采用1 Unity单位 = 1 像素(在“设计”分辨率阶段)通常是较好的。对于UI(以及2D游戏),选择设计分辨率(我们使用HD2xHD,并将所有assets设计为以此分辨率缩放。


2  使每个场景都可以运行。

这样可以避免为了运行游戏而必须转换场景,从而加快了测试速度。如果要在所有场景中必需的场景加载之间持续存在对象,这可能需要技巧。一种方法是当持续对象不存在于场景中时,使它们作为可自行加载的单例模式。另一个技巧中将详述单例模式。


3  使用源代码控制,并学习如何有效地使用它。

assets序列化为文本。实际上,它并不会提高场景和Prefab的可合并性,但它会使变化更容易观测。

采用场景和Prefab共享策略。一般来说,多个人不应在同一场景或Prefab工作。对于小型制作团队,只要在开始工作前确保没有人制作场景或Prefab即可。交换表示场景所有权的物理标记可能很有用(如果桌面上有场景标记,你仅可以在某一场景中工作)。


将标签作为书签。

确定并坚持采用分支策略。由于场景和Prefab不能平滑地合并,分支稍显复杂。然而当你决定使用分支时,它应该结合场景和Prefab共享策略使用。


使用子模块时要小心 。子模型可能是维护可重用代码的最佳途径。但需注意几个警告事项:

元数据文件通常在多个项目中不一致。对于非Monobehaviour或非Scriptable object代码而言,这通常不是问题,但对于MonoBehaviours和Scriptable objects使用子模块可能会导致代码丢失。

如果你参与许多项目(包括一个或多个子模块项目),倘若你必须对几次迭代中的多个项目执行获取—合并—提交—推送操作以稳定所有项目的代码,有时会发生更新崩溃(并且如果其他人同时进行变更,它可能会转变为持续崩溃)。一种最大程度上降低此效应的方法是在项目初始阶段对子模块进行更改。如此一来,总是需要推送仅使用子模块的项目;它们从来无需推回。


4 保持测试场景和代码分离。

向存储库提交临时资源和脚本,并在完成后将它们移出项目。


5 如果你要更新工具(尤其是Unity),必须同时进行。

当你使用一个与先前不同的版本打开项目时,Unity能够更好地保留链接,但倘若人们使用不同的版本,有时仍然会丢失链接。


6 在一个干净的项目中导入第三方assets

并从中导出一个可供自己使用的新的资源包。当你直接向项目导入这些资源,它们有时会导致问题:

可能存在冲突(文件或文件名),尤其对于在插件目录根中存在文件或者在实例中使用StandardAssets中assets的资源。

这些资源可能被无序地放入到自有项目的文件中。如果你决定不使用或者想要移除这些assets,这可能成为一个重要问题。

请按照下述步骤使assets导入更安全:


1创建一个新项目,然后导入asset。2运行实例并确保它们能够工作。3将asset排列为一个更合适的目录结构。(我通常不对一个资源强制排列自有的目录结构。但是我确保所有文件均在一个目录中,同时在重要位置不存在任何可能会覆盖项目中现有文件的文件。4运行实例并确保它们仍可以工作。(有时,当我移动事物时会导致assets损坏,但这通常不应该是一个问题)。5现要移除所有无需的事物(如实例)。6确保asset仍可编译,并且Prefab仍然拥有所有自身的链接。若留下任何需运行的事项,则对它进行测试。7现选定所有assets,并导出一个资源包。8导入到你的项目中。


7 自动构建进程。

甚至对于小型项目,这步很有用,但对于以下情况尤为适用:

你需要构建许多不同的游戏版本。

其他拥有不同程度技术知识的团队成员需要进行构建,或者

你需要对项目进行小幅调整后才能进行构建。

详见Unity构建编译:对于如何执行的较好指导的基本和高级可能性。


8 为你的设置建立文档

大部分记录应在代码中,但是某些事项应记录在代码外。制作设计师通过耗时的设置来筛选代码。文档化的设置可以提高效率(若文档是最新的)。

对下述内容建立文档:

标签使用。

图层使用(对于碰撞,剔除和光线投射—从本质上来说,每个图层对应的使用)。

图层的GUI深度(每个图层对应的显示)

场景设置。

复杂PrefabPrefab结构。

常用语偏好。

构建设置。

通用编码


9 将所有代码放入一个命名空间中。

这避免了自有库和第三方代码之间可能发生的代码冲突。但不要依赖于命名空间以避免与重要类冲突。即使你会使用不同的命名空间,也不要将“对象”、“动作”或“事件”作为类名称。


10  使用断言。

断言对于代码中不变量的测试非常有用,它能够辅助清除逻辑错误。类提供了可用的断言。它们都可以测试一些条件,但如果不符合条件,则在控制台中写入错误信息。如果你不熟悉如何有效地使用断言,请参考使用断言编程的优点(a.k.a.断言语句)。


11  切勿对显示文本以外的任何事项使用字符串。

尤其应注意,不要使用字符串来标识对象或Prefab。但存在一些例外情形(仍然有一些内容只能通过Unity中的名称访问)。在这种情形下,将这些字符串定义为“AnimationNames”或 “AudioModuleNames”等文件中的常量。倘若这些类变为不可管理,使用嵌套类后便可类似命名AnimationNames.Player.Run。


12  不要使用“Invoke”和“SendMessage”。

这些MonoBehaviour方法通过名称调用其他方法。通过名称调用的方法难以在代码中追踪(无法找到“Usages”,而“发SendMessage”的范围更宽,因此更难以追踪)。

较简便的方法是使用Coroutines和C#操作推出“Invoke”:


你可以参考monoBehaviour模式:

this.Invoke(ShootEnemy);   //其中ShootEnemy是一个无参数的void法。

如果你实现自己的基础MonoBehaviour,你可以向其中添加自己的“Invoke”。

另一种较安全的“SendMessage”方法更难以实施。与之相反,我通常使用“GetComponent”变量以获取父对象,当前游戏对象或子对象的组件,并直接执行调用。


13  当游戏运行时,不要让派生对象混乱层次结构。

将它们的父对象设为场景对象,以便在游戏运行时更容易找到内容。你可以使用一个空游戏对象,或者甚至使用一个无行为的单例模式(详见本文后面的部分),从而更容易地从代码进行访问。将此对象命名为“DynamicObjects”。


14  明确是否要将空值(null)作为一个合法值,并尽量避免这么做

空值可辅助检测错误代码。但是,如果你使“if”默默地通过空值成为一种习惯,错误代码将很快运行,同时你只能在很久之后才会注意到错误。此外,随着每个图层通过空变量,它可以在代码深度暴露。我尝试避免将空值整体作为一个合法值。

我优先采用的常用语不是进行任何空检查,倘若它是一个问题,让代码失败。有时,在“可重用”方法中,我将检查出一个值为空的变量,并抛出一个异常,而不是将它传递至其它可能失败的方法。

在某些情形下,值可以合法为空,并且需要采取不同的方式处理。在此类情况下,添加注释来解释什么时候某些内容可能为空,并说明为什么可能为空。

常见场景通常用于inspector配置的值。用户可以指定一个值,但如果未指定任何值,则使用一个默认值。最好结合包含T值的可选类。(这有点像“可为空”)。你可以使用一个特殊的属性渲染器来渲染一个勾选框,若勾选,则仅显示数值框。

(但切勿直接使用泛型类,你必须扩展特定T值的类)。


在你的代码中,你可以采取这种使用途径:

health= healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax;


15  如果你使用“协程”,学习如何有效地使用它。

“协程”是解决许多问题的一种最有效的方法。但是难以对“协程”进行调式,同时你可以很容易地对它进行混乱的编码,从而使其他人,甚至包括你自己也无法理解其意义。

你应该知道:

 1)如何并发执行协程。

2)如何按序执行协程。

3)如何从现有程序中创建新的协程。

4)如何使用“CustomYieldInstruction”创建自定义协程。





16  利用扩展法来协同共享接口的组件。

有时可以方便地获取实施某个接口的组件,或者找到这些组件相应的对象。

下述实例使用typeof而不是这些函数的通用版本。通用版本无法协同接口使用,但typeof却可以。下面的方法将其整洁地套入通用方法之中。



17  利用扩展法使语法更简洁。

例如:


18  使用另一种防御性GetComponent方法。

有时通过RequiredComponent强制组件关系可能难以操作,但是这总是可能和可取的,特别是当你调用其它类上的GetComponent。作为一种替代方法,但需要某个组件打印找到的错误信息时,可以使用下述GameObject扩展。



19  避免对相同的事项使用不同的常用语。

在许多情况下,有多种常用法。此时,对整个项目选择一种常用法。其原因在于:

1)某些常用语不能一起工作。在某个方向中使用一种常用语强行设 计可能不适合另一种常用语。

2)对于整个项目使用相同的常用语能够使团队成员更容易理解进展。它使结构和代码更容易理解。这样就更难犯错。


常用语组示例:

协程与状态机。

 嵌套的Prefab,互相链接的Prefab和超级Prefab

数据分离策略。

对2D游戏中状态使用sprites的方法。

Prefab结构。

派生策略。

定位对象的方法:按类型,按名称,按标签,按图层和按引用关系(“链接”)。

分组对象的方法:按类型,按名称,按标签,按图层和按引用数组(“链接”)。

调用其他组件方法的途径。

查找对象组和自注册。

控制执行次序(使用Unity的执行次序设置,还是使用yield逻辑,利用Awake / StartUpdate / Late Update依赖,还是使用纯手动的方法,或者采用次序无关的架构)。

在游戏中使用鼠标选择对象/位置/目标:SelectionManager或者对象自主管理。

在场景变换时保存数据:通过,或者是在新场景加载时未毁损的对象。

组合(混合、添加和分层)动画的方法。

输入处理(中央和本地)


20  维护一个自有的Time类,这可以更容易实现游戏暂停。

包装一个“Time.DeltaTime”和“Time.TimeSinceLevelLoad”来实现暂停和游戏速度的缩放。它使用时有点麻烦,但是当对象运行在不同的时钟速率下就容易多了(例如界面动画和游戏动画)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值