Instructions

  • It is very important that you should click the option button to set your keys ,and save it
  • Click the Multiplayer button to enter the character selection interface
    • V1.0 has no AI so we can not click SinglePlayer button
  • Use the LEFT and RIGHT buttons and A button in your button settings to select and confirm the role
    • ps. A Key is not the keyboard A, but the A in the game, generally the default is U key
  • Then player 1 can use the same operation to select the map
  • After that,let’s battle!

Charcater Moves

WuKong

NormalMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
+B/D 3
+A/C 4
SpecialMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
MaxSpecialMoves
Keys Moves(temp name)
(In Max Stat)+A/C 1

Aurora

NormalMoves
Keys Moves(temp name)
+A/C 1
+A/C 2
+B/D 3
+B/D 4
SpecialMoves
Keys Moves(temp name)
+A/C 1
+B/D 2
MaxSpecialMoves
Keys Moves(temp name)
(In Max Stat)+A/C 1

本文介绍路径点的优化方式,或者叫做多线段优化、轨迹点优化

根据设定阈值,去掉路径点中的部分多余点,以达到方便传输的目的

本文用UE4蓝图和C++蓝图函数库的2个方式解释

注:多种方法可以叠加使用

本算法提供了基于UE4的Demo,PC安卓

GitHub工程下载

Demo演示动图

阅读全文 »

角色位移本质上就是渲染问题,从这条思路我们去看看角色怎么实现这一部分渲染的

起点:SetActorLocation

蓝图调用的API实际调用的是K2_SetActorLocation,然后调用到此函数

