无限/重复旋转世界的数学/计算

9
我该如何创建一个处理旋转的无限/重复世界,就像这个游戏一样:http://bloodfromastone.co.uk/retaliation.html。我通过以下层次结构编写了旋转移动的世界: Scene - mainLayer (CCLayer) - rotationLayer (CCNode) - positionLayer (CCNode) rotationLayer和positionLayer现在具有相同的大小(4000x4000 px)。我通过旋转rotationLayer来旋转整个世界,并通过移动positionLayer来移动整个世界,使得玩家始终位于设备屏幕的中心,并且是世界在移动和旋转。
现在我想要实现的是,如果玩家到达世界的边界(将世界移动以使世界边界与设备屏幕边界接触),则将世界“包裹”到相反的边界,以使世界无限延伸。如果世界没有旋转,那么这很容易,但是由于它旋转了,我不知道该如何做到这一点。我对数学一窍不通,在思考数学问题时需要帮助。
我认为我需要计算一个圆的半径,作为我的正方形世界内部的铸造厂,无论正方形世界处于什么角度,都将确保可见矩形(屏幕)始终位于世界正方形的边界内。然后,我需要计算可见矩形边界是否超出了边界圆,并且如果是,我需要计算在边界圆中移动世界所需的新的相反位置。为了说明问题,我添加了5个图像。

旋转的正方形世界再次移动到垂直位置相反的位置,以便可见矩形的底部现在击中旋转世界内缘的圆圈:
And again rotated square world moved to opposite vertical position so that bottom of visible rectangle now hitting bounds circle inside rotated world

在非旋转情况下移动positionLayer是我能够解决的数学问题,当世界/CCNode(positionLayer)被移动/定位时,它位于一个被旋转的世界/CCNode(rotationLayer)内,该世界/CCNode(rotationLayer)会发生旋转。旋转的anchor point始终在屏幕中心,但由于被移动的positionLayer位于旋转的rotationLayer内部,因此它会围绕rotationLayer的anchor point旋转。然后我就迷茫了...例如,当我将positionLayer下移足够多,使其顶边撞到屏幕顶部时,我需要像JohnPS所描述的那样对该positionLayer进行包装,但这并不简单,我需要根据rotationLayer CCNode的旋转情况将其包装成一个向量。我不知道如何做。

谢谢
Søren

3个回答

7

就像John所说的那样,最简单的方法是构建一个环面世界。想象一下,你的飞船是甜甜圈表面上的一个点,它只能在表面上移动。假设你位于两个圆(图片中的红色和紫色)相交的点上:

Circles on a torus.

如果你跟着这些圆圈走,最终会回到起点。此外,请注意,无论你在表面上如何移动,都不可能到达“边缘”。环面没有这样的东西,这就是为什么将其用作无限2D世界很有用的原因之一。另一个原因是方程非常简单。通过两个角度指定你在环面上的位置:从紫色圆圈上的“原点”出发找到红色圆圈的旋转角度和在红色圆圈上找到感兴趣点的旋转角度。这两个角度都是360度。我们称这两个角度为 thetaphi。它们是你的飞船在世界中的坐标,并且当你改变速度等时要更改它们。基本上,你将它们用作你的 xy,但必须确保在更改它们时始终使用模数(你的世界只有每个方向360度,然后会绕回)。
假设您的船坐标为 (theta_ship,phi_ship),并且朝向为 gamma_ship。您想要绘制一个正方形窗口,以船为中心,长度/宽度等于整个世界的某个百分比 n(例如,您只想一次看到四分之一的世界,则将 n=sqrt(1/4)=1/2,并将窗口的长度和宽度设置为 n*2*pi=pi)。为此,您需要一个函数,该函数接受在屏幕坐标系中表示的点(xy),并输出在世界坐标系中的点(thetaphi)。例如,如果您问它世界的哪个部分对应于 (0,0),则应返回船的坐标 (theta_ship,phi_ship)。如果船的方向为零(xythetaphi 对齐),则某个坐标 (x_0,y_0) 将对应于 (theta_ship+k*x_0, phi_ship+k*y_0),其中 k 是相关于在屏幕上能够看到多少世界以及 xy 的边界的缩放因子。旋转 gamma_ship 引入了一些三角函数,详见下面的函数。有关量的确切定义,请参见图片。 !蓝色是屏幕坐标系,红色是世界坐标系和配置变量(描述船在世界中的位置的东西)。在世界坐标系中表示的对象为绿色。

坐标转换函数可能看起来像这样:
# takes a screen coordinate and returns a world coordinate
function screen2world(x,y)
    # this is the angle between the (x,y) vector and the center of the screen           
    alpha = atan2(x,y); 
    radius = sqrt(x^2 + y^2); # and the distance to the center of the screen

    # this takes into account the rotation of the ship with respect to the torus coords
    beta = alpha - pi/2 + gamma_ship;

    # find the coordinates
    theta = theta_ship + n*radius*cos(beta)/(2*pi);
    phi = phi_ship + n*radius*sin(beta)/(2*pi));

    # return the answer, making sure it is between 0 and 2pi
    return (theta%(2*pi),phi%(2*pi))

