基于JBox2D物理引擎开发的“雷电”小游戏(三)——模拟并显示世界

上一篇文章里讲到了如何创建世界及其边界,这次我将给大家讲讲如何模拟这个世界。

什么是模拟世界

虽然我们创建世界以及作为边界的刚体,但是这个世界是需要我们不断的去计算各个刚体的状态如速度、加速度、受力情况等,而这个不断的计算就是所谓的模拟世界,所以我们需要用一个定时器不断的模拟世界,使得世界能够运转。

模拟世界

ScheduledThreadPoolExecutor定时器

在这里,我选择使用ScheduledThreadPoolExecutor定时器,因为它比Timer的功能强大、性能优越,所以推荐大家使用ScheduledThreadPoolExecutor进行世界的模拟而不是Timer。

创建模拟世界任务MyTask

ScheduledThreadPoolExecutor的用法与Timer类似,所以也需要自己写一个MyTask让定时器去跑这个任务,这次我们的任务便是模拟世界,下面来看看代码:

//用作复制bl
public ArrayList<MyBody> _bl = new ArrayList<MyBody>();
//模拟的的频率
private float timeStep = 1.0f / 60.0f;
//迭代越大,模拟约精确,但性能越低
private int iterations = 10;
...

private class MyTask implements Runnable {
            private GameView gv;//游戏视图
            public MyTask(GameView gameView) {
                this.gv = gameView;//初始化成员变量
            }

            @Override
            public void run() {
                try {
                    if (!isGameOver) {//判断游戏是否进行中
                        gv.activity.world.step(timeStep, iterations); //开始模拟
                        _bl.clear();//清空_bl
                        for(MyBody mpi:bl){
                            _bl.add(mpi);//复制bl到_bl中
                        }
                        gv.repaint(); //绘制整个世界
                    }
                    ...
                }catch (Exception e){
                }
            }
        }

代码中的GameView是一个继承SurfaceView类的一个类,gv.repaint()为绘制世界,在下面会讲到。
_bl用于复制bl中的刚体,目的在于将动态改动的bl复制到一个静态的 _bl中,便于以后创建、销毁刚体时不会出错。
timeStep即为模拟频率,1/60表示60Hz。
iterations为迭代,具体的意思我也不是太清楚,这个值的大小对我的项目没有什么影响,设置为10就好了。
gv.activity.world.step()即为我们所需要的模拟了,这个函数让我们的世界正常运转,所以一定不要忘了。

设置MyTask

创建好了MyTask,我们需要将其添加至定时器中:

private ScheduledThreadPoolExecutor stpe;
private MyTask myTask = new MyTask(this);

private void playGame(){
            //参数表示打开的线程池大小
            stpe = new ScheduledThreadPoolExecutor(5);
            stpe.scheduleAtFixedRate(myTask, 0, 1000/60, TimeUnit.MILLISECONDS);
            ...
            }

ScheduledThreadPoolExecutor可以指定线程池的大小,即指定可以并发的线程数量。
scheduleAtFixedRate()四个参数意思为:任务、开始任务的起始时间、任务间间隔、时间单位。
TimeUnit.MILLISECONDS表示单位为毫秒。
scheduleAtFixedRate表示会在每一个myTask执行完毕后再开始计算时间间隔。

显示世界

经过我们的努力,总算是让世界运转起来了,不过虽然是运转起来了,但是却并没有把世界的“模样”显示给用户,所以我们接下来要做的便是将世界显示出来。
在创建刚体根类的时候,我给大家讲过,我们是通过canvas将刚体画出来的,所以我们采用的方法即为用画布时时画出世界的状态,以呈现出世界的模样。

GameView

在项目中,我创建了一个GameView用作显示游戏界面,GameView类是继承SurfaceView的一个类。在这里继承SurfaceView是因为SurfaceView很适合用作游戏界面,它具有双画布机制,两张画布轮流显示,即在显示一张画布时,另一张画布会画即将显示的界面,保证了界面的刷新的流畅。
下面就先看看部分代码:

public class GameView extends SurfaceView implements SurfaceHolder.Callback {

    private MainActivity activity;//父activity
    private Paint paint; //画笔
    ...

    public GameView(MainActivity activity) {//构造器
                super(activity);//调用父类
                this.activity = activity; //初始化成员变量
                this.getHolder().addCallback(this);//设置生命周期回调接口的实现者
                paint = new Paint();  //创建画笔
                paint.setAntiAlias(true); //打开抗锯齿
                playGame();//开始游戏
                ...
    }

    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {}

    //创建时被调用
    public void surfaceCreated(SurfaceHolder holder) {repaint();}

    //销毁时被调用
    public void surfaceDestroyed(SurfaceHolder arg0) {}

    public void repaint() {
            SurfaceHolder holder = this.getHolder();//得到回调接口的对象
            Canvas canvas = holder.lockCanvas(); //获取并锁定画布
            try {
                synchronized (holder) { //同步处理
                    OnDraw(canvas);  //绘制
                }
            } catch (Exception e) { //捕获异常
                e.printStackTrace(); //打印堆栈信息
            } finally {
                if (canvas != null) {//判断canvas是否为空
                    holder.unlockCanvasAndPost(canvas);//解锁画布
                }
            }
    }
    ...

holder.lockCanvas()即在获取未显示的那张画布,并锁定这张画布开始绘制,绘制完毕后通过holder.unlockCanvasAndPost(canvas)解锁画布并显示,这即为SurfaceView的双画布机制。
当然,以上只是一些简单的构造,真正开始绘制世界的是onDraw()函数。

onDraw

做了这么多的准备工作后,我们现在终于可以开始绘制世界了,下面先来看看onDraw()函数的代码吧:

public void OnDraw(Canvas canvas) {//绘制方法
    if (canvas == null) {//判断canvas是否为空
        return;//canvas为空则返回
    }
    canvas.drawARGB(255, 123, 123, 123); //设置背景颜色
    for (MyBody mb : _bl) { //遍历所有刚体
        mb.drawSelf(canvas, paint); //绘制
    }
    ...
}        

设置背景颜色的目的在于用背景色将画布之前的内容覆盖掉。
通过遍历复制bl后的 _bl的刚体,对每个刚体进行绘制。
OK,到这里,我们的世界就完完整整的模拟并被绘制出来了,有木有很激动!有木有!有木有!
咳咳~~现在显示是显示出来了,不过现在这个世界也就只有边界,其余的物体是没有的,所以即使显示出了世界没有什么可以看到的东西,不过不要方,相信看过上一篇文章后,你也会自己向世界中添加刚体了。当然,后面也会接着讲如何创建其他刚体的。

这次就先讲这么多,咱们下篇文章再见~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值