I am stumped on one key animation for a game I'm making:
A large (20x400) black rectangle represents a laser which can kill the player. The rectangle does not move, but it appears at an arbitrary angle (so it's actually a right-angle polygon). Just before the laser appears, I want the same space to shimmer lightly to signal to the player that they should leave the area of the laser.
In an ideal (but unrealistic) world, I would essentially want every pixel in the rectangle to have ~1/10 chance of being black every frame.
I've reviewed posts on similar animations here, and the closest effect I could find is this post regarding Wave Racer. That dev just pre-rendered three frames of animation in all 360 degrees. I think my laser would end up being an image object as large as the whole screen (at some angles), so I think this solution would be overkill.
None of the pre-provided dither patterns seemed to achieve a random shimmer or twinkle, but I'm not very familiar with using them.
Does anyone have any ideas about how to achieve a shimmer/twinkle/speckle pattern in a given rectangle (which is not horizontal or vertical)?
I'm considering three ways of doing this, which all seem to have different efficiency penalties. I'm hoping before I start creating all three someone might be able to suggest a better way. Any help would be appreciated!
Options I'm considering
- Pre-render (either on boot or as png files) ~20 frames of "random" noise using "for x=0, w do drawPointAt(x, rand(0, h))". Then I would rotate this massive screen-width set of ~ 20 images according to the angle of the laser each time.
- Create a 400x240 image containing a pattern of random noise. Store the laser polygon to an image file each time the angle is set. For each frame of shimmer, use some XOR filtering to cause the shimmer pattern to show up only in the polygon; shift the noise image by rand() x every frame to make it look random.
- For each x along the polygon's width (at a given angle), randomly draw one or more points within the vertical space of the polygon. This would involve possibly hundreds of rand() and drawPointAt() calls each frame, so this seems like the worst option.
Edit: I'd love a real solution, but option 3 was good enough.
I would still love input from anyone with a better solution, but the following solution only drops 1-2 frames. It uses 40 math.random()
calls and 40 drawPixel()
calls once every 3 frames.
The code below creates 5 layers of random noise. Every three frames, one layer is updated. This creates an effect where pixels appear and disappear independently, rather than flickering like tv static. On each update, 40 random points are drawn to one layer within the four corners of the rectangular polygon. Every frame, all five layers are drawn on top of each other to create the effect.
function BossOne:laserShimmerSetup()
self.laserShimmerTimer = 0
for i=1, 5 do -- this initializes each of the 5 shimmer layers
self:laserShimmerDisplay(i)
end
self.laserShimmerTimer = 15
end
function BossOne:laserShimmerDisplay(lasernumber)
--lasernumber represents 1 of 5 layers of shimmer that update independently
lasernumber = lasernumber or 0
-- updates one layer each 3 frames for less flicker
if self.laserShimmerTimer == 15 then
lasernumber = 5
elseif self.laserShimmerTimer == 12 then
lasernumber = 4
elseif self.laserShimmerTimer == 9 then
lasernumber = 3
elseif self.laserShimmerTimer == 6 then
lasernumber = 2
elseif self.laserShimmerTimer == 3 then
lasernumber = 1
end
local laser = self:laserMath() -- pulls the coordinates of the laser's four corners
--i.e.:
-- (x1,y1)----------------(x4,y4)
-- | |
-- (x2,y2)----------------(x3,y3)
if lasernumber <=5 and lasernumber >=1 then
gfx.lockFocus(self.lasershimmer[lasernumber])
gfx.clear(gfx.kColorClear)
--this section would ideally change automatically if laserwidth is later updated, but it doesn't.
--manually update this if laserwidth changes
--current width of laser is 20, length is 400
for i=1, 40 do --change number to increase or decrease density of shimmer
local relative_height = math.random(1, 20) /20
local relative_width = math.random(1, 400) / 400
local height_change_x = (laser.x2 - laser.x1)
local height_change_y = (laser.y2 - laser.y1)
local width_change_x = (laser.x4 - laser.x1)
local width_change_y = (laser.y4 - laser.y1)
local pixel_x = laser.x1 + height_change_x * relative_height
pixel_x += width_change_x * relative_width
local pixel_y = laser.y1 + height_change_y * relative_height
pixel_y += width_change_y * relative_width
gfx.drawPixel(pixel_x, pixel_y)
end
gfx.unlockFocus()
end
self.laserShimmerTimer -= 1
if self.laserShimmerTimer <= 0 then
self.laserShimmerTimer = 15
end
for i=1, #self.lasershimmer do
self.lasershimmer[i]:draw(0,0)
end
end