CG 大作业
CG大作业
内容
成员:马梓培 李涛 熊蔚然
我们选取交互这个选题,想复现出⼀款游戏,“火柴人打羽毛球”。本地双人对打。
我们最终实现的效果:
- 控制火柴人的移动,包括左移右移,向上跳跃。以及击打羽毛球
- 根据球拍的角度,去实现对击打羽毛球的模拟,包括球速以及角度
- 背后场馆观众,可以通过纹理贴图实现
以下是网络上一些游戏的截图:
1 火柴人控制原理
我是基于“GAMES105-计算机角色动画基础”课程,来建模以及控制火柴人动作的。
关节的种类
因为实现的是二维平面的运动,所以关节的自由度为1,只会在一个平面上进行顺时针or逆时针的旋转。
前向运动学
从根节点往叶结点乘变换矩阵
- 朝向:关节的局部坐标系相对于世界坐标系的旋转
- 旋转矩阵的逆就是旋转矩阵的转置: 正交矩阵的性质
某一个关节处的全局旋转矩阵 = 父关节旋转矩阵 * 该关节的局部旋转矩阵
2 火柴人建模(蓝色为关节):
举例来说,左手臂小臂的全局旋转矩阵 = 左手臂大臂的全局旋转矩阵 左手臂小臂的局部旋转矩阵;右小腿的全局旋转矩阵 = 右大腿的全局旋转矩阵 右小腿的局部旋转矩阵……
在具体实现时,通过glPushMatrix(),先把当前的矩阵压入栈中,然后进行旋转,最后通过glPopMatrix(),把当前矩阵弹出栈,恢复到之前的状态。
部分代码实现:
1 | glPushMatrix(); // ArmA |
这里的实现中,ArmA表示大臂,ArmB表示小臂。
例如,我们想要小臂进行旋转,需要先将大臂的旋转矩阵压入栈中。在此基础之上,才能进行小臂的局部旋转。小臂是依托于大臂的,两者之间存在一定耦合性。
2.1 移动
由建模直接画出,但是关节间是存在依赖关系的。
左移、右移,我模拟了人走路的姿势:
可以看到,正是小腿基于大腿的旋转,最后的移动是自然的,符合人类走路的方式。
1 | right_legA = -legA_angle_vec[index]; |
具体实现时,因为人的走路姿势是循环的,我构造了两个数组,分别表示大腿和小腿的旋转角度。通过 index = (++index) % 5
来更新下标,达到循环遍历数组的效果。这里我为了游戏的流畅性,将循环的频率设置为5,因此最后的人物移动可能看起来是会有点间断,不连续。
2.2 跳跃
火柴人的跳跃,我结合了物理学建模。
实时垂直方向速度:
实时垂直方向位置:
这里时间 t
的获取我是通过调用 <chrono>
这个库来实现的。首先,先定义一个 Common.cpp
源文件,定义startTime变量
std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
因此,startTime变量在程序开始运行时就被定义了。在其他文件中,我们可以一样使用 <chrono>
库获得当前时间,再用extern引用startTime,两者相减,便可以知道程序运行的时间。出于方便,我又定义了一个 Common.h
头文件,用 extern
引用startTime。其他文件直接 include 'Common.h'
即可,不需要重复extern。
因此,当我们想实现跳跃时。我们需要知道两个关于时间的变量,分别是跳跃起始时间,当前时间。当前时间-跳跃起始时间,便是跳跃时间。
1 | // myglWideget.cpp |
2.3 挥拍
挥拍的实现,和上面的思路基本一致。唯一不同的是,手臂旋转的角度,通过$sin(\theta)$实现,其中$\theta$属于[0,180]度。
1 | if (timeSinceWave > 0.5) { |
-135和-100度,分别是持拍的手臂和大臂的旋转角度。
另外,我还建模了发球时人物的挥拍,具体实现原理类似。
3 羽毛球运动建模
3.1 球速
建模击打羽毛球后,羽毛球的旋转角度,以及速度大小。
$|v_{球}| = |v_{球}|+|v_{拍}|+k\theta-C$
击打回羽毛球后,羽毛球的球速等于当前羽毛球的球速+固定拍子动能+拍子旋转角度-羽毛球球拍吸收的动能。
这是对羽毛球运动的一个简单的建模,并且符合现实。随着拍子旋转角度越大,对羽毛球速度的提升越大。对应于$k\theta$。并且羽毛球拍会吸收固定的动能,对应于$-C$。
1 | float cur_bmt_speed = sqrt(curspeed_x * curspeed_x + curspeed_y * curspeed_y); |
3.2 球速更新
飞行时,通过这两个公式更新球速:
对应羽毛球的角度,通过反正切函数来获得。
实现代码
1 | curposition_x = preposition_x + timeSinceHit * curspeed_x; |
羽毛球检测击中
根据球拍的全局旋转矩阵获得当前时刻的球拍的中心坐标,判断羽毛球当前时刻的位置是否与球拍中心位置接近。只要两者的距离小于设定的阈值,便视为击中。
1 | float racket_global_angle = player1->arm + 135; |
最终的效果:
实验总结
这个大作业,还是蛮有意思的。它设计到了方方面面。包括物理,数学的建模,以及人物运动时,身体各个关节处的耦合性。我是学习了Games105后才对其有了浅薄的理解。从头到尾,我们的大作业都是100%原创的(至少我的部分是),这还是蛮有挑战的。所幸,在老师的建议和同学的帮助下(深夜一起debug,感谢我的好homie 李涛),我们最终完成了这个大作业。我的收获也是颇丰,虽然火柴人的建模很简单,但是我对前向运动学知识的理解加深了很多。父关节和子关节之间的联系,是很关键的,这是让火柴人动起来的重要因素!!