Camera follow (w/ scale & viewport) flickering


(azrafe7) #1

Hi,

I was trying to implement a camera follow class that can consider viewport size and screen scaling, but I’m having quite the problems.

It kind of works if I just jump to where the camera is supposed to be (although I’m not pleased by the effect of it). But if I go from scale 1x to 2.5x the player slowly crawls into view and often flickers like crazy.

What I’d wish to achieve is to be able to define how fast the camera will move to the target object/point, and to make sure that the target is put into view quickly (and in a pleasing-to-see way). All while having a smooooth camera movement. (don’t know if my troubles come from rounding issues - doubt it - , but I’ve tried to cast to int and nothing good came back).

Relevant code called from World.update() - obj is the camera-tracked entity:

public function cameraFollow(obj:*, scale:Number):void 
{	
	// desired position of camera on target 
	// ([.5, .5] means centered in the viewport - drawn in punk pink)
	viewportPoint = new Point(.5, .5);
	
	// set viewport size and top-left offset (based on scale)
	if (scale < 1) {
		viewportSize = new Point(FP.width * scale, FP.height * scale);
		screenTopLeft = new Point(FP.halfWidth * (1 - scale), FP.halfHeight * (1 - scale));
	} else {
		viewportSize = new Point(FP.width, FP.height);
		screenTopLeft = new Point(0, 0);
	}
	
	// center viewport in window
	FP.screen.x = screenTopLeft.x;
	FP.screen.y = screenTopLeft.y;
	
	// final camera position
	targetPoint = new Point( -viewportPoint.x * viewportSize.x / scale + obj.x, 
							 -viewportPoint.y * viewportSize.y / scale + obj.y);
	
	// if I use this then all is fine - kind of - ... but I also want 
	// friction AND the target to stay in view!!
	/*
	FP.camera.x = targetPoint.x;
	FP.camera.y = targetPoint.y;
	*/
	
	// difference vector (from FP.camera to targetPoint)
	diffVector = targetPoint.clone().subtract(new Point(FP.camera.x, FP.camera.y));
	
	var friction:Number = .9;		// friction, so that camera doesn't jump to target, but approaches it slowly
	var speed:Number = 350;			// camera speed
	
	// move camera (jump to targetPoint if distance is minimal - not working as expected!!)
	if (diffVector.length > 1 * scale) {
		diffVector.normalize(1);
		FP.camera.x += diffVector.x * speed * friction * FP.elapsed * scale;
		FP.camera.y += diffVector.y * speed * friction * FP.elapsed * scale;
	} else {
		FP.camera.x = targetPoint.x;
		FP.camera.y = targetPoint.y;
	}
	
	// adjust camera x for world boundaries
	FP.camera.x = FP.clamp(FP.camera.x, 0, WIDTH - viewportSize.x / scale);
}

SWF to see what it looks like (press SPACE to change the scaling (pink rect represents the viewport)): CameraScaleTest

I’m totally sure I’ve done something really stupid along the way, but holy crop I can’t figure it out. Hints anyone?

PS: I’ve just noticed that there are some problems with the right world boundary when played in the browser (but weirdly works well when run through the Flash Player) o.O.


(Nicole Brauer) #2

I hope I can add a little to this. I’m having similar troubles but with a much simpler set-up.

I have a camera following my player entity smoothly with this code:

FP.camera.x = merge_number(FP.camera.x, Player.x, .05);

(...)

public function merge_number(numA:Number, numB:Number, amount:Number):Number
{
	return (numA+((numB-numA)*amount))
}

And I get the same problem. While my player is moving it looks alright, but when the player stops and the camera slowly comes to a halt the player jitters around in a very similar fashion to azrafe7’s Scale-Test.

I think it might have something to do with the camera and rounding issues, since this offsetting or “jittering” doesn’t happen when two entities have the same coordinates.

Hopefully I didn’t misunderstand your issue azrafe7 and was just going on a tangent here.


(azrafe7) #3

Hey, found out what the culprit was. Unplugging the brain for a few hours has done wonders!

