Corona Lua 显示对象 y 属性的十分之三像素 (!)



当我运行此代码(基于"DragMe"示例应用程序)时,在非常特殊的情况下,我得到了非常意外的结果。

我的"snapToGrid"函数将 x 和 y 设置为拖动后最接近的整数 100 值。

"检查"功能在创建、移动或旋转对象(通过单击它)后输出 x 和 y 值。

如果将对象放在第 5 行(因此 y = 500)并旋转它,您将看到 y 值更改为 499.99996948242,但这不会发生在任何其他行中。

这怎么解释呢? 我注意到,如果没有将物理实体添加到显示对象中,则不会发生这种情况。

我知道我可以在旋转后调用 snapToGrid,或者在将其用于其他任何事情之前对值进行舍入,但我认为这里有一个重要的机会让我学习一些关于 Corona 的有用知识。

local function snapToGrid(t)
    modHalf = t.x % t.width
    if modHalf > t.width/2 then
        t.x = t.x + (t.width-modHalf)
    end
    if modHalf < t.width/2 then 
        t.x = t.x - modHalf
    end
    modHalfY = t.y % t.height
    if modHalfY > t.height/2 then
        t.y = t.y + (t.height-modHalfY)
    end
    if modHalfY < t.height/2 then 
        t.y = t.y - modHalfY
    end
    display.getCurrentStage():setFocus( nil )
    t.isFocus = false
    return true
end
function rotatePiece(target)
    if target.rotation == 270 then
        target.rotation = 0
    else
        target.rotation = target.rotation + 90
    end
end
local function dragBody(event)
    local target = event.target
    local phase = event.phase
    local halfWidth = target.width/2
    local halfHeight = target.height/2
    --get tileX and tileY relative to tile centre
    local tileX = event.x-target.x
    local tileY = event.y-target.y
    local modHalf = ""
    local snap = 15
    if phase == "began" then
        -- Make target the top-most object
        display.getCurrentStage():setFocus( target )
        -- Spurious events can be sent to the target, e.g. the user presses 
        -- elsewhere on the screen and then moves the finger over the target.
        -- To prevent this, we add this flag. Only when it's true will "move"
        -- events be sent to the target.
        target.isFocus = true
        -- Store initial position
        target.x0 = event.x - target.x
        target.y0 = event.y - target.y
    elseif target.isFocus then
        if phase == "moved" then
            -- Make object move (we subtract target.x0,target.y0 so that moves are
            -- relative to initial grab point, rather than object "snapping").
            target.x = event.x - target.x0
            target.y = event.y - target.y0
            if target.x > display.contentWidth - (target.width/2) then target.x = display.contentWidth - (target.width/2) end
            if target.y > display.contentHeight - (target.height/2) then target.y = display.contentHeight - (target.height/2) end
            if target.x < 0 + (target.width/2) then target.x = 0 + (target.width/2) end
            if target.y < 0 + (target.height/2) then target.y = 0 + (target.height/2) end
            modHalf = target.x % target.width
            if modHalf < snap then
                target.x = target.x - modHalf 
            end
            if modHalf > ((target.width) - snap) then 
                target.x = target.x + ((target.width)-modHalf)
            end
            modHalfY = target.y % target.height
            if modHalfY < snap then
                target.y    = target.y - modHalfY 
            end
            if modHalfY > ((target.height) - snap) then 
                target.y = target.y + ((target.height)-modHalfY)
            end
            hasMoved = true
            return true
        elseif phase == "ended" then
            if hasMoved then 
                hasMoved = false
                snapToGrid(target)
                --tile has moved
                examine(target)
                return true
            else
                --rotate piece
                    rotatePiece(target)
                    display.getCurrentStage():setFocus( nil )
                    target.isFocus = false
                    --tile has rotated
                    examine(target)
                    return true
            end
        end
    end
    -- Important to return true. This tells the system that the event
    -- should not be propagated to listeners of any objects underneath.
    return true