1
return RootComponent->MoveComponent(Delta, GetActorQuat(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);

只看这个关键代码,调用RootComponentMoveComponent

USceneComponent
MoveComponent

外部调用此函数后里面直接调用到带Impl后缀的虚函数

这个函数在UPrimitiveComponent内有重写

USceneComponent内主要调用了函数ConditionalUpdateComponentToWorld,而UPrimitiveComponent因为有碰撞等属性所以又多了很多逻辑,这里不多描述

UpdateComponentToWorldWithParent

此函数通过ConditionalUpdateComponentToWorld调用,后又调用到虚函数UpdateComponentToWorld,此虚函数在UActorComponent声明,在USceneComponent重写

然后再直接调用到此函数

1
2
3
4
5
6
7
8
9
if (Parent && !Parent->bComponentToWorldUpdated)
{
Parent->UpdateComponentToWorld();

if (bComponentToWorldUpdated)
{
return;
}
}

如果有父级组件,先去处理他

然后调用到函数PropagateTransformUpdate

PropagateTransformUpdate

该函数执行了

  • 更新体积(Bounds)
  • 更新变换(Transform)
    • 更新Attach的子组件的变换信息
  • 更新寻路信息

其中有个关键的函数UActorComponent::MarkRenderTransformDirty

然后经过简单判断以后调用UActorComponent::MarkForNeededEndOfFrameUpdate

然后调用了UWorldMarkActorComponentForNeededEndOfFrameUpdate

看主要代码

1
2
3
4
5
6
7
8
void UActorComponent::MarkRenderTransformDirty()
{
if (IsRegistered() && bRenderStateCreated)
{
bRenderTransformDirty = true;
MarkForNeededEndOfFrameUpdate();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void UActorComponent::MarkForNeededEndOfFrameUpdate()
{
if (bNeverNeedsRenderUpdate)
{
return;
}

UWorld* ComponentWorld = GetWorld();
if (ComponentWorld)
{
ComponentWorld->MarkActorComponentForNeededEndOfFrameUpdate(this, RequiresGameThreadEndOfFrameUpdates());
}
else if (!IsUnreachable())
{
// 如果没有世界,执行如下代码
DoDeferredRenderUpdates_Concurrent();
}
}
UWorld
MarkActorComponentForNeededEndOfFrameUpdate

代码在LevelTick.cpp L883

主要执行了把组件添加到了等待渲染的列表ComponentsThatNeedEndOfFrameUpdate_OnGameThread里,当然还有一个非游戏线程的ComponentsThatNeedEndOfFrameUpdate

1
2
3
4
5
6
7
8
9
10
if (bForceGameThread)
{
FMarkComponentEndOfFrameUpdateState::Set(Component, ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num(), EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);
ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Add(Component);
}
else
{
FMarkComponentEndOfFrameUpdateState::Set(Component, ComponentsThatNeedEndOfFrameUpdate.Num(), EComponentMarkedForEndOfFrameUpdateState::Marked);
ComponentsThatNeedEndOfFrameUpdate.Add(Component);
}

到此我们大概知道了这一条线的终点了,然后去看看哪里使用到了这个数组

SendAllEndOfFrameUpdates

确认使用上面数组是在此函数内,查找引用发现此函数在很多地方被调用,如SceneRendering.cpp,UnrealEngine.cpp大概就是要更新场景最新的渲染的时候就会调用,先不理会,看这个函数内实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
{
if (Component)
{
if (Component->IsRegistered() && !Component->IsTemplate() && !Component->IsPendingKill())
{
FScopeCycleCounterUObject ComponentScope(Component);
FScopeCycleCounterUObject AdditionalScope(STATS ? Component->AdditionalStatObject() : nullptr);
Component->DoDeferredRenderUpdates_Concurrent();
}

check(Component->IsPendingKill() || Component->GetMarkedForEndOfFrameUpdateState() == EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);
FMarkComponentEndOfFrameUpdateState::Set(Component, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked);
}
}
ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Reset();
ComponentsThatNeedEndOfFrameUpdate.Reset();

遍历了所有数组成员,主要执行了DoDeferredRenderUpdates_Concurrent,回头看USceneComponent内的如果没有UWorld就直接调用了这个函数

UActorComponent
DoDeferredRenderUpdates_Concurrent

RecreateRenderState_Concurrent,SendRenderTransform_Concurrent,SendRenderDynamicData_Concurrent告诉引擎需要处理相应的渲染任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void UActorComponent::DoDeferredRenderUpdates_Concurrent()
{
checkf(!IsUnreachable(), TEXT("%s"), *GetFullName());
checkf(!IsTemplate(), TEXT("%s"), *GetFullName());
checkf(!IsPendingKill(), TEXT("%s"), *GetFullName());

FScopeCycleCounterUObject ContextScope(this);

if(!IsRegistered())
{
UE_LOG(LogActorComponent, Log, TEXT("UpdateComponent: (%s) Not registered, Aborting."), *GetPathName());
return;
}

if(bRenderStateDirty)
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentRecreate);
RecreateRenderState_Concurrent();
checkf(!bRenderStateDirty, TEXT("Failed to route CreateRenderState_Concurrent (%s)"), *GetFullName());
}
else
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentLW);
if(bRenderTransformDirty)
{
// Update the component's transform if the actor has been moved since it was last updated.
SendRenderTransform_Concurrent();
}

if(bRenderDynamicDataDirty)
{
SendRenderDynamicData_Concurrent();
}
}
}
SendRenderTransform_Concurrent

这个函数在基类只做了基础实现,主要是派生类重写实现主要逻辑,类似的也包括SendRenderDynamicData_Concurrent,CreateRenderState_Concurrent

1
2
3
4
5
6
7
8
{
check(bRenderStateCreated);
bRenderTransformDirty = false;

#if LOG_RENDER_STATE
UE_LOG(LogActorComponent, Log, TEXT("SendRenderTransform_Concurrent: %s"), *GetPathName());
#endif
}
UPrimitiveComponent

紧接上面,我们看看有形状体积等信息的UPrimitiveComponent

SendRenderTransform_Concurrent
1
2
3
4
5
6
7
8
9
10
11
void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
UpdateBounds();
const bool bDetailModeAllowsRendering = DetailMode <= GetCachedScalabilityCVars().DetailMode;
if( bDetailModeAllowsRendering && (ShouldRender() || bCastHiddenShadow))
{
GetWorld()->Scene->UpdatePrimitiveTransform(this);
}

Super::SendRenderTransform_Concurrent();
}

看到在实现基类逻辑之前执行了FScene内的函数UpdatePrimitiveTransform

FScene

代码在 RendererScene.cpp中

UpdatePrimitiveTransform

UpdatePrimitiveTransform函数中会判断该Primitive有没有SceneProxy,如果有SceneProxy则判断该Primitive是否需要重新创建,如果需要重新创建则需要先删除,然后再添加该Primitive;如果没有SceneProxy则直接加入到场景中。接着我们具体看一下AddPrimitive函数的具体实现:

