wow镜头模拟

        3D游戏编程中,镜头的控制相当重要,不同的镜头表现,能给玩家完全不同的体验;比如《跑跑卡丁车》中的跟随镜头,每当甩尾的时候,镜头也会有相应的运动轨迹,如果只是单单的垂直俯视,那肯定全无甩尾的感觉。废话少说,这里分析下魔兽世界中主角的跟随镜头行为,因为这个跟随模型很简单,网上也很少有镜头跟随模型相关的文章,希望能起到个抛砖引玉的效果。阅读本文需要有一定3D图形学基础。


 在wow中,主角的镜头主要可以分解为3种运动,这3中运动相互独立,为编程带来了方便:

1.镜头forward方向的进、退,根据鼠标滚轮控制

2.绕主角垂直中心轴的转动,鼠标按住左键后左右移动控制

3.绕某水平轴的转动,鼠标按住左或右键后上下移动控制

玩过wow的朋友可以细想一下,主要方式是不是就这三种。其它方面,比如wow中有个选项,主角移动的时候镜头会自动旋转到人物后方;还有就是镜头因为场景碰撞离主角很近以后,当碰撞没有的时候,会自动退回到原始的跟随距离等。这里将分析并实现以上描述的5点。


接下来做一些约定:

世界坐标的垂直向上方向为Y轴正方向

同时主角的Y轴始终与世界Y轴一致,主角可在XOZ平面内旋转

主角的朝向为其Z轴正方向,主角的X轴和Z轴始终在XOZ平面内,即其y分量为0

镜头的朝向为其Z轴正方向,镜头的X轴始终水平,在XOZ平面内

镜头与场景采用球形碰撞


不太明白的可以看示意图:


                                              相机跟随模型侧视图 

侧视图中,椭球体代表主角,点T是主角的Root Bone位置,一般为脚底;点P是相机位置;点A是主角垂直中心轴上一个点,相机始终盯着这个点



                                            相机跟随模型俯视图,对应3种运动的第2种

俯视图中可以看到,主角的Z轴代表其正前方,相机的Z轴代表其观察方向


在这里,相机的运动其实就是向量P->A的运动:

P->A缩短、变长就是运动方式1

P->A绕主角垂直中心轴360°无限制旋转就是运动方式2,见俯视图

P->A绕经过A点的某水平轴有限制旋转就是运动方式3

下面看下运动方式3的有限制旋转示意图:


                                             相机俯仰角示意图,对应3种运动的第3种

theta角为向量P->A上下旋转的最大限制角,可以设定为上下对称,也可以设定为不对称(不过需要另外写代码判断,这里不做分析)

        为简化编程,我们将向量P->A的运动在跟随目标(主角)的局部坐标系内先实现,再转换到世界坐标系。跟随目标的X轴和Z轴见‘相机跟随模型俯视图’,Y轴见‘相机跟随模型侧视图’,在此局部坐标系中,点T成为坐标系原点


        鼠标的运动可以分为三种:左右移动,上下移动和滚轮滚动,这里分别叫做delta X, delta Y,delta Z,简称 dx, dy, dz,dx、dy的符号跟屏幕坐标系定义有关,这些参数可以从系统处获取,这里不讲。

鼠标左右移动的时候,dx不为0,向左dx < 0,向右dx > 0

鼠标上下移动的时候,dy不为0,向上dy < 0,向下dy > 0

鼠标滚动轮动的时候,dz不为0


        下面根据实现代码逐步分析,代码是用python写的,熟悉c/c++的朋友很容易看懂,代码复制下来并不能直接运行,因为依赖其它模块,要用到自己游戏中需要做些修改

先定义一个类CMouseInputEvent,上层逻辑将镜头用到的相关输入通过该类传递给镜头控制类:

KEY_PAD = 0x01

KEY_TRG = 0x02

KEY_RLS = 0x04

#KEY_PAD表示按键当前是按下状态,KEY_TRG表示按键当前有按下动作,KEY_RLS表示按键当前有松开动作

#下面的lbutton、rbutton、mbutton都是KEY_PAD/KEY_TRG/KEY_RLS按位或的组合


