HaxeFlixel / flixel

Free, cross-platform 2D game engine powered by Haxe and OpenFL
https://haxeflixel.com/
MIT License
1.93k stars 433 forks source link

Copying a FlxCamera's pixels to a FlxSprite doesn't keep zoom, scroll, etc. of the camera #2145

Open grayhaze opened 6 years ago

grayhaze commented 6 years ago

Code snippet reproducing the issue:

package;

using flixel.util.FlxSpriteUtil;

import flash.geom.Point;
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;

class PlayState extends FlxState
{
    var cameraOriginal:FlxCamera;
    var circleSprite:FlxSprite;
    var cameraCopy:FlxCamera;
    var cameraCopySprite:FlxSprite;

    override public function create():Void
    {
        super.create();
        cameraOriginal = new FlxCamera(0, 0, Std.int(FlxG.width / 2), Std.int(FlxG.height));
        FlxG.cameras.add(cameraOriginal);
        cameraOriginal.zoom = 3;
        circleSprite = new FlxSprite((FlxG.width / 4) - 32, (FlxG.height / 2) - 32);
        circleSprite.makeGraphic(64, 64, 0, true);
        circleSprite.drawCircle(32, 32, 32, 0xffff0000);
        circleSprite.updateHitbox();
        circleSprite.cameras = [cameraOriginal];
        add(circleSprite);
        cameraCopy = new FlxCamera(Std.int(FlxG.width / 2), 0, Std.int(FlxG.width / 2), Std.int(FlxG.height));
        FlxG.cameras.add(cameraCopy);
        cameraCopySprite = new FlxSprite(0, 0);
        cameraCopySprite.makeGraphic(Std.int(FlxG.width / 2), Std.int(FlxG.height), 0, true);
        cameraCopySprite.cameras = [cameraCopy];
        add(cameraCopySprite);
    }

    override public function update(elapsed:Float):Void
    {
        super.update(elapsed);
        cameraCopySprite.fill(0);
        if (FlxG.renderBlit) {
            cameraCopySprite.pixels.copyPixels(cameraOriginal.buffer, cameraOriginal.buffer.rect, new Point());
        } else {
            cameraCopySprite.pixels.draw(cameraOriginal.canvas);
        }
    }
}

Observed behavior:

I'm trying to copy the contents of a camera to a sprite so that I can apply effects to that sprite, such as applying an alpha mask by using copyChannel() on the resulting BitmapData (e.g. https://stackoverflow.com/questions/48075991/is-there-a-way-to-apply-an-alpha-mask-to-a-flxcamera?rq=1). Before getting to the point of applying effects however, I'm having difficulty getting the camera pixels to copy over 1:1 while observing camera zoom, scroll, etc. The above example creates a single sprite containing a red circle, which is placed in the source camera. That camera's zoom is then set to 3, resulting in the circle being rendered at 300% of its original size. When I copy the pixels from this camera to a new sprite, which is displayed in a second camera, it seems that the current zoom of the source camera is not observed and this results in the circle displaying at its original size in the target sprite.

2018-04-28 10_30_54-flxproject

Expected behavior:

I would expect the target sprite to be a 1:1 copy of the source camera's current view, including zoom, scroll, etc.

Gama11 commented 6 years ago

Does it work with Lime 2.9.1 + OpenFL 3.6.1?

grayhaze commented 6 years ago

No, I get the exact same result with 2.9.1 and 3.6.1.

grayhaze commented 6 years ago

I assume that a FlxCamera's pixels represent the state of the camera's view before any transforms are applied for camera zoom, etc. Do I perhaps need to apply a matrix to the copyPixels() or draw() calls to apply these transforms manually? Is there a way of accessing the relevant matrix from the FlxCamera, as the internal matrices seem to be private?

JoeCreates commented 6 years ago

You shouldn't need to make adjustments. I think I had this working previously. I'll check later today.

On Sat, 28 Apr 2018, 11:34 Kevin Purcell, notifications@github.com wrote:

I assume that a FlxCamera's pixels represent the state of the camera's view before any transforms are applied for camera zoom, etc. Do I perhaps need to apply a matrix to the copyPixels() or draw() calls to apply these transforms manually? Is there a way of accessing the relevant matrix from the FlxCamera, as the internal matrices seem to be private?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/HaxeFlixel/flixel/issues/2145#issuecomment-385163934, or mute the thread https://github.com/notifications/unsubscribe-auth/ACbEvDvitj424G5TBcj-xUD6C1tTmnu-ks5ttEWhgaJpZM4TrVOm .

grayhaze commented 6 years ago

As an aside, compiling the above code to Flash produces the following output:

2018-04-28 13_43_31-adobe flash player 29

I don't actually use the Flash target in my main project, but it appears that the behaviour in this target is to copy the camera viewport correctly, but still at the default zoom.

grayhaze commented 6 years ago

So I've got this sort of working on cpp targets, using a transform matrix built using public properties of the camera:

    override public function update(elapsed:Float):Void
    {
        super.update(elapsed);
        cameraCopySprite.fill(0);
        var transformMatrix = new Matrix();
        transformMatrix.translate(-(0.5 * cameraOriginal.width * (cameraOriginal.scaleX - cameraOriginal.initialZoom) / cameraOriginal.scaleX), -(0.5 * cameraOriginal.height * (cameraOriginal.scaleY - cameraOriginal.initialZoom) / cameraOriginal.scaleY));
        transformMatrix.scale(cameraOriginal.scaleX * 0.997, cameraOriginal.scaleY * 0.997);
        cameraCopySprite.pixels.draw(cameraOriginal.canvas, transformMatrix);
    }

The translation calculation is based on those done in FlxCamera to calculate offsets, and that part works. The scale calculation requires multiplying the camera's scale by some seemingly arbitrary value. If that multiplication isn't done, or the value by which I'm multiplying passes a certain threshold (which varies depending on the zoom), the resulting image is empty. If it weren't for this I'd be happy just performing these transforms whenever I need to copy the camera.

2018-04-28 15_14_02-flxproject

grayhaze commented 6 years ago

So this works. I have no idea why though. 😁

    override public function update(elapsed:Float):Void
    {
        super.update(elapsed);
        cameraCopySprite.fill(0);
        cameraCopySprite.pixels.draw(cameraOriginal.canvas);
        cameraCopySprite.fill(0);
        var transformMatrix = new Matrix();
        transformMatrix.translate(-(0.5 * cameraOriginal.width * (cameraOriginal.scaleX - cameraOriginal.initialZoom) / cameraOriginal.scaleX), -(0.5 * cameraOriginal.height * (cameraOriginal.scaleY - cameraOriginal.initialZoom) / cameraOriginal.scaleY));
        transformMatrix.scale(cameraOriginal.scaleX, cameraOriginal.scaleY);
        cameraCopySprite.pixels.draw(cameraOriginal.canvas, transformMatrix);
    }