AddPrimitive

此函数主要做了如下几件事情

  • 创建SceneProxyPrimitiveSceneInfo
  • 利用FPrimitiveSceneInfo封装UPrimitiveComponent的信息,继续在render线程中进行操作
  • 给渲染线程发送2个命令,分别是设置PrimitiveTransform信息和将PrimitiveSceneInfo加入到渲染线程的数据集合中

代码如下

1
2
3
4
5
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
// Create the primitive scene info.
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
1
2
3
4
5
6
7
8
9
10
11
// Create any RenderThreadResources required.
ENQUEUE_RENDER_COMMAND(CreateRenderThreadResourcesCommand)(
[Params](FRHICommandListImmediate& RHICmdList)
{
FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);

// Create any RenderThreadResources required.
SceneProxy->CreateRenderThreadResources();
});
1
2
3
4
5
6
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[Scene, PrimitiveSceneInfo](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(PrimitiveSceneInfo->Proxy->GetStatId());
Scene->AddPrimitiveSceneInfo_RenderThread(RHICmdList, PrimitiveSceneInfo);
});
AddPrimitiveSceneInfo_RenderThread

AddPrimitiveSceneInfo_RenderThread函数是在渲染线程执行的,完成PrimitiveBoundsPrimitiveFlagsCompact等数据的初始化后,并调用LinkAttachmentGroup处理primitive的父子关系,调用LinkLODParentComponent处理LOD父子关系,然后调用FPrimitiveSceneInfo::AddToScene

AddToScene最终会把物体添加到列表DrawList 中去完成绘制,这里不继续深挖了,埋个扣子,以后来解开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Add the primitive to its shadow parent's linked list of children.
// Note: must happen before AddToScene because AddToScene depends on LightingAttachmentRoot
PrimitiveSceneInfo->LinkAttachmentGroup();

// Set lod Parent information if valid
PrimitiveSceneInfo->LinkLODParentComponent();

// Add the primitive to the scene.
const bool bAddToDrawLists = !(CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread() && !WITH_EDITOR);
if (bAddToDrawLists)
{
PrimitiveSceneInfo->AddToScene(RHICmdList, true);
}
else
{
PrimitiveSceneInfo->AddToScene(RHICmdList, true, false);
PrimitiveSceneInfo->BeginDeferredUpdateStaticMeshes();
}
小结

由此我们就大致整理一下思路

设置Actor的位置,其实就是把新旧位置做一个插值,把这个插值传递给继承自USceneComponentMoveComponent方法,然后在下一帧渲染出新的位置

这里也接上了并解释了之前AI_MoveTo的内容了

上一张流程简图

补充
场景中静态物体的渲染顺序堆栈表参考
1
2
3
4
5
6
7
8
9
10
11
ActorComponet::ExecuteRegisterEvents
UPrimitiveComponent::CreateRenderState_Concurrent
FScene:: AddPrimitive
FScene:: AddPrimitiveSceneInfo_RenderThread
FScene:: AddStaticMeshes
FStaticMesh::AddToDrawLists
FMobileBasePassOpaqueDrawingPolicyFactory::AddStaticMesh
ProcessMobileBasePassMesh
FDrawMobileBasePassStaticMeshAction:: Process
AddMeshToStaticDrawList
//加入到scene GetMobileBasePassDrawList中

最终加入队列 以DrawingPolicykey map队列

1
TStaticMeshDrawList<TMobileBasePassDrawingPolicy<FUniformLightMapPolicy, 0> > MobileBasePassUniformLightMapPolicyDrawList[EBasePass_MAX];

前言

如图所示,我们经常在UE4内看到如此的异步节点,简单说此类节点的输出并非像函数一样瞬间完成,而是拥有自己的生命周期,此类节点一般在右上角有一个时钟标志

本文讲解如何制作类似AI_MoveTo的异步节点

另一篇AI_MoveTo简单分析和扩展介绍AI_MoveTo的简单运行机制和其他扩展方式


  • 2020.11.24更新:

更新各类移动扩展函数库以及K2Node

github

image-20201124134801666

  • 2021.2.25更新:

基于UBlueprintAsyncActionBase的异步节点 跳转

阅读全文 »

UK2Node_AIMoveTo