class   CMouseInputEvent:
def __init__(self):     #构造函数
self.lbutton = 0    #left   button
self.rbutton = 0    #right  button
self.mbutton = 0    #midden button
self.mousedx = 0
self.mousedy = 0
self.mousedz = 0
def setMouse(self, lbt, rbt, mbt, dx, dy, dz):
self.lbutton = lbt
self.rbutton = rbt
self.mbutton = mbt
self.mousedx = dx
self.mousedy = dy
self.mousedz = dz
class   CFollowCameraAI:
def __init__(self, cmr, scn, target, ofst, lkatofst):
self.UnitY = math3d.vector(0, 1, 0)  #向上的Y轴
self.input    = CMouseInputEvent()
self._camera = cmr                       #场景camera引用
self._scene = scn                          #场景引用
#跟随目标
self.followTarget = target              #跟随目标引用
self.cameraOffset = ofst               #camera位置相对跟随目标的偏移,向量T->P,局部坐标系参数
self.lookatOffset = lkatofst            #camera lookAt的点相对跟随目标的偏移,向量T->A,局部坐标系参数
#控制参数
self.minOffsetLen = 5.0                #最小跟随距离,向量P->A的最短距离
self.maxOffsetLen = 168.0           #最大跟随距离,向量P->A的最长距离
self.limitTgn = math.tan(1.4)        #最大俯视/仰视角度(1.4弧度大约为80°)
#速度控制
self.x_scroll_speed = 0.008         #X,左右方向旋转速度,控制向量P->A绕目标Y轴旋转
self.y_scroll_speed = -0.0022     #Y,上下方向旋转速度,控制向量P->A绕H平面旋转
self.z_scroll_speed = 0.05           #Z,前后方向滚动速度,控制向量P->A长短
self.rotback_angular_speed = 0.1     #自动回转速度,控制同x_scroll_speed
self.z_backaway_speed = 40.0         #轴向位置插值速度,控制同z_scroll_speed
#碰撞检测
self.radius = 0.8                            #camera采用球形碰撞,这是碰撞球半径
self.collided_shorten = False      #某内部状态标志位
u = math3d.vector(1, 1, 1) * self.radius
self._col_shape = collision.col_object(collision.SPHERE, u)    #构建球形碰撞体
def Update(self, dTime, bTargetMoving, bRotBackEnabled):
#dTime,帧时间,单位秒
#bTargetMoving, bool, 目标是否在移动
#bRotBackEnabled, bool, 是否允许camera横向转回到跟随目标背后
rot_back_request = bTargetMoving                      #跟随目标移动的时候才转到背后,停下的时候不会转
LookAt = self.lookatOffset - self.cameraOffset    #向量P->A
#远近控制
if self.input.mousedz != 0:
curLength = abs(LookAt)          #向量当前长度
LookAt.normalize()
#根据滚轮位移计算需要缩放向量P->A多少距离,并限定其范围
curLength -= self.input.mousedz * self.z_scroll_speed
if curLength > self.maxOffsetLen:
curLength = self.maxOffsetLen
elif curLength A绕目标Y旋转一定角度
#横向旋转矩阵
upRot = math3d.matrix.make_rotation(self.UnitY, self.input.mousedx * self.x_scroll_speed)
LookAt *= upRot              #旋转向量P->A,横向
#上下控制
if self.input.mousedy != 0:
#左中右三键都可以控制上下方向
if ( ((self.input.lbutton & KEY_PAD) and (not (self.input.lbutton & KEY_TRG)))
or ( (self.input.rbutton & KEY_PAD) and (not (self.input.rbutton & KEY_TRG)))
or ( (self.input.mbutton & KEY_PAD) and (not (self.input.mbutton & KEY_TRG))) ):
#计算纵向旋转轴,该轴与目标Y和Z垂直;相当于目标将其Z方向旋转到向量P->A的水平分量方向
right = LookAt.cross(self.UnitY)
right.normalize()
#纵向旋转矩阵
rightRot = math3d.matrix.make_rotation(right, self.input.mousedy * self.y_scroll_speed)
temp = LookAt * rightRot     #旋转向量P->A,纵向
#纵向旋转后需要做俯仰角限定
#先计算当前水平分量对应的最大垂直分量
maxY =  math.sqrt(temp.x * temp.x + temp.z * temp.z) * self.limitTgn
if temp.y < -maxY:
temp.y = -maxY
elif temp.y > maxY:
temp.y = maxY
temp.normalize()
LookAt = temp * abs(LookAt)  #最终方向向量 * 向量P->A原始长度
#放开左键后,自动转到目标背面
if bRotBackEnabled and rot_back_request and not (self.input.lbutton & KEY_PAD):
dst = math3d.vector(0, 0, -1)    #要转到哪根轴(跟随目标的局部坐标系,正前方是(0,0,1),背后就是(0,0,-1))
src = math3d.vector(LookAt.x, 0, LookAt.z)    #当前轴水平分量
src.normalize()                  #src归一化,方便计算,dst也是归一化向量
t =  src.cross(dst)              #src转到dst,因此旋转轴是src叉乘dst,因为是两个水平向量叉乘,结果应该是垂直方向的向量
cp = t.dot(self.UnitY)         #cp = t.y = sin(theta),src、dst、self.UnitY的混合积
dp = src.dot(dst)                #计算src到dst的夹角,dp = cos(theta);当src与dst重合时,dp == 1或-1;当src与dst垂直时,dp == 0
abcp = abs(cp)                  #因为是src叉乘dst,sin(theta)有正负之分;当src与dst重合时,cp == 0;当src与dst垂直时,cp == 1
#非尾部且非夹角很小;当dp> 0时说明src与dst夹角小于90°,即src在主角背后;当dp<0时src在主角前方
if  not ((abcp < 0.1) and (dp > 0)):
r = self.rotback_angular_speed
if abcp >= 0.1:     #非正向且非夹角很小
r *= cp / abcp
if abs(r) > abcp:
r = -cp          #旋转角度比实际夹角小
yRotBack = math3d.matrix.make_rotation(self.UnitY, r)
LookAt *= yRotBack
#计算需求位置
mat = self.followTarget.rotation_matrix               #跟随目标的方向矩阵
mat.translation = self.followTarget.position         #跟随目标的位置,点T
fwd = mat.mulvec3x3(LookAt)                              #fwd是LookAt的世界坐标,将最终变成camera的方向向量
self.cameraOffset = self.lookatOffset - LookAt   #self.cameraOffset的局部坐标(局部坐标-局部向量)
ofst = self.cameraOffset * mat                             #ofst是self.cameraOffset的世界坐标,点P
#碰撞检测,需要在世界坐标系里执行
start = self.lookatOffset * mat                 #start是self.lookatOffset的世界坐标,点A
delta = ofst - start                                    #ofst是self.cameraOffset的世界坐标,点P
#由近及远插值
if self.collided_shorten:                           #如果相机在自己z轴向遇到过碰撞,即向量P->A当前长度可能小于预定长度,则缓慢插值到预定长度
curOfstLen = abs(self._camera.position - start)
reqOfstLen = abs(delta)
if curOfstLen < reqOfstLen:
curOfstLen += self.z_backaway_speed * dTime * ((bTargetMoving and 2.0) or 1.0)
if curOfstLen > reqOfstLen:
curOfstLen = reqOfstLen
delta.normalize()
delta *= curOfstLen
ofst = start + delta
else:
self.collided_shorten = False
hit, hit_point, normal, tt, color, objs = self._scene.col.sweep_test(self._col_shape, start, start + delta)
if hit:
self.collided_shorten = True                    #上次曾碰到过,记个标志位
ofst = hit_point + normal * self.radius     #沿碰撞表面法线外移radius,减少camera穿面情况
#设定最终方位
self._camera.set_placement(ofst, fwd, mat.up)   #camera pos, camera forward vector, camera up vector

有不明白的地方可以交流,也欢迎更好的建议。




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/352175.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

魔兽世界服务器卡 邮件寄不出去,魔兽世界怀旧服邮件收不到怎么办 WOW怀旧服邮件取不出来解决方法...

魔兽世界怀旧服邮件收不到是游戏邮箱玩法&#xff0c;玩家们邮寄金币与物品给朋友时有时候等了很久还没到达喔&#xff0c;很多玩家想知道魔兽世界怀旧服邮件收不到怎么办、WOW怀旧服邮件取不出来解决方法呢&#xff0c;跑跑车游戏网为大家带来介绍。 *魔兽世界怀旧服邮件收不到…

ROS:launch启动文件的使用方法

目录 一、launch文件结构二、launch文件语法2.1根元素2.2参数设置2.3重映射、嵌套 三、示例3.1示例一3.2示例二3.3示例三3.4示例四 一、launch文件结构 由XML语言写的&#xff0c;可实现多个节点的配置和启动。 不再需要打开多个终端用多个rosrun命令来启动不同的节点了 可自动…

Unity3D学习笔记(二十三)导入WOW角色

今天看到新闻&#xff0c;魔兽世界最新的资料片《潘达利亚的迷雾》就要在十月二日上线了。这次中国大陆服务器总算是有机会版本与全球同步&#xff0c;和世界上其他地区的玩家在Raid进度上一决高下。 作为一名几乎没有存在感的业余玩家&#xff0c;好像跟我也没有什么关系。 倒…

威固的MOM,你的WOW 「 WOW 手武之道」威固巅峰技术交流赛圆满收官

近日&#xff0c;由全球特种材料公司伊士曼旗下汽车膜品牌威固&#xff08;V-KOOL&#xff09;举办的2022威固WOW手武之道技术交流会&PK赛&#xff0c;顺利收官。来自各地服务商的多位技师光芒尽显&#xff0c;展示贴装艺术&#xff0c;分别赢得广州站、南京站、郑州站及成…

WOW!Illustrator CS6完全自学宝典pdf

下载地址&#xff1a;网盘下载 编辑推荐 由一线设计师联合打造的最详细、最权威的Illustrator自学宝典。内容完整、详细&#xff0c;实例时尚&#xff0c;视觉感超强。 内容简介 《WOW!Illustrator CS6完全自学宝典&#xff08;全彩&#xff09;》以这一系列过程为主线&#xf…

CSS3 会跳舞的三角形

会跳舞的三角形&#xff0c;这个动效使用了两个动画变换来实现&#xff0c;一个是水平方向的运动&#xff0c;一个是径向的旋转。 在两个方向的运动速度上加以一定的控制&#xff0c;就可以出来不同的舞蹈节奏感。 把这两个三角形换成CSS3卡通图片&#xff0c;可以进一步加工…

WOW制作小地图

。。。。。。。。。。。。。。。。。 原本只是想用unity自带的GUI功能实现魔兽世界的小地图效果&#xff0c;结果折腾了一个晚上。 原来的思路如下&#xff1a; 根据玩家坐标&#xff0c;计算出应显示的地图缩略图部分&#xff08;128128&#xff09;&#xff1b;用GUI遮罩将非…

wow

写博客就有积分&#xff1f; 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一…

wow怎么修改服务器地址,wow如何修改登录服务器地址

wow如何修改登录服务器地址 内容精选 换一换 由裸金属服务器自动分配的网络是禁止修改的,在只有SSH登录的情况下修改,有可能会导致裸金属服务器无法连接。如果裸金属服务器存在自定义vlan网络网卡,您可以配置或修改该网卡的网络。 容器镜像服务是一种支持容器镜像全生命周期…

Depcheck 检查前端项目中未使用的依赖包

前言 随着前端项目的迭代&#xff0c;项目中一部分的依赖包可能没被项目所使用的&#xff0c;手动查找这些依赖包耗时又繁琐&#xff0c;有没有根据能够快速的帮助我们识别和清理项目中未使用的依赖包呢&#xff1f; Depcheck 简介 Depcheck 是一款用于分析项目中依赖关系的…

斩获阿里offer,这份258页面试宝典也太顶了....

测试三年有余&#xff0c;很多新学到的技术不能再项目中得到实践&#xff0c;同时薪资的涨幅很低&#xff0c;于是萌生了跳槽大厂的想法 但大厂不是那么容易进的&#xff0c;前面惨败字节&#xff0c;为此我辛苦准备了两个月&#xff0c;又从小公司开始面试了半个月有余&#…

深入理解API网关Kong:动态负载均衡配置

深入理解API网关Kong&#xff1a;动态负载均衡配置 背景 在 NGINX 中&#xff0c;负载均衡的配置主要在 upstream 指令中进行。upstream 指令用于定义一个服务器群组和负载均衡方法。客户端请求在这个服务器群组中进行分发。 NGINX 提供了以下几种负载均衡方法&#xff1a; …

图论与算法(4)图的深度优先遍历应用

1. 无向图的联通分量个数 1.1 联通分量个数 无向图的联通分量个数是指图中无法通过边连接到其他分量的顶点集合的个数。可以通过深度优先搜索或广度优先搜索来计算无向图的联通分量个数。 1.2 记录联通分量 &#xff08;1&#xff09;多个联通量的数&#xff1a; 7 6 0 1 0…

linux 应用程序 键盘,在Linux下安装Noted:适用于Linux的键盘驱动的笔记应用程序...

得益于Pop!_OS 20.04和Regolith Linux之类的发行版&#xff0c;键盘驱动的台式机环境逐渐风行一时。Noted是一个新的笔记应用程序&#xff0c;可在Linux和macOS上免费使用&#xff0c;该应用程序是受Notational Velocity(流行的macOS开源笔记记录应用程序)启发的&#xff0c;其…

xheditor可视化富文本编辑器

简洁易用的基于jQuery的富文本编辑器xheditor从CSDN上已经改版退出了&#xff0c;新版的Markdown编辑器将原版的编辑文章相关SEO的设置也设为自动获取了&#xff0c;总的感觉现在的编辑器没有原来那么方便了。本文来自http://xheditor.com/&#xff0c;纪念在CSDN上用过感觉最好…

Guitar Pro中文版免费激活注册机码V2021.20.7下载地址问题疑难解答

很多玩音乐的小伙伴都有一个共同的难题&#xff0c;目前很多编曲软件都是由国外引进来的&#xff0c;自然是以英文版为主&#xff0c;那作为国人的我们使用起来自然就不是那么容易&#xff0c;当然技术在更新&#xff0c;这个问题自然也是要有解决的方案的&#xff0c;今天小编…

好用的Mac窗口管理器:Rectangle for Mac

Rectangle Mac中文版是一个基于Spectacle应用程序的开源窗口管理器&#xff0c;用Swift编写&#xff0c;能够使用键盘快捷键移动窗口并调整其大小。Rectangle mac中文免费版适用于绝大数应用,并拥有维护良好的开源库,持续更新.欢迎大家下载体验Rectangle mac窗口管理器 Spectac…

Affinity Photo for Mac中文破解版永久激活方法

Affinity Photo for Mac中文破解版是一款可以和PS媲美的专业修图软件&#xff0c;专为Mac用户设计&#xff0c;affinity photo中文版采用最佳 PSD支持技术&#xff0c;支持您需要的所有图片处理调整和修改的功能&#xff0c;是一款非常不错的专业图像编辑软件。小编现为大家带来…

toad 连接mysql8.0_toad for mysql免费版

MySQL是一种关系型数据库, 想对其进行专业的管理就来下载toad for mysql免费版吧! 它是一款非常好用MySQL数据库管理工具, 它能够帮助我们更加有效的编写SQL语句. 拥有自动执行数据库对象、版本控制集成、宏记录和回放等超多功能, 需要就来下载toad for mysql免费版吧! toad fo…

iStat Menus 6 for Mac中文破解版激活方法无需激活码

iStat Menus 6 Mac 中文破解版是Mac OS平台上十分优秀的一款系统与硬件监控软件&#xff0c;通过istat menus for mac 我们可以实时掌握自己的Mac电脑情况&#xff0c;可以查看硬件温度、查看即时网速、显示CPU使用率等等&#xff0c;非常实用。小编现为大家带来istat menus ma…