概述
4.Teaching The World To Wrap: using modular arithmetric to make the world wrap around
This post is about wrapping coordinates so that when you go off one side of the world, you come back on the opposite side, or: one use of modular arithmetic.
这篇帖子讨论坐标的卷动,这样当你走到游戏世界的一端时可以从另一边走出来,或者说:讨论模运算的使用。
A feature that used to be quite prevalent in 2D games was a “wrapped” world: when you reached the right-hand side of the world, instead of stopping or bouncing off, you would appear on the left-hand side (and vice versa). The top and the bottom of the world would usually operate in a similar way. Asteroids is a classic example, but various other arcade and strategy (Settlers, Civ, etc) games have used the same mechanism.
过去在2D游戏中非常普遍的特征就是“卷动的”世界:当你抵达世界的右边界时并不会停下来活弹回来,而是会出现在左边界(反之亦然)。对于游戏世界的上端和下端通常也做类似的处理。星球大战是一个经典的例子,此外其他各种街机游戏和策略游戏也使用同样的机制。
The Simple Method
一个简单的方法
The simple way to implement world-wrapping is to check if the location you are trying to move to is beyond the bounds of the world: if it is, then move to the opposite border of the world.
实现世界卷动的简单方法是检测移动目的地是否接近了世界的边缘:若是,则移动到世界的另一边。
In Greenfoot, we must be careful about how to implement this, because Greenfoot already prevents your location from going outside the world. So if you try to implement world-wrapping along these lines:
在Greenfoot中去实现这个功能时必须要小心,因为Greenfoot是禁止坐标超出世界边缘的。比如按照以下代码去实现世界卷动:
setLocation(getX() - 5, getY()); // Move 5 units left if (getX() < 0) ...
The above won’t work: the setLocation method will already have prevented you moving beyond x=0, so getX() will never be negative.
上面的代码不能正确运行:setLocation方法会阻止移动时的x值小于0,因此getX()将不可能会负数值。
Instead this check must be done before you tell Greenfoot to set the location. The easiest way to do this is to override (i.e. replace) the setLocation method to add in this check, before calling the original setLocation method. Here’s the code for wrapping just the x coordinate:
除此之外,检测工作必须在Greenfoot设定位置之前进行。最简便的办法是覆盖(即替换)setLocation方法,并在调用原始的setLocation方法之前加入检测的代码。以下仅是x坐标卷动的代码:
public void setLocation(int x, int y) { if (x >= getWorld().getWidth()) { x = 0; } if (x < 0) { x = getWorld().getWidth() - 1; } super.setLocation(x, y); }
The first if-statement checks if the desired X coordinate is greater than or equal to the world width (say, 640). If it is, it is moved to the opposite side: x=0. The second if-statement checks if the desired X coordinate is negative, and if it is, it is moved to the maximum X coordinate: world-width minus one (e.g. 639). Finally, we pass the potentially-adjusted coordinates to the original setLocation method.
第一个if语句检测当前的x坐标值是否大于等于世界的宽度(比如640)。若是,则移动到相对的一边:x=0.第二个if语句检测当前x坐标值是否为负数,若是则移动到x坐标的最大值处:世界的宽度值减1(比如639)。最后我们将调整过后的坐标值传给原始的setLocation 方法。
The logic for the Y coordinates is identical, but using the world height instead of width. I’ve uploaded the Greenfoot scenario with simple X and Y wrapping implemented for you tohave a play, or todownload and view the source.
对y坐标的处理方法是一致的,但是使用世界的高度值来替代宽度值。我上传了一个游戏剧本是关于x和
y坐标卷动的,你可以试玩一下,或者下载后查看源代码。
The More Accurate Method
更加精确的方法
There is a small issue with our previous method. Let’s say you have a small, 20-wide world, and you are at position (14, 2), heading exactly right at 5 units per frame. You’ll move to (19, 2) — no problem. Then the next frame after that, you’ll try to move to (24, 2), and then the above code will kick in and you’ll get placed at (0, 2). Here’s a diagram of that:
关于之前问题有一个小小的争议。我们假设这儿有一个20个单位宽的小型世界,你所处的位置在(14,2),以每帧5个单位的速度向右移动。毫无疑问你将会移动到(19.2)处。然后下一帧你将会移动到(24,2)处,但是由于上面代码的作用,你将会到达(0,2)处。效果如图:
From this diagram, the problem may not be totally obvious. But since our world is wrapped, it should be as if the left-hand edge is directly next to the right-hand edge. When we draw it like that, we can see the problem:
从这个图来看,问题并不是那么明显。然而由于世界是卷动的,其左边缘应该可看作是是直接与右边缘相邻的。当我们用下图表现的话便会发现问题:
From this diagram, you can see that in effect we only moved 1 unit rather than 5. So every time you cross the world boundary, you have a “slow frame” where you don’t move as far. This is not very noticeable with low speeds, but it can become noticeable with higher speeds, or if you have multiple actors in formation. If we were at (19, 2), we should have moved to (4, 2). Similarly, if we were at (18, 2), we should have moved to (3, 2), and so on. You’ll notice the pattern is that we are subtracting 20 (the world width) from the coordinate we would normally move to. So (19, 2) should move to (24, 2), but we instead move to (4, 2). You can also see that the logic works the other way: (4, 2) should move to (-1, 2) if they are moving 5 units left, but instead they move to (19, 2). So in that case we add on the world width. Let’s set this down in code:
从该图可以看到,我们事实上只移动了1个单位而不是5个。因此每当你越过世界边缘时,你将会遇到一个“慢帧”,它要小于平时的移动距离。在速度慢时这个问题不太明显,而速度快时或有多个角色组队移动时就会很明显。如果你处在(19,2),我们应该移动到(4,2)。类似地,如果我们处在(18,2),我们应该移动到(3,2),等等。你会注意到一个模式,即我们把移动目标的坐标值减去了20(世界的宽度值)。因而(19,2)本来应该移动到(24,2),但替换后我们移动到(4,2)。该逻辑可以表现为另一种形式:如果向左移动5个单位,则应该从(4,2)移动到(-1,2),但是替换后我们移动到(19,2)。于是在那种情况下我们加上世界的宽度值。代码如下:
public void setLocation(int x, int y) { int width = getWorld().getWidth(); int height = getWorld().getHeight(); if (x >= width) { x -= width; } if (x < 0) { x += width; } if (y >= height) { y -= height; } if (y < 0) { y += height; } super.setLocation(x, y); }
You can go have a play with this improved version. And as a quick exercise: this code isn’t completely foolproof. See if you can work out why not and figure out the simplest fix, before you take a look at the source code (which does have the foolproof answer).
你可以去试玩一下这个改进的版本。作为一个简单的演示,以上代码并不是万无一失的。看看你是否能够在查看源代码(没使用最优方案)之前找到问题所在并用最简单的方式解决。
The Technical Term
术语
This wrapping of coordinates is an example of modular arithmetic (a quick overview is available onSimple Wikipedia). Other examples of modular arithmetic include hours on the 24-hour clock: after 23 you go back to 0, or degrees: after 359 degrees you go back to 0 degrees. And in fact, if you know a little about computer arithmetic, you’ll know that addition and subtraction on many finite integer types in computing resemble modular arithmetic: for example, if you try to add 1 to a byte value of 127 in Java, you’ll get -128. There are several more uses of modular arithmetic in computing, and I will come back to some of them in future.
以上坐标卷动是关于模运算的一个简单示例。其他模运算的例子包括24制时钟:23点过后回到0点,或者角度值:359度之后回到0度。事实上,如果你对计算机的算术运算稍有了解的话,你便会知道许多有限整型的加减运算类似于模运算:比如说,java中如果要给值为127的字节类型加1的话,将会得到-128.计算机信息处理中很多地方使用到模运算,我将在今后进行讨论。
As a final note, some of you may know that Java has a “modulo” operator — but that operator is not very useful for solving the problem in this post. I leave it to you as an exercise to figure out why not. (Why not try modifying my code to use the modulo operator?)
最后一点说明,有些人或许知道java中有取模操作符——但是该操作符并不适用解决本帖中的内容。作为一个练习,我将这个问题留给你思考。(何不试试用取模操作符替代我的代码?)最后
以上就是眼睛大方盒为你收集整理的4.使用模运算来实现世界的卷动的全部内容,希望文章能够帮你解决4.使用模运算来实现世界的卷动所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复