【案例设计】模拟 视线 影响玩家移动规则的设计演变思路

开发平台:Unity 2020
编程平台:Visual Studio 2022

前言

  在游戏中,玩家观测世界的方式是通过主摄像机(Main Camera)进行。将摄像机视线中的画面呈现于屏幕上,即游戏视线。在开发中,这类游戏视线的应用范围广泛,例如 Unity Technology 提供的 Occlusion Culling(遮挡剔除)的整体优化手段。或是 《Secret Library》 游戏中,对 Area Room 模型的优化加载方式,亦或是用于显示游戏角色 Monster 阵营的特性。本文以探讨基于游戏视线的娱乐性构思与设计。

思考:如何判断对象是否在摄像机范围内

  判断对象,最可能想到的使用方式是 Physics.RayCast() 物理射线检测。即通过 Camera.Main 发射射线进行识别与获取对象 Transform 组件信息,以判断对象是否在摄像机范围内。在Unity 2018版本中,有一款名为 InstanceOS 的优化插件,也是后来 Unity 版本中 Occlusion Culling(遮挡剔除) ,相比较下两者大同小异。
  Unity Technology 提供的 OC 原理是针对设置 Occluder StaticGameObject 对象进行 视窗判断。在场景视窗内的游戏对象,保持激活状态;不可见的游戏对象将保持禁用状态,以最终达到运行时最佳优化效果。

思考:视线检测的演变设计


1)仿逐屏幕坐标的物理射线检测

  通过使用多个屏幕坐标,批量性创建物理射线,对射线碰撞对象进行检测信息。有则返回游戏对象信息,无则返回Null

public void LateUpdate()
{
    
    
	List<Vector2> points = GetScreenPoints(100, 100);

	foreach (var item in points)
    {
    
           
    	Ray _Ray = ScreenPointToRay(item);
    	string result = Physics.Raycast(_Ray, out RaycastHit hit) ? "I m Looking!" : "I m not Looking";
        Debug.Log(result);
    }
}
  • GetScreenPoints(int x, int y):获取屏幕坐标点数据集,返回类型List<Vector2>。该方法基于 Screen.widthScreen.height 进行均等份屏幕坐标。为后续射线做准备。
  • Screen.width = Camera.main.pixelWidth Screen.height = Camera.main.pixelHeght

问题点:批量性使用物理射线对计算机性能损耗程度高。(官方文档与民间开发者实践反馈与分享)


2)基于屏幕坐标的检测方式

public string CheckIsInScreen_ScreenPoint()
{
    
    
	// targetTF 即目标 Transform组件
	Vector3 current = Camera.main.WorldToScreenPoint(targetTF.position);
    if (current.x >= 0f && current.x <= Screen.width && current.y >= 0f && current.y <= Screen.height)
    {
    
    
       return "In Screen";
    }
    return "Not In Screen";
}
  • Camera.WorldToScreenPoint():返回 Vector3 类型的坐标数据。
  • 在屏幕坐标环境下,其界定是否在屏幕范围内的计算基于 Screen.widthScreen.height 两种数据。该数据由 Unity 配置或自助检测得到。
  • 其返回会的最终 current 数据类型(x, y, z)均可有效使用。例如 x, y 组合判断是否在屏幕范围内, z 判断物体与相机之间的间距。

问题点

  1. 缺乏可配置的视线检测,例如策划配置这部分的存在一定数值的视距影响。则当物体处于无限远但仍然在视距范围内的问题。或遮挡物体环境下,被遮挡物体依旧在当前屏幕内(仅未渲染)。
  2. 默认中心位置位于 Target 游戏对象中心,若其位置正好位于边缘位置,则其模型的宽厚仍将暴露一部分在屏幕范围内。

3)基于视口坐标的检测方式

public string CheckIsInScreen_ViewportPoint()
{
    
    
	Vector3 current_Screen = Camera.main.WorldToScreenPoint(target.position);
	Vector3 current = Camera.main.ScreenToViewportPoint(current_Screen);
	if (current.x >= 0f && current.x <= 1 && current.y >= 0f && current.y <= 1f)
	{
    
    
		return "In Screen";
	}
	return "Not In Screen";
}
  • 判断是否在屏幕窗口内的方式 限定 xy轴值 限定于[0, 1]范围内,无须使用 Screen.widthScreen.height
  • 同屏幕坐标 问题点反馈。

思考:解决边缘位置,模型部分暴露的问题


   Transfrom 组件仅作为模型 Center / Pivot 模式下的一个点存在。对暴露在屏幕空间下的部分模型区域,最先设想到的是模型的 Mesh(网格),模型的绘制需要网格信息与片元信息,在 Unity 中以 MeshFilter 与 MeshRenderer 两个组件共同完成模型的绘制。 所以 MeshRenderer(网格渲染)成为解决模型部分暴露在屏幕空间下的解决方法。


OnBecameVisibaleOnBecameInvisible (消息机制)

public void OnBecameVisible() => Debug.Log("In Screen");
public void OnBecameInvisible() => Debug.Log("Out Screen");
  • OnBecameVisible() :当物体出现在 摄像机视角下时,执行一次命令内容。
  • OnBecameInvisible():当物体消失在 摄像机视角下的那一刻,执行一次命令内容。

  值得注意的是,对 Mesh Renderer 或准确意义上说是继承于 Renderer 基类的对象均可使用此类方法。且此方法仅适用于被检测的对象自身。要求该对象拥有且激活该类型的 Renderer 子类组件。
  在表现上,并非是肉眼见透过屏幕空间观察到对象出现或消失后即可发送消息。而是在小时后相机视野移动一小部分忽略不计的距离后进行消息传送。总体上并不影响问题。

问题点

  • 目标 与 摄像机 中存在遮挡物的情况下,该两方法依旧正常执行。
    解决方法(暂):使用 ScreenPointToRay() 建立射线检测机制(单条),用于判断对象是否为障碍物 或 目标对象。是则发送消息,否则不然。
    其他说明:在检测上建议设立白名单忽略层,例如 玻璃、铁网(贴图)等层可能会阻碍正常的射线检测。应避免此情况发生。

实践:仿SL编号173的判定逻辑。

行为逻辑描述:

  • 在摄像机观察范围内:视野范围内,不可移动,直到观察者主动偏移至视野内无该对象情况下。
  • 在摄像机观察范围外:自由高速移动。

  • 建立触发机制 - 观测机制。当物体渲染出现至 摄像机 视野范围内时,启用视口坐标的观测方法。
    1)在多人游戏下,建议使用 MyCameraCustomCamera 两种标签(Tag)区别 自己客户端与其他客户端用户的摄像机。
    2)仅针对己摄像机情况下进行 视口坐标/屏幕坐标(二选一)进行位置判断。同理,在触发退出机制时,通知服务器终止位置判断。
    :考量到联网数据传递与实际出现最高权限干涉出现多数目的编号对象下,独立各自的检测机制。交由服务器计算结果处理。
  • 建立计算规则 - 影响逻辑。在 屏幕坐标/视口坐标 的计算判断中。重点的距离判断(Z轴轴值)
    须注意:其计算的距离属于 目标 - 摄像机。实际上距离最低限制不局限于0f。有关具体合适的数据可调整。而为保证游戏性,也会设置距离最大值进行限制。使其仅在距离范围内生效规则。

小结

  或许 在遮挡物 判断上 Unity 有提供此类的API方法。或者整体的实现上更具备最优方案待发现与学习了解。期待这篇思路分享有助于开发者的开发进程。

猜你喜欢

转载自blog.csdn.net/qq_51026638/article/details/124796549