keeping objects visible while deactivated during a flickscreen room transition (SOLVED)

jjg27

Member
I've been following this tutorial to create a flickscreen room transition (think original Legend of Zelda)

I mostly copied his code, but with a few changes. I don't like how he has 4 separate objects on each side of the room, each with their own instance creation code. Instead I made a single invisible non-persistent object called objTransition which is placed in each room and has four collision_line checks just outside the edges of the screen. Upon collision, objTransitionAnimation (persistent) is created, which does the animation effect and then destroys itself.

Unfortunately, several things are not working...

1) If objTransitionAnimation is set to visible, it works almost as desired (see 2 and 3 below) but only once! I can't even move to other rooms afterwards, let alone see a transition.
2) In the above it is visible, my player sprite appears on the wrong side of the screen for one frame before being moved to the correct place. I know that it's because my move player code is in the Destroy event of objTransitionAnimation, but if I move that code anywhere else (I've tried lots of places...) the transition simply doesn't happen.
3) Enemies in the old room are correctly frozen in place for the transition effect, but new enemies in the next room move around while the transition is happening. How do I freeze them in place (and the player, for that matter!) until the transition is over?
4) If objTransitionAnimation is set to invisible, going between rooms works every time now but with no animation.

Here's the code. Thanks for any help - I'm still new and this has been driving me crazy all week!

Code:
enum transition {right, up, left, down};
global.transition_direction = transition.right;
global.next_room = rmRight;

