UE-C++进阶之路 | Epic 大钊 视频学习记录(侵删)

原视频文字全记录,侵删
如有错处,参见 原视频链接

一. 虚幻社区经理-大钊介绍

  • 知乎:虚幻引擎
  • B站:虚幻引擎官方
  • QQ群:1018846968,微信群:unrealengine
  • QQ:2722937652,微信:jack1046
  • Unreal Circle
  • MegaGrants
  • 技术布道
  • 传声筒
  • 10+ C++经验

二. 学习引擎的一般路线

新手村-核心骨干-江湖高手-坐镇大神-行业大佬

1. 新手村-学习引擎

  • 学习引擎的编辑器使用
  • 学习蓝图,简单的交互
  • 学习引擎的各种功能模板:动画,AI,材质
    UMG,网络,粒子等等
  • 学会下载配置使用插件
    能力标志:可以做一个小独立游戏(有UI有逻辑有动画…)

2. 核心骨干-掌握引擎

  • 游戏的一些模板可以用C++实现,例如函数库实现计算逻辑
    但是存在一些问题
    (1) 懂Actor,不懂Object
    (2) 懂Object,不懂Asset
  • 特定模块可以用C++编写,战斗逻辑,AI…
  • 开始思考C++和蓝图如何更好结合的程序框架
  • 开始收集整理一些常用的C++函数库(引擎)
  • 开始学会创造自己的插件,在不同的项目使用
  • 开始明确自己专攻的方向(只讨论程序方向)引擎每个模块都有深度
    能力标志:C++和蓝图那个方便使用那个,使用起来适得其所

3. 江湖高手-懂得引擎

  • 开始研究并理解引擎里模块的机制原理
  • 开始在C++层开发支撑类和功能,引擎扩展Slate,改造渲染…
  • 对引擎不合心意的地方,可以着手修改Hack,修改引擎bug
    能力标志:对引擎的模块,都有信息可以找到源码并分析和扩展遗漏的功能,缺的只是时间!

4. 坐镇大神-超越引擎

  • 可以重写某个引擎模块,做的比引擎的作者还好
  • 经常负责优化引擎来适配自己的项目
  • 改造引擎的一部分,改造开发配套流程,有机高效融入进自己的工作流中
    能力标志:引擎于他只是自己项目框架工作流中的一个工作

5. 行业大佬-创造引擎

  • 对引擎的整体结果了如指掌
  • 可以站在整个引擎行业的高度创造更高生产力工具
  • 编写创造的引擎功能或插件,对行业造成了深远的影响
    能力标志:做事情,先从行业的高度来思考

三. 虚幻C++的进阶之路

5W+1H
What?Why?Who?When?Which?How?

1. What?-什么是虚幻C++?

  • 扎实的标准C++基础能力
  • 采用C++编写游戏逻辑
  • 适应虚幻引擎的底层C++框架
    • Module配置和插件使用
    • UBT、UHT
    • 掌握Core模块的C++库:FString,TArray,UE_LOG…
    • 熟悉了解掌握CoreUObject:宏,GC,序列化
    • 熟悉C++和蓝图交互的方式

2. What-学会虚幻C++的标志是什么?

  • 懂得解决各种编译链接错误,常是因为Module配置出错
  • 懂得一些常见的编写套路,CreateDefaultSubObject,UPROPERTY…
  • 理解UObject的内存管理机制,不会经常造成内存崩溃
  • 可以在源码里找到自己想要的代码块

3. Why?-为什么虚幻要开放C++来编写GamePlay?(为什么用C++写UE)

  • 性能!性能!性能!
  • 更高端,更需要压榨机能(更高的平台)
  • 更底层,更有能力优化性能(接触底层)
  • 更大项目,强类型更有能力管理项目(大项目)
    (无类型语言一时爽,重构火葬场)
  • 更开发,更有灵活的定制性
  • 更易迭代,不需要时时更新接口层
  • 更相信开发者,进入了C++的自由领域,就得有自我负责的觉悟,(自己写的Bug自己Fix,上下限取决于自己)

当然还有友好的蓝图

