学习
实践
活动
专区
工具
TVP
写文章
专栏首页Bairuo的文章UE4 Nav Modifier实用性修改思路

UE4 Nav Modifier实用性修改思路

Recast/Detour是Unity、Unreal都使用的导航中间件,不过不同引擎对它们的包装方式并不相同,所以使用上感觉还是有一些区别,部分项目服务器使用导航时甚至可能完全脱离Unreal、Unity引擎。

UE4中在生成导航后在范围内再标记特定的区域需要使用到NavModifier

它看起来就是可以帮你抠掉一个笔刷样子的导航区域使得这个区域不可以被导航

说抠掉其实不完全对,每个NavModifier可以选择一个AreaClass,每个AreaClass类型背后实际上对应一个8位的AreaID和16位的AreaFlag,它背后实际所做的是帮你标记一个区域,使得这个区域有对应的AreaId和AreaFlag,如果配合NavQueryFilter可以实现某个区域部分agent可以路过,而另一部分agent不可以。

https://www.zhihu.com/video/1575441313071038464

问题1.NavModifier Volume支持任意形状

UE的NavModifier只能用Convex Volume

这种限制的背后也是有原因的,判断两物体相交或者一个点在是否在一个空间内通常都需要使用到凸形状,非凸形状得通过Convex Decomposition算法分解为凸形状。在UE对应ModifyVolume生效的机制:判断体素化后的小方块(在Recast中称为span)是否在Convex Volume中

NAVMESH_API dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* pos, const float radius, const float height, const unsigned char areaId);

//@UE4 BEGIN: more shapes
NAVMESH_API dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* pos, const float* extent, const unsigned char areaId);

NAVMESH_API dtStatus dtMarkConvexArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* verts, const int nverts, const float hmin, const float hmax, const unsigned char areaId);

NAVMESH_API dtStatus dtReplaceCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* pos, const float radius, const float height, const unsigned char areaId,
	const unsigned char filterAreaId);

NAVMESH_API dtStatus dtReplaceBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* pos, const float* extent, const unsigned char areaId, const unsigned char filterAreaId);

NAVMESH_API dtStatus dtReplaceConvexArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
	const float* verts, const int nverts, const float hmin, const float hmax, const unsigned char areaId,
	const unsigned char filterAreaId);

可以看到,修改Area的函数只有这几个,UE背后也是这样来用的

	switch (Modifier.GetShapeType())
	{
	case ENavigationShapeType::Cylinder:
             ...
	case ENavigationShapeType::Box:
             ...
	case ENavigationShapeType::Convex:
             ...
	case ENavigationShapeType::InstancedConvex:
	     ...
	default: break;
	}

这里有个巧妙的思路,可以直接在代码中拿生成过Convex Collision的Mesh作为Volume

FAreaNavModifier::FAreaNavModifier(const FKConvexElem& Convex, const FTransform& LocalToWorld, const TSubclassOf<UNavAreaBase> InAreaClass)
{
	TArray<FVector> Verts;
	for (int32 VertexIndex = 0; VertexIndex < Convex.VertexData.Num(); VertexIndex++)
	{
		Verts.AddUnique(Convex.VertexData[VertexIndex]);
	}
	
	Init(InAreaClass);
	SetConvex(Verts.GetData(), 0, Verts.Num(), ENavigationCoordSystem::Unreal, LocalToWorld);
}

扩展一下Modifier,这样就可以拿任意物体作为Volume形状了

问题2.NavModify物体,而非Volume

有了根据任意Convex Volume标记导航Area的机制但实际上还是不太够,比如游戏中有大量程序化生成不规则形状的面片(比如水面),我们想要标记这些面片为某种区域,但我们不太可能在DDC软件中先拉伸再塞回来生成Convex Collision再塞到Modifier里面去吧?这样不仅流程麻烦而影响内存和性能(在大世界游戏中,客户端本地的导航网格可能是动态生成)

UE Modifier或许是从动态障碍物出发考虑的,Dynamic生成模式下可缓存heightfeild数据

	if (bRegenerateCompressedLayers)
	{
		CompressedLayers.Reset();

		bSuccess = GenerateCompressedLayers(BuildContext);

		if (bSuccess)
		{
			// Mark all layers as dirty
			DirtyLayers.Init(true, CompressedLayers.Num());
		}
	}

	if (bSuccess)
	{
		bSuccess = GenerateNavigationData(BuildContext);
	}