end
local onTouch = function(event)
    if event.phase == "began" then
        local tile = {}
        img = display.newRect(event.x,event.y,100,100)
        img:addEventListener( "touch", dragBody )
        snapToGrid(img)
        --top right corner and top middle solid 
        topRight = {16,-16,16,50,50,50,50,-16}
        --top left and left middle solid 
        topLeft = {-16,-16,-16,-50,50,-50,50,-16}
        --bottom right and right middle solid 
        bottomRight = {16,16,16,50,-50,50,-50,16}
        --bottom left and bottom middle solid 
        bottomLeft = {-16,16,-16,-50,-50,-50,-50,16}
        physics.addBody( img, "static",
            {shape=topRight},
            {shape=topLeft},
            {shape=bottomLeft},
            {shape=bottomRight}
        )
        --new tile created
        examine(img)
        return true
    end
end

function examine(img)
    print("--------------------------------------------------")
    if img ~= nil then
        print("X: "..img.x..", Y: "..img.y)
    end
    print("--------------------------------------------------")
end
local img
local physics = require( "physics" )
physics.setDrawMode( "hybrid" )
--draw gridlines
for i = 49, display.contentHeight, 100 do
    local line = display.newLine(0,i,display.contentWidth,i)
    local line2 = display.newLine(0,i+2,display.contentWidth,i+2)
end
for i = 49, display.contentWidth, 100 do
    local line = display.newLine(i,0,i,display.contentHeight )
    local line2 = display.newLine(i+2,0,i+2,display.contentHeight )
end
--init
physics.start()
Runtime:addEventListener("touch", onTouch)

有几种可能性。首先,由于Lua中没有整数,因此所有数字都是双精度浮点值。根据Lua维基上的FloatingPoint,

某些供应商的 printf 实现可能无法处理 准确打印浮点数。信不信由你,一些 可能会错误地打印整数(即浮点数)。这 可以表现为在 Lua 中错误地打印某些数字。

实际上,请尝试以下操作:

for i=1,50,0.01 do print(i) end

您将看到很多数字的打印完全符合您的预期,许多数字打印时错误为 10^-12 甚至 2 x 10^-12。

但是,当您不将其提供给物理模块时,您的x打印正常。因此,物理模块肯定会对物体位置进行一些计算并对其进行更改。对于动态对象(由于碰撞检测),我当然希望如此,但在这里您的对象似乎是"静态的"。因此,即使对于静态物体,物理学也必须调整x。调整是如此之小,以至于在屏幕上看不到(您无法感知任何小于像素的运动),但您是对的,问为什么很有趣。

SO post Lua:减去十进制数不会返回正确的精度值得一读;它有一些你可能会觉得有趣的链接,并给出了一个简洁的例子,十进制0.01不能以2为底精确地表示;就像1/3不能以10为底表示(但它可以用3为底:它将是0.1个基数3!)。这个问题表明,尽管Lua(或者可能是底层的C)足够聪明,可以将0.01打印为0.01,但它无法将10.08-10.07打印为0.01。

如果你真的想动摇你对浮点值的理解,试试这个:

> a=0.3
> b=0.3
> print(a==b)
true
> -- so far so good; now this: 
> a=0.15 + 0.15
> b=0.1 + 0.2
> print(a,b)
0.3     0.3
> print(c==d)
false -- woa!

这可以通过以下事实来解释:二进制中的 0.15 有一个小误差,与 0.1 或 0.2 的误差不同,因此就位而言,它们并不相同;尽管在打印时,差异太小而无法显示。您可能需要阅读浮点指南。

实际上,只有

当您添加物理特性时,才会出现此问题,因此我认为可以公平地说,该问题是由将控制权转移到box2d引起的,而不是仅由Corona处理数字引起的。 我在科罗纳论坛上问过并得到了这个答案。

http://forums.coronalabs.com/topic/46245-display-object-that-has-static-physics-body-moves-very-slightly-when-rotated/

最新更新