简介:这套UE4生存类游戏工程源码采用纯蓝图方式实现,完整覆盖资源收集、制造合成、交互玩法和存档机制,适合从零接触蓝图或希望拓展生存制造类项目经验的开发者。资源共422个文件,以359个uasset蓝图与资产文件为主体,另有29个ini配置、3个umap地图、1个uproject工程文件,以及bin缓存、json、png、sav存档和配套docx说明文档,压缩包体积约451.66MB。工程目录结构规范,Content集中存放模型、材质、音频等美术资源,蓝图系统按功能模块组织,清晰展示物品交互、配方列表和资源管理逻辑;Saved目录保存运行时数据,Config目录提供游戏参数调整入口,Intermediate和DerivedDataCache则包含引擎预处理内容,有助于理解UE4项目运行与构建流程。目前已有2249人加入学习。通过研读这份源码,既能掌握纯蓝图项目的组织方式和可视化编程技巧,也能学习生存制造类游戏的核心设计模式,包括资源循环、制造配方、UI交互和持久化存储,为进一步开发完整游戏提供了真实可参考的工程样例。
1. UE4生存类游戏工程源码:为什么值得从拆开做起
生存游戏最卡进度的不是某个单点功能,而是系统之间的咬合。玩家捡到一颗浆果,要同时改重量、加饥饿回复、刷新快捷栏;砍倒一棵树,要产出木材、掉落概率、发出声音让埋伏的AI听到;夜晚一到,AI索敌半径变大,光照却要缩到篝火范围。没有一条验证过的数据流,独立开发者用蓝图堆三个月,也堆不出一个能完整跑一圈的循环。UE4生存类游戏工程源码的价值,在这里就很直接:它给你一套已经咬合住的数据流骨架,状态组件、背包组件、AI控制器、建造网格都按生存玩法设计好,换皮就能玩。
这套源码能解决的事,是把“从零到一能玩的生存Demo”从三到六个月压缩到一两周。你会看到典型的生存基础设施:四维角色状态(生命、饥饿、口渴、体力)、DataTable驱动的物品与合成、行为树AI、网格建造、昼夜循环,以及SaveGame存档接口。适合一个人也能扛的独立开发者,适合三五人小团队快速验证玩法,也适合想研究“工程结构”而不是反复调UI的引擎学习者。新手能顺藤摸到每个逻辑所在的位置,熟手能直接在关键类里做替换。
但拿来就用的源码不等于零成本。版本不对打不开、构建光照后发黑、外接设备映射失效、FToolBarBuilder导致编辑器崩溃、角色骨骼矩阵数据错乱,这些坑都是真实存在的。下文按“拆结构 → 跑通工程 → 调参数 → 避坑 → 做改造”的顺序展开,把能直接照抄的步骤和参数放在中间,最后给你一份经得起演示的发布收尾清单。
2. 读源码先读骨架:生存Demo的系统模块怎么拆
拿到工程源码第一件事不是双击打开,而是先看清楚它把自己的玩法循环拆成了哪几个模块。生存类源码和一般ARPG模板最大的区别在于:它通常没有把“血量”当作唯一核心,而是让四维状态、背包、AI、建造、昼夜循环互相咬合。下面这几个模块是生存源码里的标配,也是你要改动的真正入口。
2.1 角色状态组件:四维数值、衰减曲线与死亡判定
生存源码里,角色状态通常是一个挂在角色或PlayerState上的UStatusComponent,至少持有Health、Hunger、Thirst、Stamina四个float,有条件的还会加体温和中毒值。真正重要的不是这些数值本身,而是它们每分钟都在衰减,衰减速率又由一张曲线驱动。
// StatusComponent.h 核心骨架 UCLASS(ClassGroup=(Survival), meta=(BlueprintSpawnableComponent)) class SURVIVAL_API UStatusComponent : public UActorComponent { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="状态") float Health; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="状态") float Hunger; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="状态") float Thirst; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="状态") float Stamina; UPROPERTY(EditAnywhere, Category="衰减") UCurveFloat* HungerDecayCurve; };衰减在Tick里执行,核心写法如下:
void UStatusComponent::TickDecay(float DeltaSeconds) { float CurrentHour = GetWorld()->GetTimeSeconds() / 60.f; float Decay = HungerDecayCurve ? HungerDecayCurve->GetFloatValue(CurrentHour) : 0.01f; Hunger = FMath::Max(0.f, Hunger - Decay * DeltaSeconds); if (Hunger <= 0.f) { Health -= HungerDamagePerSecond * DeltaSeconds; } if (Health <= 0.f) { OnDeath.Broadcast(); } }逻辑说明:衰减值不是写死的常量,而是从曲线上按当前游戏内小时读出来的。这样同一套代码可以在白天给低衰减,在深夜给高衰减,不用反复改逻辑。FMath::Max保证饥饿值不会跌到负数引发数值溢出。
参数说明里最值得你动手的是HungerDecayCurve。常见初始值是0.02每秒,肚饿大概八十多分钟从满掉到零,这个速度适合Demo演示;如果你做的是硬核生存,把夜间那一段调到0.05到0.08。调节曲线时不需要重新编译,在编辑器里刷新曲线资产即可。死亡判定放组件里广播,UI只负责监听,不要在蓝图里到处给状态值赋值。
2.2 物品与背包:DataTable驱动的物品字典与堆叠规则
生存源码的背包,绝大多数都用DataTable当物品字典。物品Id、名称、重量、堆叠上限、类型、图标、使用效果全部写进一张表。新增物品时只加一行,UI和逻辑自动认识它。
// DT_Items.csv,在UE编辑器里导入为DataTable Id,Name,Weight,StackMax,Category,IconPath,UseEffect wood,木头,1,99,Resource,/Game/UI/Icon/wood,None stone,石头,1.2,99,Resource,/Game/UI/Icon/stone,None axe,石斧,2.5,1,Tool,/Game/UI/Icon/axe,None berry,浆果,0.2,20,Food,/Game/UI/Icon/berry,Hunger+5 bandage,绷带,0.4,10,Medical,/Game/UI/Icon/bandage,Health+15这种设计的价值在于:改物品数值只用改表,不动代码。背包组件的核心函数是AddItem,它要先查表拿到StackMax,再决定是堆进已有格子还是开新格子。
bool UInventoryComponent::AddItem(FName ItemId, int32 Count) { FItemRow* Row = ItemTable->FindRow<FItemRow>(ItemId, TEXT("")); if (!Row) return false; // 优先堆叠到已有槽位 for (int32 i = 0; i < Items.Num(); i++) { if (Items[i].ItemId == ItemId && Items[i].Count < Row->StackMax) { int32 Add = FMath::Min(Count, Row->StackMax - Items[i].Count); Items[i].Count += Add; Count -= Add; if (Count == 0) return true; } } // 再开新槽,背包满则返回 false,由调用方决定丢弃或提示 return false; }逻辑说明:这个函数先尝试把新增物品塞进同类物品旧格子,只有当旧格子满了或没有同类时才开新格子。返回false时,调用方要把剩余Count作为“捡不起来”的提示丢出来,而不是默默吞掉。
参数上,StackMax决定堆叠手感:木头99、浆果20、工具1,这是很常见的配方。如果你发现玩家永远缺格子,优先查是不是所有物品都塞进了StackMax,而不是急着扩容背包数组。背包容量建议独立定义成MaxSlots,避免和表里的StackMax混在一起。
2.3 AI生物与行为树:感知状态、游荡、追踪与攻击切换
生存源码里的AI,哪怕只是陪跑的兔子,也建议走“感知组件 + 黑板状态 + 行为树”的组合。最忌讳的是在Tick里用距离判断写一长串If。
常见做法分三步:第一步,AIController挂UAIPerceptionComponent,让AI“看见”玩家或“听见”声音;第二步,感知回调里把LastSeenLocation和E_AIState写进Blackboard;第三步,BehaviorTree的根节点用Selector,按状态分支成游荡、追踪、攻击三个Sequence。核心回调长这样:
void ASurvivalAIController::OnPerceptionUpdated(const TArray<AActor*>& UpdatedActors) { for (AActor* Actor : UpdatedActors) { if (!Actor || !Actor->IsA<ASurvivalCharacter>()) continue; if (GetPerceptionComponent()->HasSense(SightSense)) { Blackboard->SetValueAsObject(TEXT("TargetActor"), Actor); Blackboard->SetValueAsEnum(TEXT("AIState"), (uint8)EAIState::Track); } } }逻辑说明:这里不直接“立刻攻击”,而是先把状态置为Track。行为树里的追踪Sequence负责移动到LastSeenLocation,到达后再把状态改成Attack。这样AI从发现玩家到动手之间有一个天然缓冲,玩家有反应时间,AI也不至于瞬移贴脸。
参数上值得调的是感知半径和听觉半径。白天感知半径给1200左右,夜间可以乘1.3到1.5;听觉半径不要给太大,否则一次砍树让全图AI都冲过来,就会变成生存游戏里的“全场拉怪”。行为树每个节点之间要记得调Interval,比如游荡状态每隔3到5秒评估一次,而不是每帧评估,否则AI会走得非常神经质。
2.4 建造网格与昼夜循环:把“世界”也变成两个可调系统
生存源码里的建造系统虽然各个工程写法不一,核心动作都一样:从画面中心发射一条射线,把命中点吸附到网格,再检查能不能放得下。网格吸附的常见做法是FMath::GridSnap:
FVector SnapLocation = FVector( FMath::GridSnap(RayHitLocation.X, GridSize), FMath::GridSnap(RayHitLocation.Y, GridSize), FMath::GridSnap(RayHitLocation.Z, GridSize) );GridSize通常取50或100。50适合小体量木屋,100适合大型基地。建造源码里还会有一个“结构支撑”检查,常见设计是给每个建造物挂一个UStructureComponent,记录它依赖的上一个支撑物。只要地基被拆,所有挂在它上面的墙和屋顶都要顺序塌。你要改的往往不是倒塌动画,而是“支撑链的长度上限”,上限越大,玩家能造出的悬浮建筑越夸张。
昼夜循环相对独立,一般用一个Global Time变量驱动方向光旋转和温度表。常见参数是:一个完整昼夜20到30分钟;早晨6点光照强度从暗到亮;夜间开启篝火半径内的暖色调点光;温度表连接角色状态组件,温度过低时每秒扣少量生命。这里最有用的习惯是把“一天时长”和“夜晚起始小时”用两个公开变量暴露在蓝图里,最后调难度时只需拖这两个数值,不用碰C++。
3. 把工程跑起来:版本对齐、生成工程文件与第一局试玩
很多人拿到源码卡在第一步:双击.uproject没有反应,或者打开后一堆红色报错。多数情况不是源码坏了,而是引擎版本和依赖没对齐。
3.1 检查.uproject:引擎版本和插件依赖先对齐
用文本打开工程的.uproject文件,先看EngineAssociation字段。如果工程写的是4.26,而你本机装了4.27,双击会提示版本不匹配。看依赖的方式也一样,Plugins节点下会列出工程启用的插件,任何一个缺失或版本不对,都会在启动时弹红字。
# 查看工程绑定的引擎版本 cat Survival.uproject | grep EngineAssociation # 查看启用了哪些插件 cat Survival.uproject | grep -A 20 Plugins如果本机只有一个高版本引擎,可以在右键菜单里用“Switch Unreal Engine version”切换。注意切版本之后要重新编译,尤其是工程带C++模块时,二进制不匹配会出现一堆链接错误。
3.2 打开前初始化子模块
不少生存工程会把公共资产或插件做成子模块,同步到本地时文件夹是空的,不初始化就直接打开,报“找不到模块”。先执行子模块更新,能少走一段弯路。
git submodule update --init --recursive这段命令会把工程引用的第三方代码同步到本地。不是所有工程都需要,但如果你发现Plugins目录里有些文件夹是空的,先跑这一句准没错。
3.3 生成工程文件并编译
C++工程要先生成Visual Studio工程文件。在.uproject右键菜单里选“Generate Visual Studio project files”,然后用VS打开生成的.sln,选择Development Editor配置编译。命令行方式也支持:
"你的引擎目录/Engine/Build/BatchFiles/Build.bat" SurvivalEditor Win64 Development -Project="你的工程路径/Survival.uproject"参数说明:SurvivalEditor是目标名称,必须和源码模块名一致;Win64 Development是平台和配置;-Project指定工程路径。第一次编译耗时可能很长,如果中途报错,优先看是不是VS版本和引擎要求不匹配。UE4常见的组合是VS2019配4.25到4.27,换到VS2022时有少数老模块需要改编译配置。
3.4 第一局试玩要看的三个位置
进地图之后,先不要急着跑,打开Output Log看三类信息:第一,有没有“Failed to load”开头的模块或资产日志;第二,角色生成时状态组件有没有打印初始化数值;第三,AI控制器有没有报BehaviorTree空引用。如果你是主程,可以顺手在角色BeginPlay加一句屏幕提示:
if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::Printf(TEXT("生存模式已启动,当前时间:%d时"), Hour)); }这条屏幕输出能让你在试玩时一眼确认GameMode、Pawn和AI都进入了正确状态。试玩标准建议定成“20分钟不死”:收集五种资源、合出一把工具、度过一个夜晚,能跑完这个闭环,说明工程源码本身没问题,后面改参数至少有个可靠底座。
4. 参数调优:数值平衡、AI行为和外接设备映射的改法
源码能跑起来之后,第一步要动的不是代码,而是数据和曲线。这个阶段最大的坑是“改了一个参数,看不出效果”,所以下面按参数类别给出一份可直接对照的调整路径。
4.1 生存数值平衡:先用表格定死基线,再放曲线
做生存数值,最怕今天觉得饥饿太快,明天又觉得太慢,来回横跳。建议先把基线值定下来,再让曲线只在“夜晚变快”这一件事上起作用。
| 参数 | 初始值 | 建议区间 | 说明 |
|---|---|---|---|
| Health | 100 | 80~150 | 越低战斗压力越大 |
| Hunger衰减 | 0.02/s | 0.01~0.04 | 白天0.02,夜晚按曲线加 |
| Thirst衰减 | 0.03/s | 0.02~0.05 | 口渴可比饥饿更致命 |
| Stamina恢复 | 0.06/s | 0.04~0.08 | 会影响攀爬和跑步手感 |
调节时优先改曲线资产,不要改代码里的默认值。曲线的好处是你可以放心在试玩中实时调,改完保存立刻生效,不需要重新编译,也不会丢原始基线。常见的难度节奏是:前5分钟所有衰减都很慢,让玩家有时间找水和食物;第10分钟后开始加速;夜晚达到峰值。这套节奏能让新手在Demo演示里活过20分钟。
4.2 AI行为参数:白天和夜晚各给一套状态阈值
AI参数不建议全局只有一个值,而是拆成“白天参数表”和“夜间参数表”。最简单的做法是在AI Blackboard里存一个IsNight布尔值,行为树的感知半径和攻击间隔引用这个变量。
典型参数组合:白天时感知半径1200,攻击间隔2.5秒,玩家跑出1600范围AI就放弃追踪;夜间感知半径涨到1800,攻击间隔缩到1.5秒,放弃范围扩到2200。这样玩家会明确感受到夜晚更危险,而不是AI数值变高但手感没变化。改这部分时我习惯在行为树每个分支的Task里加一个UE_LOG,输出当前状态切换点,比如“Track→Attack”。看不到状态切换就乱调数值,只会越改越玄学。
4.3 外接设备映射:键盘快捷栏、手柄摇杆和自定义外设的绑定方法
“外接设备映射”在UE4里是个集中地翻车的区域。最常见的是手柄插着,游戏界面却只显示键盘按键;或者方向盘、脚踏一类的自定义外设带入后,轴映射完全没反应。根源在于UE4有两套输入体系:旧式Action/Axis Mappings和新版Enhanced Input,工程源码经常两套共存。
旧式输入系统的配置在Config/DefaultInput.ini里,轴映射长这样:
[/Script/Engine.InputSettings] +ActionMappings=(ActionName="UseItem", bShift=False, bCtrl=False, bAlt=False, Key=F) +ActionMappings=(ActionName="CycleSlot", bShift=False, bCtrl=False, bAlt=False, Key=One) +AxisMappings=(AxisName="MoveForward", Scale=1.000000, Key=W) +AxisMappings=(AxisName="MoveForward", Scale=-1.000000, Key=S) +AxisMappings=(AxisName="MoveForward", Scale=1.000000, Key=Gamepad_LeftY)参数说明:Gamepad_LeftY就是手柄左摇杆的垂直轴。很多外设映射失效,是这份ini里只有键盘映射,没有Gamepad条目。先把Gamepad相关轴补齐,再谈优先级。
新版Enhanced Input的做法则是在IMC_Default里给每个Action添加键位,Name冲突时以最后AddMappingContext的优先级为准。如果你外接设备映射总是失灵,先打开项目设置的输入面板,看设备事件有没有被识别到。如果设备本身没有事件进来,那就是系统层没识别,再怎么改映射都没用。这一步很基础,但确实是我见过的最常见翻车点。
4.4 让改动快速生效:热重载与曲线刷新
改C++参数时,用Live Coding热编译;改DataTable和曲线时,保存后直接在运行中的编辑器里看效果。不要把数值写死在蓝图变量里,否则每次都要进蓝图界面去翻。
关于“生效后先测一次基线”的做法:我一般会在DefaultEngine.ini里把初始时间调成白天8点,先把白天难度验证完,再切到夜晚时间验证AI。理由很简单:白天变量少,崩溃时排错容易;夜间变量多,出了问题往往分不清是光照锅还是AI锅。
5. UE4生存工程实战避坑:光照发黑、外接设备映射与工具链崩溃的常见问题排查
这一段全部来自真实踩坑,每条都按“现象 → 原因 → 解决”写出。遇到同款症状时,可以直接照着处理,不必把源码从头翻到尾。
5.1 构建光照后场景发黑
现象:执行Build Lighting之后,原本正常的场景整体变暗甚至全黑,尤其室内更明显。重新启动编辑器会好一些,但再次构建又黑。
原因:最常见的是Lightmass设置不合适,或者场景里没有放Lightmass Importance Volume。如果这个体积没覆盖玩家活动区域,Lightmass就不会在那些地方计算高质量光照,结果就是大面积自阴影和黑色暗部。
解决:在场景边界拉一个覆盖游戏区域的Lightmass Importance Volume;把World Settings里的Indirect Lighting Quality调到1.5或2.0;确认Sky Light没有被意外设为0强度。还有一类“构建后发黑”是后处理体积里的Exposure设置过低,这种一眼能看出来,先把自动曝光恢复再继续查光照。
5.2 外接设备映射时好时坏,重启后失效
现象:手柄进入游戏后摇杆能用,但按键时好时坏;或者踩脚踏板时偶尔触发、偶尔不触发。重新插拔设备后短暂恢复,过一会又失灵。
原因:工程里新旧两套输入系统在抢同一个Action。旧式Action Mappings和Enhanced Input Mapping Context同时绑定到UseItem,当一个键同时被两套系统监听,UE的输入分发顺序会导致冲突,外接设备尤其明显。
解决:只保留一套输入体系。如果工程源码默认走Enhanced Input,就把DefaultInput.ini里的旧式Action Mappings清掉;如果走旧式,就把Enhanced Input的Mapping Context移除。判断方式是在设备事件面板里看按键触发次数,同一个按键一次触发会输出两条日志,基本就是冲突了。
5.3 FToolBarBuilder导致编辑器启动或打开关卡时崩溃
现象:工程带自定义编辑器工具模块,平时编译没问题,但启动编辑器后一旦打开关卡或进入某个面板,就弹出崩溃日志,堆栈里有FToolBarBuilder相关帧。
原因:FToolBarBuilder注册得太早。很多模块在StartupModule()里直接注册工具栏,这时引擎的UI框架还没准备好,CommandList为空或者委托对象尚未构造,等UI刷新时就崩了。
解决:把工具栏注册推迟到FCoreDelegates::OnPostEngineInit再执行,并在该模块的ShutdownModule()里做反注册。
void FSurvivalEditorModule::StartupModule() { // 不要在StartupModule里直接创建FToolBarBuilder FCoreDelegates::OnPostEngineInit.AddRaw(this, &FSurvivalEditorModule::RegisterToolbar); } void FSurvivalEditorModule::RegisterToolbar() { if (!CommandList.IsValid()) { return; // 防御:CommandList还没准备好就直接跳过 } FToolBarBuilder Builder(CommandList, FMultiBoxCustomization::None); Builder.AddToolBarButton(MyCommand); }逻辑说明:OnPostEngineInit保证引擎和编辑器UI初始化完成后才摆按钮,CommandList.IsValid()兜底,避免空指针。这是编辑器扩展里极其典型的崩溃来源,和生存源码本身没有直接关系,但几乎所有带工具链的工程源码都会中招。
5.4 角色骨骼错位、蒙皮拉伸:用矩阵特征码快速定位
现象:角色播放攻击动画时,某块骨骼出现非正常缩放,模型像被压扁或拉长,换一个骨骼资产又恢复,但过一段时间再次出现。
原因:动画蓝图中Transform叠加错误,在某个节点把世界空间和组件空间矩阵混用,导致骨架上出现非刚体变换。UE4动画系统依赖“平移+旋转+均匀缩放”的组合,一旦矩阵被赋予非等比缩放,蒙皮就会撕裂。
解决:在C++里取骨骼矩阵,用行列式和迹的组合做矩阵特征码校验。刚体变换矩阵的行列式应接近1,迹能反映矩阵整体的相似性。打印出来,能快速定位是哪一帧哪块骨骼开始畸变。
FMatrix BoneMatrix = Mesh->GetBoneMatrix(BoneIndex); float Determinant = BoneMatrix.Determinant(); if (!FMath::IsNearlyEqual(Determinant, 1.f, 0.01f)) { UE_LOG(LogTemp, Warning, TEXT("骨骼变换失真: %s, det=%f"), *BoneName, Determinant); }逻辑说明:行列式偏离1说明矩阵里混入了缩放。用这个日志再配合动画调试功能找到坏帧,比用眼睛盯着模型猜测来得快得多。这是我处理角色诡异形变时最顺手的一招,浪费不了多少时间就锁定到动画蓝图的某个Multiply节点。
5.5 存档读档后物品消失或状态重置
现象:游戏内保存正常,退出重进后背包空了一半,或者角色回到出生点而建造物还在。
原因:存档结构里存的是物品槽位索引,而不是物品Id。当DataTable里的行顺序变化后,索引就想当然指错了物品;还有一类原因是保存了UObject引用,重启后引用失效,静默读成Null。
解决:存档里必须同时存物品的FName Id和数量,不要存行号;每次读取前校验Id对应的行是否存在。给存档增加一个版本号字段,之后数据结构变更时可以走“旧档迁移”逻辑而不是直接崩溃。读档完成后加一个“数量校正”步骤:超过StackMax的堆叠自动拆分,空槽自动顺移。
6. 从源码到可发布Demo:存档改造、多人化边界与性能收尾
源码能玩、参数能调、坑能避,接下来是把Demo推到“能给别人演示、能跑完整流程”的状态。我的习惯是做三件事:存档可靠性、多人化边界判断、性能收尾。
存档改造我优先做三件事:加版本号、物品Id映射表、存档数据校验。版本号解决以后改表结构导致旧档全废的问题;Id映射表解决Item改名后老存档读空的问题;校验步骤在读取后执行一遍“无效物品自动删除、空槽自动压缩”。这套东西不复杂,但能救你在演示前半小时一记“存档崩了”的致命打击。
多人化改造要拎清边界。生存游戏联机,不是单纯同步角色位置,而是确定“状态归属权”。物品生成、资源刷新、AI行为习惯由服务端控制;客户端只发送操作意图,比如“我按了采集键”。如果工程源码里到处都是客户端直接SpawnActor,那多人化的改造量会非常大。小团队做Demo阶段,建议先不做完整多人,只保留存档和本地多人分屏。这样既能演示玩法,又不会被网络同步拖死。
性能收尾按三张表推进:第一,所有静态物件的LOD距离和剔除距离;第二,草、碎石、树叶这类高频实例的密度与绘制距离;第三,光照构建后的整体帧耗时。用Stat GPU看渲染耗时,用Stat FPS看原始帧率,确保在过夜场景时帧率不掉成幻灯片。
我个人的教训是:越是接近发布,越要锁住关键数据结构。最后一周只允许调曲线、改DataTable、微调AI参数,不轻易动背包存档和物品Id字段。生存源码改起来很爽,但爽过头就是演示前夜的无底洞。希望帮到你。
本文还有配套的精品资源,点击获取