这是我们最熟悉的编辑器模式下的AI_MoveTo节点,也就是那个自带OnSuccess和OnFailed的异步节点,在蓝图中大多数时候使用起来都得心应手非常方便,但是它有个最大的缺点,异步节点意味着他有自己的生命周期,不能放到函数中使用,同样的,在代码中也没有此类节点,那么接下来我们尝试解决这一问题

翻看此节点的代码,没几行代码,主要看如下

阅读全文 »

UE4自带的DestructibleMesh系统的破碎方式比较单一,可以用英伟达的ApexPhysXLab工具制作自定义破碎文件,然后导入UE4

本文介绍通过使用这一工具制作UE4破碎物体的流程

工具下载地址点击下载

PhysXLab流程

如下图所示,导入3D模型

三种破碎方式

Slice模式

如下图所示,红色框内为主要设置的参数

效果如下

Cutout模式

主要用黑白通道图来切割,注意黑白通道只是平面投射,跟UV无关,所以纹理如果要配合切割需要单独制作配合好的纹理及黑白图

效果如下

Voronoi模式

类似UE4内的破碎

效果如下

导出

UE4流程

用一个模拟子弹来制造破碎效果

最近由于项目需要用到鼠标在场景利绘制曲线,方案有很多,比如SplineMesh,ProceduralMesh 等等,回头一想用特效也可以试试

本案例使用虚幻4.22版本演示

用Niagara的Ribbon绘制样条线的功能

模块函数 NiagaraModuleScript
  • 模块函数是Niagara系统的函数库,是niagara系统最主要区别于老例子系统的其中一点

创建函数库 NM_Test1

完成如上节点连接

注意:变量的前缀是对应模块,这个不能随意更改,创建的时候就选好模块会自动添加前缀;如果是系统已有变量,就不需要自己更改名称,如Emitter.age,表示发射器的生命周期

  • User.SP:User分类下的Spline变量,本案例用下面参数代替
  • Module.Count:函数输入参数,本函数中用于控制发射数量
  • SampleSplinePositionByUnitDistanceWS:输入0-1输出曲线从开始到结束的世界空间位置
  • ExecutionIndex:当前粒子的序号

简单描述这个函数的功能就是根据发射粒子在所有发射粒子中的百分比对应到曲线spline的长度百分比

粒子发射器 Emitter

随意创建一个发射器,删除多余节点,配置图所示

刚开始比较容易混淆的概念是 Emitter的spawn,update与Particle的Spawn,Update的概念

  • Emitter针对的就是整个发射器的操作,约等于老系统的Required和部分Spawn的内容,如整个发射器的生命周期,是否循环
  • Particle控制每个粒子的属性和功能,如每个粒子的大小颜色等
  • 不恰当的比喻就是Emitter就是Actor,Particle就是Component

图中关键节点解释

  • 创建3个User变量,User变量可以到外部设置
  • 创建Emitter模块的Emitter.SpawnCount变量,暴力一点可以直接跳过直接用User的
  • 发射器创建的时候用User.SpawnCount设置Emitter.SpawnCount
  • Emitter.SpawnCount去设置SpawnBurstInstaneous的发射数量
  • 修改Render模块中的粒子类型为Ribbon
  • 设置Ribbon的宽度
  • 在Spawn(在Update也可以) 中加上自定义的函数NM_Test1,并把EmitterSpawnCount设置为输入参数
粒子系统 NiagaraSystem

汇总所有Emitter的集合,同时可以实例化到场景或者类里的类;同时也可以再次修改Emitter中所有参数

如图所示,可以使用多个发射器,可以设置User模块的参数,可以拖拽不同发射器的作用时间(类似动画)

本案例中只需要添加一个发射器

画线类 DrawItem

  • 目前版本貌似没有更直观的方法设置Spline变量,强行用设置Actor变量尽然是可以的(lll¬ω¬)

  • 提供对外函数AddPoint(FVector)用于添加曲线点

PlayerController

不要忘记开启项目设置里Input模块下的UseMouseForTouch

蓝图比较简单,简单描述就是在鼠标按下移动后用射线点通过DrawItem添加曲线点

总结
  1. 用Niagara类绘制曲线模型算是比较简单方便的,Niagara在性能优化上也不错
  2. 曲线在长度到一定程度后会有明显分段情况出现,不够圆滑,目前版本没有公开Ribbon的设置段数的变量
  3. 据说Niagara在4.25才是正式版,目前无法保证bug问题