4. Why-为什么要学习虚幻C++,可以不学吗?

  • 方便扩展功能,直达引擎的底层结构
  • 更好的性能,性能热点的优化
  • 方便调试引擎,理解引擎流程和修改Bug
  • 学习游戏引擎知识的好途径(不然做到东西的上限取决于别人写的API的上限)
  • 很多时候,只有理解了Why,才能更好的解决How的问题
  • 大项目必须有某种Hold得住的语言(不止蓝图)
  • 虚幻程序员:从初级到中级的标志,其他的领域其实可以不学

5. Why-只有蓝图可以吗?

  • 平衡蓝图和C++(官方文档有介绍)
  • 没法实现定制化的需求
  • 游戏逻辑只能达到有限的优化
  • 不懂C++,失去了理解别人插件最有力的工具
  • 项目大到一定项目就很难管理(文本编程有固有优势)
  • 从使用工具到创造工具

6. Who-团队里有什么人需要学虚幻C++?

  • 核心系统GamePlay程序员
  • 实现功能的时候有可能需要扩展引擎的人,TA(技术美术),渲染…
  • 引擎工具编写人员
  • 引擎部的支持人员
    (源码开放很幸福)

7. When-什么时候推荐开始学虚幻C++?

  • 先把蓝图学明白用明白了
    (否则就像游戏开启了困难模式)
  • 熟悉引擎GamePlay框架概念之后
  • 也学会了引擎的各模块功能,动画蓝图,AI,UMG…
  • 总而言之,先在自己工作领域把引擎用明白了(不懂就是不懂,实事求是,不要带着模模糊糊的概念)

8. When-什么时候算学会了蓝图?

  • 蓝图太易用,从而导致很多人轻视
  • 蓝图五脏俱全,类,结构,枚举,接口,回调
  • 蓝图也是讲究设计模式和结构的
  • 蓝图程序也要易于重构(职责划分清楚)
  • 蓝图也可以数据驱动,灵活掌握引擎提供的各种赋值工具,有机结合起来
  • 可以做到,不代表一定要时时这么做,特别是做原型的时候

9. Which-虚幻C++有哪些学习要点?

  • Core (源码库)
  • GamePlay (游戏模式之类)
  • Module-UBT-C#
  • 反射-UHT
  • CoreUObject-GC
  • 套路
  • 各模块的“地方习俗”

1. Core

  • 首先C++基础真的要去巩固,很多人栽在这上面(真的重要哈)
  • UE4使用C++11,所以需要学习到能看懂:
    • TArray,TSet,TMap等各种容器
    • FString,FName, FText的操作互转
    • TSharedPtr,TSharedRef,TWeakPtr等各种智能指针
    • 有能力理解Delegate,TAttribute,TSubClassOf的机制和用法
    • 多看看设计模式,这也才能更好理解UE4代码的结构
    • 多线程的知识,这样才能用好FRunnable等多线程同步
    • 人脑展开宏???

这部分学习同步进行,但如果只是使用的话,C++98其实就可以跑,
哪里去学?在源码的Core模块

2. GamePlay

  • GamePlay的C++编写
    • Actor的创建,组装 Component,BeginPlay,Tick,碰撞输入事件绑定
    • 引擎GamePlay对象的继承组织使用
    • UObject自定义对象的组织管理(根据逻辑而定)
    • 引擎数据对象的使用,Config,DataTable
    • 功能模块的C++层级编写(UMG,AI,动画)注意有机结合(别偏向一边)

在代码库Engine/Class/GameFramework

3. Module-UBT-C#

