custom room transitions, and how to avoid sound stuttering when changing rooms

Started by EnterTheStory (aka tolworthy), Mon 29/03/2010 12:09:44

Previous topic - Next topic

EnterTheStory (aka tolworthy)

I don't know if this topic belongs here, but I often search for help on topics like this, so maybe this thread will be helpful to others.

There have been several threads over the years about sound breaking up when changing large rooms on slow computers, and there was no general purpose solution (other than "don't use such large rooms"). So I wrote a workaround. It also works as a way to have almost unlimited custom room transitions (discussed at the very end).

I haven't made this into a module because it's really just a set of suggestions, and would benefit from being developed by a better coder than me (any expert module writers reading? I'm looking at you, SSH :)

The problem:
When loading large and complex rooms (e.g. more than, say, 2 Mb) on an old computer or cheap laptop, sometimes the room is not loaded before the music buffer or cache runs out, so the music struggles and users complain. I'm getting this more often with AGS3 than with 2.72, or perhaps I just never noticed before. It's my own fault for designing crazy rooms and having a cheap computer :)

The solution:
AGS compresses room background images, so a very large room will load very quickly if it uses just a few plain colors. Example: a 3000x480 room using a photographic background will result in a room size of over 2Mb, but the same photo when reduced to 4 colors (and still saved at 24 bit) is not much more than 400k. So load rooms with simplified graphics then add the full background in stages. Result: AGS can handle everything in small stages and the music runs smoothly, even on your old laptop. Hide the change behind a GUI with a copy of the previous background.

notes
You can go as wide as 10,000x480 before the simplified background grows over 1Mb (when the slowest computers start to stutter). All my standard rooms are 480 pixels high, and the code reflects that, but obviously you can adapt the code for any size.

All this might sound like extra work, and it is, but everything is automated except making the low color version of your original background and one stop to manually importing all sliced images as sprites, then taking a note of the resulting sprite numbers (maybe these steps can be automated too - can sprites be imported through code?). That takes about a minute per room and only need to be done once per room.

Preparation:
When making a background, make an extra copy with just four colors. Use this 4 color version in the editor, so you can see where to put floors, hotspots, etc. Then split the original copy into 640 pixel wide segments. AGS handles 640x480 images with ease on even the slowest machine. I wrote the following code for splitting images, but I'm sure that others can write something better.
Code: ags

DynamicSprite* spriteFromBmp;
function makeImageSlice(String imagename, int part)// part = 0,1,2,etc.
{ //------------------------ get bmp --------------------------------------
  String nameWithBmp =imagename;// for convenience I always give my source image the same name: "tosplit.bmp"
  if(nameWithBmp.IndexOf(".bmp")<0)nameWithBmp =nameWithBmp.Append(".bmp");// in case they forgot to add ".bmp"
  spriteFromBmp = DynamicSprite.CreateFromFile(nameWithBmp);// read it in
  if (spriteFromBmp == null){ Display("Sorry can't find '%s'",nameWithBmp);return 0;}
  if(spriteFromBmp.Height !=480){ Display("'%s' is only %d high",nameWithBmp, spriteFromBmp.Height);return 1;} 
  //------------------------ crop it --------------------------------------
  int x =part * 640;             // e.g. start at x=0,640,1280,etc.
  if(spriteFromBmp.Width <x) return 2; //already got to the end of the image?
  int width =640;
  if(spriteFromBmp.Width <(x +width)) width =spriteFromBmp.Width -x; //need to grab less than 640 pixels?
  spriteFromBmp.Crop(x,0,width,480);
  //------------------------ rename and save ------------------------------
  int nameLength =imagename.IndexOf(".bmp"); // make sure you have the name without the extension
  if(nameLength >0)imagename =imagename.Substring(0, nameLength);//e.g. 0123.bmp has length 4
  String suffix =String.Format("_%d",part); // e.g. "_0", "_1", etc.
  imagename = imagename.Append(suffix);
  nameWithBmp =imagename.Append(".bmp");// e.g. "tosplit_0.bmp" etc.
  spriteFromBmp.SaveToFile(nameWithBmp); return 3; // success
}
function trySplitting(String imagename) // call this from any convenient button or hotkey
{ int part =0; int result =3;
  while((part <=8)&&(result ==3))
  { if(result ==3)result =makeImageSlice(imagename,part);part++; }
  part--;// compensate for final not-followed loop
  Display("Split '%s' into %d parts",imagename, part);// you should now have a series of slices in your compiled folder
  spriteFromBmp.Delete();// release the memory
}

Make a sprite folder called 'wide rooms' and a sub folder with the room number, import those sliced images as sprites, and make a note of their numbers.

When leaving a room:
You'll need to create your own 'player change room' function, e.g. 'changeRoom2(where,x,y)' and always use it instead of player.changeRoom.  Having your own change room code is probably a good idea anyway, as it lets you check for things like queued room changes (which could crash 2.72, don't know about 3.x), or for rooms that are not accessible early in the game, or costumes that need to change between rooms, etc.  Just before you change to a large room, call the following code.
Code: ags

