[{"data":1,"prerenderedAt":1443},["ShallowReactive",2],{"page-UE4-10":3,"page-count-UE4":1442},[4,272,775,895,1096],{"id":5,"title":6,"body":7,"date":247,"description":13,"extension":248,"meta":249,"navigation":252,"path":264,"seo":265,"stem":266,"tags":267,"__hash__":271},"blogs\u002F_legacy\u002F2017\u002F2017-05-03-ue4-physx-and-substepping.md","UE4中的Physx物理",{"type":8,"value":9,"toc":238},"minimark",[10,14,17,20,25,28,31,37,40,47,50,53,56,61,64,68,71,74,79,82,85,90,93,97,100,111,114,120,123,132,138,141,147,150,156,159,167,170,173,179,182,188,191,195,198,201,204,207,213,223,226,229],[11,12,13],"p",{},"为了保证物理引擎能够更精准的模拟，UE4有提供Physics Sub-Stepping功能，同时引擎也有提供相应的接口方便自行进行物理操作。",[11,15,16],{},"当前UE4版本4.16 P1。",[11,18,19],{},"UE4中对物理引擎的接口暴露还是相当多的，只是一般情况下不会使用到而已。",[21,22,24],"h2",{"id":23},"sub-stepping","Sub-Stepping",[11,26,27],{},"UE4的物理分步是所谓的半锁定步长形式的，无论如何分步的主要作用是提高物理模拟的精确度。针对的情况是，在不同的设备上，发布的程序可能会以不同的帧率运行。在有的设备上有可能会被进行奇怪的垂直同步设定，这些都有可能导致与帧率同步的物理模拟出现神奇的处理结果。",[11,29,30],{},"物理分步功能目前还不是特别完善，在代码中能看到注释：",[32,33,34],"blockquote",{},[11,35,36],{},"This feature is still experimental. Certain functionality might not work correctly",[11,38,39],{},"Sub-Stepping的设置在项目设置>Engine>Physics中。",[11,41,42],{},[43,44],"img",{"alt":45,"src":46},"image","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-2.png",[11,48,49],{},"在默认的情况下，不启用分布，作用于物理引擎的设定是Max Physics Delta Time。当前值是0.033333，也就是说物理引擎本身的运行不小于每秒30帧。",[11,51,52],{},"当启用了物理分步之后，引擎将会在每一帧中进行分步计算，当帧率下降到一帧时间大于最大分步数*最大分步时间的情况下，引擎将不再进行分步计算而是等待。无论如何，过小的物理分步将会导致更高的运算负荷，而且从某种程度上也并不是必要的。",[11,54,55],{},"引擎中有的与物理有关的代码中有提供bAllowSubstepping这样的选项来进行区分，当调用这些函数的来源是物理分步发出的事件时才需要将其置为FALSE。",[11,57,58],{},[43,59],{"alt":45,"src":60},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-3.png",[11,62,63],{},"物理分步还有三个扩展的属性，这些属性是分步时进行分步之间进行加权的平滑处理的。",[21,65,67],{"id":66},"async-scene","Async Scene",[11,69,70],{},"分步选项中还有一个Stepping Async的开关，这个选项是作用于Async Scene的，就是说是否对Async Scene进行分步计算。",[11,72,73],{},"但是Async Scene本身是需要另外开启的",[11,75,76],{},[43,77],{"alt":45,"src":78},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-4.png",[11,80,81],{},"在Physx中，有区分Synchronous与Asynchronous两种模式，而UE4中对其都有支持。Synchronous scene是通常的物理模拟发生的地方。而Asynchronous中存放的一般是可破坏物品等静态的、与游戏逻辑关联不大的物品。",[11,83,84],{},"静态的物品是同时存在于这两个Scene中的，而动态物品可以通过选项设定到Async Scene中。",[11,86,87],{},[43,88],{"alt":45,"src":89},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-5.png",[11,91,92],{},"但是静态物品会无视这个选项的，在碰撞中可以选择让物体在移动时也在Async Scene中检测碰撞。",[21,94,96],{"id":95},"custom-physics","Custom Physics",[11,98,99],{},"UE4中有提供介入物理引擎的方式，通过在bodyinstance或者PhysicsScene中定义的下面的函数进行事件绑定就好了：",[101,102,107],"pre",{"className":103,"code":105,"language":106},[104],"language-text","void AddCustomPhysics(FCalculateCustomPhysics& CalculateCustomPhysics);\n","text",[108,109,105],"code",{"__ignoreMap":110},"",[11,112,113],{},"与基本的Event Dispatcher的实现一样，要使用这个功能首先要声明一个",[101,115,118],{"className":116,"code":117,"language":106},[104],"FCalculateCustomPhysics OnCalculateCustomPhysics;\n",[108,119,117],{"__ignoreMap":110},[11,121,122],{},"而与通常的事件不同的是，AddCustomPhysics必须在每次Tick时都进行绑定，这样它才会在接下来的物理执行中被调用。",[11,124,125,126,131],{},"通常的Tick的",[127,128,130],"a",{"href":129},"\u002F2017-03-25-ue4-time-mange\u002F#TickGroup","TickGroup","都是在PrePhiscs中的，也就是说这个注册是针对每一个引擎Tick循环进行的。",[101,133,136],{"className":134,"code":135,"language":106},[104],"void UMyStaticMeshComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)\n{\n  Super::TickComponent(DeltaTime, TickType, ThisTickFunction);\n  GetBodyInstance()->AddCustomPhysics(OnCalculateCustomPhysics);\n}\n",[108,137,135],{"__ignoreMap":110},[11,139,140],{},"而自己要定义的事件则绑定到OnCalculateCustomPhysics中就行了",[101,142,145],{"className":143,"code":144,"language":106},[104],"OnCalculateCustomPhysics.BindUObject(this, &UMyStaticMeshComponent::MyCustomPhysx);\n",[108,146,144],{"__ignoreMap":110},[11,148,149],{},"分发器本身的定义是",[101,151,154],{"className":152,"code":153,"language":106},[104],"DECLARE_DELEGATE_TwoParams(FCalculateCustomPhysics, float, FBodyInstance*);\n",[108,155,153],{"__ignoreMap":110},[11,157,158],{},"在使用自定义物理时，需要注意，一些物理相关的类型是无法进行蓝图暴露的。如果强行暴露给蓝图，例如添加UCLASS之类的修饰的话，就会不经提醒出现这样的报错:",[32,160,161,164],{},[11,162,163],{},"Unrecognized type 'FCalculateCustomPhysics' - type must be a UCLASS, USTRUCT or UENUM",[11,165,166],{},"Inappropriate '*' on variable of type 'FBodyInstance', cannot have an exposed pointer to this type.",[11,168,169],{},"其实只要把蓝图暴露的修饰符去掉就可以了。",[11,171,172],{},"另外如果要直接调用Physx的API的话，就必需在项目的build.cs中添加\"PhysX\"和\"APEX\" 这两个Module的包含，类似下面这样就可以了",[101,174,177],{"className":175,"code":176,"language":106},[104],"PublicDependencyModuleNames.AddRange(new string[] { \"Core\", \"CoreUObject\", \"Engine\", \"InputCore\", \"PhysX\", \"APEX\" });\n",[108,178,176],{"__ignoreMap":110},[11,180,181],{},"这样的话就可以通过",[101,183,186],{"className":184,"code":185,"language":106},[104],"PRigidBody = GetBodyInstance()->GetPxRigidBody_AssumesLocked();\n",[108,187,185],{"__ignoreMap":110},[11,189,190],{},"来进行Physx的API调用了。",[21,192,194],{"id":193},"damping-and-force","Damping and Force",[11,196,197],{},"Physx对球体的运动模拟上有些许问题，通常的建议是对球体作用Linear Damping和Angular Damping让其运动停止。",[11,199,200],{},"但是如果在对物理真实度要求较高的情况下就会有一个问题，那就是Damping在速度较高时减速比速度较低时快，而在速度接近零时需要一定的时间才能够停住运动。当然，如果Damping很高的情况下也是会直接停的。",[11,202,203],{},"所以我们可以通过对RigidBody直接施加力量来达到减速的效果。",[11,205,206],{},"但是通常情况下最好不要直接对速度进行修改，而是通过AddForce之类的操作间接的对速度进行操作。因为物理引擎内部很多模拟计算是以“当前”速度为基准进行的，如果直接进行速度的设置，可能会导致一些计算的结果不太真实。而AddForce的话就可以将加速度等计算交由物理引擎自行处理。",[101,208,211],{"className":209,"code":210,"language":106},[104],"void UNewStaticMeshComponent::SetBallVelocity(FVector fvVelocity, bool bAdd \u002F*= false*\u002F)\n{\n   if (bAdd && fvVelocity.Size() == 0.0f) return;\n\n  UE_LOG(LogTemp, Log, TEXT(\"[UNewStaticMeshComponent] SetBallVelocity: speed %s and %s add\"), *fvVelocity.ToString(), bAdd?TEXT(\"is\"):TEXT(\"not\"));\n  PxVec3 PNewVel = U2PVector(fvVelocity);\n  if (bAdd)\n  {\n    PRigidBody->addForce(PNewVel, physx::PxForceMode::eVELOCITY_CHANGE);\n  }\n  else\n  {\n     PRigidBody->setLinearVelocity(PNewVel);\n  }\n}\n",[108,212,210],{"__ignoreMap":110},[11,214,215,216,222],{},"这里需要注意的是，Physx与UE4的setLinearVelocity调用形式虽然是相同的，但第二个参数的意义是不同的。Physx的第二参数并不是Add to velocity而是auto wake。详细的addForce之类的可以参照[",[127,217,221],{"href":218,"rel":219},"http:\u002F\u002Fdocs.nvidia.com\u002Fgameworks\u002Fcontent\u002Fgameworkslibrary\u002Fphysx\u002Fapireference\u002Ffiles\u002FclassPxRigidBody.html",[220],"nofollow","Physx文档","]。",[11,224,225],{},"这样的话再添加上一些接触判定逻辑就可以实现自己的球体运动模拟了。",[21,227,228],{"id":228},"源码",[11,230,231,232,237],{},"一些更多的细节部分请参考[",[127,233,236],{"href":234,"rel":235},"https:\u002F\u002Fgithub.com\u002Fsteinkrausls\u002FPhysxTest",[220],"Github","]上的源码，里面只保留了测试代码，有些多余的代码没有完全去掉~",{"title":110,"searchDepth":239,"depth":240,"links":241},2,3,[242,243,244,245,246],{"id":23,"depth":239,"text":24},{"id":66,"depth":239,"text":67},{"id":95,"depth":239,"text":96},{"id":193,"depth":239,"text":194},{"id":228,"depth":239,"text":228},"2017-05-03","md",{"layout":250,"status":251,"published":252,"author":253,"author_login":255,"author_email":256,"wordpress_id":257,"wordpress_url":258,"date_gmt":259,"excerpt":260},"post","publish",true,{"display_name":254,"login":255,"email":256,"url":110},"风铃","flinkor","flinkor@foxmail.com",1659,"\u002F\u002F?p=1659","2017-05-03 15:15:23 +0000",{"type":8,"value":261},[262],[11,263,13],{},"\u002F2017-05-03-ue4-physx-and-substepping",{"title":6,"description":13},"_legacy\u002F2017\u002F2017-05-03-ue4-physx-and-substepping",[268,269,270],"UE4","物理","Substeping","6Y9M-KkcwG2tKy8g5yLG0UdDxx2RabVFCSooiRT8HNI",{"id":273,"title":274,"body":275,"date":756,"description":279,"extension":248,"meta":757,"navigation":252,"path":766,"seo":767,"stem":768,"tags":769,"__hash__":774},"blogs\u002F_legacy\u002F2017\u002F2017-05-01-ue4-patch-release-dlc.md","UE4补丁与DLC",{"type":8,"value":276,"toc":741},[277,280,283,286,290,299,302,305,311,314,319,322,327,330,337,342,345,350,359,362,367,370,375,378,383,386,389,392,397,402,405,408,411,416,419,424,427,431,434,437,443,446,449,454,458,461,466,469,474,477,482,485,488,508,511,515,518,521,524,532,535,538,541,546,549,555,558,561,564,567,571,574,577,580,583,594,597,600,603,607,610,616,619,624,627,632,635,638,641,644,649,652,656,659,665,668,671,676,679,682,685,691,694,699,702,705,710,713,716,721,724,729,732,735,738],[11,278,279],{},"UE4的打包操作本身还是比较简单的，但是考虑到发布之后的升级与补丁，却还是有一些麻烦。",[11,281,282],{},"当前UE4版本为4.16 P1。",[11,284,285],{},"在动手做东西之前，先了解一下UE4本身对于打包系统的架构是很重要的，否则如果到了Release的阶段发现代码设计上很多地方与系统有冲突的话就会很麻烦。就像是等到开始做联网版本的游戏时才发现很多机制、功能的设计都与UE4的Replication架构不符的话就会需要大幅度的代码变更。",[21,287,289],{"id":288},"unreal-frontend","Unreal Frontend",[11,291,292,293,298],{},"虚幻的打包操作在[",[127,294,297],{"href":295,"rel":296},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FDeployment\u002Findex.html",[220],"官方文档","]中有进行描述，一切打包相关的工作，都以Unreal Frontend为核心进行。当然，Unreal Frontend还提供了部署、自动化测试等功能。",[11,300,301],{},"总体而言，Unreal Frontend的功能还不成熟，很多选项的设定让人费解，也没有提供足够的文档说明，所以可能会遇到一些奇怪的BUG。最麻烦的是，到目前为止，DLC系统依然处于WIP状态，使用起来有很多的不便。",[11,303,304],{},"目前主要会使用到的是Project Launcher模块，在下方的Custom Lanch Profiles区域的右边有加号按钮，可以在那里添加自定义的打包配置。配置大致分为以下区域：",[11,306,307],{},[308,309,310],"strong",{},"Project",[11,312,313],{},"选择打包操作的目标项目，由于Unreal Frontend的设置是全局的，所以在一个项目中配置了之后在其他项目中也可以使用。",[11,315,316],{},[308,317,318],{},"Build",[11,320,321],{},"选择是否构建，以及构建的模式，并提供了是否构建UAT（Unreal Automation Tool）的选项。UAT主要提供一些自动化测试方面的功能，根据社区一些用户的反馈来看，需要源码版本引擎才能正常的使用。",[11,323,324],{},[308,325,326],{},"Cook",[11,328,329],{},"烘焙相关选项，一般都是用By the book选项然后勾选相关目标平台。",[11,331,332,333,222],{},"进行Release\u002FPatch\u002FDLC打包的操作都在By the book选项下。其他的一些详细的可选项功能可以参考[",[127,334,297],{"href":335,"rel":336},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FDeployment\u002FCooking\u002Findex.html",[220],[11,338,339],{},[308,340,341],{},"Package",[11,343,344],{},"是否打包以及如何打包，这里需要注意的是当前版本下对DLC进行打包是会出现BUG的，报错如下：",[32,346,347],{},[11,348,349],{},"Program.Main: ERROR: AutomationTool terminated with exception: System.Exception: Couldn't update resource",[11,351,352,353,358],{},"搜索之后发现似乎是一个[",[127,354,357],{"href":355,"rel":356},"https:\u002F\u002Fissues.unrealengine.com\u002Fissue\u002FUE-42880",[220],"BUG","]，因此目前对DLC可以不需要打包，而是自己将生成的文件进行拷贝就可以了。",[11,360,361],{},"打包的生成目录在Saved\\StagedBuilds\\中。",[11,363,364],{},[308,365,366],{},"Archive",[11,368,369],{},"归档，引擎会在打包后将打包好的内容拷贝到指定的目录去。不使用的话不要勾选就可以了。",[11,371,372],{},[308,373,374],{},"Deploy",[11,376,377],{},"部署，提供了几种部署渠道，可以部署到目前可用的设备上。不使用的话可以直接选中Not Deploy。",[11,379,380],{},[308,381,382],{},"Launch",[11,384,385],{},"测试运行的形式，可以对服务端模式、语言环境、初始地图等进行设置。",[21,387,388],{"id":388},"打包模式",[11,390,391],{},"打包模式目前有Release、Patch、DLC三种，其中DLC处于WIP状态，并没有足够的文档支持，而且今后可能会有不少变动。",[11,393,394],{},[43,395],{"alt":45,"src":396},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb.png",[398,399,401],"h3",{"id":400},"release-version","Release Version",[11,403,404],{},"UE4提供的Release的概念是通常的发行版本控制，勾选Create a release version of the game for distribution就可以开启了。而Patch与DLC都是以某个Release版本为基础进行打包的。",[11,406,407],{},"因此使用Release打包的时候就必须填Name of the new release to create，而Path与DLC则必须填写Release version this is based on。",[11,409,410],{},"DLC是不可以选中Create a release version这个选项的，会报错：",[32,412,413],{},[11,414,415],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Can't create a release version at the same time as creating dlc.",[11,417,418],{},"而选中了这个选项还填写based on version的话也会报错：",[32,420,421],{},[11,422,423],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Only staged builds can be paked, use -stage or -skipstage.",[11,425,426],{},"这个错误也会出现在使用Iterative cooking选项时，如果使用这个选项同时又勾选使用Pak的话，根据当前烘焙缓存的状况不同，也会报这个错。",[398,428,430],{"id":429},"generate-patch","Generate patch",[11,432,433],{},"生成补丁，目前一个Release下只能有一个补丁，对于生成的pak文件，补丁中带后缀_P，会被优先加载并覆盖。同样的pak文件，多加一个_P的话优先级就会更高。",[11,435,436],{},"在Mount函数中，可以看到",[101,438,441],{"className":439,"code":440,"language":106},[104],"if ( PakFilename.EndsWith(TEXT(\"_P.pak\")) )\n{\n  PakOrder += 100;\n}\n",[108,442,440],{"__ignoreMap":110},[11,444,445],{},"凡是以_P结尾的pak文件，会获得额外的100优先级。在进行Patch打包时，引擎也会自动的加上这个后缀。",[11,447,448],{},"这里有一个问题，如果勾选了Store all content in a single file的情况下，即便是在不同的Release版本之间，_P的patch也会被优先加载。还没有测试过如果去除了Save package without versions选项是否会有这样的现象。",[11,450,451],{},[43,452],{"alt":45,"src":453},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-1.png",[398,455,457],{"id":456},"iterative-cooking","Iterative cooking",[11,459,460],{},"增量烘焙必须去掉勾选Save package without versions，否则launch按钮会变成灰色，提示：",[32,462,463],{},[11,464,465],{},"unversioned build cannot be incremental",[11,467,468],{},"这里面的Iterative cooking目前只能在同一个Release版本中进行增量烘焙，如果Releasse的版本迁移了的话，是不能在版本之间进行增量烘焙的，强行勾选会被拒绝操作：",[32,470,471],{},[11,472,473],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.AutomationException: Can't use iterative cooking \u002F deploy on dlc or patching or creating a release",[11,475,476],{},"官方的说明是这样的：",[32,478,479],{},[11,480,481],{},"The asset registry and pak file will be needed for any future patches or DLC to check against.",[11,483,484],{},"也就是说保存的content注册关系是为了DLC和patch而存在的，并没有说可以作为Release之间的版本迁移的依据。",[11,486,487],{},"使用Iterative cooking时还有一点需要注意，那就是引擎当前无法区分Ship和DevelopMent之间的烘焙结果，如果切换Build模式时没有去掉这个选项的话，就会导致烘焙资源的混杂，出现类似如下错误：",[32,489,490,505],{},[11,491,492,493,497,498,501,502],{},"Assertion failed: GDefaultMaterials",[494,495,496],"span",{},"Domain"," != NULL ",[494,499,500],{},"File:D:\\Build++UE4+Release-4.16+Compile\\Sync\\Engine\\Source\\Runtime\\Engine\\Private\\Materials\\Material.cpp"," ",[494,503,504],{},"Line: 429",[11,506,507],{},"Cannot load default material 'engine-ini:\u002FScript\u002FEngine.Engine.DefaultMaterialName'",[11,509,510],{},"另外，使用增量会导致不在当前选择的地图内容也会被Build进来，但是无论如何，项目设定中的默认地图都是会别build进来的。",[398,512,514],{"id":513},"build-dlc","Build DLC",[11,516,517],{},"UE4中的DLC是以Plugin的形式进行实现的，DLC Name那里填写的就是插件的名称，是不能随便乱填的。",[11,519,520],{},"插件的话在Plugins窗口直接新建就可以了，如果是蓝图项目的话，只会有一个Content Plugin，选择他就可以了。另外，似乎在打包时如果有地图存在的话，没有被地图引用的资源就不会被打包，需要注意。",[11,522,523],{},"打包DLC中如果出现",[32,525,526,529],{},[11,527,528],{},"Cook: LogInit:Display: LogCook:Error: Engine or Game content ..\u002F..\u002F..\u002FEngine\u002FContent\u002FTutorial\u002FFoliage\u002FFoliage_Intro_Tutorial.uasset is being referenced by DLC!",[11,530,531],{},"Cook: LogInit:Display: LogCook:Error: Engine or Game content ..\u002F..\u002F..\u002FEngine\u002FContent\u002FTutorial\u002FSubEditors\u002FDestructibleMeshEditorTutorial.uasset is being referenced by DLC!",[11,533,534],{},"之类的BUG，可以尝试勾选Include engine content。",[11,536,537],{},"关于DLC，一些旧的资料中会提到Plugin形式无法被引擎自动加载，目前测试过已经没有这个现象了。即便是在Release之后添加的Plugin，在做DLC的打包之后，直接扔到项目目录中也会被自动加载。不知是否是因为使用DevelopMent打包的关系。",[11,539,540],{},"由于当前Package DLC不知为何会报错，类似这样",[32,542,543],{},[11,544,545],{},"UATHelper: Packaging (Windows (64-bit)): Program.Main: ERROR: AutomationTool terminated with exception: System.Exception: Couldn't update resource",[11,547,548],{},"其实Pak已经打包成功了，直接Achive即可。",[11,550,551],{},[43,552],{"alt":553,"src":554},"msohtmlclipclip_image0014","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0014_thumb.png",[11,556,557],{},"将生成的内容直接复制到打包程序里面，就会被自动加载了，即便Release打包的时候并没有这个DLC的Plugin也是可以的。",[21,559,560],{"id":560},"补丁形式",[11,562,563],{},"官方在Patch的文档中有提到补丁的覆盖优先，由于Patch是从Release的版本开始进行差分的，如果两个Patch都是针对整个游戏的话，那么前一个Patch就没有意义了，可以删除。",[11,565,566],{},"这样一来，为了防止每次更新的内容过大，我们就必须对项目的组织进行设计，目前我们可以通过Maps或者Chunks来做内容差分管理。",[398,568,570],{"id":569},"maps","Maps",[11,572,573],{},"在进行打包时，被打包地图及其所引用的资源有改变的都会被打包，只是是否会被游戏采用取决于Pak的加载优先级。",[11,575,576],{},"而不被选中的地图中，即便相关资源有变动，也不会被打包到Patch中去，但是DefaultMap始终都会被打包。",[11,578,579],{},"各个地图打包之后除了DefaultMap之外都是独立的，无论命名成什么都会被自动加载，而_P的优先级会被应用到整个Pak文件上。",[11,581,582],{},"而目前，只要在扫描目录里面的pak文件，都会被自动加载。从FPakPlatformFile::Initialize的调用中看到，除了在SHIPPING模式下提供的命令行外，有如下的目录会被添加到扫描中：",[32,584,585,588,591],{},[11,586,587],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::GameContentDir()));",[11,589,590],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::GameSavedDir()));",[11,592,593],{},"OutPakFolders.Add(FString::Printf(TEXT(\"%sPaks\u002F\"), *FPaths::EngineContentDir()));",[11,595,596],{},"也就是说，可以在一开始打包时，只选中Default地图，之后其他的地图可以每个或每组地图单独选中，然后打包成Patch改成地图名称的形式添加到游戏中去。",[11,598,599],{},"这样一来，就可以很方便的添加后续的地图内容到发布版本中去了，只是多少还需要维护一个文件列表。",[11,601,602],{},"总体而言，使用一个空地图作为程序本体，将通用的逻辑、材质放到其中，其他地图大致保持各自独立。这样每次就可以对对应的模块进行更新替换就行了。",[398,604,606],{"id":605},"chunks","Chunks",[11,608,609],{},"Chunks这个功能可以在编辑器中指定内容将会被打包到哪一个Patch中去，但是这个功能必须在编辑器设定中Genera>Experiment中打开：",[11,611,612],{},[43,613],{"alt":614,"src":615},"msohtmlclipclip_image001","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image001_thumb.png",[11,617,618],{},"打开选项之后，就可以在内容管理器的右键菜单中看到：",[11,620,621],{},[43,622],{"alt":45,"src":623},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-6.png",[11,625,626],{},"Chunk相关的选项了。对不同的资源设定了Chunk之后就可以在打包时选中这个功能，打包之后就会有不同的分包了。",[11,628,629],{},[43,630],{"alt":45,"src":631},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-7.png",[11,633,634],{},"Chunk功能必须在Pak开启时使用，因为不Pak打包的话，原本的项目文件就已经足够Chunk了。",[11,636,637],{},"另外，如果要使用Http chunk install功能的话，不要将Default Map放到Chunk中去，否则由于本地不会保留那些Chunk，程序将会由于没有Default Map而无法开启。",[11,639,640],{},"如果对在Patch的时候也使用chunk的话，将会对不同的chunk包生成Patch，但是即使该chunk包的内容没有改变，也依然会生成一个1K的Dammy。",[11,642,643],{},"不过这其中最大的限制是，对chunk进行patch时只能针对Release时在AssetRegistry注册过的地图，如果添加没有注册过的地图则无法打包成功。",[32,645,646],{},[11,647,648],{},"Program.Main: ERROR: AutomationTool terminated with exception: AutomationTool.CommandUtils+CommandFailedException: Command failed (Result:3): D:\\Code\\UE_4.16\\Engine\\Binaries\\Win64\\UnrealPak.exe F:\\Ue_Patch\\1.0_nomap\\WindowsNoEditor\\P001\\Content\\Paks\\P001-WindowsNoEditor_P.pak -create=\"C:\\Users\\Admin\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakList_P001-WindowsNoEditor_P.txt\" -encryptionini -enginedir=\"D:\\Code\\UE_4.16\\Engine\" -projectdir=\"E:\\Ues\\P001\" -platform=Windows -abslog=\"C:\\Users\\Admin\\AppData\\Roaming\\Unreal Engine\\AutomationTool\\Logs\\D+Code+UE_4.16\\PakLog_P001-WindowsNoEditor_P.log\" -installed -order=E:\\Ues\\P001\\Build\\WindowsNoEditor\\FileOpenOrder\\CookerOpenOrder.log -UTF8Output -generatepatch=E:\\Ues\\P001\\Releases\\5.0\\WindowsNoEditor\\P001-WindowsNoEditor.pak -tempfiles=D:\\Code\\UE_4.16\\TempFiles -multiprocess -patchpaddingalign=2048. See logfile for details: 'UnrealPak-2017.05.07-11.00.34.txt'",[11,650,651],{},"会出现类似这样的错误。",[398,653,655],{"id":654},"http-chunks-install","Http Chunks Install",[11,657,658],{},"在使用Chunk的基础上，还可以进一步的使用Http Chunks Install功能，这个功能通常被用于移动端的碎片化安装功能",[11,660,661],{},[43,662],{"alt":663,"src":664},"msohtmlclipclip_image0016","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0016_thumb.png",[11,666,667],{},"不过在桌面端也可以进行使用。其实实现中并没有支持断点续传，不过事实上，分块下载本身就在一定程度上充当了断点续传的功能。",[11,669,670],{},"使用起来也比较简单，直接勾选并填写输出路径、版本名称之类的就可以了。",[11,672,673],{},[43,674],{"alt":45,"src":675},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-13.png",[11,677,678],{},"不过要注意确保其输出目录是确实存在的，和其他的目录设定不同，这里是会进行写入检测的。",[11,680,681],{},"另外，在Chunk Version Name里面输入之后记得敲回车才能确认输入。这里的输入框功能有点奇怪，直接填完之后点别的地方不会确认输入，会直接清空。",[11,683,684],{},"如果是移动端打包的话，无需添加配置文件，直接点击添加按钮盘的下拉三角即可：",[11,686,687],{},[43,688],{"alt":689,"src":690},"msohtmlclipclip_image0011","\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fmsohtmlclipclip_image0011_thumb.png",[11,692,693],{},"打包成功的话会在输出目录输出类似这样的内容：",[11,695,696],{},[43,697],{"alt":45,"src":698},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-8.png",[11,700,701],{},"Http chunk install的功能也是以一个chunk为目标进行的，只是每个chunk会被分成更小的块，方便传输。也就是说，要使用这个功能，还是需要有一个自行设计的文件列表来进行版本管理的。",[11,703,704],{},"要进行碎片安装，直接对某个文件执行Request Content：",[11,706,707],{},[43,708],{"alt":45,"src":709},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-9.png",[11,711,712],{},"这里面填写的URL对应的就是上面生成的那些内容，需要自行部署到服务器上。",[11,714,715],{},"如果Success的话，会返回一个Mobile Pending Content的引用，继续",[11,717,718],{},[43,719],{"alt":45,"src":720},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-10.png",[11,722,723],{},"如果成功的话就可以对这个引用进行Mount了",[11,725,726],{},[43,727],{"alt":45,"src":728},"\u002Fwp-content\u002Fuploads\u002F2017\u002F05\u002Fimage_thumb-11.png",[11,730,731],{},"这样就可以使用这个包的内容了，这套函数本身会检测本地有没有pak文件，有的话就不会进行下载了。",[11,733,734],{},"上面填写的Install Directory在windows下是以项目内容目录下的PersistentDownloadDir为基础的，所以填写..\u002FContent\u002FPaks\u002F的话就可以到达自动加载目录，下次游戏运行时包中的内容就可以直接使用了。不过要留意的是，如果被自动加载了的话，那个文件就无法被直接替换掉了。",[21,736,737],{"id":737},"总结",[11,739,740],{},"由于UE4并未提供Release版本之间的增量功能，要实现地图下载之类的功能的话，可以使用DLC或者分Map打包。如果现在的打包实在无法满足需求的话，也可以选择自己用UnreakPack进行打包并自行进行Mount管理来进行补正，有这个需求的话可能会遇到FPackageName::RegisterMountPoint之类函数的使用。不过相信打包功能在之后的版本更新中会有所增强，如果不是有紧急的打包需求的话，不建议跳这个坑。",{"title":110,"searchDepth":239,"depth":240,"links":742},[743,744,750,755],{"id":288,"depth":239,"text":289},{"id":388,"depth":239,"text":388,"children":745},[746,747,748,749],{"id":400,"depth":240,"text":401},{"id":429,"depth":240,"text":430},{"id":456,"depth":240,"text":457},{"id":513,"depth":240,"text":514},{"id":560,"depth":239,"text":560,"children":751},[752,753,754],{"id":569,"depth":240,"text":570},{"id":605,"depth":240,"text":606},{"id":654,"depth":240,"text":655},{"id":737,"depth":239,"text":737},"2017-05-01",{"layout":250,"status":251,"published":252,"author":758,"author_login":255,"author_email":256,"wordpress_id":759,"wordpress_url":760,"date_gmt":761,"excerpt":762},{"display_name":254,"login":255,"email":256,"url":110},1646,"\u002F\u002F?p=1646","2017-05-01 10:32:13 +0000",{"type":8,"value":763},[764],[11,765,279],{},"\u002F2017-05-01-ue4-patch-release-dlc",{"title":274,"description":279},"_legacy\u002F2017\u002F2017-05-01-ue4-patch-release-dlc",[268,770,771,772,773],"Release","Patch","DLC","UnrealPak","cJwigzRyewKqORjMzLTqhnQa8zSJWqk_pxLSbY0jaAA",{"id":776,"title":777,"body":778,"date":879,"description":782,"extension":248,"meta":880,"navigation":252,"path":889,"seo":890,"stem":891,"tags":892,"__hash__":894},"blogs\u002F_legacy\u002F2017\u002F2017-04-26-ue4-active-class-redirects.md","UE4资源重定向",{"type":8,"value":779,"toc":875},[780,783,786,789,792,797,800,806,809,812,815,822,828,831,834,837,843,846,849,852,857,860,866,872],[11,781,782],{},"在混合使用C++和蓝图进行编码时，有时会因为代码的变动而导致蓝图无法正确的识别基础的类。此时可以使用资源重定向进行解决。",[11,784,785],{},"当前UE4版本4.15.1。",[11,787,788],{},"如果是在内容管理器中进行删除、重命名、移动等操作的话，引擎会尝试自动执行重定向，或者给出提示。只要不操作失误的话就不会有什么大问题。",[11,790,791],{},"但当C++定义的基类由于各种原因不存在时，由于引擎不会给出提示，有时就会出现灾难性的结果，比如这样：",[11,793,794],{},[43,795],{"alt":45,"src":796},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-11.png",[11,798,799],{},"或者更严重的，变成这样：",[11,801,802],{},[43,803],{"alt":804,"src":805},"SNAGHTMLf68450","\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002FSNAGHTMLf68450_thumb.png",[11,807,808],{},"由于蓝图的基类不存在了，这时候就算强行打开蓝图类，也有可能会变成奇怪的情况。引擎本身无法识别基类，蓝图的类型判定就会出错，即便有替换的目标类也无法进行指定。想要将基类指定为AActor都无法进行。如果此时有作为替换的类的话，就可以求助于资源重定向了。",[21,810,811],{"id":811},"重定向配置",[11,813,814],{},"资源重定向可以在配置文件DefaultEngine.ini中设定，这个文件位于项目目录下Config目录中。",[11,816,817,818,821],{},"在其中找到",[494,819,820],{},"\u002FScript\u002FEngine.Engine","，如果没有的话直接添加即可，按照如下格式添加即可：",[101,823,826],{"className":824,"code":825,"language":106},[104],"[\u002FScript\u002FEngine.Engine]\n+ActiveClassRedirects=(OldClassName=\"OceanManager\",NewClassName=\"\u002FScript\u002FOcean.OceanManager\")\n+ActiveClassRedirects=(OldClassName=\"WaveSetParameters\",NewClassName=\"\u002FScript\u002FOcean.WaveSetParameters\")\n+ActiveClassRedirects=(OldClassName=\"WaveParameter\",NewClassName=\"\u002FScript\u002FOcean.WaveParameter\")\n",[108,827,825],{"__ignoreMap":110},[11,829,830],{},"其中OldClassName为旧的类名称，不用指定Package名称，而NewClassName则必须指定Package名称。",[11,832,833],{},"Package名称一般就是项目的名称，如果类是定义在插件中的话，就是插件的名称。上面的格式中，Ocean就是项目的名称。",[11,835,836],{},"重定向配置还有一个额外的参数：",[101,838,841],{"className":839,"code":840,"language":106},[104],"+ActiveClassRedirects=(OldClassName=\"MyClass\",NewClassName=\"MyClassParent\",InstanceOnly=\"true\")\n",[108,842,840],{"__ignoreMap":110},[11,844,845],{},"可以为重定向提供一个过渡的阶段，只有类的实例会被替换成新的类，而原有的类依然继续存在。",[21,847,848],{"id":848},"合并重定向",[11,850,851],{},"包括内容管理器自动进行的重定向在内，长期累积之下，项目中就会累积很多重定向，这个时候就需要用到清理功能。清理重定向的功能在内容管理器中就可以使用，在任何一个目录上点击右键就可以看到：",[11,853,854],{},[43,855],{"alt":45,"src":856},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-12.png",[11,858,859],{},"同时也可以使用命令行工具进行清理",[101,861,864],{"className":862,"code":863,"language":106},[104],"UE4Editor.exe \"E:\\Ues\\Trace\" -run=FixupRedirects –testonly\n",[108,865,863],{"__ignoreMap":110},[11,867,868],{},[43,869],{"alt":870,"src":871},"SNAGHTML9585f4","\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002FSNAGHTML9585f4_thumb.png",[11,873,874],{},"不过在有的时候还是会出现C++类重新指定失败的情况，所以一般在重定向操作之后，如果已经没有必要继续使用旧类的情况下，当时就把重定向清理掉比较好。",{"title":110,"searchDepth":239,"depth":240,"links":876},[877,878],{"id":811,"depth":239,"text":811},{"id":848,"depth":239,"text":848},"2017-04-26",{"layout":250,"status":251,"published":252,"author":881,"author_login":255,"author_email":256,"wordpress_id":882,"wordpress_url":883,"date_gmt":884,"excerpt":885},{"display_name":254,"login":255,"email":256,"url":110},1632,"\u002F\u002F?p=1632","2017-04-26 14:14:24 +0000",{"type":8,"value":886},[887],[11,888,782],{},"\u002F2017-04-26-ue4-active-class-redirects",{"title":777,"description":782},"_legacy\u002F2017\u002F2017-04-26-ue4-active-class-redirects",[268,893],"ActiveClassRedirects","b-o6mKcJJtyTIAVSY5TXqiLI35aVfUs_cVLrh0ZqHR0",{"id":896,"title":897,"body":898,"date":1080,"description":902,"extension":248,"meta":1081,"navigation":252,"path":1090,"seo":1091,"stem":1092,"tags":1093,"__hash__":1095},"blogs\u002F_legacy\u002F2017\u002F2017-04-23-ue4-render-target-preview.md","UE4渲染目标使用",{"type":8,"value":899,"toc":1064},[900,903,905,908,916,919,922,927,931,934,938,941,946,949,952,955,958,961,964,967,970,973,976,981,984,987,990,995,998,1001,1005,1008,1011,1015,1018,1022,1025,1028,1031,1036,1039,1042,1045,1050,1053,1055],[11,901,902],{},"UE4的Render Target提供了渲染缓存功能，可以制作很多有趣的特效。",[11,904,785],{},[11,906,907],{},"Render Target作为一种动态生成内容的方法，为材质系统提供了更加精细的控制。引擎本身提供的Scene Capture功能也是可输出到Render Target并连接到材质上作为摄像展示功能的。通过Render Target也能实现类似神秘海域里面的擦拭物体表面灰层的功能。",[11,909,910,911,915],{},"本文参照[",[127,912,297],{"href":913,"rel":914},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FEngine\u002FRendering\u002FRenderTargets\u002FBlueprintRenderTargets\u002FOverview\u002Findex.html",[220],"]进行完成，由于官方文档的介绍并不详细，所以在这里记录下自己的经验和理解。",[21,917,918],{"id":918},"高度场绘制器",[11,920,921],{},"高度场绘制器是通过将玩家操作反映到Render Target，然后传入材质来提升World Position来实现的，是一个非常简单的示例，基本按照官方的指引操作即可。",[11,923,924],{},[43,925],{"alt":45,"src":926},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-5.png",[398,928,930],{"id":929},"mat_heighfieldpainter","MAT_HeighfieldPainter",[11,932,933],{},"这个材质是最终应用到物体上的材质,Heightfield参数是Render Target的传入路径。可以看到，传入的高度场被拼接为(0,0,Z)的Vector输出到了World Position上，这就会导致被应用了材质的物体的对应位置向上偏移，同时传入的Render Target也到达了自发光颜色，使得升起的部分有了白色的效果。",[398,935,937],{"id":936},"mat_forcesplat","MAT_ForceSplat",[11,939,940],{},"这个材质是用来绘制Render Target的，直观的讲，就是在指定的点生成一个由内向外的Gradient一样的效果，作用到高度场上就是上图中那样的锥形效果。材质在实现上，首先根据传入的Force Position，mask之后，让其与UV相减，之后再求取向量长度的话，那么自然就会成为一个由那个点向外扩散的圆了：",[11,942,943],{},[43,944],{"alt":45,"src":945},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-6.png",[11,947,948],{},"这里要注意的是，UV在材质中是以左上角为(0,0)的，在材质蓝图中分别展示为R和G两个通道，但是负数全是黑色的。求出向量长度之后的操作就比较单纯了，通过减法和取整就可以得到目标的“圆点”了。",[398,950,951],{"id":951},"蓝图",[11,953,954],{},"蓝图的设置上比较方便，因为官方有提供在网页上复制蓝图脚步的功能，只要拷贝过去就可以了。基本上的功能也非常的直观。",[11,956,957],{},"在ConstructionScript中构建并指定材质，由于这里的功能比较简单，RenderTarget时在这里直接构建的；",[11,959,960],{},"在BeginPlay时打开Input交互，同时将RenderTarget设置给动态材质；",[11,962,963],{},"鼠标按下时标记MouseDown，此时Tick事件就会开始进行Trace操作；",[11,965,966],{},"整个蓝图的核心就是通过这个Trace给物体施加Damage事件，并在Damage处理中对进行RenderTarget的绘制。",[398,968,969],{"id":969},"绘制点的获取",[11,971,972],{},"需要注意的是，官方在获取绘制点的时候使用了一个简便的方法，这点官方也有给出注释说明。如果想要在所有的Mesh上都实现类似的效果，而不只是在例子中才能使用的话，需要使用Trace时获得的UV来进行计算。",[11,974,975],{},"而这个功能是4.13才有的新功能，要使用的话必须在项目设置>引擎>Physics中打开：",[11,977,978],{},[43,979],{"alt":45,"src":980},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-7.png",[11,982,983],{},"使用时要注意勾选Trace Complex才行。",[21,985,986],{"id":986},"流体表面",[11,988,989],{},"其实就是一个水波纹的效果，会在用户点击和玩家碰撞的地方产生扩散衰减的水波纹效果。是比高度图稍微复杂一些的效果，也是非常实用的效果。",[11,991,992],{},[43,993],{"alt":45,"src":994},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-10.png",[11,996,997],{},"由于需要的效果比较复杂，所以用到了三个缓存用Render Target以及一个绘制法线贴图的Render Target。",[11,999,1000],{},"而相对的，绘制用材质ForceSplat可以采用高度场里面用到的材质，是一样的。",[398,1002,1004],{"id":1003},"watermaterial","WaterMaterial",[11,1006,1007],{},"这个材质是应用于“水面”的材质，其本身的材质很简单，和通常的水面材质不同的是，将输入的Texture应用到了世界位置便宜与法线上。",[11,1009,1010],{},"其中Heightfield的应用方式与高度场的材质应用是相同的。",[398,1012,1014],{"id":1013},"heightsim","HeightSim",[11,1016,1017],{},"这个是计算水面波纹的核心材质，原理上，首先将PreviousHeight1分别朝四个方向进行TexelSize单位的移动，然后相互叠加后与原有的图形进行相减，就能得到一个向外扩散的外沿了。之后再减去可以算作完全消退的PreviousHeight2作进一步的修正，就是当前的Height了。",[398,1019,1021],{"id":1020},"computenormal","ComputeNormal",[11,1023,1024],{},"根据当前的绘制水面的高度图计算出法线贴图，使用的数学原理与HeightSim类似。",[398,1026,951],{"id":1027},"蓝图-1",[11,1029,1030],{},"蓝图实现上的整体逻辑和高度场示例中差不多，复杂之处在于使用tick函数来计算和更新了水波。其中最大的核心在这里：",[11,1032,1033],{},[43,1034],{"alt":45,"src":1035},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-8.png",[11,1037,1038],{},"每次计算的时候通过交换3个Render Target，分别作为Current，Prev1， Prev2来进行水波扩散的更新模拟计算，而法线的运算反而简单，只是在“绘制”执行的时候对“当前”水波进行法线计算而已。",[21,1040,1041],{"id":1041},"小心有坑",[11,1043,1044],{},"官方亲切的提供了在网页中复杂蓝图节点的功能，但是要注意的是，当前版本中有一个BUG，材质蓝图中的所有参数名被多次使用时复制后会被自动重命名，并且这个过程是没有提示的，最后材质中就会变成这样：",[11,1046,1047],{},[43,1048],{"alt":45,"src":1049},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-9.png",[11,1051,1052],{},"此时材质参数无法正常传递，就会出现奇怪的特效。",[21,1054,228],{"id":228},[11,1056,1057,1058,1063],{},"由于官方当前在文档中并没有提供一些必要的Mesh等资源，需要从Content Example中复制，为了方便大家研究，已经将完成的版本上传到了[",[127,1059,1062],{"href":1060,"rel":1061},"https:\u002F\u002Fgithub.com\u002Fsteinkrausls\u002FRtg_Test",[220],"GitHub","]上，欢迎取用。",{"title":110,"searchDepth":239,"depth":240,"links":1065},[1066,1072,1078,1079],{"id":918,"depth":239,"text":918,"children":1067},[1068,1069,1070,1071],{"id":929,"depth":240,"text":930},{"id":936,"depth":240,"text":937},{"id":951,"depth":240,"text":951},{"id":969,"depth":240,"text":969},{"id":986,"depth":239,"text":986,"children":1073},[1074,1075,1076,1077],{"id":1003,"depth":240,"text":1004},{"id":1013,"depth":240,"text":1014},{"id":1020,"depth":240,"text":1021},{"id":1027,"depth":240,"text":951},{"id":1041,"depth":239,"text":1041},{"id":228,"depth":239,"text":228},"2017-04-23",{"layout":250,"status":251,"published":252,"author":1082,"author_login":255,"author_email":256,"wordpress_id":1083,"wordpress_url":1084,"date_gmt":1085,"excerpt":1086},{"display_name":254,"login":255,"email":256,"url":110},1615,"\u002F\u002F?p=1615","2017-04-23 14:26:42 +0000",{"type":8,"value":1087},[1088],[11,1089,902],{},"\u002F2017-04-23-ue4-render-target-preview",{"title":897,"description":902},"_legacy\u002F2017\u002F2017-04-23-ue4-render-target-preview",[268,1094],"RenderTarget","bAxg5KEnuL8U7tmCW8-5niZcxunUKbD5aFWgOYaDb3o",{"id":1097,"title":1098,"body":1099,"date":1425,"description":1103,"extension":248,"meta":1426,"navigation":252,"path":1435,"seo":1436,"stem":1437,"tags":1438,"__hash__":1441},"blogs\u002F_legacy\u002F2017\u002F2017-04-20-ue4-multiplayer-and-network-overview.md","UE4网络连接与多人游戏",{"type":8,"value":1100,"toc":1412},[1101,1104,1107,1110,1113,1121,1124,1130,1133,1136,1139,1142,1145,1148,1151,1154,1157,1162,1165,1171,1174,1179,1182,1187,1190,1196,1199,1205,1208,1214,1217,1220,1223,1228,1231,1234,1240,1243,1249,1257,1260,1263,1266,1269,1272,1275,1278,1281,1284,1287,1290,1295,1298,1309,1312,1318,1321,1327,1330,1333,1336,1343,1365,1369,1372,1375,1378,1381,1384,1387,1394,1398,1405],[11,1102,1103],{},"UE4的Replication系统在设计上非常的高效和迅速，要制作联网游戏相关功能的话使用起来也很方便。",[11,1105,1106],{},"当前UE4版本为4.15.0。",[21,1108,1109],{"id":1109},"概述",[11,1111,1112],{},"蓝图中的GameMode是自带支持Replication系统的，GameMode也是整个Replication系统在逻辑设计上的中心。如果在设计游戏时，不需要联网功能，目前引擎已经提供了一个新的基类GameModeBase来使用。",[11,1114,1115,1116,222],{},"网络模式相关的官方文档在[",[127,1117,1120],{"href":1118,"rel":1119},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002Findex.html",[220],"这里",[11,1122,1123],{},"网络模式的调试可以在PIE中进行，点击调试旁边的下拉三角就可以进行设置。Server的模式分为DedicatedServer和ListenServer，如果在多客户端模式下进行屏幕调试输出，会发现输出会自动表明输出者的身份，在源代码中可以找到它的实现方式：",[101,1125,1128],{"className":1126,"code":1127,"language":106},[104],"UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, false);\nFString Prefix;\nif (World)\n{\n  if (World->WorldType == EWorldType::PIE)\n  {\n    switch(World->GetNetMode())\n    {\n      case NM_Client:\n        Prefix = FString::Printf(TEXT(\"Client %d: \"), GPlayInEditorID - 1);\n      break;\n      case NM_DedicatedServer:\n      case NM_ListenServer:\n        Prefix = FString::Printf(TEXT(\"Server: \"));\n      break;\n      case NM_Standalone:\n      break;\n    }\n  }\n}\n",[108,1129,1127],{"__ignoreMap":110},[11,1131,1132],{},"这个宏也可以自己拿来做一些判断用，例如区分独立服务器与监听服务器。",[11,1134,1135],{},"另外，如果要使用官方模板进行功能测试的话需要注意：",[11,1137,1138],{},"TopDown模板的角色移动是基于NavMesh的，而这个机制本身是不支持直接联网的，因此角色不会移动。",[11,1140,1141],{},"而默认的Blank模板的Pawn是没有CharacterMoveMentComponent的，当然也就不会同步角色移动。",[21,1143,1144],{"id":1144},"属性复制",[11,1146,1147],{},"Replication系统的核心是复制，“真正”的UWorld更新是在Server上运行的，而Client上的UWorld则接受来自Server的复制更新，同时Client也会进行一定的“预测”，但是一切状态值以Server上的为准。这样的设计下，只要采用合理的逻辑，就可以杜绝作弊。",[11,1149,1150],{},"UE4中Replication在设置完成后是自动运行的，在使用时只要留意，凡是需要所有客户端都获得的状态更新，包括Spawn、Replicate的值，都必须在服务器上进行。在客户端上进行修改只会让他们在下一次同步时被覆盖掉。",[398,1152,1153],{"id":1153},"开启复制",[11,1155,1156],{},"属性复制可以在Actor的属性面板中开启",[11,1158,1159],{},[43,1160],{"alt":45,"src":1161},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb.png",[11,1163,1164],{},"C++中则需要在构造函数中指定",[101,1166,1169],{"className":1167,"code":1168,"language":106},[104],"bReplicates = true;\n",[108,1170,1168],{"__ignoreMap":110},[11,1172,1173],{},"开启复制之后，对于具体的属性，只要指定为可复制即可：",[11,1175,1176],{},[43,1177],{"alt":45,"src":1178},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-1.png",[11,1180,1181],{},"其中Replicated就是复制模式，而RepNotify会在属性被变更时触发一个通知事件，非常的有用。在蓝图下，这个事件会自动生成：",[11,1183,1184],{},[43,1185],{"alt":45,"src":1186},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-2.png",[11,1188,1189],{},"而在C++模式下，就要做一些额外的设定操作，除了指定属性可复制之外",[101,1191,1194],{"className":1192,"code":1193,"language":106},[104],"UPROPERTY(Replicated)\nuint32 bFlag:1;\n",[108,1195,1193],{"__ignoreMap":110},[11,1197,1198],{},"还要设置同步属性表",[101,1200,1203],{"className":1201,"code":1202,"language":106},[104],"void AReplicatedActor::GetLifetimeReplicatedProps(TArray\u003C FLifetimeProperty > & OutLifetimeProps) const\n{\nDOREPLIFETIME(AReplicatedActor, bFlag);\nDOREPLIFETIME(AReplicatedActor, IntegerArray);\n}\n",[108,1204,1202],{"__ignoreMap":110},[11,1206,1207],{},"如果要使用RepNotify的话，就必须在UPROPERTY宏中进行指定",[101,1209,1212],{"className":1210,"code":1211,"language":106},[104],"UPROPERTY(ReplicatedUsing=OnRep_Flag)\nuint32 bFlag:1;\n",[108,1213,1211],{"__ignoreMap":110},[11,1215,1216],{},"然后实现这个函数方可。",[398,1218,1219],{"id":1219},"条件复制",[11,1221,1222],{},"Replication有一个重要的设置是条件复制，在蓝图中可以看到选项",[11,1224,1225],{},[43,1226],{"alt":45,"src":1227},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-3.png",[11,1229,1230],{},"意思都比较明显，关于Owner以及Simulated、Autonomous的意思在下面会有说明，而Replay指代的是官方的回放系统，如果没有使用到的话就可以不用管它。",[11,1232,1233],{},"C++中要调整这个属性则需要用到属性表：",[101,1235,1238],{"className":1236,"code":1237,"language":106},[104],"void AActor::GetLifetimeReplicatedProps( TArray\u003C FLifetimeProperty > & OutLifetimeProps ) const\n{\n  DOREPLIFETIME_CONDITION( AActor, ReplicatedMovement, COND_SimulatedOnly );\n}\n",[108,1239,1237],{"__ignoreMap":110},[11,1241,1242],{},"在C++中还可以使用额外的自定义标志位来进行更新控制：",[101,1244,1247],{"className":1245,"code":1246,"language":106},[104],"void AActor::PreReplication( IRepChangedPropertyTracker & ChangedPropertyTracker )\n{\n  DOREPLIFETIME_ACTIVE_OVERRIDE( AActor, ReplicatedMovement, bReplicateMovement );\n}\n",[108,1248,1246],{"__ignoreMap":110},[11,1250,1251,1252,1256],{},"但是这样可能会降低效率，更详细的说明可以参照[",[127,1253,297],{"href":1254,"rel":1255},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002FActors\u002FProperties\u002FConditions\u002Findex.html",[220],"]。另外，在文档中有说明，属性表是无法在同步确立之后进行修改的。",[398,1258,1259],{"id":1259},"参数设置",[11,1261,1262],{},"Replication中还有一些可选参数，其中比较重要的是Relevant机制，这个机制通过判定Actor当前是否与Player相关来有效的削减同步的数据量",[11,1264,1265],{},"Always Relevant会使得Actor始终被同步到各个客户端",[11,1267,1268],{},"Net Use Owner Relevancy则会让Actor的同步相关性与持有者相同",[11,1270,1271],{},"Only Relevant To Owner使得Actor只与自己的持有者保持相关",[11,1273,1274],{},"这里Owner的定义指代持有其的PlayerController或者Pawn，而是否相关的判定机制是可以重载的，在类中对IsNetRelevantFor函数进行重载即可。",[11,1276,1277],{},"还有一个特殊的标志位是bTearOff，一旦Server设置了这个值，Replication机制会将控制权交给所有的Client，实质上Actor就变成了不同步的物体，由各个Client进行控制。",[11,1279,1280],{},"而ReplicateMovement将会使得物体的移动被同步，也就是速度、位置这些信息。",[11,1282,1283],{},"bNetLoadOnClient会使得物体在Server上加载\u002FSpawn后在Client上被自动加载。",[21,1285,1286],{"id":1286},"远程调用",[11,1288,1289],{},"远程函数调用提供Clinet与Server之间的函数调用桥梁，调用模式分为三种：",[11,1291,1292],{},[43,1293],{"alt":45,"src":1294},"\u002Fwp-content\u002Fuploads\u002F2017\u002F04\u002Fimage_thumb-4.png",[11,1296,1297],{},"选中蓝图中的函数就能看到这个设置，它们之间的区别如下：",[32,1299,1300,1303,1306],{},[11,1301,1302],{},"NetMulticast: 多路传送，由服务器调用所有的客户端，比如用于播放特效",[11,1304,1305],{},"Server: 在服务器上运行，只有角色标志为ROLE_Authority或ROLE_AutonomousProxy的Actor才能调用",[11,1307,1308],{},"Client: 在其所属的客户端上运行",[11,1310,1311],{},"其中，Server函数还可以提供额外的校验函数以方便对传输到服务器的值进行检测，这个设置只能在C++中使用，蓝图中并未看到相关选项：",[101,1313,1316],{"className":1314,"code":1315,"language":106},[104],"UFUNCTION(Server, Reliable, WithValidation)\nvoid Server_ReliableFunctionCallThatRunsOnServer();   UFUNCTION(Client, Reliable)\nvoid Client_ReliableFunctionCallThatRunsOnOwningClientOnly();   UFUNCTION(NetMulticast, Unreliable)\nvoid Client_UnreliableFunctionCallThatRunsOnAllClients();\n",[108,1317,1315],{"__ignoreMap":110},[11,1319,1320],{},"对函数进行实现",[101,1322,1325],{"className":1323,"code":1324,"language":106},[104],"void AReplicatedActor::Server_ReliableFunctionCallThatRunsOnServer_Implementation()\n{\n}\nbool AReplicatedActor::Server_ReliableFunctionCallThatRunsOnServer_Validate()\n{\n   \u002F\u002F 返回FALSE的话该Server函数就不会被执行\n   return true;\n}\nvoid AReplicatedActor::Client_ReliableFunctionCallThatRunsOnOwningClientOnly()\n{\n}   \nvoid AReplicatedActor::Client_UnreliableFunctionCallThatRunsOnAllClients_Implementation()\n{\n\n}\n",[108,1326,1324],{"__ignoreMap":110},[11,1328,1329],{},"函数还可以选择是否为可靠函数，UnReliable的设定将会使得引擎在网络带宽压力大的情况下丢弃这个函数的执行。",[11,1331,1332],{},"应当尽量避免频繁的远程调用以及在远程调用中直接传输并设置Server参数的行为，而只将输入传递到Server。",[21,1334,1335],{"id":1335},"游戏架构",[11,1337,1338,1339,222],{},"官方的指引文档中就有介绍过游戏架构,但实际上在做单机游戏时,将逻辑放在很多类中其实是等效的,这点会让人很迷惑。其实架构中的许多类是在多人模式下才会体现出他们的不同的，详细的内容可以看[",[127,1340,297],{"href":1341,"rel":1342},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002FBlueprints\u002Findex.html",[220],[1344,1345,1346,1350,1353,1356,1359,1362],"ul",{},[1347,1348,1349],"li",{},"GameInstance: 不参与网络复制，作为游戏实例而存在，在关卡切换载入时依然存在，可以用于存储用户本身的数据",[1347,1351,1352],{},"GameMode: 只存在于Server上，核心逻辑存在于这里",[1347,1354,1355],{},"GameState: 在Server和所有Client之间复制，用于存储当前游戏相关的数据",[1347,1357,1358],{},"PlayerController: 每个Client只持有自己的PlayerController，并与Server进行同步",[1347,1360,1361],{},"PlayerState: 存储每个Player的信息，会同步到所有的Client。",[1347,1363,1364],{},"Pawns: 与所有的Client同步，但是与PlayerController以及PlayerState不同的是，Pawn死亡之后会被销毁，直到Respawn为止。",[398,1366,1368],{"id":1367},"role与同步","Role与同步",[11,1370,1371],{},"Server和Client都可以使用Role系统来判断持有的Actor状态，Role被分为四种",[11,1373,1374],{},"ROLE_Authority：对Actor拥有控制权",[11,1376,1377],{},"ROLE_None：不参与网络复制",[11,1379,1380],{},"ROLE_SimulatedProxy：接受来自服务器更新的模拟控制器，使用已知信息进行预判",[11,1382,1383],{},"ROLE_AutonomousProxy：接受来自玩家的直接控制，配合玩家输入进行预判",[11,1385,1386],{},"客户端的模拟预判可以有效防止网络延迟引起的Lag，但是在处理角色动作，尤其是Blink之类的操作时，依然会导致奇怪的现象。",[11,1388,1389,1390,222],{},"为了防止这些现象，官方对CharacterMovmentComponent作了增强，这个类会维护一个动作列表，在同步时，Client会被要求进行动作列表的重放，而不是直接更新位置。详细的可以参考[",[127,1391,297],{"href":1392,"rel":1393},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002FCharacterMovementComponent\u002Findex.html",[220],[21,1395,1397],{"id":1396},"misc","Misc",[11,1399,1400,1401,222],{},"UE4提供了服务器进行无缝和非无缝关卡切换的函数，其具体的限制和功能可参看[",[127,1402,297],{"href":1403,"rel":1404},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002FTravelling\u002Findex.html",[220],[11,1406,1407,1408,222],{},"同时，为了提供轻便的服务器状况查询，而不是被强制加载关卡，UE4有提供Online Beacons的功能，由于不会用到就没有继续研究了，详情见[",[127,1409,297],{"href":1410,"rel":1411},"https:\u002F\u002Fdocs.unrealengine.com\u002Flatest\u002FCHN\u002FGameplay\u002FNetworking\u002FOnlineBeacons\u002Findex.html",[220],{"title":110,"searchDepth":239,"depth":240,"links":1413},[1414,1415,1420,1421,1424],{"id":1109,"depth":239,"text":1109},{"id":1144,"depth":239,"text":1144,"children":1416},[1417,1418,1419],{"id":1153,"depth":240,"text":1153},{"id":1219,"depth":240,"text":1219},{"id":1259,"depth":240,"text":1259},{"id":1286,"depth":239,"text":1286},{"id":1335,"depth":239,"text":1335,"children":1422},[1423],{"id":1367,"depth":240,"text":1368},{"id":1396,"depth":239,"text":1397},"2017-04-20",{"layout":250,"status":251,"published":252,"author":1427,"author_login":255,"author_email":256,"wordpress_id":1428,"wordpress_url":1429,"date_gmt":1430,"excerpt":1431},{"display_name":254,"login":255,"email":256,"url":110},1599,"\u002F\u002F?p=1599","2017-04-20 15:03:17 +0000",{"type":8,"value":1432},[1433],[11,1434,1103],{},"\u002F2017-04-20-ue4-multiplayer-and-network-overview",{"title":1098,"description":1103},"_legacy\u002F2017\u002F2017-04-20-ue4-multiplayer-and-network-overview",[268,1439,1440],"MultiPlayer","Replication","PzRHWaoekIzNbx0GPfE_pJ_Ew2JUROcQg2OmymCgFC8",85,1788763178319]