1. 思考:为什么用C#来管理编译流程?合适的头文件包含和库链接
  • 主要问题
    • 一个.cpp文件就是一个编译单元
    • include就是复制粘贴
    • include哪些文件?
    • 链接那些dll?
    • UE4支持众多平台,包括Windows,IOS,Android等,因此UE4为了方便你配置各个平台的参数和编译选项,简化编译流程,UE4实现了自己的一套编译系统,否则我们就得接受各个平台再单独配置一套项目之苦了。
    • UnrealBuildTool(UBT,C#):UE4的自定义工具,来编译UE4的逐个模块并处理依赖等。我们编写的Target.cs,Build.cs都是为这个工具服务的。
    • UnrealHeaderTool(UHT,C++):UE4的C++代码解析生成工具,我们在代码里写的那些宏UCLASS等和#include “*.generated.h”都为UHT提供了信息来生成相应的C++反射代码
    • 一般来说,UBT会先调用UHT,解析一遍C++代码,生成相应其他代码。然后看是调用平台特定的编译工具(VisualStudio,LLVM)来编译各个模块。最后启动Editor或者是Game。
  • 优缺点
    • 优点:C#足够容易读,C#足够灵活制定逻辑
      ,C#可以动态编译,方便收集信息,C#足够强大可以调用其他工具
    • 缺点:C#和C++混合经常搞得有些人糊涂,
      C++项目里混进C#没有智能提示不够友好
    • 解决:常见的错误都是这两者报出的,记住常用用法就行了,有问题再查

悄悄说,UBT用的是NMake Build System

2. 学习要点
  • 学会链接模块,项目和插件可包含多个模块
  • UBT调用UHT生成代码,然后调用MSBuild编译代码
  • Build.cs是重点:(ModuleRules.cs)
  • Build发生的三件事情
    • UBT收集项目的所有c++文件和c#文件
    • UBT调用UHT,UHT根据宏生成反射C++代码
    • 调用MSBuild将收集的文件和反射C++代码一起编译
3. ModuleRules.cs-模块链接
  • PublicDependencyModuleNames:

    • public链接的模块名称,最常用
    • 在自己的public和private里包含对方的public
  • PrivateDependencyModuleName:

    • private链接的模块名称,只引用不暴露
    • 在private里包含对方的public,不扩充自己的public
  • DynamicallyLoadedModuleName:

    • 动态链接的模块名称,在runtime被ModuleManager加载,保证先编译
4. ModuleRules.cs-头文件include
  • PublicIncludePaths
    • public包含的路径
    • 定义自己向外暴露的public,默认“Public”和“Classes”
  • PrivateIncludePaths
    • private包含的路径
    • 定义自己的private,给自己内部引用,默认“Private”,一般用来定义Private子目录。当然也可以路径包含Private/Sub,但这是一种方便形式。
5. ModuleRules.cs-头文件include模块
  • PublicIncludePathModuleNames:
    • public包含的模块名称,可以只写模块名称
  • PrivateIncludePathModuleName:
    • private包含的模块名称,可以只写模块名称
  • 用途
    • 只包含对方模块的.h文件,比如模块,虽然挺少见
    • 更多是动态链接,先包含头文件信息,之后加载
5. ModuleRules.cs-第三方库链接
  • PublicAdditionalLibraries
    引用的第三方lib路径
  • PublicSystemLibraryPaths
    引用的系统lib路径,其实也只是lib,只不过对于一些更"系统"底层的库用这个名字更友好一些
  • PublicDelayLoadDlls
    延迟加载的dll
6. ModuleRules.cs-其他常用
  • PublicDefinitions+PrivateDefinitions
    额外的其他C++宏定义
  • Target:
    • 得到当前的编译目标信息
    • Platform(平台):Win64,Android,IOS
    • Configuration(配置):Debug,Development,Shipping

很多人分不清public和private包含(如果只在cpp用,就不必暴露出来,用private)

4. 反射-UHT和CoreObject…

  • UHT
    • 理解.generated.h和.generated.cpp
    • 理解MODULENAME_API的含义,常见的犯错地方
    • 掌握使用这些宏的含义和用法(反射的重要标记)
      – UCLASS()
      – USTRUCT()
      – UENUM()
      – UPROPERTY()
      – UFUNCTION()
      – UINTERFACE()
      – GENERATED_BODY()
    • 清晰理解类型和对象之间的关系
      (ClassReference和ObjectReference)
    • 理解UClass*,UScriptStruct*,UField*,UFunction*,UProperty*
    • 掌握通过反射遍历对象的属性,读入写入(常见)
    • 掌握通过反射遍历对象的函数,并调用的方式(少一点)
    • 通过对象找类型,通过类型找对象
    • 理解"对象用类型描述,类型也是对象"
  • CoreObject
    • GC:理解对象之间的关系,标记清扫,有一些对象是Root!
      • 在用法感觉上可简单和C#类比
      • 注意只有用UPROPERTY标记的才参加GC! 因为要根据UProperty*来分析引用链!
      • 注意FMyStruct和UObject的混用,FGCObject::AddReferencedObjects
      • 自从用了UE4,我再也不new/delete了! (因为UE4重载了运算符)
    • CDO:理解类型和对象实例化,模板
      • 理解ClassDefaults作为模板的作用
      • 理解CDO在序列化中的意义作用
      • 通过UClass::GetDefaultObject来获得CDO信息
    • Package:理解对象的相互组织方式
      • 对象可以包含子对象
      • 序列化时,把一系列对象用一个对象包起来,这个对象叫做包
      • Package也可以相互引用,根据对象相对路径…

5. 套路

1. UHT的套路
  • 宏!
2. 模块链接套路
  • 几个常见属性.AddRange!
3. Actor创建的套路
  • ConstructorHelpers
  • CreateDefaultSubobject
  • SetupAttachment
4. GamePlay继承的套路
  • 尽量别再关卡蓝图里写逻辑
  • 想要结构良好,尽量遵循引擎结构,各司其职
  • GI,GM,GS,PC,Pawn,PS,继承,全都得会
  • 遵循推荐结构,会发现后期扩展和支持联机有天然优势
5. C++和蓝图交互的套路:
  • UPROPERTY,UFUNCTION
  • C++定义基类写逻辑,蓝图继承配置可视化是种推荐易扩展高性能的方式
  • 函数库是个好东西
6. 事件绑定的套路:
  • DELEGATE,MULTICAST_DELEGATE,EVENT,DYNAMIC
  • Input:BindAxis,BindAction
  • Collision:Hit,Overlap,AddDynamic
  • Slate&UMG Event:SLATE_EVENT(FOnClicked,OnClicked)
  • FTimerManager
7. 引擎常用方法的套路
  • Engine/Class/Kismet有好多库
  • GameplayStatics很常用,可以访问GamePlay的很多对象
  • UKismetSystemLibrary,系统目录等功能,LineTree
  • UKismetMathLibrary,数学

10. Which-虚幻C++有哪些学习难点?

  • C++基础太弱!工具越便利,人们接触底层的机会越少
  • 数据结构算法,操作系统多线程
  • 数学太渣:线性代数,向量矩阵,牛顿力学…
  • 游戏开发知识不够:渲染,动画,AI…
  • 项目开发经验不足:不懂得如何设计一个"足够好"得项目代码框架
  • 知识要点太多记不住:宏太多,API记不住
  • 翻找"借鉴"代码的能力不足:软件工程软技能不够

但是记住:比你聪明的人往往还比你勤奋…比你勤奋的人比你聪明

11. How-那应该如何学习虚幻C++?

1. 学习从模仿开始!形成肌肉记忆,也知道了去哪里抄!

  • 模仿模板项目! 比如C++标准第一人称项目
  • 模仿引擎源码,最权威也最借鉴,缺点是你首先要找得到,然后看得懂
  • 模仿社区项目,例如ARPG,或者搞到的其他人项目或者插件,优点是接地气,缺点是水平参差不齐,一般写的比较杂乱,不要被带到沟里,因此建议多讨论交流
  • 多看一些教程文章和视频,知乎,B站,YouYube,能吸收很多知识营养,包括本期直播!

2. 勇敢尝试动手改改

  • 着手微调一些地方观察变化
  • 遵循套路增加一些功能看能否做到

3. 总结规律

  • 总结你发现观察到的概念,规律,规范,注意事项
  • 用工具记录下来,鼓励社区分享贡献 !
  • 然后你发现有人开始叫你大佬 !

4. 善于积累

  • 可以弄个测试项目,用一个最小的场景测试你项理解的功能,这是你的车间
  • 有用的代码功能块,可以用插件函数库封装起来,在项目之间复用,形成你的代码资产
  • 对于概念,可以用思维导图来分析理解,理清关系和区别
  • 尝试自己画流程图,能用图表示出来才说明自己理解透彻

5. 玩具项目实践

  • 大大有助于自己思考项目架构,因为人在做自己想做的项目时都超兴奋的!
  • 用项目级的实践来考验自己的掌握能力,只有把手弄脏才能明白很多学习阶段没发现的问题
  • 万一成了呢 ?

大钊: 这辈子我是没法精通UE4了…但是学一分有一分的喜悦 !
Moota: +1

6. 个人常用工具

  • VS+VA: 打开VS我就心情喜悦~(???)
  • 思维导图:整理概念和流程,记录分析进度
  • Processon:画流程图
  • 笔记应用:记录一些搜集到的笔记文章和注意事项
  • RSS订阅:订阅一些国外blog文章
  • 音乐:写代码就像在战斗!

7. 个人常逛资源

  • UE4官方YouTube:
    https://www.youtube.com/user/UnrealDevelopmentKit/videos
  • 知乎: https://www.zhihu/com/org/xu-huan-yin-qing-24,技术文章
  • B站: https://space.bilibili.com/138827797,技术视频

四. 虚幻C++推荐项目实践

(要嘛看不起锤子VS拿起锤子啥都想砸,指蓝图)

1. 应该有怎么样的思想觉悟?

  • 有个流派看不起蓝图,代码原教旨主义
  • 有个群体畏惧厌恶C++,比前浪还早到沙滩上
  • 真正厉害的人应该是:我全都要!它们都是我可以利用的工具之一,工具就自然有擅长和薄弱,扬长避短,用不好是我的问题,不是工具的锅.
  • 空谈用好没有用,真正的用好是建立在真正理解的基础上,所以得学习!
  • 归根到底是为了项目得高效,开发,迭代,维护
  • 所以有些东西你其实不用学,反正你也学不完…
  • 但未雨绸缪还是得多学,为了未来项目的高效
  • 所以选择方向很重要,项目资源有限,个人精力也有限
  • 如何在有限的精力高效的学习,需要有清晰的学习路径和方法
  • 所以我做了这期直播…

2. 合适的比例配方是什么?

  • 我见过100%蓝图的游戏项目和企业建筑项目
  • 我也见过100%C++的游戏项目
  • 他们有些是因为不会,有些是因为不屑,但他们都失去了一些东西…
  • 根据80/20原则,差不多20%核心用C++,80%表层蓝图
  • 但也根据项目类型和个人技能掌握情况而定
  • 大概规则:
    • 偏向引擎底层,偏向性能热点,偏向稳定的,采用C++
    • 偏向表现层,偏向经常操作的,偏向多变的,采用蓝图

3. 好用的套路

  • 用插件来在项目间共享代码,动手编码前多想想
  • 变量类型尽量的偏向抽象基类,比如UStaticMeshComponent和UPrimitiveComponent
  • Include尽量IWYU,include what you use,加快编译速度
  • 善用C++前置声明,减少include依赖,加快编译
  • 蓝图函数库是个很有用的宝库
  • C++代码里不要写数据配置!尝试数据驱动!C++ ->BP ->DataTable
  • 蓝图也是要做好设计模式功能划分的,才能更好的协作
  • 再说一次,项目一开始就C++定义基类+蓝图继承,后面肯定会发现这很有先见之明
  • 没有C++基类的蓝图类,也可以reparent基类,记得先备份!
  • 重构了C++基类名字,导致蓝图找不到父类?用CoreRedirects来修复
  • 如果可以不改引擎,尽量不改!就算编译版引擎也可以用过UObject来打洞Hack,和C++奇技淫巧来访问private成员
  • 如果想好要改,在改动上下包宏或注释标记,以后merge方便

五. 虚幻C++核心概念

  • 一切从GEngine开始:UEngine* GEgine;UGameEngine/UEditorEngine

    • 从GEngine可以开始获得Worlds,游戏播放和编辑的场景也都是个world
    • 获取游戏视口进行slate叠加或截图等
  • GEditor对做编辑器扩展很有用:UEditorEngine*GEditor;

    • 可以获得当前选择Actor等有用信息
    • 可以获得当前视口
    • 可以调用编辑器功能
    • 可以获得事件回调
  • 编辑器也是个游戏,也都是各种UObject组成的,所以只要你能获取到正确的对象,就能为所欲为了!

  • NewObject的背后,基本上所有的对象都存放在对象池里,意味着你能获取到任何对象

  • Actor里是可以runtime动态创添加Component的,想想Spline,想想编辑器本质只是在跑的一个Game.

六. 虚幻引擎源码剖析经验分享

1. 源码剖析方法论

1. 时空观

  • 时:注重时间上的先后,观察函数的调用流程,事件的触发时机
  • 空:注重数据的吞吐转换,观察信息的采集,数据的加工消化利用
  • 有时要专注考察某个方面,有时要同时拥有两种脑袋
  • 时空的交互,组成了有机的功能架构支撑,调度起资源,启动内循环,响应外部刺激

2. 信息论

  • 最小信息原则=依赖最少=越稳定
  • 信息本质也是能量,只有掌握了足够的信息,才有足够能量实现某些功能
  • 数据是信息,代码结构是信息,项目协作方式也是信息

3. 从开始到结束

  • 一开始有哪些东西?
  • 第一把点火的推力是什么?
  • 启动后自循环是那些推力?
  • 有哪些外部输入的刺激?
  • 向外部吐出了那些数据?向外部施加了那些力?
  • 最后剩下什么?

4. 保持谦逊但不迷信

  • 首先怀疑是自己太傻,最后才觉得人家写的不够好,97.34%最终会发现是自己的问题
  • 觉得源码写得不够好,自己必须能切实的在充分理解问题区间的基础上,指出错误并写出更好方案,否则没有资格批评!忌空谈!
  • 认识到引擎开发人员也是普通人,是人就会犯错,但大概率人家水平比自己高,小概率会疏忽
  • 认识到引擎是个历史悠久的大工程,里面充满了历史馈赠的遗产,包袱和迷雾,所以很多觉得不够好的原因是历史带来的,不应该过于苛责

5. 比起好奇心更应该保持耐心

  • 常常会困惑这是干嘛的?
  • 常常会理解不了为什么这么做?有什么特殊的用意?
  • 数据用来用去,代码调来调去,感觉一团乱麻!
  • 调用链条太长,自己的脑容量堆栈不足以存储…
  • 所需的背景知识有欠缺…
  • 无人可问,孤军奋战…
  • 直面磅礴的源码库,深感自己的渺小!
  • 对引擎更新又激动又害怕,对自己一天只有24小时这个事实深感无力!(Moota:实际是总的时间少了)

承认自己是个凡人,余生只够掌握一小块知识,和自己妥协,从此虚心向别人请教,对别人的分享心存感激,为自己也能尽一份力分享而欣喜~

2. 源码剖析工具

1. 工具越简单越好,专注核心内容,别浪费时间调整格式

  • 放弃除了VS之外的源码阅读工具,额外的都是负担,间接一层就多了一层信息同步消耗
  • 放弃UML图系列工具,只取其思想,放弃其形式,对于上百个类来说,人类无法理解UML,哪怕各角度的表达,更何况还得手调其格式排版,一般情况下每新加一个类都得调整一遍
  • 对于已经确定的内容,才需要画图来加深理解,别一边理解一边画图,90%的时间在调整图排版
  • 我个人最后选择了思维导图(Xmind),有不足,但足够简单可理解
  • 最后写文章,我用Markdown码字,Processon画流程图

2. 调试过程

  • 开Debug配置编译源码,debug everything!
  • 一边调试一边记录
  • 在引擎源码内加debug输出,根据打印数据信息确定数据内容和先后
  • 抓住主脉络,旁支情况记一下以后再收拾

3. "借鉴"的艺术

1. 寻找

找!顺藤摸瓜的找!

  • WidgetReflector定位代码块
  • VA查找所有引用或调用
  • VS字符串查找
  • Everything工具定位文件
  • 开Debug断点查事件调用
2. 功底

瞧的懂大概是咋回事

  • 见多识广
  • 连蒙带猜
  • 相信它一定在,只是自己找不着
3. 搬运
  • 能找到办法直接调用的就别拷贝代码
  • 能否找到调用的代码路径的功底在于:能否事先理解各模块对象的组织关系,事先修行有必要
  • 别在runtime下想法子调用Editor的代码,不合法也费劲
  • 找到目标代码块:输入和输出,小心内部的依赖
  • 自己嫁接过来的时候要想法子给予足够的数据
  • 如果是流程上插一脚,想法找到关键的"旋钮"
  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值