这是一份Multiplayer RPG Template的说明文档,内容包含其中主要系统的配置和制作方法

此案例是基于UE4的ListerServer建立的局域网联机游戏demo,参考作品WOW

基于UE4版本4.25

demo尚且有未完善的地方,后续会跟进优化和修复

技能系统

如上图所示,此图为技能表的配置信息,下面简述其中重要的参数

  • Action: 这个变量代表这个行为的唯一ID,原则上跟表格的RowName相同,很多逻辑中需要通过这个ID查找关于这个技能的所有信息,或者通过这个ID执行这个技能等
  • Display(struct):3个参数用来显示这个技能一般信息,比如名字或者说明还有图标的显示,在UMG上有相关的体现,当然你也可以用来自定义
  • CastData(struct):这个是一个施法的结构体,主要设置的是施法的方式和条件
    • Type:比如默认的读条施法,持续施法,攻击叠加类的施法等方式
    • Time:施法所需要的时长,0意味着瞬发
    • Duration:如果是持续施法,那么这个参数意味着引导时间
    • KeepAttackStat:施法完以后会继续进行普通攻击
    • MotionCast:移动施法,设置为否的话必须站立施法,一旦移动就打断施法
    • NotTargetCast:不需要目标的施法,一般用于范围技能比如火雨之类
  • Mp:消耗魔法值,战士为怒气值
  • CD:冷却时间
  • MaxRange:最大释放范围,按照虚幻单位计算,0意味着无限制
  • LearnGold/Level:学习该技能的前置条件,0意味着无要求
  • RangeSelectRange:如果是范围选择的技能,此参数表示作用的半径
  • TargetType:目标类型,作用地方还是友方等
  • SkillClass:技能实例的类,无需手动创建
SkillRef

这个是通过表格配置的SkillClass创建的,无需手动创建,你只需要制作这个类的逻辑和效果

这个类是继承自BP_BaseSkill,里面有一些乱七八糟的蓝图逻辑,不过并不是全部都是必须使用的,这个类很多情况下你可以自由发挥,反正技能释放了以后就通过这个类来控制这个技能实例的运行

下面就通过IceBullet这个技能类来说明一下

IceBullet继承于ProjectileBase,如上图所示,这是这个类的基本参数类型

  • speed:飞行速度,依赖ProjectileComponent
  • StopDistance:停止的距离,因为这个类是追踪目标飞行的,所以会总有接近并且到达目标的时候,到达目标并停止以后会产生一段逻辑
  • Duration:持续时间,如果没有命中到达时间以后也会摧毁自己
  • Sound/FX:相关的特效配置,Start可以用于产生的时候播放,Hit可以用于打中目标以后播放,但都需要手动释放,
    • ProjectileBase已经实现了BeginPlay以后调用Start的效果
    • 在打中目标后已经实现播放Hit的效果
  • BuffData:如果这个技能有buff/debuff可以设置这个类,需要手动调用方法来创建并运行buff类,如下图

BuffRef

这是Buff的实例类,通过SkillRef内部调用CreateBuff方法创建

通过重写BuffInitBuffEnd方法来实现自定义的逻辑,如果是拥有持续效果,那么你可以重写BuffDelta 方法来制作持续调用的逻辑

装备系统

如上图所示,这个是装备信息的配置表,下面简单介绍其中主要参数的作用

  • BodyPart:装备对应的位置
  • Mesh/Mat:用于显示这个装备实体的模型,装备在人物上或者丢弃到世界中的显示
  • Property:如字面意思就是2种属性,其中Adv属性会随着Base的更改而变化

道具系统

道具系统类似于装备系统

  • EffectType:作用类型,本案例只制作了治疗效果和增加魔法的效果
  • Value: 可以设置作用效果的范围值,当然这个不是必须的,具体是下面的道具类内部实现
  • CanBeStacked:如果是否那么一个道具占据背包栏的一格,否则么可以堆放到一起
  • ActionClass:道具类实现逻辑的类,参考Skill的实例实现,具体逻辑可以写到这个类里面

掉落系统

如上图所示,掉落系统的配置放在DataSystem 这个类里面,为什么没有配置表格呢?O(∩_∩)O

