14
\$\begingroup\$

I want to change my dark and depressing background to a happy grassy background, in real-time, such that the happy background is shown in a radius around a game character.

A forcefield of happiness, if you will.

How can this be done as performantly as possible when rendering to a canvas in a custom view?


UPDATE: Here's how it works in my head:

    private int hModeX, hModeY;
    private float hModeRadius = 0.1f;
    private float hModeStart = 0;
    happyModeBg = Bitmap.createScaledBitmap(happyModeBg, getWidth(),getHeight(), true);
    hModeX = getWidth()/2;
    hModeY = getHeight()/2;

private void initHappyMode(Canvas canvas){
    hModeRadius = 75 * thread.getDelta();

    if(hModeRadius > 500) {
        hModeStart = timestep; //What is timestep?
    }

    //context.arc(x, y, radius, 0, 2 * Math.PI, false); <- your version
    canvas.save();
    canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint) // <- my version
    //context.clip(); <- dont know what this does or if there is an equivilant
    canvas.drawBitmap(happyModeBg, 0, 0, null);
    canvas.restore();
    //requestAnimationFrame(step); <- dont know what this does since I cant seem to find it for android

}

I'm using canvas.drawArc() to make a circle, but I'm definitely missing something.

\$\endgroup\$
  • 1
    \$\begingroup\$ technically it is called World Healing Wave \$\endgroup\$ – ratchet freak Jan 30 '14 at 10:55
  • \$\begingroup\$ I just use canvas on a custom view run by my own game-loop at 60fps \$\endgroup\$ – Green_qaue Jan 30 '14 at 13:55
  • \$\begingroup\$ Could you render the scene twice? Once for the healed version, once for the dead version, and then draw the healed over the dead in an ever increasing circle until the circle covers the whole screen. \$\endgroup\$ – Wolfgang Skyler Jan 30 '14 at 15:09
  • \$\begingroup\$ Thats kind of what I have been thinking. Just dont know how to go about that with a bitmap. And I thout maybe there is a better way since rendering 2 bgs at once is very costly \$\endgroup\$ – Green_qaue Jan 30 '14 at 15:37
  • 2
    \$\begingroup\$ I think I see what the confusion was about -- Android's Canvas does things a little bit differently than the HTML <canvas>, which I thought you meant! I've edited my answer to explain how clipping works in general, with some Android-specific links and example code. \$\endgroup\$ – Anko Feb 5 '14 at 11:21
20
+50
\$\begingroup\$

Demo

GameDev Healing Wave

GameDev Meta: Henshin Main!! :D

The code uses a canvas clip region and requestAnimationFrame for maximum quality and efficiency. (It's better live.)

I assumed you meant HTML canvas! Even if you didn't, other rendering engines (such as Android's 2D rendering pipeline, which you might have meant) also support hardware-accelerated clip regions. The code will likely look similar.

Requesting animation frames with requestAnimationFrame (or another platform's provided solution) results in higher-quality animation by letting the engine determine render timing.

This renders smoothly on a low-range netbook and on my Android phone.

Explanation

To make this more generally useful, let's really understand clipping.

Let's say we have these two rectangles:

two rectangles, one overlapping the other

Clipping works just as well on lines, points, images or anything else you might want to render. I'm just sticking to rectangles for simplicity.

We don't want the red rectangle to be visible here (black circle) at all.

two rectangles, the red rectangle's desired cut-away area in black

So we can instruct the renderer to clip the red rectangle by that circle.

two rectangles; the red one has been clipped

In other words, when the red rectangle is drawn, it is drawn everywhere except where that black circle would have been.

Different renderers have different ways of specifying what region to clip by. In JavaScript, with the HTML <canvas>, I did essentially

// Draw the blue rectangle

context.save(); // Save the current state of the graphics context,
                // so we can go back to it.

// Draw the black circle
context.clip(); // Tell the renderer that was our clip region,
                // since the last `context.save()`.

// Draw the red rectangle.
// It will appear clipped by the black circle.

context.restore(); // Tell the renderer we should restore all
                   // the clipping settings back to what they were
                   // `context.save`d as.

// From here on, nothing is clipped anymore.

Now, in an Android Canvas, you'll want to be doing a similar thing, but the renderer expects slightly different code:

// Draw the blue rectangle

canvas.save(); // Same idea as above -- just setting a point we can
               // restore to later.

// Now, `canvas.clipPath` expects a `Path` object.
// Let's create one that contains a circle.
Path path = new Path();
path.addCircle(100, 100, 100, Direction.CW); 
canvas.clipPath(path);

// Draw the red rectangle
// It will be clipped by the circle defined in the `path` that we
// used as the `clipPath`.

canvas.restore(); // Aaaand restore the state.

// Things drawn here won't be clipped anymore.

Android documentation on the matter might be handy. There are good questions like this one on StackOverflow about more things you might want to try with it.

\$\endgroup\$
3
\$\begingroup\$

Working implementation using Ankos method:

canvas.drawBitmap(depressingBg, 0, 0, null);
canvas.save();
radius += 400*thread.getDelta(); // expansion speed
Path path = new Path();
// draw around player position
path.addCircle(player.x, player.y, radius, Direction.CW);
canvas.clipPath(path);
canvas.drawBitmap(happyBg, 0, 0, null);
canvas.restore();

Thanks to Anko for all the help!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.