Link Search Menu Expand Document

Camera Transitions

For our next trick let’s try adding sliding camera transitions, here’s an example

We’re going to implement this by adding an in_transtion flag to oControl. Then it can handle the transitioning and follow logic seperately. We’ll also add invisible markers to the room. This will be check points where the camera won’t cross them unless the player crosses them

First let make an oTransMarker object, give it the sTransMarker sprite, and then make it invisible

Then we got to add code. The complete code segment was so big I opted to break it down into separate snippets. Luckily we’re near the end of the course, so I know you can handle it 😉

// oControl Create Event
left_bounds = 0;
right_bounds = room_width;
with(oTransMarker){
	if(x < other.right_bounds){
		other.right_bounds = x;
	}
}

is_transitioning = false;
trans_steps = room_speed;
trans_amt = 0;
trans_start_x = 0;
trans_target_x = 0;

left_bounds / right_bounds: This will be used in our clamping logic. Previously we clamped to be bounded by the room, meaning we clamped on the range 0, room_width. With the markers involved, we need to update this depending on the marker position. left_bounds will be the x of the marker to the player’s left (or 0 if there isn’t one), and right_bounds will be the x of the marker to the player’s right (or room_width if there isn’t one)

with(oTransMarker): I’m assuming the the player is starting at the left side of the room (to the left of all oTransMarker objects). So that means right_bounds should match the x of the left most oTransMarker (which would be the one directly to right of the player). Using with as a loop is a handy trick for finding the smallest x among the oTransMarker objects. Every time we find a smaller one we update right_bounds accordingly. Read over this carefully, since I’ll be using a lot of similar with tricks in this section

is_transitioning: This represents whether we’re curring in a sliding camera transition. This will complete alter our camera logic in the step event. The following trans_ variables are all used during that transition

trans_steps: This is how many steps the transition will take

trans_amt: This is how far along we are in the transition. This will always start at 0 and end at 1

trans_start_x / trans_target_x: This is the start and ending x positions of the transition. Note that there’s no y because we keep that constant during the transition

// oControl Step Event
if(is_transitioning){
	//// APPLY TRANSITION ALONG X
	trans_amt += 1 / trans_steps;
	cam_x(lerp(trans_start_x, trans_target_x, trans_amt));
	if(trans_amt >= 1){
		is_transitioning = false;
	}
}else{
	...
}

