手游新手引导功能(圆形和矩形遮罩)

在游戏开发中经常会做新手引导功能,这是我目前在的公司一款手游产品
引导功能会遇到以下几个问题
1:圆形,矩形遮罩
2:事件穿透
3:业务需求
(强引导/弱引导)

现在市面上的手游引导可分为强引导,弱引导
我根据策划需求制作的是弱引导 ,在策划配置的触发情况下出现引导,也就是说1引导和2引导之前没有强关联性,便于玩家自由操作
先看下游戏效果图如下
在这里插入图片描述
在这里插入图片描述
上面两张是具体的效果图,下面是遮罩shader根据目标GameObject大小切割成圆形/矩形
在这里插入图片描述
在这里插入图片描述
在本项目中UI层使用的是xlua 后面代码基本以lua 展示

第一点:圆形/矩形shader遮罩

Shader “UIMask/GuideRoundAndRectangleMask”
{
Properties
{
[PerRendererData] _MainTex(“Sprite Texture”, 2D) = “white” {}
_Color(“Tint”, Color) = (1,1,1,1)

	_StencilComp("Stencil Comparison", Float) = 8
	_Stencil("Stencil ID", Float) = 0
	_StencilOp("Stencil Operation", Float) = 0
	_StencilWriteMask("Stencil Write Mask", Float) = 255
	_StencilReadMask("Stencil Read Mask", Float) = 255
	_ColorMask("Color Mask", Float) = 15

	[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0

	_Center("Center", vector) = (0, 0, 0, 0)
	_Radius("Radius", Range(0,2000)) = 1000
	_MaskType("Mask Type",Float) = 0  // 0 圆 1 矩形
	_Rectangle("Rectangle",Vector) = (0,0,0,0)
}

	SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}

		Stencil
		{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]

		Pass
		{
			Name "Default"
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO

			};

			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;

			float _Radius;
			float2 _Center;
			float _MaskType;
			float4 _Rectangle;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

				OUT.texcoord = IN.texcoord;

				OUT.color = IN.color * _Color;
				return OUT;
			}
			sampler2D _MainTex;
			fixed4 frag(v2f IN) : SV_Target
			{
				half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

				if (_MaskType == 0) {
					if (distance(IN.worldPosition.xy, _Center.xy) <= _Radius)
					{
						color.a = 0;
					}
				}
				else {
					//UnityGet2DClipping这个函数实现了判断2D空间中的一点是否在一个矩形区域中
					if (UnityGet2DClipping(IN.worldPosition.xy, _Rectangle))
					{
						color.a = 0;
					}
				}
				return color;

			}
		ENDCG
		}
	}

}
在shader 中标注的很清楚,我们对圆形遮罩定义圆心和半径,根据 (distance(IN.worldPosition.xy, _Center.xy) <= _Radius) 计算 区域是否透明
这里提供lua 调用C#代码设置shader数据代码

/// <summary>
/// 设置圆形点击遮罩
/// </summary>
/// <param name="targetGo"></param>
/// <param name="maskCenterGo"></param>
/// <param name="circleValue"></param>
public void SetCircleGuideMask(GameObject targetGo,GameObject maskCenterGo,float circleValue,float x=0f,float y=0f)
{
    // 设置事件透传对象
    gameObject.GetComponent<EventPermeate>().target = targetGo.gameObject;
    //设置聚焦pos
    Vector3 newV3 = GetScreenPoint(maskCenterGo);
    var center = new Vector4(newV3.x+x, newV3.y+y, newV3.z, 0f);
    material.SetFloat("_MaskType", 0f);
    material.SetVector("_Center", center);
    // 设置半径
    material.SetFloat("_Radius", circleValue);
}

targetGo 是点击业务界面的按钮对象,maskCenterGo 是显示圆形遮罩中心点对象,circleValue 是圆半径,x,y 屏幕中心点偏移,GetScreenPoint(go)是获取go在屏幕坐标

 private Vector3 GetScreenPoint(GameObject go)
    {
        var target = go.GetComponent<RectTransform>();
        if (target == null) return Vector3.zero;
        Canvas canvas = getUICanvas();
        Vector3[] corners = new Vector3[4];
        target.GetWorldCorners(corners);
        var diameter = Vector2.Distance(WordToCanvasPos(canvas, corners[0]), WordToCanvasPos(canvas, corners[2])) / 2f;
        float x = corners[0].x + ((corners[3].x - corners[0].x) / 2f);
        float y = corners[0].y + ((corners[1].y - corners[0].y) / 2f);
        Vector3 center = new Vector3(x, y, 0f);
        Vector2 position = Vector2.zero;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, center, canvas.GetComponent<Camera>(), out position);
        center = new Vector3(position.x, position.y, 0f);
        return center;
    }

