Open UltraDrone opened 7 months ago
Holy moly, you sure put the work into this one.
For some additional context, I reported this issue (or at least a subset of it) before the public GitHub system. Ticket number #209686. My request was closed, as this was determined to be intended behavior. The message I got is the following (pasted here since the ticket itself is private) emphasis added:
[...] This bug in question has been determined to be not a bug by the engineers with the following message:
"Collisions will only be flagged as happening if the bound boxes overlap and that overlap crosses a pixel centre. In this sample the calculation is at the mercy of floating point equality as the left bound is precisely on the pixel centre. If you make the offset value var OFFSET = 0.500001; then you will see that the collision is detected as it was expected.
Mathematically I wholeheartedly agree that this should be a collision, but in order to maintain some compatibility with the previous collision system this compromise had to be settled on. It also provides a better correlation to precise collisions which are tested on pixel centres." [...]
Per my follow-up request at the time, the Documentation (under Bounding Boxes) was updated to specify this:
For two instances to be in collision, their bounding boxes have to overlap. At a pixel level, an overlap is counted when the centre of that pixel is covered.
Summary: As I understand it, as of May 2023 (I reported it first in September 2022) this is considered intended behavior. However, I agree with UltraDrone and would like to see this seriously considered.
Description
place_meeting
在游戏中存在的bug——遮罩边缘被限定在像素中间A bug in the game with
place_meeting
--The mask edge is limited to the middle of the pixel摘要: 本文通过放大窗口并可视化精灵的碰撞遮罩,发现无论遮罩类型是什么,
place_meeting
的遮罩边缘都会被限定在x.5px的位置①。这种情况下当物体进行浮点数移动时,物体可能会在重叠后仍未检测到碰撞。虽然此bug对碰撞遮罩的影响不超过一个像素的厚度,但是本文作者强烈希望yoyo能重视此问题并正确修复此bug②。Abstract: In this paper, by enlarging the window and visualizing the collision mask of the Sprite, we find that regardless of the mask type, mask edge of
place_meeting
is restricted to the x.5 pixel①. In this case, when the object is moving to fractional coordinate, it may overlaps with other objects and still can not detect the collision. Although the impact of this bug on the collision mask is no more than one pixel thick, the author of this article strongly hopes that yoyo will take this issue seriously and fix the bug correctly②.注①:这里指的是
place_meeting
的检测到的上下左右边界总是(整数 + 0.5)px。注②:本文使用的GM的IDE版本是beta
v2024.400.0.516
,runtime版本是v2024.400.0.537
。Note ①: What this means is that the detected upper, bottom, left, and right boundaries of
place_meeting
are always (integer + 0.5) px.Note ②: The GM IDE version used in this article is beta
v2024.400.0.516
and runtime versionv2024.400.0.537
.目录
1. 前言 Introduction
我是一个中国人,并不擅长英文,因此将中文原文与机翻译文放在一起,若出现译文表述不清的情况,请以中文原文为准。事情是这样的,前些时间YYG在四月beta中修复了一些关于
collision
函数的bug,这使得collision
函数在游戏中更加好用了。但是我发现在四月beta中place_meeting
函数仍存在bug。为了帮助YoYo修复这一bug,我设计了科学合理的debug方案让YoYo理解bug的详细特征。As a Chinese, I am not good at English, so I put the original Chinese text together with the machine translation. If the translation is not clear, the original Chinese text shall prevail. The thing is, YYG fixed some bugs with the
collision
function in the April beta a while back, which made thecollision
function more usable in the game. But I foundplace_meeting
function still have a bug in the April beta. In order to help YoYo fix this bug, I designed a scientific and reasonable debug scheme so that YoYo could understand the detailed features of the bug.2. 设计方案 Design Scheme
碰撞问题最好的debug办法就是可视化碰撞遮罩。我们只需要遍历屏幕上所有的像素点,让其对目标精灵做点碰撞测试,然后将所有与精灵碰撞到的点绘制在相应位置即可。由于我们研究的是亚像素级别的误差,必须放大视野才能看清所有细节。综上,想要看清楚实际运行时的真实碰撞遮罩需要做到以下两点:
The best debugging method for collision problems is to visualize collision masks. We just need to traverse all the pixels on the screen, have them perform point collision tests on the target sprite, and then draw all the points that collide with the sprite at the corresponding positions. Since we are studying sub-pixel level errors, it is necessary to enlarge the window to see all details clearly. In summary, to see the actual collision mask during game clearly, the following two points need to be achieved:
另外,为了证明
place_meeting
确实存在bug,我们需要一个正确的案例作为对照组,从而进行对照实验。因为GM中的物理是基于box2d
的,而box2d
作为一个历史悠久的开源库,我们可以假定他是正确的,而GM中物理检测碰撞的效果也确实比place_meeting
更为精准,所以我们使用phsics_test_overlap
作为对照组进行比对。In addition, in order to prove that
place_meeting
does have bugs, we need a correct case as a control group, so as to conduct a controlled experiment. Since the physics in GM is based onbox2d
, andbox2d
is an open source library with a long history, we can assume that he is correct, and the physics in GM does acutally detect collisions more accurately thanplace_meeting
. So we usedphsics_test_overlap
as the control group for comparison.下面的测试工程为一种可行的解决方案。为了让大家直观地感受到bug的真实性并复现此bug,本人强烈建议大家按照下面的工程自己实践一下。这样才能深刻领悟本文所描述的问题。
The following testing project is a feasible solution. In order to make everyone intuitively feel the authenticity of the bug and reproduce it, I strongly recommend that you practice it yourself according to the following project. Only in this way can we deeply understand the problem described in this article.
3. 测试工程 Testing Project
在测试工程中,我们需要如下资产:
In the test project, we need the following assets:
3.1. 相机设置 Camera Setting
为了方便我们在运行时任意放大界面观察细节,我们新建一个
obj_camera_control
。它的创建事件和步事件的代码如下:To make it easier for us to zoom in and see the details at runtime, we create a new
obj_camera_control
. The code for its create event and step event is as follows:创建事件:
Create Event:
步事件:
Step Event:
3.2. 网格绘制 Grid Rendering
为了方便我们在运行时观察小数坐标下的遮罩,我们新建一个
obj_grid
。它的绘制事件的代码如下:To make it easier for us to see the mask in decimal coordinates at run time, we create a new
obj_grid
. Its code for drawing events is as follows:绘制事件:
Draw Event:
3.3. 物体设置 Object Setting
为了测试遮罩,我们需要设置墙体作为碰撞目标。
To test the mask, we need to set the walls as collision targets.
为了能直观观察遮罩与物体的关系,我们新建一个8px*8px的
spr_wall
,设置它的原点坐标为中心
,且尽可能使它的每个像素点都不太相同,然后设置他的碰撞遮罩类型为旋转的矩形
。In order to visualize the relationship between the mask and the object, we create a
spr_wall
with 8px * 8px, set its origin coordinate as theMiddleCenter
, and make every pixel of it as different as possible, and set its collision mask type toRectangle with rotatiion
.由于我们要对
physics_test_overlap
和place_meeting
同时进行测试,所以我们新建两个object分别叫做obj_wall_phisics
和obj_wall_place
,它们均绑定spr_wall
作为精灵。Since we want to test
physics_test_overlap
andplace_meeting
at the same time, we need to create two new objects calledobj_wall_phisics
andobj_wall_place
. They all bindspr_wall
as their sprites.为了使
obj_wall_phisics
能够使用物理,我们需要勾选Uses Physics
。To enable
obj_wall_phisics
to use Physics, we need to checkUses Physics
.obj_wall_phisics
和obj_wall_place
的步事件和绘制事件如下:The step and draw events for
obj_wall_phisics
andobj_wall_place
are as follows:obj_wall_phisics
步事件:obj_wall_phisics
Step Event:obj_wall_phisics
绘制事件:obj_wall_phisics
Draw Event:obj_wall_place
步事件:obj_wall_place
Step Event:obj_wall_place
绘制事件:obj_wall_place
Draw Event:3.4. 碰撞检测 Mask Detection
physics_test_overlap
和place_meeting
都需要至少一个1px*1px的遮罩才能正常检测与目标的碰撞,因此我们新建一个1px*1px的spr_pixel
用于检测碰撞。physics_test_overlap
andplace_meeting
both require at least one 1px * 1px mask to properly detect collisions with the target, so we create a new 1px * 1pxspr_pixel
for collision detection.然后新建
obj_mask_detection
绑定spr_pixel
用于检测obj_wall_phisics
和obj_wall_place
的遮罩。为了使其能够使用物理,我们需要勾选Uses Physics
。Then create a new
obj_mask_detection
bindingspr_pixel
to detectobj_wall_phisics
andobj_wall_place
masks. To enable it to use Physics, we need to checkUses Physics
.obj_mask_detection
的创建事件、步事件和绘制事件如下:The create, step, and draw events of
obj_mask_detection
are as follows:创建事件:
Create Event:
步事件:
Step Event:
绘制事件:
Draw Event:
3.5. 房间设置 Room Setting
我们新建一个房间叫
rm_test
。设置他的宽高为32px * 32px,勾选Enable Physics
,将重力全部设置为0,设置比例尺为1。Let's create a new room called
rm_test
. Set its width and height to 32px * 32px, checkEnable Physics
, set all gravity to 0, and set thePixels To Meters
to 1.然后我们编辑背景层,设置背景颜色为灰色,方便我们进行观察。
Then we edit the
background layer
and set the background color to gray for easy observation.最后我们回到实例层,将我们所有的物体放进房间,具体如下:
Finally we go back to the
instance layer
and put all our objects into the room as follows:4. 使用方法与运行效果 Usage and diagram
运行游戏,不出意外的话会得到下图的内容:
Run the game, if nothing else, you'll get something like this:
以下为我们实现的功能:
obj_wall_phisics
和obj_wall_place
的实际图像和函数检测到的遮罩图像,我们可以通过按下H显示或不显示遮罩,此外,我们还绘制了它们的bbox区域。obj_wall_phisics
和obj_wall_place
的角度,通过WSAD键可以修改obj_wall_phisics
和obj_wall_place
的坐标,我们可以观察不同角度不同坐标下obj_wall_phisics
和obj_wall_place
的遮罩。大家也可以尝试更换精灵图片、更改遮罩类型来测试不同情况下的检测结果。
The following are the functions we have implemented:
obj_wall_phisics
andobj_wall_place
and the mask detected by the function, which we can show or not show the mask by pressing H.obj_wall_phisics
andobj_wall_place
can be changed by the QE key, and the coordinates ofobj_wall_phisics
andobj_wall_place
can be changed by the WSAD key. We can look at the masks ofobj_wall_phisics
andobj_wall_place
at different angles and in different coordinates.You can also try to change the Sprite picture, change the mask type to test the detection results in different situations.
5. 结果与讨论 Results and discussion
现在让我们来分析碰撞结果。
Now let's analyze the results of the collision.
由于
physics_test_overlap
和place_meeting
都需要至少一个1px * 1px的遮罩才能正常检测与目标的碰撞,而GM的像素坐标又处于像素的左上角,所以当我们从左方或上方检测遮罩时,检测到的遮罩范围会多出1px,这是正常的。因此,我们检测的碰撞结果应该如下图所示:Because
physics_test_overlap
andplace_meeting
both require at least one 1px * 1px mask to properly detect the collision with the target, and the pixel coordinate of GM is in the upper left corner of the pixel, when we detect the mask from the left or above, The detected mask area will be 1px more, which is normal. Therefore, the collision results we detect should be as follows:接下来我们观察通过
physics_test_overlap
检测出的碰撞遮罩:Next we observe the collision mask detected by
physics_test_overlap
:可以看到,基于box2d的
physics_test_overlap
检测的遮罩形状和我们推测的基本一致,说明我们的推导过程基本无误。It can be seen that the mask shape detected by
physics_test_overlap
based on box2d is basically consistent with our speculation, indicating that our deduction process is basically correct.接下来我们观察通过
place_meeting
检测出的碰撞遮罩:Next we observe the collision mask detected by
place_meeting
:可以看到检测到的遮罩非常奇怪,甚至无法覆盖整个图像,我们用1px * 1px的方块与其碰撞,可以看到方块很明显地凹陷进了
obj_wall_place
:As you can see, the detected mask is very strange, it doesn't even cover the whole image, we bump it with the 1px * 1px square, and you can see that the square is clearly sunken into
obj_wall_place
:另外,当我们使用WSAD移动
obj_wall_place
,我们发现,遮罩的上下左右边界并不会跟跟着obj_wall_place
连续地变化,而是离散地处于像素网格的中心:In addition, when we use WSAD to move
obj_wall_place
, we find that the top, bottom, left and right boundaries of the mask do not change continuously withobj_wall_place
, but are discretely centered in the pixel grid:这意味着遮罩的上下左右边界总是被限定在x.5px的位置。
This means that the top, bottom, left and right borders of the mask are always limited to the position of x.5 px.
6. 总结 Summarize
将以上结果总结如下:
physics_test_overlap
检测出来的碰撞遮罩基本正确。place_meeting
的碰撞遮罩的边缘总是处于x.5 px的位置,这会导致物体在小数坐标时碰撞结果不正确。place_meeting
这一bug会产生不预期的行为,在写游戏时极易写出Bug,希望yoyo能认真对待并正确修复此问题。The above results are summarized as follows:
physics_test_overlap
is basically correct.place_meeting
is always at the position of x.5 px, which causes objects to collide incorrectly in decimal coordinates.The
place_meeting
Bug causes unexpected behavior, it is very easy to meet bugs when programming, I hope yoyo will take this seriously and fix it properly.Expected Change
No response
Steps To Reproduce
How reliably can you recreate this issue using your steps above?
Always
Which version of GameMaker are you reporting this issue for?
2024.400 (Betas)
Which platform(s) are you seeing the problem on?
Windows
Contact Us Package Attached?
Sample Project Added?