GMS 2 Seamlessly Looping Rooms by using Views and Surfaces?

Discussion in 'Programming' started by CoconutBun, May 23, 2019.

  1. CoconutBun

    CoconutBun Member

    Joined:
    May 22, 2019
    Posts:
    1
    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
     
    MadTinkerer likes this.
  2. MadTinkerer

    MadTinkerer Member

    Joined:
    Nov 21, 2016
    Posts:
    38
    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: Jun 10, 2019
  3. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,537
    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.
     
  4. Misty

    Misty Member

    Joined:
    Jun 22, 2016
    Posts:
    1,005
    In 3d this is rather trivial but I am not sure about a 2d solution.
     
  5. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,537
    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
            }
        }
    }
     
  6. MadTinkerer

    MadTinkerer Member

    Joined:
    Nov 21, 2016
    Posts:
    38
    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.

    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: Jun 14, 2019 at 11:13 PM
  7. Misty

    Misty Member

    Joined:
    Jun 22, 2016
    Posts:
    1,005
    Try it in 3d, once you master that, extrapolate it in ways that will apply to 2d.
     
  8. MadTinkerer

    MadTinkerer Member

    Joined:
    Nov 21, 2016
    Posts:
    38
    Do you have an example? Perhaps a link to a tutorial or article that explains how to do it GML?
     
  9. Misty

    Misty Member

    Joined:
    Jun 22, 2016
    Posts:
    1,005
    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.
     
  10. MadTinkerer

    MadTinkerer Member

    Joined:
    Nov 21, 2016
    Posts:
    38
    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:

    Resizing the surface clears the surface. So you're probably drawing a completely transparent surface every step.
     
    Last edited: Jun 14, 2019 at 11:26 PM
  11. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,537
    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: Jun 15, 2019 at 6:41 AM
  12. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,537

    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.
     
  13. MadTinkerer

    MadTinkerer Member

    Joined:
    Nov 21, 2016
    Posts:
    38
    Yeah, sorry for the confusion. :confused:
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice