图像切换技巧

有时,应用程序的设计要求“图像切换”,即开发者在给定位置显示一个图像,然后在一段时间或某个操作后,将该图像切换为另一个图像。 这是一个理论上的例子。

local myImage = display.newImageRect( "image1.png", 64, 64 )

-- After some time or upon some event
myImage:swapImage( "image2.png" )

然而,Solar2D 并非如此工作,因为 :swapImage() 不是内置方法。 当 Solar2D 构建图像时,它必须从应用程序包的文件系统中读取文件,为图像分配纹理内存,将 PNG 或 JPG 压缩数据渲染为四个颜色通道,并返回一个显示对象。 要“交换”该图像,Solar2D 需要销毁该对象并创建一个新图像,实际上导致与本例中所示相同的工作量。

local myImage = display.newImageRect( "image1.png", 64, 64 )

-- After some time or upon some event
display.remove( myImage )
myImage = display.newImageRect( "image2.png", 64, 64 )

在这个理论场景中,通过使用 display.remove() 调用销毁 image1.png,您可能不打算很快再次使用它。 这种方法的优点是它最大限度地减少了纹理内存,因为一次只加载两个图像中的一个。 但是,如果您想恢复原始图像,您将陷入这种图像加载和卸载的循环中。 这本身就是一个低效的过程,会影响性能。 因此,如果您需要更频繁地交换图像,则应探索其他技术。 让我们看看几个选项。

双图像切换

如果您有两个图像,您可以简单地加载它们,将它们添加到显示组中,并将图像作为组的参数引用。 在此示例中,我们加载两个图像,redBallblueBall。 然后我们将它们放置在相同的位置,使一个可见而另一个不可见。

-- Create a basic display group
local imageGroup = display.newGroup()

-- Create a red ball inside the group
local redBall = display.newImageRect( imageGroup, "red-ball.png", 128, 128 )
redBall.x = display.contentCenterX
redBall.y = display.contentCenterY

-- Create a blue ball inside the same group
local blueBall = display.newImageRect( imageGroup, "blue-ball.png", 128, 128 )
blueBall.x = display.contentCenterX
blueBall.y = display.contentCenterY

-- Make the blue ball invisible
blueBall.isVisible = false

现在让我们关注交换代码。

for i = 1,imageGroup.numChildren do
    if ( imageGroup[i].isVisible == false ) then
        imageGroup[i].isVisible = true
    else
        imageGroup[i].isVisible = false
    end
end

很简单,这段代码循环遍历 imageGroup 的子级,它们显然是 redBallblueBall。 如果子级不可见,则将其切换回可见,反之亦然 (从可见切换到不可见)。.

填充切换

您还可以利用图形“填充”方法来交换图像。

local image1 = { type="image", filename="red-ball.png" }
local image2 = { type="image", filename="blue-ball.png" }

local ball = display.newRect( display.contentCenterX, display.contentCenterY, 128, 128 )
ball.fill = image1
ball.isShowing = "image1"

此方法消除了对显示组的需求 — 我们只需创建一个基本显示对象(在本例中是一个与图像大小相同的矩形),并用red-ball.pngimage1 画笔填充它。 我们还为球声明了一个附加属性 isShowing,用于交换代码,如下所示。

if ( ball.isShowing == "image1" ) then
    ball.fill = image2
    ball.isShowing = "image2"
else
    ball.fill = image1
    ball.isShowing = "image1"
end

多图像切换

有时您需要交换两个以上的图像。 在这种情况下,我们可以回到显示组模型,将所有图像加载到一个数组中,并通过它们的索引号访问它们。 在这里,我们遍历 imageCache 表来创建显示对象,并将它们存储在 balls 表中,并使每个对象都不可见。 然后,我们再次使第一个可见。

local imageGroup = display.newGroup()

local imageCache = {
    "red-ball.png",
    "blue-ball.png",
    "green-ball.png",
    "yellow-ball.png",
    "purple-ball.png"
}

local balls = {}
for i = 1,#imageCache do
    balls[i] = display.newImageRect( imageGroup, imageCache[i], 128, 128 )
    balls[i].x = display.contentCenterX
    balls[i].y = display.contentCenterY
    balls[i].isVisible = false
end

imageGroup.isShowing = 1
balls[imageGroup.isShowing].isVisible = true

现在,要管理交换,我们可以执行以下代码,将局部变量 showingImage 设置为我们要显示的图像的索引。 在这种情况下,值 4 将显示yellow-ball.png图像,因为该图像在 imageCache 数组的第 4 个位置声明。

local showingImage = 4

for i = 1,imageGroup.numChildren do
    if ( i == showingImage ) then
        imageGroup[i].isVisible = true
    else
        imageGroup[i].isVisible = false
    end
end

图像表单和精灵

虽然上述方法都完全有效,但使用单个图像文件并不是内存和加载时间的最佳利用方式。 此外,在项目目录中创建/存储大量单个文件可能会变得混乱。 这就是我们鼓励使用 图像表单 的原因,在图像表单中,您可以加载包含所有单个图像的单个图像表单/图集,然后从中显示特定帧。 图像表单需要更多的初始设置,但好处是值得的。

将上述代码转换为图像表单可能如下所示。

local imageGroup = display.newGroup()

local sheetOptions = {
    width = 128,
    height = 128,
    numFrames = 5,
    sheetContentWidth = 640,
    sheetContentHeight = 128
}
local ballSheet = graphics.newImageSheet( "ballSheet.png", sheetOptions )

local balls = {}
for i = 1,sheetOptions.numFrames do
    balls[i] = display.newImageRect( imageGroup, ballSheet, i, 128, 128 )
    balls[i].x = display.contentCenterX
    balls[i].y = display.contentCenterY
    balls[i].isVisible = false
end

imageGroup.isShowing = 2
balls[imageGroup.isShowing].isVisible = true

从代码的角度来看,这与上面的数组方法没有太大区别,除了使用图像表单带来的效率提升之外 — 但是图像表单是通往精灵的门户,这是一种交换图像的绝佳方式!

精灵帧切换

Solar2D 包含一个全面的 精灵 引擎。 虽然术语“精灵”似乎仅表示游戏中动画对象,但您应该将其简单地视为可用于多种目的的有序图像序列,包括交换图像。

让我们看一个基于精灵的版本,它建立在上面的图像表单版本之上。

local sheetOptions = {
    width = 128,
    height = 128,
    numFrames = 5,
    sheetContentWidth = 640,
    sheetContentHeight = 128
}
local ballSheet = graphics.newImageSheet( "ballSheet.png", sheetOptions )

local ball = display.newSprite( ballSheet, { name="balls", start=1, count=sheetOptions.numFrames } )
ball:setFrame( 2 )
ball.x = display.contentCenterX
ball.y = display.contentCenterY

与使用图形填充的版本一样,我们不再需要显示组 (imageGroup),因为精灵根据定义是多帧对象,可以自行驻留在任何组中。 设置图像表单后,只需使用图像表单和序列数据调用 display.newSprite()。 接下来,使用 object:setFrame() 方法选择要显示的帧,精灵引擎将处理其余部分 — 无需像其他一些示例中那样切换其他图像的可见性。

总结

如您所见,"交换图像" 概念有多种方法和途径,最适合的方法取决于您的需求和设计细节。 从单个图像到填充再到精灵,Solar2D 都能满足您的需求!