C# 设置矩形遮罩

/// <summary>
/// 设置矩形点击遮罩
/// </summary>
/// <param name="targetGo"></param>
/// <param name="maskCenterGo"></param>
/// <param name="circleValue"></param>
public void SetRectangleGuideMask(GameObject targetGo, GameObject maskCenterGo)
{
    // 设置事件透传对象
    gameObject.GetComponent<EventPermeate>().target = targetGo.gameObject;
    RectTransform rec = maskCenterGo.GetComponent<RectTransform>();
    Vector3[] _corners = new Vector3[4];
    rec.GetWorldCorners(_corners);
    Canvas canvas = getUICanvas();
    Vector2 pos1 = WordToCanvasPos(canvas,_corners[0]);//选取左下角
    Vector2 pos2 = WordToCanvasPos(canvas,_corners[2]);//选取右上角
    if(material==null)
    {
        material = GetComponent<Image>().material;
        if (material == null)
        {
            Debug.LogError("GuideMask.cs material == null ");
            return;
        }
    }
    material.SetVector("_Rectangle", new Vector4(pos1.x, pos1.y, pos2.x, pos2.y));
    material.SetFloat("_MaskType", 1f);
}

矩形相关参数我就不一一解释了 和圆形代码参数类似

第二点:事件穿透

这里策划提出的需求是不克隆拷贝引导玩家要点击的按钮,我的做法是将

GetComponent<EventPermeate>().target = targetGo.gameObject

进行事件穿透,只触发目标的点击事件,
EventPermeate.cs源码如下:

public class EventPermeate : MonoBehaviour,IPointerClickHandler,IPointerDownHandler, IPointerUpHandler
{
    [HideInInspector]
    public GameObject target;

    public void OnPointerDown(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerDownHandler);
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.pointerUpHandler);
    }
    public void OnPointerClick(PointerEventData eventData)
    {
        PassEvent(eventData, ExecuteEvents.submitHandler);
        PassEvent(eventData, ExecuteEvents.pointerClickHandler);
    }
    public void PassEvent<T>(PointerEventData data, ExecuteEvents.EventFunction<T> function)
        where T : IEventSystemHandler
    {
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(data, results);
        GameObject current = data.pointerCurrentRaycast.gameObject;
        for (int i = 0; i < results.Count; i++)
        {
            if (target == results[i].gameObject)
            {
                ExecuteEvents.Execute(results[i].gameObject, data, function);
                break;
            }
        }
    }
}

第三点:业务需求

策划定了两张表,cfg_noviceGuideBase,引导基础表,cfg_noviceGuideStep引导分步表
在这里插入图片描述
id :引导的主键ID,skip :该引导触发后延迟几秒显示跳过按钮 防止用户引导中卡死,condition:该引导触发条件,parameter:参数,priority:优先级,firstStep:引导触发后开始的第一步id 关联到-cfg_noviceGuideStep。
对于:引导分步表
在这里插入图片描述
在这里插入图片描述
我们的引导有断线重连的概念 – 玩家进行引导ID为1,步骤1002 时手机突然断网掉线/或没电关机、再次上线 ID为1的引导是否继续、从哪个步骤开始 由nextStep和key 决定在这里插入图片描述
下面开始说明lua 代码相关
在这里插入图片描述
GuideManager.lua 引导功能管理代码负责引导启动与UI预制体交互的,
GuideActionFactory.lua 输出引导行为工厂代码 根据业务需要有如下UI 表现方式
在这里插入图片描述
下面lua代码是引导行为基类

local ActionBase = INew:new({
	actionType=nil,
	guideStep=nil,
	actionParaDict=nil,
	paraDict=nil,
	showFingle=false,
	textDes = nil,
	fingerType = nil,
	actionWaitTime = 0.2,
	actionCompleteTime = 0.3,
	})
function ActionBase:Init(guideStep,paraDict)
	self.guideStep=guideStep
	self.paraDict=paraDict
	if self.guideStep:IsKeyStep() then
		--GuideManager.StoryStepMgr.ReqNewbieMsg(self.guideStep.stepID)
	end
	self.textDes = self.paraDict.language
	self.fingerType = self.paraDict.fingerDirection
	self.effectData = self.paraDict.behaviorParameter
	self:OnInit(guideStep,paraDict)
