GameMaker Seamlessly Looping Rooms by using Views and Surfaces?

C

CoconutBun

Guest
I've been trying to get a seamlessly looping room working in game maker studio 2. If you ever played Yume Nikki or used RPG Maker its kinda like the room looping in that.

I'm a little new to using multiple views and haven't used surfaces before so I'm running into some trouble.

My current plan is to have 2 views
one that shows the normal game view and other that isn't shown to the player but would show the whole room
Then I turn that second view into a surface and draw that outside of the room on each side and corner
Then I just teleport the player to the other side of the room whenever they reach the edge
This should make the room look as though it never ends and keeps repeating or as though you have walked around a sphere.

My current problem is that I can't get the view[1] to be drawn as a surface, I'm not really sure what I'm doing wrong so I'm just gonna send all the code I have and maybe someone could have a look at it

CREATE EVENT
Code:
//following and movement
follow = -1;
xTo = x;
yTo = y;
moveSpeed = 1;

looping = false;

if (instance_exists(o_Player)) {
    follow = o_Player;
}

view_width = 480;
view_height = 270;

window_scale = 2;

window_set_size(view_width * window_scale, view_height * window_scale);
alarm[0] = 1;

surface_y = surface_create(room_width, room_height);
END STEP EVENT
Code:
/// @description
#macro view view_camera[0]
#macro roomView view_camera[1]

camera_set_view_size(view, view_width, view_height);

if (instance_exists(o_RoomSettings)) {
    looping = o_RoomSettings.looping
} else {
    looping = false;
}

if (instance_exists(follow)) {
    xTo = follow.x;
    yTo = follow.y;
    
    if (looping = false) {
        xTo = min(xTo, room_width - (view_width / 2))
        xTo = max(xTo, view_width / 2)
        
        yTo = min(yTo, room_height - (view_height / 2))
        yTo = max(yTo, view_height / 2)
    }
    
    x += (xTo - x) / moveSpeed
    y += (yTo - y) / moveSpeed
    
    
    camera_set_view_pos(view, x - view_width / 2, y - view_height / 2);
    
    
}
if (looping = true) {
    //view that shows the whole room
    camera_set_view_size(roomView, room_width, room_height);
    camera_set_view_pos(roomView, 0, 0);
}
POST DRAW
Code:
//Draw the things

if (instance_exists(o_RoomSettings)) {
    if (o_RoomSettings.looping = true) {
        //create surface of room
        if (!surface_exists(surface_y)) {
            surface_y = surface_create(camera_get_view_width(roomView), camera_get_view_height(roomView));
            
        }
        surface_resize(surface_y, camera_get_view_width(roomView), camera_get_view_height(roomView));
        
        view_surface_id[1] = surface_y;
        
        draw_surface(surface_y, room_width, room_height);
        
    }
}
ALARM[0]
Code:
window_center();
ROOM START
Code:
view_enabled = true;
view_visible[0] = true;
keep in mind that this code is only drawing a surface under the room. Im gonna add the rest after/if I get that one working
 
M

MadTinkerer

Guest
I was trying to do the same thing but with dynamically resizing views, before I realized how it interfered with zooming and prevented you from being able to properly rotate the view at all. So then I was trying to figure out how to do it with multiple surfaces, before I realized that I should search the forums first in case someone thought of a solution.

As I understand it, each view has a surface(see view_surface_id ). If you make view[1] the size of the room, it should draw to it normally during the draw event. Then you could surface_copy_part of view[1]'s surface to view[0]'s surface on the post-draw event. When view[0] passes the right side, surface_copy_part of the left side of view[1]'s surface and vice versa and so on.

EDIT: By the way, part of your problem is that "view_surface_id[1] = surface_y" should be "view_set_surface_id[1] = surface_y". But the other part of the problem is that you actually don't need to create another surface for the view when the surface already has a view. surface_copy_part(view_surface_id[0], x, y, view_surface_id[1], xs, ys, ws, hs) should work without needing to create another surface.

You might want to create another surface just for copying and stitching the opposite sides of the level, and then copying to view_surface_id[0], but copying directly from view_surface_id[1] to view_surface_id[0] should work as well.
 
Last edited by a moderator:

TheouAegis

Member
I was trying to do the same thing but with dynamically resizing views, before I realized how it interfered with zooming and prevented you from being able to properly rotate the view at all. So then I was trying to figure out how to do it with multiple surfaces, before I realized that I should search the forums first in case someone thought of a solution.

As I understand it, each view has a surface(see view_surface_id ). If you make view[1] the size of the room, it should draw to it normally during the draw event. Then you could surface_copy_part of view[1]'s surface to view[0]'s surface on the post-draw event. When view[0] passes the right side, surface_copy_part of the left side of view[1]'s surface and vice versa and so on.

EDIT: By the way, part of your problem is that "view_surface_id[1] = surface_y" should be "view_set_surface_id[1] = surface_y". But the other part of the problem is that you actually don't need to create another surface for the view when the surface already has a view. surface_copy_part(view_surface_id[0], x, y, view_surface_id[1], xs, ys, ws, hs) should work without needing to create another surface.

You might want to create another surface just for copying and stitching the opposite sides of the level, and then copying to view_surface_id[0], but copying directly from view_surface_id[1] to view_surface_id[0] should work as well.
Views draw to the application surface by default. They only draw to a different surface if you actually create a different surface and specify that target surface. By default, no matter how many views you have in your game, there's only one surface created automatically.


Also, @OP you need to set the view_surface_id before the draw event, otherwise the view won't know where to actually draw.
 
M

Misty

Guest
In 3d this is rather trivial but I am not sure about a 2d solution.
 

TheouAegis

Member
Bear in mind also that wrapping rooms is not just a visual problem. As the player nears the edges of the room, he needs to be able to see not just the other side of the room, but all the instances on the other side of the room as well. Simple enough once the viewing mechanics are set up, sure. But not only does the player need to see what's on the other side of the room, so too do any NPCs or other players (if it's a multiplayer game). What this means is if objectA is on the far left of the room and objectB is on the far right of the room, normal collision checks would fail because the distance between the two objects is just under room_width apart rather than mean(objectA.sprite_width,objectB.sprite_width). So all collisions also need to be handled with wrapping.

Code:
var dx = (bbox_left+bbox_right+room_width/2) mod room_width,
dy = (bbox_top + bbox_bottom + room_height/2) mod room_height,
sw = sprite_width,
sh = sprite_height;
with all {
    if abs(dx-(bbox_left+bbox_right+room_width/2) mod room_width) < mean(sw+sprite_width)
    && abs(dy-(bbox_top+bbox_bottom+room_height/2) mod room_height) < mean(sh+sprite_height) {
        with other {
            //run your collision code
        }
    }
}
 
M

MadTinkerer

Guest
Views draw to the application surface by default. They only draw to a different surface if you actually create a different surface and specify that target surface. By default, no matter how many views you have in your game, there's only one surface created automatically.
EDIT: Nevermind, I was thinking of a project where the views had been assigned surfaces and I forgot that step. So I think you're right about that.

Bear in mind also that wrapping rooms is not just a visual problem.
Yes, but visual wrapping is a start. After that, how game objects react depends on whether they're looking at the raw position of other objects, using paths and mp_grids, whether they care about tiles, and so on. But visual wrapping is a general problem that I was hoping had a solution by now.
 
Last edited by a moderator:
M

Misty

Guest
Try it in 3d, once you master that, extrapolate it in ways that will apply to 2d.
 
M

MadTinkerer

Guest
Try it in 3d, once you master that, extrapolate it in ways that will apply to 2d.
Do you have an example? Perhaps a link to a tutorial or article that explains how to do it GML?
 
M

Misty

Guest
Do you have an example? Perhaps a link to a tutorial or article that explains how to do it GML?
Basically you draw a 2nd cam and save whatever the cam sees to a surface. Then you put that surface in the Draw GUI or something.

In GMS2 it uses cams for 2d, so it may be possible in it but not sure.
 
M

MadTinkerer

Guest
That's more or less what CononutBun is trying to do.

EDIT: Speaking of which, I think I found the problem. From the surface_resize page:

Note that this will neither crop nor stretch the contents of the surface, but rather it destroys the current surface and recreates it with the same handle (surface_id) with the new dimensions, meaning that it will need to be cleared and drawn to again (unless it is the application_surface in which case GameMaker Studio 2 will do this automatically).
Resizing the surface clears the surface. So you're probably drawing a completely transparent surface every step.
 
Last edited by a moderator:

TheouAegis

Member
That's incorrect. See the GMS2 documentation on view_surface_id. Every view has it's own surface that is then drawn to the application surface if the view is visible. You can manipulate the default view surfaces in all the same ways as any other surface, though doing so isn't usually necessary.
EDIT: I just saw your edit. lol

