UE4 UI点击透明判断

1 篇文章 0 订阅

目的:

如图,判定鼠标是否点击到红色和紫色圆形;

UE4做不到:

UE4点击会将整个图片的矩形区域做为判定条件,所以无法精确到透明位置不点击。

做法(两种):

一、直接在图片上监听点击事件,通过拿图片材质的透明的判断点击是否有效。参考可见实现UMG中自定义不规则形状按钮

(缺点1、只能实现一张图片透明判断 2、做不了点击穿透 )

(可另外参考 编写自定义控件 缺点:改源码,比较难(哈哈))

二、在外层做监听,通过换算坐标拿到图片材质相对位置的透明度来判断是否有效

(优点:能实现多张图的判断  半优点:做到伪点击穿透 缺点:对ui层级有要求)

 

以下是第二张方法的实现和大概逻辑流程说明:

先贴代码再说明

/**.h **/	

//基于imageArr判断点击的位置是否是空白(透明度小于某个值)
//ui做法需要限制,image必须在接收点击事件界面的直接子节点,否则会导致位置换算有误
//@param imageArr 用来判断空白的贴图数组
//@return true:点击空白
UFUNCTION()
static bool IsClickTheBlank(int Alpha ,const FGeometry& MyGeometry, const FPointerEvent& MouseEvent,TArray<UImage*>imageArr);

/**.cpp*/
bool ULuaCallCppFunctionLib::IsClickTheBlank(int Alpha, const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, TArray<UImage*>imageArr)
{
	if (imageArr.Num() <= 0)
	{
		return true;
	}

	bool bResult = true;

	FVector2D LocalPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());//先对于MyGeometry所在slot的屏幕坐标

	for (int32 i = 0; i < imageArr.Num(); ++i)
	{
		UImage* image = imageArr[i];
		
		UTexture2D* AdvancedHitTexture = (UTexture2D*)image->Brush.GetResourceObject();
		if (bResult && nullptr != AdvancedHitTexture)
		{
			UCanvasPanelSlot* canvasSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(image);

			//计算出描点左上角,中心点alignment为(0,0)的相对父节点坐标
			FVector2D parentSize = MyGeometry.GetLocalSize();//ui做法需要限制,image必须在接收点击事件界面的直接子节点,否则会导致位置换算有误

			FVector2D relativeLUPos = FVector2D(0, 0);//相对父节点的左上角坐标
			FVector2D relativeRDpos = FVector2D(0, 0);//右下角坐标

			FVector2D pos = canvasSlot->GetPosition();
			FMargin offset = canvasSlot->GetOffsets();
			FVector2D size = canvasSlot->GetSize();
			FAnchors abchors = canvasSlot->GetAnchors();
			FVector2D alignment = canvasSlot->GetAlignment();
			
			//判断锚点是一个点还是一个范围
			//X Y要分开(难啊)
			if (abchors.Minimum.X == abchors.Maximum.X)
			{
				float alignmentOffsetLU = size.X * -alignment.X;//中心点导致的左上角偏移
				float alignmentOffsetRD = size.X * (1 - alignment.X);//中心点导致的右下角角偏移

				//描点偏移
				float abchorsOffset = parentSize.X * abchors.Minimum.X;
				relativeLUPos.X = pos.X + alignmentOffsetLU + abchorsOffset;
				relativeRDpos.X = pos.X + alignmentOffsetRD + abchorsOffset;
			}
			else
			{
				relativeLUPos.X = parentSize.X * abchors.Minimum.X + offset.Left;
				relativeRDpos.X = parentSize.X * abchors.Maximum.X - offset.Right;
			}
			if (abchors.Minimum.Y == abchors.Maximum.Y)
			{
				float alignmentOffsetLU = size.Y * -alignment.Y;//中心点导致的左上角偏移
				float alignmentOffsetRD = size.Y * (1 - alignment.Y);//中心点导致的右下角角偏移

				//描点偏移
				float abchorsOffset = parentSize.Y * abchors.Minimum.Y;
				relativeLUPos.Y = pos.Y + alignmentOffsetLU + abchorsOffset;
				relativeRDpos.Y = pos.Y + alignmentOffsetRD + abchorsOffset;
			}
			else
			{
				relativeLUPos.Y = parentSize.Y * abchors.Minimum.Y + offset.Top;
				relativeRDpos.Y = parentSize.Y * abchors.Maximum.Y - offset.Bottom;
			}

			FVector2D imageSize = relativeRDpos - relativeLUPos; //图片大小

			//适配旋转
			FVector2D relativeMidPos = FVector2D(LocalPosition) - (relativeLUPos + relativeRDpos)/2; //转换为图片中心点为圆心的坐标系

			float angle = image->GetRenderTransformAngle();
			float cosV = FMath::Cos(-angle / 57.2958);
			float sinV = FMath::Sin(-angle / 57.2958);
			float px2 = cosV * relativeMidPos.X - sinV * relativeMidPos.Y;
			float py2 = sinV * relativeMidPos.X + cosV * relativeMidPos.Y;
			relativeMidPos.X = px2;
			relativeMidPos.Y = py2;

			//判断是否在image内
			if (FMath::Abs(relativeMidPos.X) < imageSize.X/2 && FMath::Abs(relativeMidPos.Y) < imageSize.Y / 2)
			{
				//描点左上角,中心点alignment为(0,0)的相对父节点坐标
				FVector2D clickRelativePos = relativeMidPos + (relativeLUPos + relativeRDpos) / 2 - relativeLUPos;

				中心点旋转变换
				//float px1 = clickRelativePos.X - (size.X / 2); //alignment为(0.5,0.5)时的坐标位置,
				//float py1 = clickRelativePos.Y - (size.Y / 2);
				//float angle = image->GetRenderTransformAngle();
				//float cosV = FMath::Cos(-angle / 57.2958);
				//float sinV = FMath::Sin(-angle / 57.2958);
				//float px2 = cosV * px1 - sinV * py1;
				//float py2 = sinV * px1 + cosV * py1;

				旋转后坐标
				//clickRelativePos.X = px2 + (size.X / 2);
				//clickRelativePos.Y = py2 + (size.Y / 2);

				FVector2D mapPos = clickRelativePos / (relativeRDpos - relativeLUPos);

				float px = floor(mapPos.X * AdvancedHitTexture->PlatformData->SizeX);
				float py = floor(mapPos.Y * AdvancedHitTexture->PlatformData->SizeY);

				int BufferPosition = py * AdvancedHitTexture->PlatformData->SizeX + px;

				FColor* ImageData = (FColor*)((AdvancedHitTexture->PlatformData->Mips[0]).BulkData.Lock(LOCK_READ_ONLY));
				if (!ImageData) {

				}
				else {
					if (ImageData[BufferPosition].A > Alpha)
					{
						//非空白
						bResult = false;
					}
				}

				//绘制材质透明度
				
				/*UE_LOG(LogTemp, Log, TEXT("BufferPosition index:%d posX:%d,posY:%d ImageData[BufferPosition].A:%d Alpha:%d isBlank:%d"), i, px, py, ImageData[BufferPosition].A, Alpha, (ImageData[BufferPosition].A <= Alpha ? 1 : 0));

				for (int32 i = 0; i < AdvancedHitTexture->PlatformData->SizeY; ++i)
				{
					FString lineStr = "";
					for (int32 j = 0; j < AdvancedHitTexture->PlatformData->SizeX; j++)
					{
						int index = i * AdvancedHitTexture->PlatformData->SizeX + j;
						if (ImageData[index].A <= Alpha)
						{
							lineStr += "0";
						}
						else
						{
							lineStr += "1";
						}
					}
					UE_LOG(LogTemp, Log, TEXT("%s"), *lineStr);
				}*/

				AdvancedHitTexture->PlatformData->Mips[0].BulkData.Unlock();
			}
		}
	}
	return bResult;
}