当然也可以,你可以自己创建一个类似的表格然后替换其中的逻辑,这个很简单

这个配置信息是一个Map键值对,键对应的是角色的类,值即是掉落信息数组

  • Action:选择装备/道具等的Action名称
  • Type:选择是装备/道具,当然你要是想掉落技能的话理论上也可以,请自由发挥
  • Amount:掉落数量
  • Probability:概率,这个概率计算是在角色创建的时候就完成的,非击杀的时候

任务系统

如上图所示,这是任务表格的配置信息

  • Titile:任务的标题,会显示在任务栏内的抬头标题部分
  • Target:任务的目标
  • DynamicTargetStat:这个会动态的刷新,具体实现的逻辑在下面的TaskClass内
  • Descripion:任务描述
  • Reward:奖励
TaskRef

如上图所示,每接受一个任务以后就会产生一个任务类来作为该任务的观察者(代理)

具体这个任务是干什么的你可以通过实现这个类内的逻辑来管理,同时设置任务目标里的动态信息

其他

怪物刷新

怪物刷新是在场景里放置一个BP_AIGenerator管理类来完成的

  • MaxNum:刷新的最大数量
  • AIClass:设置刷新角色的类,超过1个的时候就随机从中刷新
  • Range:以该管理类的位置为中心的半径内刷新怪物

This is Multiplayer RPG Template Documentation,its about configuration and production method of the main system

This case is based on UE4’s ListerServer built-in LAN online game demo, refer to the work WOW

Based on UE4 version 4.25

There are still some imperfections in the demo, which will be followed up for optimization and repair

Skill system

As shown in the above figure, this figure is the configuration information of the skill table. The important parameters are briefly described below

  • Action: This variable represents the unique ID of this behavior. In principle, it is the same as the RowName of the table. In many logics, you need to use the ID to find all information about the skill, or use the ID to execute the skill
  • Display(struct):3 parameters are used to display the general information of this skill, such as the display of the name or description and icons, which are related to the UMG, of course, you can also customize
  • CastData(struct):This is a spell-casting structure, which mainly sets the method and conditions of the spell-casting
    • Type:For example, the default reading spell casting, continuous casting, attack superimposing casting and other methods
    • Time:The time it takes to cast, 0 means instant
    • Duration:If it is a continuous cast, then this parameter means the lead time
    • KeepAttackStat:After the cast is finished, the normal attack will continue
    • MotionCast:Move cast, if set to No, you must stand cast, once you move, interrupt cast
    • NotTargetCast:Does not require target casting, and is generally used for range skills such as Fire and Rain
  • Mp:Mana cost, Warrior Rage
  • CD:Cooling time
  • MaxRange:Maximum release range, calculated in Unreal Units, 0 means unlimited
  • LearnGold/Level:Prerequisites for learning this skill, 0 means no requirement
  • RangeSelectRange:If it is a skill selected by range, this parameter indicates the radius of the effect
  • TargetType:Target type, place of action or friend
  • SkillClass:Skill class, no need to create manually
SkillRef

This is created by the SkillClass configured in the table, there is no need to manually create it, you only need to make the logic and effects of this class

This class is inherited from BP_BaseSkill, which has some messy blueprint logic, but not all are necessary to use. In this case, you can freely play in many cases. Anyway, after the skill is released, you can control this skill through this class Running the instance

Let’s explain it through the skill class of IceBullet

IceBulletInherit fromProjectileBase,As shown in the figure above, this is the basic parameter type of this class

  • speed: flight speed, depends on ProjectileComponent
  • StopDistance: the distance to stop, because this class is to track the target flight, so there will always be when approaching and reaching the target, a logic will be generated after reaching the target and stopping
  • Duration: Duration, if there is no hit, the time will destroy yourself
  • Sound / FX: related special effects configuration, Start can be used to play when it is generated, Hit can be used to play after hitting the target, but all need to be manually released
       - The effect of calling Start after ProjectileBase has realized BeginPlay
       - The Hit effect has been achieved after hitting the target
  • BuffData: If this skill has buff / debuff, you can set this class, you need to manually call the method to create and run the buff class, as shown below

BuffRef

This is an instance class of Buff, created by internally calling CreateBuff method of SkillRef