While moving the camera sometimes happens that you overshoot the target position by a little amount (and it’s more noticeable when the displacement is small). At that point, when the new target position is calculated it tries to move the camera back, overshooting the target position in the opposite direction. And welcome jittering!

You can fix it by using FP.approach() (yes, there’s already a function that does exactly that!), which behaves like your merge_number(), but avoids overshooting the target.

Discovered, at my own expenses, that the amount parameter MUST be positive.

Here’s a fool-proof version of it:

/**
 * Approaches the value towards the target, by the specified amount, without overshooting the target.
 * @param	startValue		The starting value.
 * @param	targetValue		The target that you want value to approach.
 * @param	amount			How much you want the value to approach target by.
 * @return	The new value.
 */
public static function approach(startValue:Number, targetValue:Number, amount:Number):Number
{
	var delta:Number = Math.abs(amount);
	if (startValue < targetValue - delta) {
		return startValue + delta;
	} else if (startValue > targetValue + delta) {
		return startValue - delta;
	} else {
		return targetValue;
	}
}

Might issue a pull request if the mantainers are willing to push it.


(Nicole Brauer) #4

My function is actually a bit more like FP.lerp() (actually, it’s exactly the same and I have no idea why I’m not just using that) where FP.lerp slowly moves towards the target and slows down as it approaches it and FP.approach moves towards the target with a constant speed / amount until it’s at the target position.

I was thinking that you were looking for a solution where it slows down while getting closer. So the issue for me still remains, but I don’t want to sway too far from the topic.

But I noticed that for me FP.approach() still makes my entities jitter a little bit, at least at slow speeds.


(azrafe7) #5

Yeah, you’re right: I do want my cam to slow down while getting closer.

Misread your code and thought you were using a constant approach.

The main point of my previous post, however, is that I think my problem actually IS the overshooting. So now I can start coding the distance-aware camera movement knowing what to take care of.

EDIT: Since you’re using lerp you shouldn’t have the same problem. Do you have a testing swf showing the issue (it can also possibly be a Flash bug).

EDIT2: Just noticed you’re not using FP.elapsed in your code (I’m assuming your game is in variable step mode).


(Nicole Brauer) #6

Whoops, I might have misread your posts then. :smile:

So I’m not sure anymore if we’re actually talking about the same problem. But just in case I made a stripped down version of one of the game’s maps where you can see the problem.

http://megaswf.com/file/2591192.swf

(Just wait for the guy to walk all the way right and when the camera slowly comes to a stop he starts jittering around)

And yes it uses variable time steps, I can’t remember why but I had too much trouble with fixed mode. But it still jitters around no matter what mode I choose, just tested that.

EDIT: Ugh, stupid swf file hosts. It somewhat lags and the swf is cut off but you should be able to still see the player.


(Alex Larioza) #7

Where do you have the code that updates the camera?


(azrafe7) #8

After a few attempts I was able to reproduce your issue in a test file, using your lerp function for camera movement. It was a rounding issue (as you hinted at in your first post). And adding

player.x = int(player.x);

when the yellow block is in his final position seems to fix it. Wonder if it’s actually that in your case too.


(Nicole Brauer) #9

Hm… yeah. That seams like a resonable solution.

I tried something similar with rounding the player position all the time but that made the movement of the player very unsmooth and abrupt, so I just left it as is.

I guess that your solution will work. I should have put more thought into that.

Anyway, thank you for helping me out. I will try this later, but I’m certain it will solve the issue.

Did you get any further with solving your own issue?

EDIT: Just tested it out, it works like a charm. I feel really stupid that I didn’t think of this.


(Abel Toy) #10

Do it. I’m willing to push it :stuck_out_tongue:


(azrafe7) #11

Yes, but I’m still working on the code. I’m considering to use your lerp approach though, it’s less convoluted. And glad to know the solution I proposed for your issue has worked out!


I was going to, but then a second thought occurred to me: as it is written now is more powerful, as the same function can also be used as a fleeFrom() function by passing a negative amount parameter. Slightly changing the function documentation and explaining this might still be very helpful though!