I am trying to achieve the following challenging effect: I want to move the white "curtain" down in order to reveal the red box. (Note: in the screenshots below the curtain is white and the background is grey)
The problem is in the view hierarchy.
In order for the box to stay hidden in the initial position, it has to be placed behind the curtain, but in order to be shown in the final position, it has to be on top of the curtain.
How can I "cheat" and make it seem like the curtain really reveals the box with a smooth animation?
Thanks!
You need 2 images and a mask. Your fully obscured gray area and your box with white background. The image for your curtain is only a mask of the bottom edge. This is so it can draw the bottom fringe for the curtain and not obliterate the gray overlapping region.
Set a starting position at the top, each frame: Draw/copy only the size of the curtain mask, copying the corresponding red box region through the curtain mask. Move the starting position down one scan line and wait for the next frame. Repeat until done.
Essentially, there is no white curtain, only what is revealed of the "hidden" image which contains white background for the box. Depending on how you're drawing, your mask image could be another image with an alpha channel.
Edit: As requested, some example code. However, it is very possible that whatever you are using to get graphics on the screen already has draw routines with masking and your would be better off using that. This snippet is untested but should provide the logic and work pretty much anywhere. I'm not familiar with iOS and have no idea what format your image pixels are, 24 bit, 32 bit, etc. and use "PixelType" as a substitute.
This also assumes the white curtain edge with a black background was made as an 8 bit image in a paint program, and black is zero and white anything else. It should be the same width as both of the other images and only as tall as needed for the curtain edge.
struct Mask
{
char *mData; // set this to the image data of your 8 bit mask
int mWidth; // width in pixels, should be the same as your 2 images
int mHeight; // height in pixels of the mask
};
int iRevealPos = 0; // increment each frame value until box is revealed.
// Hopefully, your pixel type is a basic type like byte, short or int.
void Reveal(PixelType *foreground, PixelType *background, Mask *mask)
{
int height = (iRevealPos < mask->mHeight) ? iRevealPos : mask->mHeight; // account for initial slide in
PixelType *src = background + (iRevealPos * mask->mWidth); // background box at current reveal position
PixelType *dst = foreground + (iRevealPos * mask->mWidth); // matching foreground screen position
int count = mask->mWidth * height;
char *filter = mask->mData;
if ((iRevealPos < mask->mHeight)) // adjust for initial slide in
filter += (mask->mHeight - iRevealPos) * mask->mWidth;
while (count--)
{
if (*filter++) // not black?
*dst++ = *src++; // copy the box image
else // skip this pixel
{
src++;
dst++;
}
}
// if you create your mask with a solid white line at the top, you don't need this
if (iRevealPos > mask->mHeight) // fixup, so the mask doesn't leave a trail
{
src = background + ((iRevealPos-1) * mask->mWidth);
dst = foreground + ((iRevealPos-1) * mask->mWidth);
count = mask->mWidth;
while (count--)
*dst++ = *src++;
}
iRevealPos++; // bump position for next time
}
If you create your mask with a solid white line or 2 at the top you don't need the second loop which fixes up any trail the mask leaves behind. I also allowed for the curtain to slide in rather than fully pop in at the start. This is untested so I may have got the adjustments for this wrong.