end
function ActionBase:SetSkipShow()
	if not self.paraDict.nextStep then 
		GuideManager.SetUIsSkipBtnState(false)
	end
end
function ActionBase:GetTxtDesAndPos()
	if not self.textDes or self.textDes == "-1" then
		return nil
	end
	local tab = string.split(self.textDes,';')
	local posTab = string.split(tab[2],':')
	return tab[1],Vector2.New(tonumber(posTab[1]),tonumber(posTab[2]))
end
function ActionBase:GetFingerData()
	if not self.fingerType or self.fingerType =="-1"then
		return nil
	end
	local pos = self.paraDict.fingerCoordinate
	local posTab = string.split(pos,':')
	return self.fingerType,Vector2.New(tonumber(posTab[1]),tonumber(posTab[2]))
end
function ActionBase:GetStepId()
	return self.paraDict.id
end
function ActionBase:OnInit(guideStep,paraDict)
end
function ActionBase:AddLisnter()
end
function ActionBase:RemoveLisnter()
end
function ActionBase:Action()
	self:ImmediatelyAction()
	uiManager:DelegateLuaFunctionToUnityInvoke(function()
		self:WaitAction()
	end,self.actionWaitTime)
end
function ActionBase:ImmediatelyAction()
end
function ActionBase:WaitAction()
end
function ActionBase:DispatchCompleteMessage()
	EventManager.Dispatch(GlobalsConst.GuideEventID.EVENT_COMPLETE_CONDITION_LOCAL_COMPLETE,{stepID = self.guideStep.stepID})
end
function ActionBase:OnActionComplete()
	self:OnImmediatelyActionComplete()
	self:SetSkipShow()
	uiManager:DelegateLuaFunctionToUnityInvoke(function()
	self:OnWaitActionComplete()
	end,self.actionCompleteTime)
end
function ActionBase:OnWaitActionComplete()
end
function ActionBase:OnImmediatelyActionComplete()

end
function ActionBase:Exit()
	self:OnExit()
end
function ActionBase:OnExit()
end

GuideOpenConditionFactory.lua 引导触发条件工厂类,根据业务需要有如下条件:
在这里插入图片描述
触发条件基类如下:

local ConditionBase = INew:new({
	guidManager = nil,
	conditionType = nil,
	guideId=nil,
	conditionParaDict=nil,
	isPass = false
	})
function ConditionBase:Init(guidManager,guideId,paraDict)
	self.guidManager = guidManager
	self.guideId=guideId
	self.conditionParaDict=paraDict
	self:OnInit(self.guideId,self.conditionParaDict)
end
function ConditionBase:OnInit(guideStep,paraDict)
end
function ConditionBase:IsPass()
	return self.isPass
end
function ConditionBase:AddLisnter()
end
function ConditionBase:RemoveLisnter()
end
function ConditionBase:EnoughScene()
end
function ConditionBase:Exit()
	self:OnExit()
end

GuideStep.lua 是每一个分步的具体操作,就不一一细说了
GuideStepManager.lua 是一个大步下管理若干个分步的管理类
示例工程(2017 unity工程)
guidePackage

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Unity新手引导遮罩是一种常见的界面设计元素,用于引导新手玩家了解游戏界面和操作。它通常被用于高亮显示特定的界面元素,并给予玩家相应的提示。 Unity中提供了一些内置的方法来创建新手引导遮罩。首先,开发人员可以使用UGUI(Unity的用户界面系统)来创建游戏界面,之后可以添加一张透明的图片作为新手引导遮罩层。然后,可以使用Unity的2D或3D特效来绘制遮罩层所遮挡住的界面元素。这些特效可以使被遮挡的区域变得模糊或是使其颜色变暗,以突出要引导的界面元素。 在引导过程中,可以根据玩家操作的进度来动态地更新遮罩层的位置和形状。例如,当玩家点击一个按钮时,可以使遮罩层逐渐展开,直到完全显示该按钮为止。还可以使用动画效果来增强引导的效果,如淡入淡出或平滑移动的效果。 同时,为了提供更多的交互性,可以为遮罩层添加响应玩家操作的功能。比如,在遮罩层上添加一个按钮,当玩家点击该按钮时,遮罩层可以自动更新到下一个引导步骤,或者直接跳转至其他相关的功能界面。 总之,Unity的新手引导遮罩是一个非常实用的功能,可以帮助开发人员创建出更具吸引力和易于理解的游戏体验。它不仅可以引导玩家了解游戏的操作流程,还可以提供更好的用户体验,使玩家更容易上手游戏。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值