这次需要做一个以跑跳为主题的游戏demo,想法先不说,首先创建工程:
cocos new Jump -p com.ziqiang.cocos.game -l cpp -d /Users/...(最后一项是位置,即工程存放的路径)
Tips:
1 还有一个tip写在这里,在游戏中创建一个字符label用如下的语句来创建,程序中一定会用到:
</pre></p><p style="font-size: 11px; margin-top: 0px; margin-bottom: 0px; font-family: Menlo;"><pre name="code" class="cpp" style="color: rgb(255, 255, 255);"><span style="background-color: rgb(0, 0, 0);">auto jumpItem = MenuItemLabel::create( Label::createWithSystemFont( "Jump", "" ,30), CC_CALLBACK_1(HelloWorld::menuCloseCallback,this) );</span>
或者分两行:
auto labelup = Label::createWithSystemFont("UP", "Arial", 100);
auto upMenuItem = MenuItemLabel::create(labelup, CC_CALLBACK_1(HelloMap::menuCallback, this));
2 关于schedule函数来实时更新物体位置
schedule这个函数经常用到,它用来描述节点对象上定时执行的方法,物体跳起与落下的动作是通过渲染场景中该物体的位置变化实现的。
要描述位置的变化,需要给物体设置初始位置并指定位移,
其中,初始位置是一个二维的点,当然也可以用向量描述.
位移就是位置的变化,即物体由初始位置运动到终位置的一段向量.
所以t时刻的位置就是初始位置与位移之和。这三个量都是向量:
初始位置是我们设置的,想要获得终位置,位移的获知是关键。
位移怎么变,取决于作者想写一个什么运动,匀速运动,匀变速运动,还是变加速运动等等
如果是实际的物理世界中,
物体做匀速运动,速度为V,每一微小的时间段dt里面的位移是:
ds=V*dt
物体做匀变速运动,加速度位a,每一微小的时间段dt里面的速度变化是:
V=a*dt
对应位移就是:
ds=(V0+dv)*dt 注:V0是这段位移起始点上物体的速度。
注:变加速运动不说,对于有重力作用的一些运动,这些就够了。
因为对于游戏中世界呈现是通过渲染实现的,每渲染一次就会做相应的运算一次,所以游戏中的物理世界的时间轴是离散的。
如果我的schedule函数如下句所写:
this->schedule( schedule_selector( HelloWorld::s_sche), 1.0f );
执行这个语句就意味着我1秒钟执行一次s_sche。而s_sche函数就是用来更新物体位置的,即物体的位移信息包含在这个函数中。
但是如果参数设置不合适,虚拟世界的运动就会很假。重点就是设置加速度,初速度等参数。
所以问题来了,怎样设置这些物理参数,我们的运动就会看起来更像真的呢?
我们如果想“模拟”真实的现实世界,可以先想象这样一个现实世界,然后假定我们的模拟是现实世界的等时间段抽样即可,相当于隔一段时间为现实的物理世界拍一次照片。这个时间段就是上面schedule函数中的1s。如果设置成1.0f/60.0f就代表1/60s拍一次照。
在程序中每拍一次照片的时间,需要按照程序计算一次新的位置,我们把这个算法写进s_sche函数中就能在等时间间隔的执行了。
另外,把手机屏幕看作现实世界的缩小画面的话,就需要进行虚拟世界和物理世界两域中距离的换算了,我们一般游戏中精灵跳跃的高度一般都会很高,往往比自身的身高还高,但在玩家的游戏体验上,这点是能够接受的。所以针对精灵跳起的高度比自己身高还高的情况,与其说跳起,不如说被抛起。
所以现在假设一个直径是40像素(40p 注:这里一单位p代表一单位的像素宽度)的圆球,那么通常来看,这个40像素的圆球在现实世界中可以假设成直径20cm的一个圆球,后面我们要将这个球抛起,所以起码假设它和我们双手的尺寸差不多大。根据这个能接受的假设,我们就有换算关系:
40p=20cm=0.2m
即 1m=200p
通过百度,我们可以查到抛起一个物体,物体离开手的初速度大概是30m/s左右,当然这个值取决于托起物体的距离,你手作用于物体的力及离开手的时间,这里就先不详述。那么根据30m/s的这个初速度假设,游戏世界中的初速度就可以是:
30m/s=6000p/s
现实物理世界加速度是大概取-10m/(s^2),换算成游戏中,就是:
-10m/(s^2)=-2000p/(s^2)
假设初始高度为0,所以向上抛起这个物体,1s后物体的位置是:
当然也可以带高中公式:
即(30*1-0.5*10*1)m=25m=5000p
5000p已经远远超出游戏的屏幕高度750p了,上面公式中可以看到,a,t都没有修改的余地,唯一可以修改的就是初速度v(0),可见30m/s太大了,我们想个办法来倒推多少比较合适。
假设物体向上运动的顶点是一半的屏幕高度,即375p处,就是1.875m,物理世界中顶点处的速度是0,可以根据下面两公式列方程:
v(t)-v(0)=at
s(t)=v(0)t+0.5at^2
其中v(t)=0,s(t)=1.875m,a=-10m/(s^2)
解得v(0)大概是6.1237m/s (注:(6^(1/2))/2)
即在游戏中是:1224.74p/s
验证一下这个数据是否合理:
用6.1237m/s的速度向上抛物,1s后物体的位置是:
(6.1237*1-0.5*10*1)m=1.1237m=224.74p
是个可能看起来舒服的结果了。
那么1/60s在哪儿呢?
(6.1237*(1/60)-0.5*10*(1/60)^2)m=0.100672m=20.135p
因为1/60s是cocos2d的渲染时间间隔,如果按照这个计算,就是第一次渲染时发现这个物体移动了20像素的位置。
同样可以算出以下数据:
2/60s时:39.71
3/60s时:58.73
4 :77.20
5 :95.11
6 :112.47
7 :129.28
根据方程
做图,直观的看到:(注:s-t图)
大概一秒多,物体就落回地面了。
这是比较舒服的方式。
可以直接把上面的公式写成代码用在程序里面
void HelloWorld::jump_up()
{
m_pRole->setPositionY((6.1237*(count*0.016666667)-5*(count*0.016666667)*(count*0.016666667))*200);
}<span style="color:#ffffff;">
</span>
这段代码中的count就是方程中的x,先在p_sche中写count++,就可以用了。一开始我写的是count/60,因为往往计算结果是无理数,所以结果是double型,达不到每秒60次,效果会有很强的顿挫感,即便如上面写成count*0.016666667,也会有顿挫感。索性简化成下面这样:
m_pRole->setPositionY((6.1237*(count*0.02)-5*(count*0.02)*(count*0.02))*200);
实践证实,还是会有轻微的卡顿。再简化:
m_pRole->setPositionY((6*(count*0.02)-5*(count*0.02)*(count*0.02))*200);
还是不理想,继续简化:
m_pRole->setPositionY(20*count-0.3*count*count);
效果还是不理想。所以问题应该出在乘法上。
相比较物理世界而言,游戏中的时间是离散的,那么下一时刻的速度v2=v1+at
下一时刻的位置就是s2=s1+v2t,在s-t图上看,相当于用一条一条的来拟合这条抛物线。时间间隔取得越窄越接近,顿挫感越差。这个顿挫感和之前那个顿挫感不是一回事,之前那个是运算速度跟不上渲染速度造成的,这个顿挫感是时间间隔设置太大造成的。当然在这种方法里,你可以设置感觉舒服的时间间隔,来有效的平衡顿挫感和计算量。
把这个思路运用到该想法中,设置t是(1/60)s,a=-10m/(s^2)=-2000p/(s^2)则
v2=v1-33
s2=s1+0.02*v2
同时设置初速度是1225p/s,写下面代码:
f_ySpeed = f_ySpeed-33;
m_pRole->setPositionY(m_pRole->getPositionY()+0.02*f_ySpeed);
还是有顿挫感,因为还有乘法。这里面的关键点是消除0.02*f_ySpeed。
要知道,匀变速运动中速度的变化是线性的,如果吧0.02这个系数提前给第一个方程乘过,后面就可以直接加了,所以初速度修正为:
0.02*1225p/s = 25p/s (从这里可以看出 现实世界速度 = 虚拟世界速度*抽样时间)
33修正为0.02*33=0.66,反观因为33=2000/60,此时可以将2000/60这个整体定义为我们虚拟世界中的加速度大小。
所以一般的,如果我们计算出虚拟世界的加速度应该是2000p/s^2,那么经过1/60s的抽样后,可以将2000/60理解为加速度大小。
这样就是:
f_ySpeed = f_ySpeed-0.66;
m_pRole->setPositionY(m_pRole->getPositionY()+f_ySpeed);
卡顿感没有了。
根据上面的思路,我们可以逆过来讨论:
如果我们写成如下代码:设置初速度是10,加速度是1,抽样时间是1/60s,
f_ySpeed = f_ySpeed-1;
m_pRole->setPositionY(m_pRole->getPositionY()+f_ySpeed);
那么这段代码描写的现实世界中的初速度,加速度分别是多少呢?大概多高的时候停止运动呢?
根据上面的思路,1=虚拟世界的加速度*抽样时间,即虚拟世界的加速度是60,
而 虚拟世界的加速度=现实世界的加速度*抽样时间,即现实世界的加速度时3600p/s^2
假设200p=1m,那么现实世界加速度就是18m/s^2 (注:距离的换算需要通过精灵的大小和场景中的一些图片尺寸综合考虑)。
又因为
虚拟世界速度=现实世界速度*抽样时间
则现实世界速度是600p/s,即3m/s
所以该物体是以3m/s初始速度向上抛,运动时的加速度大概是18m/s^2向下(话说所以空气阻力造成的加速度是8m/s^2向下,还挺大呢)
f_ySpeed = f_ySpeed-0.8;
m_pRole->setPositionY(m_pRole->getPositionY()+f_ySpeed);
运行一下,也没有卡顿的感觉。看起来还是很舒服。如果我们把抽样时间也写做一个变量,通过改动抽样时间来看运动何时卡顿:
float count = 0.03;
构造函数中写:
f_ySpeed( 1225*count ),
f_deltaySpeed( 2000*count*count),
p_sche中写:
f_ySpeed = f_ySpeed-f_deltaySpeed;
m_pRole->setPositionY(m_pRole->getPositionY()+f_ySpeed);
根据测试,0.03s即1/30s时运动还是显得比较光滑的,大于这个数值开始就有顿挫感了(用的iphone6模拟器测试)
下一篇文章再说吧,这篇内容太多了,就这样