if(is_transitioning){: This ensures that we follow the new camera logic when we’re in the middle of transition

trans_amt += 1 / trans_steps;: I want trans_amt to go from 0 to 1 over the course of trans_steps steps. If we do the math, that means we need to increase it by 1 / trans_steps every step for that to work. To verify I can do trans_steps steps multiplied by 1 / trans_steps per step, and I get 1. Perfect!

cam_x(lerp(trans_start_x, trans_target_x, trans_amt));: This is probaly the key line of this segment. We have our starting and endpoint points for the transition: trans_start_x and trans_target_x, and then we want to transition between them using the trans_amt variable. It’ll start at 0, meaning our cam_x will be set to trans_start_x, but by the time trans_amt reaches 1, we’ll be at trans_target_x

I thought lerp had an effect where it get’s faster as it get’s closer? Is that still happening?: Ah, no it’s not. In the original example, I had the amount locked at .1, and the first number was getting closer to the target every frame. With the range getting smaller and smaller, than meant that the amount of the range was yielding a smaller number as well. This time it’s different, the start / ending range remains the same for the entire transition. Also I’m increasing the amount by the same value every frame. So that results in the camera moving at a constant speed throughout the transition

cam_y: Note that we didn’t have any logic for cam_y. We’ll just leave that untouced during this transition

if(trans_amt > 1){: This means we’ve reached the end of the transition, and when this happens we’re ready to move back to the normal operations

// oControl Step Event
if(is_transitioning){
	...
}else{
	//// CENTER ON PLAYER
	//// SMOOTH CAMERA
	//// CLAMP TO BOUNDS
	cam_x = clamp(cam_x, left_bounds + max_shake_dist, right_bounds-cam_w()-max_shake_dist);
	cam_y = clamp(cam_y, max_shake_dist, room_height-cam_h()-max_shake_dist);

	//// CAMERA SHAKE
	//// APPLY

	//// TRIGGER TRANSITION TO THE RIGHT
	if(oPlayer.x > right_bounds){
		// update bounds
		left_bounds = right_bounds;
		right_bounds = room_width;
		with(oTransMarker){
			if(other.left_bounds < x and x < other.right_bounds){
				other.right_bounds = x;
			}
		}

		// update trans variables
		is_transitioning = true;
		trans_amt = 0;
		trans_start_x = cam_x();
		trans_target_x = left_bounds + max_shake_dist;
	}

	//// TRIGGER TRANSITION TO THE LEFT
	// TODO
}

clamp(cam_x, left_bounds + max_shake_dist, right_bounds-cam_w()-max_shake_dist);: For this I used to have 0 and room_width, but now that we have markers the bounds could be any value, so replaced those with left_bounds and right_bounds respectively

oPlayer.x < left_bounds: When the player has moved to the left of the camera, that means we’re ready to slide the camera to the left

right_bounds = left_bounds;: When the camera’s done moving, the right side of the view should align with the same marker that’s currently on the left side. So we set right_bounds equal to left_bounds

left_bounds = 0;: The camera is about to move left, but we don’t know if there’s a new marker further left to be a new bound. So I’ll start off with 0 by defeault and then we’ll increase it if we find new ones

with(oTransMarker): Here we have another with() loop which is trying to find the largest marker. In this case we start with a range (0, right_bounds), and everytime we find a marker within that range, we update the left_bound. This way we’ll have the right most marker within the range by the time we’re done, and the left_bounds and right_bounds values should correspond to consecutive

is_transitioning = true;: This will make the next step follow the transition logic instead of the player following logic

trans_amt / trans_start_x / trans_target_x: These variables set us up to be used with lerp() later. trans_target_x is the most involved. When we go back to to player following mode and we apply the clamp function, we don’t want the view to clamp abruptly. So since the clamp logic has left_bounds + max_shake_dist set to the minimum, we’re going to use that as the transition target before we go back into following mode

When you place a few markers in the room and test it out, we should see the screen shifting when the player walks by the invisible markers. Awesome!

TODO: But what about the left side?

We can largely copy and paste, but with a few critical differences

////TRIGGER TRANSITION TO THE LEFT

if(oPlayer.x < left_bounds){
	// update bounds
	right_bounds = left_bounds;
	left_bounds = 0;
	with(oTransMarker){
		if(other.left_bounds < x and x < other.right_bounds){
			other.left_bounds = x;
		}
	}

	// update trans variables
	is_transitioning = true;
	trans_amt = 0;
	trans_start_x = cam_x();
	trans_target_x = right_bounds - max_shake_dist - cam_w();
}

if(oPlayer.x < left_bounds): This time we trigger when the player exits to theleft

other.left_bounds = x;: When we loop through the trans markers, we’re looking for a new left bound this time, so we update this line accordingly

trans_target_x = right_bounds - max_shake_dist - cam_w();: Since we’re moving to the left, I have to make the target_trans_x match the maximum in our clampinglogic

Yay! Now we can do both 🎉

Camera Transition Tweaks

After playing with it a little bit, you may want a few more tweaks. For example, it’s kind of annoying when you get shot by missiles from across the room. To fix this let’s disable shooting on the missile object when we’re in a different area

// oMissileTurret Alarm 1 event
alarm[1] = random_range(3*room_speed, 5*room_speed);

if(x < oControl.left_bounds or x > oControl.right_bounds) exit;

...

Next I also want to update the player. When we’re mid transition I don’t think we should be able to move. This is a good opportunity to practice adding another state to the state machine. Want to give it a try?

// oPlayer Create Event
enum PLAYER_STATE{
	in_control,
	hurt,
	dying,
	in_transition,
}

// oPlayer Step Event
switch(state){
case PLAYER_STATE.in_transition:
	if(not oControl.in_transition){
		state = PLAYER_STATE.in_control;
	}
	break;
case PLAYER_STATE.hurt:
case PLAYER_STATE.dying:
	// do nothing
	break;
case PLAYER_STATE.in_control:
	//// HORIZONTAL
	//// VERTICAL
	//// SHOOTING
	//// TRIGGER IN_TRANSITION STATE
	if(oControl.in_transition){
		state = PLAYER_STATE.in_transition;
	}
	break;
default:
	print("STATE NOT HANDLED", state);
}

// oPlayer Draw Event
switch(state){
case PLAYER_STATE.dying:
	//// DEATH
	break;
case PLAYER_STATE.in_transition:
case PLAYER_STATE.hurt:
case PLAYER_STATE.in_control:
	//// SET COLOR
	//// ARMS
	//// RESET COLOR
	break;
default:
	print("STATE NOT HANDLED", state);
}

Disabling the player could have been done in one line, but the nice thing about adding it to a stateis that we need to think through all cases

When should the player enter the in_transition state?: Only while the player is in the in_control state, otherwise we might be canceling a death animation

How should the in_transition state be drawn?: Like the non-death state, w/ arms and all

How should the in_transition state animate?: We shouldn’t need to update this, that can follow all the same rules and treat it like an idle scenario

Whic state should we enter when we exit in_transition?: definitly in_control. hurt or dying wouldn’t make any sense

This is all just the beginning of what you can do with cameras, if you want a more advanced example, check out the video / blog from game maker station

TODO: include link


This site uses Just the Docs, a documentation theme for Jekyll.