By rewriting the BuffInit and BuffEnd methods to achieve custom logic, if it has a continuous effect, then you can rewrite the BuffDelta method to make the logic of continuous calls

EquipSystem

As shown in the figure above, this is the configuration table of the equipment information. The following briefly introduces the role of the main parameters

  • BodyPart: the corresponding position of the equipment
  • Mesh/Mat: Used to display the model of this equipment entity, equipped on the character or discarded into the world
  • Property: literally means 2 kinds of properties, of which Adv property will change with the change of Base

PropSystem

The prop system is similar to the equipment system

  • EffectType:Effect type, this case only made the healing effect and the effect of increasing magic
  • Value: You can set the range value of the effect, of course, this is not necessary, specifically the internal implementation of the following prop class
  • CanBeStacked:If one item occupies one space in the backpack bar, otherwise it can be stacked together
  • ActionClass:Prop class implement logic class, refer to Skill instance implementation, specific logic can be written into this class

DropSystem

As shown in the figure above, the configuration of the drop system is placed in the DataSystem class. Why is there no configuration table? O (∩_∩) O

Of course, you can create a similar table yourself and replace the logic in it. This is very simple.

This configuration information is a Map key-value pair, the key corresponds to the role class, and the value is the drop information array

  • Action:Select the action name of equipment / props, etc.
  • Type:Choose equipment / props. Of course, if you want to drop skills, you can theoretically do it. Please play freely.
  • Amount:number of drops
  • Probability:Probability, this probability calculation is done when the character is created, when not kill

TaskSystem

As shown in the figure above, this is the configuration information of the task table

  • Titile:the title of the task, which will be displayed in the header section of the task bar
  • Target:the target of the task
  • DynamicTargetStat:This will be refreshed dynamically, the specific implementation logic is in the following TaskClass
TaskRef

As shown in the figure above, each time a task is accepted, a task class will be generated as an observer (agent) of the task

Specifically what the task is for, you can manage it by implementing the logic in this class, while setting the dynamic information in the task goal

Other

Monster refresh

Monster refresh is done by placing a BP_AIGenerator management class in the scene

  • MaxNum: the maximum number of refreshes
  • AIClass: Set the class for refreshing the character, if more than one, it will be randomly refreshed from it
  • Range: refresh monsters within a radius centered on the position of the management class

笔者之前制作了一个游戏Demo蓝图工程上架了虚幻商城,本文记录虚幻商城上传蓝图工程的注意事项

准备工作

  1. 当然是制作一份自己的工程,不要轻易白嫖别人的工程/资源,很容易被逮到,当然也有例外,下文会提到
  2. 注册并登录Epic账号,到商城卖家页面准备提交内容
    1. 需要一个公开邮箱,用于跟官方以及买家的交流,包括退款或者错误问题交流
    2. 需要提供一个Paypal账户(目前不支持支付宝)来接受汇款
    3. Epic在每月月底统计销售额,需要大于100美元才会在45日之内汇款,略慢
  3. 准备一套图片,分辨率为1920x1080,这些贴图会提交为商品预览图片
    1. 这里其中一个图片需要设置为商城浏览时的预览图片,分辨率为284*284
    2. 还有一张图片会设置为主页上上的图片,分辨率为894x488

产品信息

序号 信息 备注
1 发布者名称,产品标题和标签不包含任何版权或商标名称 我上架的时候因为是一个MMORPG的案例,加了标签**”WOW”**,结果违反了这一条规定
2 发布者名称,产品标题和标签不包含Epic的任何商标或属性 不要添加跟Epic有关系的内容
2 发布者名称,产品标题和标签不包含令人反感或不适当的语言 显而易见
3 产品标题不包含主观语言 这个不太清楚意思
4 产品标题,说明文字和标签可准确反映产品内容 字面意思
5 类别与产品的内容和功能有关 这个也容易犯错,可能为了更容易被搜索到添加了无关的分类
6 产品标题,说明文字和标签为英文,包含正确的拼写和正确的语法 不要写中文
7 所有技术信息模板字段均填充有适当的信息 把信息写满就可以
8 提交处于“待批准”状态 这个是废话,正常走流程就可以

媒体资源