直接提供一个静态函数用于查询点击是否空白,传入透明度阙值和UImage数组,通过换算点击点在UImage上的纹理信息,可以判断是否点击空白。

1、获取纹理信息

一张64*64的图片,就存有64*64个像素信息,如(0,0)存的就是左上角像素点的rgba。

通过 UTexture2D* AdvancedHitTexture = (UTexture2D*)image->Brush.GetResourceObject(); 可以获取图片的纹理。

FColor* ImageData = (FColor*)((AdvancedHitTexture->PlatformData->Mips[0]).BulkData.Lock(LOCK_READ_ONLY));获取纹理数据。

每个(x,y)点的数据 = y*size.x+ x;

 

2、将点击的坐标换算到UImage的相对坐标(比较耗时的地方)

如图,通过FVector2D LocalPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());可以计算出鼠标点击位置在整个监听界面的位置,

需要将位置转换为以图片为画布的坐标。

所以需要获取图片的UCanvasPanelSlot,通过一系列转换。

(由于屏幕坐标系为左上角为零,像右向下增长,需要将图片位置信息先转换为描点左上角,中心点alignment为(0,0)

所以有锚点是一个点还是一个范围,X Y要分开的判断)

3、

求出点击坐标在图片上的相对坐标后,通过缩放映射到对应纹理位置,便大功告成啦。

4、细节:

图1

图2

同一层级下的点击事件处理时,返回FReply::Unhandled() 是无法透传到下一个点击事件上的,所以图1上层的按钮1即使不处理点击事件,按钮2也接收不到点击事件。

图2子按钮不接受点击事件时父按钮会接收到点击事件。

总结1:点击事件的传递是父节点传递的。这也是为什么做法一做不到点击穿透的原因,除非按钮套按钮。方法二能做伪穿透因为监听层不在图片上,但也做不了与外界点击穿透(但已经够灵活了,至少能适用我们项目了)

总结2:ui里做位置转换挺麻烦的(可能因为我没找到aip),所以无法做到多层级转换,这也是方法二的弊端,也是为什么图片必须是监听层的直接子节点(或没位移变换可使用多层级)的原因。

 

 

 

END

希望对你有帮助~

 

 

 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值