DynamicSprite* roomFreeze; export roomFreeze; // a copy of the background to hide the change
function hideRoomChange()
{ roomFreeze = DynamicSprite.CreateFromScreenShot();
  gRoomChanger.BackgroundGraphic =roomFreeze.Graphic; // you'll need to create this GUI
  gRoomChanger.Transparency =0;// one of the many good things about AGS3 is transparent GUIs
  gRoomChanger.Visible =true;
}

To avoid confusion, I use it on every room just in case. Set the default room change to instant, as nobody will see it anyway, hidden behind the GUI.

when entering a new room
I don't think on_event handles "after fade in," so you'll need to refer to this code on every room:
Code: ags

function drawBackground(int slot1,int slot2,int slot3,int slot4,int slot5,int slot6,int slot7,int slot8)
{ DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
  if(slot1 >0){Wait(1);surface.DrawImage(0,0,slot1);Wait(1);} //'wait' to avoid stuttering on the slowest machines
  if(slot2 >0){surface.DrawImage(640,0,slot2);Wait(1);}
  if(slot3 >0){surface.DrawImage(1280,0,slot3);Wait(1);}
  if(slot4 >0){surface.DrawImage(1920,0,slot4);Wait(1);}
// etc., etc.
  surface.Release();//finished drawing, tells AGS it can use the changed image
}
function checkBackground() //safe to use for EVERY room, as only wide rooms trigger any response
{ if(player.Room ==3)drawBackground(56, 57, 58, 59, 60, 0, 0, 0);// the sprite numbers you noted earlier: your will be different of course
}
function fadeInRoom()
{ checkBackground();
  if(!gRoomChanger.Visible)return; // you didn't use the new code? shame on you!
  int trans =0;  while(trans <100) // I like my rooms to fade in
  { trans = trans +4;if(trans >100)trans =100;
    gRoomChanger.Transparency =trans; Wait(1);
  }gRoomChanger.Visible =false;
  if(roomFreeze !=null)roomFreeze.Delete();// releases the memory it was keeping (e.g. the big temporary image)
}


How to add your own custom transitions
Another advantage of this code, apart from making transitions easy, is you can add your own custom transitions. When you get to the new room just replace 'gRoomChanger' with a character having a full screen animated view of your choice.

By messing with surfaces and dynamic sprites you can add special features and transparency to this image: automatically create frames to change rooms by turning a page, breaking a window, exploding a nuclear bomb, the wind blowing sand over the screen, or whatever. Remember to use y and z values to ensure your transition character is in front of everything else.

Animating the image itself.
If you want to be really creative, don't use a full screen image at all, split it up into many different strips or squares, using 'CreateFromDrawingSurface.' Or make them irregular shapes by pasting on the transparent color. Then your room transitions can involve the previous room blowing up, dripping Matrix-like, roll up like a roller blind, or whatever. That last idea would be relatively simple as it only needs a few horizontal strips. Or use some more subtle transition that fits your game.

pseudo-3D transitions
If you break the screen into enough parts, and resize, flip, and rotate in the right way, you could have a crude 3D effect. Tint the segments to give a shadow effect. it wouldn't be perfect, but most room transitions take less than a second so the suer wouldn't have time to notice the rough edges.

interactive transitions
Why not make the screen segments react to the position of the mouse? The user push or slice or hack into the new room: parts of the screen could warp, explode or fall away depending on the mouse position. Anything is possible.

Just an idea. I've only tested the non-stutter code, the rest is just thinking aloud.

Pumaman

An interesting idea for a workaround, though of course you shouldn't have to do this.

If you're still getting sound jittering problems with AGS 3, I can look into it further.

EnterTheStory (aka tolworthy)

Quote from: Pumaman on Fri 02/04/2010 16:23:19If you're still getting sound jittering problems with AGS 3
Not with my new room changing code :)
I'm no expert, but I'd imagine it's unavoidable, when loading a 2 MB room plus playing music plus cross-fading plus whatever else might be going on, all on a slow computer. I don't suppose many other people have 2 MB rooms.

It's my own fault. My choice to have giant rooms (that stutter if not loaded in pieces), giant floors (where path finding doesn't work unless it's basically a rectangle), a lot of rep_ex code and workarounds (so that tiny, innocent changes in 3.x have dramatic effects), using RunAGSGame along with changing languages (Winsetup doesn't like that at all!), and so on. I think I just need to accept that I'm pushing AGS in directions it wasn't designed for, and that nobody else particularly wants :)

Pumaman

Well, as resolutions increase and 800x600 and 1024x768 games become more common, 2 MB room files are becoming a more standard size, and AGS should be able to load them without stuttering. So I would still consider it a bug in AGS if the music is jittering while changing rooms.

xenogia

As stated in a previous thread CJ do you want me to upload my game to show you the stuttering at all?

SMF spam blocked by CleanTalk