在几何上具体生效的机制是遍历每个Modifier执行问题1中的dtMarkBoxArea等函数

		for (const FRecastAreaNavModifierElement& Element : Modifiers)
		{
			for (const FAreaNavModifier& Area : Element.Areas)
			{

但是我们如果并不是想标记一个Volume内的Area,而是想标记某个物体的表面是某个Area,真的有必要这么麻烦么?如果场景中有大量自动生成的Modifier,可以想象到这个过程明显十分浪费。

似乎这从理论上来说并不是必须的,一个三角形产生的体素对应的是什么Area,我们由这个三角形本身的信息就可以知道。

Recast生成的大致流程:

rcMarkWalkableTriangles 根据坡度标记可行走(实际是标记Walkable的AreaID

rcRasterizeTriangles 体素化三角形(这一步中会生成span,span重叠合并span,span可以理解为实心小长方体)

rcFilterLowHangingWalkableObstacles

rcFilterLedgeSpans

rcFilterWalkableLowHeightSpans

rcBuildCompactHeightfield(这里生成compactSpan,即Span之间的空心部分)

rcErodeWalkableArea(根据半径缩小可行走区域,首先会构建SDF距离场,AreaID为NULL的compactSpan为边界

---区域划分

rcBuildDistanceField(也是构建距离场,这里会判断areaID是否相等

				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
					{
						const int ax = x + rcGetDirOffsetX(dir);
						const int ay = y + rcGetDirOffsetY(dir);
						const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
						if (area == chf.areas[ai])
							nc++;
					}
				}
				if (nc != 4)
					src[i] = 0;

src是距边界距离,后续就是取轮廓点了生成区域等步骤了。

思考到这里似乎就可以有了一个思路,其实我们可以考虑在体素化三角形的时候就将AreaID使用上,另外在后续过程中将AreaFlags对应上

		// fill flags, or else detour won't be able to find polygons
		// Update poly flags from areas.
		for (int32 i = 0; i < GenerationContext.PolyMesh->npolys; i++)
		{
			auto areaId = GenerationContext.PolyMesh->areas[i];
			auto areaFlag = AdditionalCachedData.FlagsPerArea[areaId];
			if(NavSystem->PreDefineAreas.Contains(areaId))
			{
				areaFlag = NavSystem->PreDefineAreas[areaId].GetAreaFlags();
			}
			GenerationContext.PolyMesh->flags[i] = areaFlag;
		}

实际证明这最终是可行的

配合一套周边修改,就可以在UE4中做到直接标记一个物体为某种Area了(这似乎在Unity中是很方便的),不过对Span进行Area修改或许还可以有一些更巧妙的操作,比如游戏中水面很多是面片,对于空旷的水域(水中没有漂浮物)可以很方便标记水面和水底,配合物理引擎将材质信息写入碰撞中或许可以进一步做到根据材质来标记Area等,也曾有人分享过通过修改Recast生成的其它阶段来进行NavLink的自动化生成。

其它.方便设置AreaFlag

AreaID和AreaFlag都可以做到导航筛选,对普通用户来说差别可能并不大,只有在动态修改的时候可能产生一些差别(PolyFlags と AreaFlags の違いについて),因为Flag和ID记录的位置不一样。

Unreal对Area的包装并不能在NavModifier里面方便地设置AreaFlag,因为在Modifier Volume中可以选择的几个类都是写死的

哪怕你创建蓝图继承自NavArea,也不能设置AreaFlag

可以考虑简单继承扩展一下NavArea

UNavArea_Flags::UNavArea_Flags(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer),
    bNavFlag0(0), bNavFlag1(0), bNavFlag2(0), bNavFlag3(0),
    bNavFlag4(0), bNavFlag5(0), bNavFlag6(0), bNavFlag7(0),
    bNavFlag8(0), bNavFlag9(0), bNavFlag10(0), bNavFlag11(0),
    bNavFlag12(0), bNavFlag13(0), bNavFlag14(0), bNavFlag15(0)
{
	DrawColor = FColor(127, 127, 0); // brownish
}

uint16 UNavArea_Flags::GetAreaFlags() const
{
	return bNavFlag0 | (bNavFlag1 << 1) | (bNavFlag2 << 2) | (bNavFlag3 << 3)
	| (bNavFlag4 << 4) | (bNavFlag5 << 5) | (bNavFlag6 << 6) | (bNavFlag7 << 7)
	| (bNavFlag8 << 8) | (bNavFlag9 << 9) | (bNavFlag10 << 10) | (bNavFlag11 << 11)
	| (bNavFlag12 << 12) | (bNavFlag13 << 13) | (bNavFlag14 << 14) | (bNavFlag15 << 15);
}

这样我们就可以用新建蓝图的方式创建新的Area以及设置AreaFlag了

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:https://www.zhihu.com/people/Bairuo/posts复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • UE4学习笔记(四): 开发体验

    逍遥剑客
  • Epic Games吴灏:超越游戏,UE4还能在行业应用领域持续发光发热

    VRPinea
  • 进阶 | CSS进阶:提高你前端水平的 4 个技巧

    作者|jaychen 原文|https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651552192&i...

    用户1097444
  • MMORPG技能管线设计经验总结

    表现丰富、机制多变的技能作为MMORPG游戏战斗体验的核心组成部分,是吸引玩家的一大亮点,本文总结了笔者在MMORPG技能系统设计上的一些经验,供大家参考。

    Kill Console
  • UE4命令行编译工程入门

    笔者是个UE4的小白,本文主要记录了一个小白是如何从零UE4的基础,一步步在命令行打出iOS的ipa包的尝试过程,本文比较浅,适合小白做为UE4工程的入门资料(...

    stringwu
  • Web前端进阶之路: 提升代码质量篇

    初级前端和高级前端有什么差别?在我看来,初级前端关注点在完成功能,高级前端能在完成功能的基础上,做的又好又快。做的好,就是代码质量高,做的快就是开发效率高。

    前端GoGoGo
  • serialVersionUID作用是什么以及如何生成的?

    先定义一个实体Student.class,需要实现Serializable接口,但是不需要实现get(),set()方法

    秦怀杂货店
  • 用Publish创建博客(三)——插件开发

    在用Publish创建博客(一)——入门[4]中我们介绍过Publish有两个Content概念。其中PublishingContext作为根容器包含了你网站项...

    东坡肘子
  • serivalVersionUID是干嘛用的?

    先定义一个实体Student.class,需要实现Serializable接口,但是不需要实现get(),set()方法

    秦怀杂货店
  • UE5中四元数的旋转技巧

    旋转角过渡:测试角度: 0,45,0旋转到 120,90,100【可以看到旋转绕了一圈】

    Jean
  • 【ue4】【使用】光照系统_光照

    为了更好地进行光照测试, 我们需要对场景做一些设置。 当然这些设置不是必要的, 只是一些个人的强迫症式的做法, 这里列举一下。

    duadua
  • 【ue4】【使用】特效系统Cascade与Niagara

    那么当我们拖动一个作为资源存在的 ParticleSystem 粒子系统时,发生了什么?

    duadua
  • vue菜单点击下划线跟随动画

    以上方案对比,发现方案1缺点较大,例如无法控制下划线长度,圆角,以及下划线渐变色等。因此采用方案2处理。

    流眸
  • 聊聊简单又灵活的权限设计(RBAC)

    丹尼尔:Hi,蛋兄,最近接到需求,需要在已有的项目加上权限相关的功能,想想我专心混前端都好久了,N久没碰表设计了,你对这些有了解吗?

    小小许
  • 用Jetpack Compose完美复刻Flappy Bird!

    Flappy Bird是13年红极一时的小游戏,其简单有趣的玩法和变态的难度形成了强烈反差,引发全球玩家竞相把玩,欲罢不能!遂选择复刻这个小游戏,在实现的过程中...

    用户9239674
  • Vue:Vue中的导航浮顶

    MrTreasure
  • ICLR2019 | 你追踪,我逃跑:一种用于主动视觉跟踪的对抗博弈机制

    主动视觉跟踪(Visual Active Tracking)是指智能体根据视觉观测信息主动控制相机的移动,从而实现对目标物体的跟踪(与目标保持特定距离)。主动视...

    AI科技评论

扫码关注腾讯云开发者

领取腾讯云代金券