文章索引

J2ME RPG游戏边学边做(九)--模拟真实世界,浅谈游戏中的运动物理学

玩过/见过不少的手机游戏,包括下载率颇高的game。除了精彩的情节、动人的音效外,不可或缺的还要有一点运动学常识,这样人物动作、物体(如子弹、汽车)运动才会惟妙惟肖,给玩家更强的带入感。以我的个人观点:这样的game才能称得上是“game”,才够资格收费。

呵呵,结束了以“反对手机游戏乱收费”的牢骚后,给大家讲讲“运动物理学”在游戏开发上的应用才是不让“人望梅止渴”的好东东。

物体做抛物线运动是游戏中基本运动物理模型之一! 在PC游戏中可以由重力公式轻易模拟,但在手机游戏中 ,由于多数手机不支持浮点运算 因此不能用 sin ,cos, 来分解初速度。 所以只能用近似模拟的方法! 我所采用的是:先放大后缩小的模拟方式,并且为了更精确加入了一定的偏移量。

先用哈希表列出0-90度的正弦值,并且把值放大100000倍,例如:

Hashtable anglevalue;
public void loadAnglevalue()
{
    anglevalue = new Hashtable();
    anglevalue.put(String.valueOf(0),new Integer(0));
    anglevalue.put(String.valueOf(30),newInteger(50000));
    anglevalue.put(String.valueOf(60),new Integer(86603));
    anglevalue.put(String.valueOf(90),new Integer(100000));
    ……


这样就可以得出各种角度的正余弦值
设初速度为V0 物体当前坐标为x=0,y=0; t为时间 g重力=10;
根剧力学公式 
Vx=V0*cos&;
Vy=V0*sin&;
再根据重力公式:
x=Vx*t;
y=Vy*t –5*t*t;

由于cos& sin&都是放大了100000倍的所以 再得到手机屏幕坐标的时候应该缩小100000倍

x=Vx*t/100000;
y=(Vy*t –5*t*t)/100000;

现在公式中除了t之外都解决了! 现在来解决时间t;


我们可以在游戏主循环的 中有不断增加t的值 但是因为主循环非常快!以毫秒计算所以我们应该加入缓冲

while (true){
    thisThread.sleep(10);
    if(bFire){
        tTemp++;
        if (tTemp >10) {
            t+=1;
            tTemp = 0;
        }
    }
}

代码中的if (tTemp >10) 这个值的判断就调整了时间的增长频率!你也可以用if (tTemp >2)来使时间增长加快 或则用其他数值让时间变慢注意的一点就是我们的时间也要放大! 
至于放大多少倍 则要看游戏的节奏!我这里暂且放大20000倍
因此公式为:

x=Vx*t/100000;
y=(Vy*t –5*t*t*20000)/100000;

还有 我们需要把 物体初始位置放在 屏幕的下放那就需要加个初试位置常量
公式变为:

x=Vx*t/100000;
y=(100000*(getHeight()-20))-(Vy*t –5*t*t*20000)/100000;

getHeight()在手机中为得到手机屏幕的高度

好了 来看看用了这个公式后的运行效果(NOKIA 7650模拟器 或则unijava模拟器)
图1




这是45度角情况下的抛物线轨迹,是不是觉得高度不够呢! 运算不够精确! 那么我们在Y上加个偏移量来增加高度

这个代码 是在平抛的时候就不需要加入高度偏移了

现在再看45度角的 抛物线
图2



如果你还不满意 还可以改动偏移数值来让模拟更精确

下面来看一些角度 在不同力度 和风速下的轨迹快照

J2ME RPG游戏边学边做(十)--如何提高j2me游戏移植性

一、编写易于移植的J2ME代码

我写第一个J2ME游戏的时候,根本就没想过移植的问题。所以那个游戏也就很难移植了。反过来,如果你已经计划好要移植了,那么事情就简单的多。这一节说的是代码问题。那就想想,不同手机之间在代码上会有哪些差异。

(1) 屏幕尺寸不同

这儿谈的主要问题,是自适应控件。所谓控件,就是菜单、文本框、列表框、进度条等等。这些控件的大小必须可以根据屏幕大小自适应的调整。按照第一篇说的方法,将屏幕大小作为变量参与到控件尺寸的计算即可得到正确的尺寸(自适应后的)。其次就是得到正确尺寸后怎么把它画出来。

这要看你的GUI是怎么画得了,如果是用线画的,那就很简单;如果使用了图片,那么就可能要更换图片了。我的控件使用了图片平铺和画线结合,所以可以很容易的改变尺寸。如果控件变大了,则绘制时增加平铺的次数即可。

顺便说一下,这些控件我只用了一个类表示,使用参数化的方法区分使用,毕竟咱要尽量少用类吧。

(2) 支持的API不同

如果你的游戏只限于使用Midp1.0,那么移植的时候就不用考虑什么了。实际上由于我们经常要使用图片翻转、象素绘制、全屏等,往往要用到厂商API或Midp2.0。显然移植的时候要考虑到这些API的差异。

我的办法是将这些api封装一层,比如我需要使用创建透明子图的API,于是封装了一个函数createSubImg。这是Nokia版本:

public static Image createSubImg(Image img,int []imgRect)
{
Image subImg = DirectUtils.createImage(imgRect[2],imgRect[3],0) ;
subImg.getGraphics().drawImage(img,-imgRect[0],-imgRect[1],20);
return subImg ;
}
这是Midp2.0版本:
public static Image createSubImg(Image img,int []imgRect)
{     
return Image.createImage(img,imgRect[0],imgRect[1],imgRect[2],imgRect[3],0) ;
}


对于不同机型,该函数的实现不同,但功能相同,因此使用这个函数的代码在移植时无需修改。当然这样做增加了一些间接性,有可能降低性能。

(3) 按键代码不同

我们知道MIDP提供了Game Action,和按键代码无关,但这不够用啊,我们完全可以定义自己的Game Action,但首先让我们定义自己的虚拟按键码吧。我使用位记录每个键的状态,每个位代表一个按键,一个int有32个位所以足够了。

当keyPressed 发生时,我记下哪些键被按下;同样当keyReleased时,将那些被松开的键使用的位清0。某个键,也就是这个键盘状态整数里的某个位,就是我定义的一个虚拟键。当然它的值总是2的n次方了,和key code完全不搭边,所以需要我们用一个映射函数将key code映射到这些虚拟键。

这个函数就是移植的关键,每个机型都要改写这个映射函数,在里面填入正确的key code。你可以在虚拟键的基础上再定义Game Action,支持在游戏中设置按键,这样就更灵活了。

(4) 封装库

如果想不更改一行代码就从MotorolaV600移植到Nokia N-Gage,那么为他们封装不同的库吧。我就这样在1分钟内完成了移植。我的库包含了一个游戏框架类(内含游戏循环和渲染函数,键盘处理,以及若干跨机型的工具函数),一个图形组管理类(管理图片的载入切割旋转绘制和动画等,有点像GameAPI中的Sprite)和一个控件类(包含了所有我需要的控件)。

这3个类封装了不同机型的所有差异,我需要为每种机型改写这三个类,当然大部分代码是相同的了。此外我还写了一个工具支持图形组管理类,所见即所得的编辑动画和管理图片,当然这也对移植有帮助。

总结:

以上几条,总得讲来,无非是拆合而以。主要是要将差异性独立出来,便于更改。但是移植总得来讲还是比较郁闷,主要原因是各种机型有各自的bug,这就需要特殊处理啦。各位写代码时一定要想好移植的问题啊