我想这就是全部了。数学只是一些相对简单的三角函数,你应该画一个小图来证明它是正确的。或者你可以使用旋转矩阵及其更大的兄弟刚体变换(特殊欧几里得群SE(2))以较自动化的方式得到相同的答案。对于后者,我建议阅读Murray,Li,Sastry的前几章,它可以在网上免费阅读。

如果您想要做相反的事情(从世界坐标转换为屏幕坐标),则需要做基本相同的事情,但反过来:

beta = atan2(phi-phi_ship, theta-theta_ship);
radius = 2*pi*(theta-theta_ship)/(n*cos(beta));
alpha = beta + pi/2 - gamma_ship;
x = radius*cos(alpha);
y = radius*sin(alpha);

谢谢Vlad提供这么详细的回答。现在假设我非常非常不擅长数学(我真的是),你能否再为我解释一下?你可以给我一个完整的伪代码示例,从只有“真实”的x和y(锚点位于中心)坐标以及旋转移动世界的宽度/高度开始。你的回答,无论多么简单,对我来说都太复杂了。例如,第一个方法返回什么(一个数组?),这个结果如何在第二个代码部分中使用? - Neigaard
World() 函数是多余的,我将移除它。我的示例展示了如何通过循环整个场景并一像素一像素地绘制它。也就是说,如果你给我 x 和 y 坐标,我将将其转换为 theta 和 phi,这些坐标将在任何处理世界的函数中使用:它们是表示世界的坐标。我将更改示例,以显示简单的坐标转换而不是完整的屏幕渲染。 - vlsd
再次感谢您,弗拉德。我很抱歉在这里表现得像个傻瓜,但我仍然不明白。所以我输入我的x和y以及船的旋转(gamma_ship,那只是屏幕旋转的度数吗?),然后它会在环面上吐出x和y坐标,对吗?然后呢?我如何使用这些坐标,它们是否总是正确的,以便我可以简单地将它们转换回屏幕坐标? - Neigaard
你如何使用坐标变换取决于你想要实现的目标。如果你想知道在屏幕上由x和y描述的像素应该绘制什么,那么你需要计算对应于该点的theta和phi,并查看环面上的该坐标。如果你想在世界上两个点之间画一条线,你将使用相反的坐标变换:(theta1, phi1)->(x1, y1)和(theta2, phi2)->(x2, y2)。然后,在屏幕上在(x1, y1)和(x2, y2)之间画一条线,但它将表示一个线(圆形等)在环面上。 - vlsd

2
您需要定义“相反边界”是什么意思。对于二维示例,请参见基本多边形。有四种方式可以将正方形的边缘映射到其他边缘,你会得到一个球体、实射影平面、克莱因瓶或环面。经典的街机游戏小行星实际上有一个环面的游戏表面。
关键是你需要将每个边界点粘合到某些其他边界点上,使其有意义且一致。
如果您的世界真正是三维的(不仅仅是二维表面地图上的三维),那么我认为您的任务变得更加困难,因为您现在的边缘是嵌入在三维世界中的表面。
编辑:
假设您有一个二维地图,并希望像小行星一样包装。
如果地图大小为 1000x1000,那么 x=0 是地图的左边界,x=999 是右边界,你正在向右看,可以看到前方 20 个单位。因此,在 x=995 处,你想要看到最多 1015,但这超出了地图的右侧边界,因此应将 1015 变为 15
如果你在 x=5 处并向左看 20 个单位,则会看到 x=-15,而你希望它实际上是 985
当你查看地图边缘以外的区域时,为了得到始终介于 0 和 999 之间的数字,需要使用取模运算符(modulo operator)
new_x = x % 1000; // in many programming languages

x为负数时,每种编程语言对x%1000的结果处理方式都不同。甚至可能是实现定义的。即它不总是正数(在0999之间),因此使用以下内容会更安全:
new_x = (x + 1000) % 1000; // result 0 to 999, when x >= -1000

所以每次移动或更改视图时,您需要重新计算您的位置和视图中任何物体的坐标。您可以应用此操作来获取地图上的x和y坐标。

我的世界是2D的,非常类似于小行星。你有什么更多的提示可以给一个数学白痴来解决这个问题吗? - Neigaard
抱歉,我对cocos2d和CCNode不够熟悉,无法理解如何使用它们来完成此操作。 - JohnPS
你能用数学和伪代码来解释一下吗?这会对我很有帮助。 - Neigaard
似乎CCNodes有能力应用转换层的功能,以在地图上的坐标和相对屏幕坐标之间进行转换。我不知道如何做到这一点。您可能可以子类化适当的CCNode并覆盖某些函数以返回包装后的坐标。但是,就像我说的那样,我真的不知道如何做到这一点或者它是否实际可行。肯定有其他人想要做你想做的事情,所以也许你可以在互联网上找到其他东西。 - JohnPS

0

我对Cocos2d还很陌生,但我认为我可以尝试帮助你解决几何计算问题,因为正如你所说,这不是一个框架问题。

首先,我会将你使用的每个图层的锚点设置在它们所有的视觉中心。

然后,让我们同意一个假设,即第一个接触边缘的部分总是一个角落。

如果你只想检查它是否在圆内,只需检查所有四条边是否都在圆内。

如果你想知道哪一条边接触了圆的周长,只需查找距离x=0 y=0最远的那一条边,因为锚点将位于中心。

如果你有不将锚点放在中间的理由,你可以使用相同的逻辑,只要在所有对象上包括一半的宽度即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接