首页 > 技术文章 > 看博客学学Android(二十)

NorthDrift 2013-11-21 22:29 原文

原文地址:

Day 18: Sprite collisions with circle shape. Pixel-perfect bullet trajectory.

Today I got fed up with the "moon hit" bug. Sometimes aliens would not get hit even if they are visible on the screen. I set up a bunch of tests with screen filled with aliens and semi-transparent moon to see what's going on. My coordinates for hit-test area were a bit off, but even when I fixed that it was not good. Box simply cannot cover the circled area well. You either miss aliens when you should hit them, or you shoot some that are completely hidden.

Shooting the moon

So I decided to switch to circle based checks. Since the moon in much larger than aliens, it would be enough to check whether all the 4 points of alien's bounding box are inside the moon's circle or not. To test this visually, I used libGDX builtin ShapeRenderer, like this:

    shapeRenderer.setProjectionMatrix(camera.combined);
    shapeRenderer.begin(ShapeType.Circle);
    shapeRenderer.setColor(1, 1, 1, 1);
    shapeRenderer.circle(sMoon.getX() + 119, sMoon.getY() + 116, 167);
    shapeRenderer.end();

This code comes after SpriteBatch is complete. The white circle is drawn over the scene. I also draw all the bounding boxes of aliens using ShapeType.Box.

An efficient way to test whether point is inside the circle is not to use square root (slow) and just compare sqared distances instead. libGDX builting function Circle.contains(x, y) does that properly, so I'm using that for checks. This works really nice. I extended the radius by a few pixels, because all the sprites have some padding and I'm really satisfied with the result:

After Shooting

Pixel-perfect bullet trajectory

In this game, bullets are fired some 50px below the bottom of the screen. I'm using atan2 function to rotate the bullet and hit the mark, but there were a couple of errors in my code, especially when you miss the target. To understand the rest of this post, please note that the game uses HitScan for all the shots in the game.

When player misses the target, the code extrapolates the trajectory to the end of the screen. Previous code that did this set the end point too far. Since the bullet is using tween engine to fly, this results in large jumps and bullet is only seen in 2-3 positions on the screen before it flies out. I fixed this code to set the end point at the edge of the screen, so now you can actually see the bullet flying.

This revealed another problem: the bullet sprite was sometimes 10-20 pixels off from the point where player touched the screen. There are three causes of this problem. First, I was using X and Y coordinates of the bullet sprite, which is the lower-bottom corner. I fixed this to use the bullet sprite center by adding half of width and height. But it was still off on some shots. Second problem was that I forgot to set the origin, so bullet was rotated around the bottom-left corner. I fixed that as well, but still some shots going to the left side of the screen were bad.

Then I realized that, when bullet rotates, width and height of the sprite changes, so the bullet center needs to be calculated after the rotation. With that fix, the bullet flies right through the point where player touched. The code looks like this:

    // fly the bullet
    LaserBullet lb = new LaserBullet(tUI, 65, 64, 20, 40);
    lb.setPosition(0, -450);
    lb.setOrigin(10, 20);
    lb.setRotation( (float)(Math.atan2(-x, 450f+y) * 180f / Math.PI) );
    Rectangle r = lb.getBoundingRectangle();
    x = (int)(x - r.width * 0.5f);
    y = (int)(y - r.height * 0.5f);
    lb.target.set(x, y);
    bullets.add(lb);
    Tween.to(lb, SpriteTweenAccessor.POSITION_XY, delay)
        .target(x, y).start(tweenManager);

第18天:圆形区域的碰撞。像素级别完美的子弹

Today I got fed up with the "moon hit" bug. Sometimes aliens would not get hit even if they are visible on the screen. I set up a bunch of tests with screen filled with aliens and semi-

今天我受够了“月行碰撞”的bug。有时候,尽管外星人已经显示在屏幕上了,但是依旧不能被射击。我建立了一批满屏的的外星人和半透明的月亮来测试看效果。我的坐标系下的打击区域有点不对,即使我将它设为固定也不行。简单的方形是不能很好地覆盖圆形区域。当你射击的时候会造成丢失或者会射中一些完全隐藏的外星人。

Shooting the moon

所以,我决定使用圆形作为基本的选择。由于月形会超出外星人很多,所以有足够的空间去检测是否外星人的四个点组成的外框的确在月形的圆内。为了测试这个样子的视觉感受,我使用libGDX来构建一个图形渲染器,像下面这样:

    shapeRenderer.setProjectionMatrix(camera.combined);
    shapeRenderer.begin(ShapeType.Circle);
    shapeRenderer.setColor(1, 1, 1, 1);
    shapeRenderer.circle(sMoon.getX() + 119, sMoon.getY() + 116, 167);
    shapeRenderer.end();

这段代码在加载完成后执行。白色的圆绘制在屏幕上方。同时也使用 ShapeType.Box绘制出所有外星人的外框。

一个测试点是否在圆内的有效的方法不是使用平方根(太慢)而是比较平方的距离。libGDX构造的Circle.contains(x,y)方法就是正确的做法,所以我使用这个来进行检测。用起来真的很好。我将直径放大了一点点,因为所有的精灵会有一些内边距,同时我真的很满意这个结果:

After Shooting

完美的子弹

In this game, bullets are fired some 50px below the bottom of the screen. I'm using atan2 function to rotate the bullet and hit the mark, but there were a couple of errors in my code, especially when you miss the target. To understand the rest of this post, please note that the game uses HitScan for all the shots in the game.

When player misses the target, the code extrapolates the trajectory to the end of the screen. Previous code that did this set the end point too far. Since the bullet is using tween engine to fly, this results in large jumps and bullet is only seen in 2-3 positions on the screen before it flies out. I fixed this code to set the end point at the edge of the screen, so now you can actually see the bullet flying.

This revealed another problem: the bullet sprite was sometimes 10-20 pixels off from the point where player touched the screen. There are three causes of this problem. First, I was using X and Y coordinates of the bullet sprite, which is the lower-bottom corner. I fixed this to use the bullet sprite center by adding half of width and height. But it was still off on some shots. Second problem was that I forgot to set the origin, so bullet was rotated around the bottom-left corner. I fixed that as well, but still some shots going to the left side of the screen were bad.

Then I realized that, when bullet rotates, width and height of the sprite changes, so the bullet center needs to be calculated after the rotation. With that fix, the bullet flies right through the point where player touched. The code looks like this:

    // fly the bullet
    LaserBullet lb = new LaserBullet(tUI, 65, 64, 20, 40);
    lb.setPosition(0, -450);
    lb.setOrigin(10, 20);
    lb.setRotation( (float)(Math.atan2(-x, 450f+y) * 180f / Math.PI) );
    Rectangle r = lb.getBoundingRectangle();
    x = (int)(x - r.width * 0.5f);
    y = (int)(y - r.height * 0.5f);
    lb.target.set(x, y);
    bullets.add(lb);
    Tween.to(lb, SpriteTweenAccessor.POSITION_XY, delay)
        .target(x, y).start(tweenManager);

推荐阅读