Code:
if collision_line(256,0,256,191,objMan,false,false)
    {
    global.transition_direction = transition.right;
    global.next_room = rmRight;
    instance_create_layer(0,0,"Instances",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(0,-1,255,-1,objMan,false,false)
    {
    global.transition_direction = transition.up;
    global.next_room = rmUp;
    instance_create_layer(0,0,"Instances",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(-1,0,-1,191,objMan,false,false)
    {
    global.transition_direction = transition.left;
    global.next_room = rmHub;
    instance_create_layer(0,0,"Instances",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(0,192,255,192,objMan,false,false)
    {
    global.transition_direction = transition.down;
    global.next_room = rmDown;
    instance_create_layer(0,0,"Instances",objTransitionAnimation);
    instance_destroy();
    }

Code:
tr_step = 0;
tr_time = 45;

surf_start = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));
surf_end = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));

surface_set_target(surf_start);
gpu_set_blendenable(false);
gpu_set_colorwriteenable(true,true,true,false);
draw_clear(c_black);
draw_surface(application_surface,0,0);
gpu_set_blendenable(true);
gpu_set_colorwriteenable(true,true,true,true);
surface_reset_target();

Code:
surface_free(surf_start);
surface_free(surf_end);

switch(global.transition_direction){
case transition.right:
    objMan.x = 8;
    break;
case transition.up:
    objMan.y = 175;
    break;
case transition.left:
    objMan.x = 247;
    break;
case transition.down:
    objMan.y = 8;
    break;
}

Code:
tr_step++;

if (tr_step > tr_time) instance_destroy();

if (!surface_exists(surf_start) or !surface_exists(surf_end))
    {
    if (room != global.next_room) room_goto(global.next_room);
    instance_destroy();
    }

Code:
var width, height, x1, y1, x2, y2;
width = display_get_gui_width();
height = display_get_gui_height();

switch (global.transition_direction)
    {
    case transition.right:
        x1 = (1 - tr_step/tr_time) * width; y1 = 0;
        x2 = (-tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.up:
        x1 = 0; y1 = (-1 + tr_step/tr_time) * height;
        x2 = 0; y2 = (tr_step/tr_time) * height;
        break;
    case transition.left:
        x1 = (-1 + tr_step/tr_time) * width; y1 = 0;
        x2 = (tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.down:
        x1 = 0; y1 = (1 - tr_step/tr_time) * height;
        x2 = 0; y2 = (-tr_step/tr_time) * height;
        break;
    }
  
if (room != global.next_room) room_goto(global.next_room);

draw_surface_stretched(surf_end,x1,y1,width,height);
draw_surface_stretched(surf_start,x2,y2,width,height);

Code:
surface_set_target(surf_end);
gpu_set_blendenable(false);
gpu_set_colorwriteenable(true,true,true,false);
draw_clear(c_black);
draw_surface(application_surface,0,0);
gpu_set_blendenable(true);
gpu_set_colorwriteenable(true,true,true,true);
surface_reset_target();
 
Last edited:

Nidoking

Member
I notice that your room_goto call appears to be in a Draw GUI event. That's bad for many reasons, particularly because it's not drawing, but if the instance is not visible, the Draw GUI event doesn't run, so there's no room transition until one of the surfaces ceases to exist. That could be part of your problem.
 

jjg27

Member
I notice that your room_goto call appears to be in a Draw GUI event. That's bad for many reasons, particularly because it's not drawing, but if the instance is not visible, the Draw GUI event doesn't run, so there's no room transition until one of the surfaces ceases to exist. That could be part of your problem.
Thanks for your reply. I have simply commented out that line of code (room_goto) in the Draw GUI event. With the objTransitionAnimation set to visible, it's a big improvement: transitions work every time and no player sprite appearing at the wrong side of the screen for a frame. However, the big problem now is that the transition is just two copies of the same old room, which then becomes the new room when the transition is finished. I know this is because the only remaining bit of code with room_goto is in the step event after both surfaces are wiped. The issue: where should the room_goto code be moved to from its initial location in Draw GUI? I've moved it to the end of the Create Event and the start of the Step Event, but in both cases this brings back the same problems: room transition only works once (completely stuck in the next room) and player sprite is on the wrong side of the screen for a frame at the end of the transition.

(For what it's worth, I changed Begin Step to Step and put the Draw GUI End code at the end of Draw GUI. This doesn't appear to have changed anything, just makes more sense to me!)
 

Nidoking

Member
It sounds like you're trying things to see what happens and judging the results based on whether or not they do what you want. That's a bad approach. That's what we call the "infinite monkeys" approach, based on the idea that if you put infinitely many monkeys in a room at infinitely many computers for an infinite amount of time, one of them will eventually create the game you want simply by random chance. That would surely be a violation of the Game Maker license, and also, one person is significantly fewer than an infinite number, and you aren't likely willing to devote an infinite amount of time. A much faster approach is to learn some concepts and understand what it is you're doing. Understand why putting the code here does this, and putting it there does that. Don't make a change unless you can explain why the old way was wrong, why the new way is right, and precisely what you expect to happen as a result. This is not magic, and it's mostly not a secret. Your game does exactly what you tell it to do, exactly as you're telling it to do it. You don't know enough to tell the game exactly what you want it to do. That is the first problem you need to fix. Then you can fix the rest of your problems in a fraction of the time, and a fraction of the number of monkeys.
 

jjg27

Member
Thanks for trying to help, but without any accompanying advice that did unfortunately come across as something like "get good". I have tried reading the manual / scouring forums and only asked here because I was still stuck.

After some monkeying around I did actually fix most of the problems. However the big problem left is that objects in the new room are not visible during the transition.
I've tried to implement this logic into the code, but obviously it's not working.
- First step: go to new room
- Second step: draw new room to a surface
- Just after that, deactivate instances
- After the transition is over, activate all instances again.

The updated code is below and nicely simplified, but I still have one question. There's one bit of code left from the original tutorial video I followed that I understand. The following code in the Draw GUI event:
Code:
surface_set_target(surf_end);
draw_surface(surf_end,0,0);
surface_reset_target();
I am not sure why this needs to be here. I have already created my screenshot of the new room on surf_end in the Step event, so why do I need to keep re-drawing the same thing each time in Draw GUI? If I remove that chunk of code, the room transition consists of two copies of the old room.

Code:
/// Initalise Variables and Create Surfaces
//Set room transition time length
tr_step = 0;
tr_time = 60;

//Create Surfaces
surf_start = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));
surf_end = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));

//Create Surface of Previous Room
surface_set_target(surf_start);
draw_surface(application_surface,0,0);
surface_reset_target();

//Move Player
switch(global.transition_direction){
case transition.right:
    objMan.x = 8;
    break;
case transition.up:
    objMan.y = 175;
    break;
case transition.left:
    objMan.x = 247;
    break;
case transition.down:
    objMan.y = 8;
    break;
}

Code:
/// Delete Surfaces
surface_free(surf_start);
surface_free(surf_end);

instance_activate_layer("Instances");

Code:
//Increment Step Counter
tr_step++;

//Delete Instance when transition is over
if (tr_step > tr_time) instance_destroy();

//If either surface gets broken, move to next room and destroy
if (!surface_exists(surf_start) or !surface_exists(surf_end))
    {
    if (room != global.next_room) room_goto(global.next_room);
    instance_destroy();
    }

//Go to next room
if (room != global.next_room)
    {
    room_goto(global.next_room);
    }

if tr_step = 2
    {
    surface_set_target(surf_end);
    draw_surface(application_surface,0,0);
    surface_reset_target();
    instance_deactivate_layer("Instances");
    }

