输入监听_UE4 C++ 不吞噬的输入监听

7c69e155d18672ad1c2933e2621cddf8.png

通常我们都是用SetupPlayerInputComponent来绑定输入事件,而且它本身也是根据Actor类的InputPriority来判断输入的优先级,确保不会同一个输入响应两个地方的绑定。不仅仅是这样Actor的输入监听,包括界面,比如鼠标左键如果点击了一个按钮,它这次输入就会被按钮吞掉了,并不会触发你其他地方绑定的左键按下事件。为什么会这样呢?看看源码就知道了,在源码里所有的输入监听都是一个个IInputProcessor,存储在InputPreProcessorList中,在FSlateApplication::InputPreProcessorsHelper中会遍历所有输入监听,当有一个响应了这次的输入,就不会触发其他的输入监听了:

19e3402d60b3ee5446a68acb58c6f2ba.png

可能很多人都没用过IInputProcessor,看到FSlateApplication类你就知道了,这已经是一个接近底层的类的,我们这里的需求是想做一个响应任意点击时,播一个动效,但不吞掉输入,该点击按钮还是响应按钮事件。

所以我也来继承实现了IInputProcessor类:

df5d804079fa4600a8bc8bb0928c6e2e.png

这些都是必须实现的接口,这里我们的输入是不想吞掉,很简单,所有这些函数都返回false,这样它既响应了自己,也不会阻断输入往下的传递。

FTouchInputProcessor类需要我们注册一下,我们就在PlayerController类的BeginPlay和EndPlay里进行注册和注销:

6fccaa1b9c6e1f0499215967b24fb14a.png

这样就把我们的输入监听注册进去了。下面再讲一下FTouchInputProcessor的应用:

声明几个成员:

19a8962c3c3a790a51cb9b0f8d1e02d5.png

TouchEffectImage是点击下去播放动效的Image控件,动效是使用UI材质,里面主要是flipbook函数来播放序列帧,为了保证每次点击下去,序列帧是从第一帧开始播放,材质的刷新由这边CPU控制。当我们的鼠标按下的时候,如果TouchEffectImage还没有就NewObject一个,并且工具鼠标点击处设置坐标添加到视口上,并且重置这次的计时器,计时器重新算,每次都是从第一帧播放点击动效。我这里的Lua部分不用在意,因为我们的界面主要是在Lua里,我从Lua里获取全屏的那个UCanvasPanel,这里要注意的是,第一次创建的控件,才添加到视口中,是拿不到正确的FGeometry的,GetCachedGeometry是拿到上一帧缓存的数据,这里调用SlateApp.Tick(ESlateTickType::All);进行手动的Tick,提前计算了TouchEffectImage的FGeometry。

bool FTouchInputProcessor::HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
{
	UnLua::FLuaRetValues RetVals = LuaStateMgr::GetLuaStateMgr().CallLuaFunctionTable("UIManager", "GetRootCanvasForCPP");
	if (RetVals.IsValid())
	{
		if (UNCanvasPanel* RootPanel = RetVals[0].Value<UNCanvasPanel*>())
		{
			if (TouchEffectImage)
			{
				if (UCanvasPanelSlot* CanvSlot = Cast<UCanvasPanelSlot>(TouchEffectImage->Slot))
				{
					CalcWidgetPosition(SlateApp, MouseEvent, CanvSlot, TouchEffectImage);
					SetTouchEffectBrush(EffectConfig.TouchEffectBrush);
					TouchEffectImage->SetVisibility(ESlateVisibility::SelfHitTestInvisible);			/*计算好Geo后再显示出来*/
					TouchTimer = EffectConfig.TouchDuration;
				}
			}
			else
			{
				TouchEffectImage = NewObject<UImage>();
				TouchEffectImage->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
				if (UCanvasPanelSlot* CanvasSlot = RootPanel->AddChildToCanvas(TouchEffectImage))
				{
					CanvasSlot->SetAnchors(FAnchors());
					CanvasSlot->SetAlignment(FVector2D(0.0f, 0.0f));
					CanvasSlot->SetAutoSize(true);
					SetTouchEffectBrush(EffectConfig.TouchEffectBrush);
					SlateApp.Tick(ESlateTickType::All);			/*手动Tick,获取最新的Geo*/
					CalcWidgetPosition(SlateApp, MouseEvent, CanvasSlot, TouchEffectImage);
					TouchTimer = EffectConfig.TouchDuration;
				}
			}

			if (TouchEffectImage)
			{
				if (UMaterialInstanceDynamic* DIM = Cast<UMaterialInstanceDynamic>(TouchEffectImage->Brush.GetResourceObject()))
				{
					/*设置动效的时间*/
					DIM->SetScalarParameterValue(TEXT("PlayTime"), EffectConfig.TouchDuration);
				}
			}
			TouchPlayTime = 0.0f;
			bDrag = true;
		}
	}
	return false;
}

然后我们在看看怎么设置Image对应的坐标:

14eda3570dc6b23a81d99cf0b2f31d99.png

首先是拿到Image的FGeometry,还有Image的大小WidgetSize。游戏视口在屏幕的坐标也要知道,因为FPointerEvent只能拿到的是屏幕控件的坐标,和视口坐标做差才能得到视口空间下点击处的坐标。如果是编辑器下,还要减去边框的大小。然后视口的ViewportGeo也要拿一下,用USlateBlueprintLibrary::TransformVectorLocalToAbsolute将视口坐标从局部坐标转换成绝对坐标,到这里还没得到Image的坐标,还要减去控件大小的一半,前面得到的是控件中心的坐标,但控件的锚点是左上角,所以减掉大小的一半得到左上角的坐标,然后就可以设置Image的CanvasSlot的坐标了。

然后还有就是TouchEffectImage的材质表现要在Tick里去更新:

afc8474ecd6505c6eeb4f643bcb78a6d.png

这里也贴一下材质的连连看吧:

c0cb8236f5337587cf0276cd3dc61ec8.png

PlayTime是这个动效需要播放的时长,在点击的时候初始化设置。Order是序列帧是几乘几的,然后CurrentTime就是我们在C++里进行更新的播放时间了。

好了,任意点击的动效讲完了,下面在讲一下在屏幕上拖拽时的拖尾效果怎么做。这里建一个UTrailingPointImage类继承UImage和FTickableGameObject,这样它除了是一个Image,也有Tick逻辑。主要成员变量是这些:

c49b95e9ce7d33ccdb7bed010cc790b9.png

大概就是在自己的Tick里对透明度进行渐变:

d73bc882636bf54d58e6bcf460091776.png

在FTouchInputProcessor的构造函数里对拖尾的Image数组TrailingArray进行初始化,提前都添加到视口的UCanvasPanel上,默认不透明度是0,虽然添加到屏幕上且是SelfHitTestInvisible的,但是是看不到的。前面我们在HandleMouseButtonDownEvent里已经标记了bDrag为true,记录自己当前是否在拖拽滑动,在HandleMouseButtonUpEvent里又将bDrag设为false。然后在HandleMouseMoveEvent里就判断是否在拖拽,如果在拖拽,按TrailingIndex序号取拖尾的Image控件设置到当前坐标坐标处,并将Opacity设为1,然后拖尾控件就会开始自己渐隐了,为了有些变化,对控件的角度进行了随机,也可以对缩放进行随机,TrailingIndex还要加1,如果超出了变回0重新开始。

a8df8d4947547efc62c263fab68041d2.png

好了,这里就完成了不会被吞噬的输入监听了,并做了两个应用例子,下面看看效果:

知乎视频​www.zhihu.com
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值