序号 信息 备注
1 图片不包含任何令人反感的图片 *
2 图像清晰且与产品的内容和功能相关 *
3 图片不会显示任何未经许可的第三方版权材料 推荐自己引擎里截图就可以,不要加有些明显版权的图片什么的
4 显示产品视觉内容的图像在虚幻引擎4中渲染 推荐引擎里截图

项目文件

基础
序号 信息 备注
1 每个项目文件链接仅承载一个具有适当文件夹结构的UE4项目或插件文件夹 你发给官方的链接也就是买家会下载的工程链接是只包含一个工程,这个一般也不会犯错
2 提供的项目与列出的支持的引擎版本匹配 版本不要搞错
3 分发方法适合产品的内容和功能 *
一般
序号 信息 备注
1 Content文件夹包含一个以项目命名的Pack文件夹 这个比较容易犯错,文件目录需要严格按照类似以下这种结构
MyProject
├── Config
├── Content
│ └── MyProject
├── MyProject.uproject |
2 Pack文件夹中的第一层文件夹是根据资产类型或特定资产命名的 你自己的文件夹里的第一个文件夹是资产类文件夹,推荐命名成Assets
3 所有资产类型都在各自的文件夹内 比如动画放到Animation文件夹,贴图放到Texture文件夹里,但是笔者并没有严格按照这个分类也没有被打道回府
4 项目不包含未使用的文件夹或资产 这个非常头大,你做项目的时候要谨慎添加资源比如帕拉共的资源,提交之前要删除所有未使用的资源,鉴于虚幻引擎清理资源的速度,可能这会花你几个晚上的时间;ps:笔者因为几张未使用的贴图被打回一次
5 所有重定向器均已清理 制作的时候注意清理/修复路径就可以了
6 命名约定为英语,字母数字,在整个项目中保持一致,并描述资产的含义 不要用中文,所有地方都别用就没事了
7 如果需要,发布者可以提供链接的或编辑者的文档/教程 笔者提供了一个油管简介视频,没有提供文档也通过了,不过还是推荐做一个文档
8 .uproject已禁用未使用的插件 关闭不适用的插件,系统默认开启的不用理会
质量
序号 信息 备注
1 内容主要不包括易于复制的资产 不清楚,笔者没有被打回
2 资产不包含视觉缺陷 字面意思
3 资产运作不影响绩效 这个翻译的不太对,应该是不影响性能之类的意思
4 所有资产均已完成且功能正常 这个笔者被打回了若干次,项目不能有明显的错误,不仅仅是编译运行错误,你游戏逻辑有错误也不行,笔者的demo里释放一个技能的时候有弹道错误也被打回来了,测试员还是蛮仔细的
5 产品包含总体良好的设计和概念 屁话
法律
序号 信息 备注
1 发布者拥有分发包含在产品中或由产品依赖的所有内容的合法权利 重头戏,资源的版权问题,如果你的美术资源都是自己做的就忽略本条,否则见下
1.Epic免费资源可以随意使用
2.其他第三方免费资源包括月限免的都不能使用
3.音效比较难定义,除非太过明显;BGM之类的不行,音乐是容易界定版权的
4.动画其实不容易界定版权,但是商城痕迹太明显的肯定不行
5.图片和模型资源版权容易界定,不要使用商城禁止的资源
2 不包含版权或商标内容 参考第一条,第一条过了这里基本也过了
3 Epic Games的示例内容或源代码的大部分内容仅用于显示/示例 意思应该就是字面意思,不要用示例工程的资源就没事了
4 未经修改的公共领域内容仅限于帮助演示,并且在描述中引用了来源 遵循第一条
5 产品不包含可能令人反感的资产 *
依赖问题
  • 这个工程不要依赖于其他工程
地图
序号 信息 备注
1 必须要有地图来展示内容 要有正式地图,建议启动地图就是正式地图
2 所有地图均已建立照明 容易被忽略的一条,记得提交之前构造光照
3 地图不会产生任何错误或相应的警告 提交之前修复所有报错信息,容易犯错的是资源引用错误
4 地图没有Z形战斗或重叠的多边形 啥翻译!!不要有重面就可以

其他问题

  1. 提交的版本需要支持当前最新版本,笔者在第一次提交的时候还是4.24版本,后面被打回后UE很不合时宜的升级到了4.25,结果工程必须升级到4.25进行重新测试,当中还遇到了很多崩溃问题,这个比较头痛