Code:
/// Transition Animation

var width, height, x1, y1, x2, y2;
width = display_get_gui_width();
height = display_get_gui_height();

surface_set_target(surf_end);
draw_surface(surf_end,0,0);
surface_reset_target();

//Update coordinates after each frame
switch (global.transition_direction)
    {
    case transition.right:
        x1 = (1 - tr_step/tr_time) * width; y1 = 0;
        x2 = (-tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.up:
        x1 = 0; y1 = (-1 + tr_step/tr_time) * height;
        x2 = 0; y2 = (tr_step/tr_time) * height;
        break;
    case transition.left:
        x1 = (-1 + tr_step/tr_time) * width; y1 = 0;
        x2 = (tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.down:
        x1 = 0; y1 = (1 - tr_step/tr_time) * height;
        x2 = 0; y2 = (-tr_step/tr_time) * height;
        break;
    }

//Draw the transition
draw_surface_stretched(surf_end,x1,y1,width,height);
draw_surface_stretched(surf_start,x2,y2,width,height);
 

Nidoking

Member
//If either surface gets broken, move to next room and destroy
if (!surface_exists(surf_start) or !surface_exists(surf_end))
{
if (room != global.next_room) room_goto(global.next_room);
instance_destroy();
}
//Go to next room
if (room != global.next_room)
{
room_goto(global.next_room);
}
This is what tells me you don't understand what you're doing very well. Why do you need both of these checks?

I'd check when the instances in the new room are drawing for the first time relative to when you capture the application surface. Maybe they're not visible yet for some reason.
 

jjg27

Member
This is what tells me you don't understand what you're doing very well. Why do you need both of these checks?

I'd check when the instances in the new room are drawing for the first time relative to when you capture the application surface. Maybe they're not visible yet for some reason.
Thanks again :)

- The second check is just to get to the next room. As for the first check, I guess this is to skip the animation if something goes wrong with the surfaces - I've read surfaces are volatile so this maybe just a safety check?

- I agree, and I think this is the crux of the issue: I need to find the moment that the instances in the new room are drawn, capture that moment on a surface and use it for the transition, then deactivate all instances, then reactivate the instances at the end. The problem is that I think I've written my code that way! I've updated the end of Step and start of Draw GUI like so.

Step
Code:
//Go to next room
if (room != global.next_room)
    {
    room_goto(global.next_room);
    }

if tr_step = 2
    {
    instance_deactivate_layer("Instances");
    }
Draw GUI

Code:
if tr_step = 1
    {
surface_set_target(surf_end);
draw_surface(application_surface,0,0);
surface_reset_target();
    }
    
if tr_step > 1
    {
surface_set_target(surf_end);
draw_surface(surf_end,0,0);
surface_reset_target();
    }
So: on the first step, the new room is entered in Step and then a screenshot is taken of the application surface in Draw GUI. Then on the second step, the instances are deactivated in Step and we keep using the same screenshot for all steps>1 in Draw GUI.

Same issue: no objects appearing during transition. I have no idea how to pinpoint the precise moment that the instances in the new room are drawn after the new room is entered.
 

Nidoking

Member
You may be running into issues with when things happen in relation to the room_goto call. At the end of that event, the game will transition rooms, and I believe all remaining Step events, all Draw events, etc. for that step are canceled. You just get Room End, Destroy/Clean Up for non-persistent instances, and then Create and Room Start for the instances in the new room or carried over from the old one. So if you're incrementing tr_step before the room_goto, then it increments a second time in the first step of the new room and captures the application surface before the instances are drawn. I usually set the timer to 0 in a Room Start event. Then you know one step will happen before it reaches 1.
 

jjg27

Member
You may be running into issues with when things happen in relation to the room_goto call. At the end of that event, the game will transition rooms, and I believe all remaining Step events, all Draw events, etc. for that step are canceled. You just get Room End, Destroy/Clean Up for non-persistent instances, and then Create and Room Start for the instances in the new room or carried over from the old one. So if you're incrementing tr_step before the room_goto, then it increments a second time in the first step of the new room and captures the application surface before the instances are drawn. I usually set the timer to 0 in a Room Start event. Then you know one step will happen before it reaches 1.
Ah! It's done!!
I followed your advice with a room start timer setting an alarm, and I made a variable called temp that I made a check for when I want the next thing to happen, then increment temp, then check for the new value of temp at the next thing I want to happen, and so on. Maybe a bit of a hack, but it works! I also had to store the vertical speed of my player character and call that again when reactivating all instances at the end.

Thank you so much for sticking with me, Nidoking, I really appreciate it!
 
Last edited:

jjg27

Member
Gonna post my finished working code here in case someone happens to want to use it.

Script Asset
Code:
//Room Transition Variables
enum transition {right, up, left, down};
global.transition_direction = transition.right;
global.next_room = rmRight;

objTransition Object (placed in every room, looks for collision with the player at the end of the screen. line coordinates and room names will need to be updated. Set to NOT Visible and NOT Persistent)
Code:
/// Player Collision
if !instance_exists(objTransitionAnimation)
{
if collision_line(256,0,256,191,objMan,false,false)
    {
    global.transition_direction = transition.right;
    global.next_room = rmRight;
    instance_create_layer(0,0,"GameObjects",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(0,-1,255,-1,objMan,false,false)
    {
    global.transition_direction = transition.up;
    global.next_room = rmUp;
    instance_create_layer(0,0,"GameObjects",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(-1,0,-1,191,objMan,false,false)
    {
    global.transition_direction = transition.left;
    global.next_room = rmHub;
    instance_create_layer(0,0,"GameObjects",objTransitionAnimation);
    instance_destroy();
    }
else if collision_line(0,192,255,192,objMan,false,false)
    {
    global.transition_direction = transition.down;
    global.next_room = rmDown;
    instance_create_layer(0,0,"GameObjects",objTransitionAnimation);
    instance_destroy();
    }
}

objTransitionAnimation (handles the transition effect, created by objTransition and then deleted afterwards. Set to Visible and Persistent)
Code:
/// Initalise Variables and Create Surfaces
//Set room transition time length
tr_step = 0;
tr_time = 60;
temp = 0;
objMan_preserve_momentum = 0;

//Create Surfaces
surf_start = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));
surf_end = surface_create(surface_get_width(application_surface),surface_get_height(application_surface));

//Create Surface of Previous Room
surface_set_target(surf_start);
draw_surface(application_surface,0,0);
surface_reset_target();

//Go to next room
if (room != global.next_room)
    {
    room_goto(global.next_room);
    }

//Save vertical speed of player object
objMan_preserve_momentum = objMan.vsp;
Code:
/// Delete Surfaces
surface_free(surf_start);
surface_free(surf_end);

//Reactivate objects and restore player vertical speed
instance_activate_layer("Instances");
objMan.vsp = objMan_preserve_momentum;
Code:
//Increment Step Counter
tr_step++;

//Delete Instance when transition is over
if (tr_step > tr_time) instance_destroy();

//If either surface gets broken, move to next room and destroy
if (!surface_exists(surf_start) or !surface_exists(surf_end))
    {
    if (room != global.next_room) room_goto(global.next_room);
    instance_destroy();
    }
   
//Move Player, then deactivate
if tr_step = 1
    {
    switch(global.transition_direction){
    case transition.right:
        objMan.x = 8;
        break;
    case transition.up:
        objMan.y = 176;
        break;
    case transition.left:
        objMan.x = 248;
        break;
    case transition.down:
        objMan.y = 8;
        break;
    }
    instance_deactivate_object(objMan);
    }
Code:
/// Make sure room has loaded before deactivating instances
///(follow variable temp)
temp = 1;
Code:
/// Transition Animation

var width, height, x1, y1, x2, y2;
width = display_get_gui_width();
height = display_get_gui_height();

//Draw surf_end before deactivating instances
if temp = 1
    {
    surface_set_target(surf_end);
    draw_surface(application_surface,0,0);
    surface_reset_target();
    instance_deactivate_layer("Instances");
    temp = 2;
    }

//Update coordinates after each frame
if temp = 2 {
switch (global.transition_direction)
    {
    case transition.right:
        x1 = (1 - tr_step/tr_time) * width; y1 = 0;
        x2 = (-tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.up:
        x1 = 0; y1 = (-1 + tr_step/tr_time) * height;
        x2 = 0; y2 = (tr_step/tr_time) * height;
        break;
    case transition.left:
        x1 = (-1 + tr_step/tr_time) * width; y1 = 0;
        x2 = (tr_step/tr_time) * width; y2 = 0;
        break;
    case transition.down:
        x1 = 0; y1 = (1 - tr_step/tr_time) * height;
        x2 = 0; y2 = (-tr_step/tr_time) * height;
        break;
    }

//Draw the transition
draw_surface_stretched(surf_end,x1,y1,width,height);
draw_surface_stretched(surf_start,x2,y2,width,height);
}
Code:
/// Make sure room has loaded before deactivating instances
alarm[0] = 1;
 
Top