Funny how you tell me to see the documentation for GMS2 to prove me wrong, but then when I do read the documentation for both view_surface_id and view_set_surface_id(), I'm just validated in being correct. But being one to accept that maybe my copy of the manual is wrong, as soon as I got home from work I opened up GMS2 and tested it out for myself, only to again be validated.

Views are rendered to the application surface unless you yourself specifically give them a NEW surface to render to.You can have multiple views all rendering to the same surface as well, if you really wanted to, but you must first create a new surface using surface_create(). I don't know how to restrict where onto the surface the view is rendered, if that's even possible, though; it seems it always renders to the top-left. When the view is rendered to the surface, the width and height of the surface are used rather than view_wport and view_hport.

Code:
show_debug_message(view_surface_id[0]);     //will return -1
surf =surface_create(camera_get_view_width(view_camera[0]), camera_get_view_height(view_camera[0]));
view_surface_id[0]=surf;
show_debug_message(view_surface_id[0];    //will return 1 or higher
Code:
///gui event
draw_surface(surf,0,0);
 
Last edited:

TheouAegis

Member
I've been trying to get a seamlessly looping room working in game maker studio 2. If you ever played Yume Nikki or used RPG Maker its kinda like the room looping in that.

I'm a little new to using multiple views and haven't used surfaces before so I'm running into some trouble.

My current plan is to have 2 views
one that shows the normal game view and other that isn't shown to the player but would show the whole room
Then I turn that second view into a surface and draw that outside of the room on each side and corner
Then I just teleport the player to the other side of the room whenever they reach the edge
This should make the room look as though it never ends and keeps repeating or as though you have walked around a sphere.

My current problem is that I can't get the view[1] to be drawn as a surface, I'm not really sure what I'm doing wrong so I'm just gonna send all the code I have and maybe someone could have a look at it

CREATE EVENT
Code:
//following and movement
follow = -1;
xTo = x;
yTo = y;
moveSpeed = 1;

looping = false;

if (instance_exists(o_Player)) {
    follow = o_Player;
}

view_width = 480;
view_height = 270;

window_scale = 2;

window_set_size(view_width * window_scale, view_height * window_scale);
alarm[0] = 1;

surface_y = surface_create(room_width, room_height);
END STEP EVENT
Code:
/// @description
#macro view view_camera[0]
#macro roomView view_camera[1]

camera_set_view_size(view, view_width, view_height);

if (instance_exists(o_RoomSettings)) {
    looping = o_RoomSettings.looping
} else {
    looping = false;
}

if (instance_exists(follow)) {
    xTo = follow.x;
    yTo = follow.y;
   
    if (looping = false) {
        xTo = min(xTo, room_width - (view_width / 2))
        xTo = max(xTo, view_width / 2)
       
        yTo = min(yTo, room_height - (view_height / 2))
        yTo = max(yTo, view_height / 2)
    }
   
    x += (xTo - x) / moveSpeed
    y += (yTo - y) / moveSpeed
   
   
    camera_set_view_pos(view, x - view_width / 2, y - view_height / 2);
   
   
}
if (looping = true) {
    //view that shows the whole room
    camera_set_view_size(roomView, room_width, room_height);
    camera_set_view_pos(roomView, 0, 0);
}
POST DRAW
Code:
//Draw the things

if (instance_exists(o_RoomSettings)) {
    if (o_RoomSettings.looping = true) {
        //create surface of room
        if (!surface_exists(surface_y)) {
            surface_y = surface_create(camera_get_view_width(roomView), camera_get_view_height(roomView));
           
        }
        surface_resize(surface_y, camera_get_view_width(roomView), camera_get_view_height(roomView));
       
        view_surface_id[1] = surface_y;
       
        draw_surface(surface_y, room_width, room_height);
       
    }
}
ALARM[0]
Code:
window_center();
ROOM START
Code:
view_enabled = true;
view_visible[0] = true;
keep in mind that this code is only drawing a surface under the room. Im gonna add the rest after/if I get that one working

And since we haven't heard back from the OP, i'll reiterate: You must set the view_surface_id PRIOR to the Drawing. Using PREDRAW should be okay, I would expect. Your code used postdraw, which runs AFTER the view is rendered.

Also, I'm pretty sure view_visible[1] must be set to true as well.
 
Top