SLIP INTO RUBY - UNDER THE HOOD PART 21: MAKING A SCENE AGAIN

In which we start our breakdown of the scene classes.

  • Trihan
  • 06/02/2021 12:05 AM
  • 5803 views
Hello, sports fans! It totally hasn't been nearly a year since I last posted one of these. You're clearly imagining things. Yep. I've obviously been doing these one a week every week since July, it's not my fault you haven't noticed...

Seriously, I'm so sorry it's been so long. A combination of life, the pandemic and just having the worst work ethic on the planet have resulted in me falling behind on so many things, but I'm starting to get caught up with things now, including getting my tutorials up to date! You probably don't remember, but last time we finally finished up with the Window classes, meaning we now get going with scenes! So let's saddle up and prepare for another rousing edition of



Today: Making a Scene

Scene_Base
Every screen you see in an RPG Maker game is a scene, and this is the mother of them all. The base class from which every scene inherits. This one is basically what drives the entire engine. It's a short one, but no less powerful for that.

def main
    start
    post_start
    update until scene_changing?
    pre_terminate
    terminate
  end


You probably definitely don't remember this, but way back in the heady days of our youth, back when we first looked at SceneManager, there was the line "@scene.main while @scene". And now, we finally come full circle and see what that line was calling.

First we call the start method, which we'll see in a second. Then we call the post_start method, which we'll also see in a second. After that, we call the update method until scene_changing? returns true. Then we call pre_terminate, and finally we call terminate.

So the main method is nothing more than calling other methods. As you'll see, different scenes will overwrite these methods with their own implementations as needed, and you can do the own with your own scenes if you have certain things that need to happen at a certain point or setup that needs to be done in one of these stages.

def start
    create_main_viewport
  end


The base start method just calls the create_main_viewport method, which again we'll see a bit further down. Basically, it creates the viewport most of the game will be displayed in, without which the graphics would have no "container" to be drawn in.

def post_start
    perform_transition
    Input.update
  end


The post_start method calls perform_transition (you guessed it, we'll see that below) and then Input.update. This is a built-in method that you can't see or edit in the script editor, but basically it updates input data, which is what detects you pressing keys.

def scene_changing?
    SceneManager.scene != self
  end


This is the method that determines in main whether to call update, and returns the result of checking whether SceneManager.scene is not equal to self (self being a reference to the scene object running the code). If you'll recall from way back in the SceneManager breakdown, the value of SceneManager.scene is changed when its "call", "goto", "return" or "exit" methods are called, at which point if the scene is now different from the one that's been running, it will stop calling update because this method will now be returning false.

def update
    update_basic
  end


The update method which is called every frame if the scene isn't changing does so much! It...calls update_basic. Which we'll look at now.

def update_basic
    Graphics.update
    Input.update
    update_all_windows
  end


First we call Graphics.update, another built-in and inaccessible method which refreshes the game screen and advances time by 1 frame. if we didn't do this, the game would never progress. Then we call Input.update as we did in the start method, and finally we call update_all_windows. Have a guess what that does. Go on, just guess! We'll see exactly how it does what it does in just a bit.

def pre_terminate
  end


The base pre_terminate method doesn't do anything by itself. It's only declared here so that child classes can replace it.

def terminate
    Graphics.freeze
    dispose_all_windows
    dispose_main_viewport
  end


In the terminate method, we call Graphics.freeze, a built-in method which freezes the current screen to prepare for a transition (it basically stops any screen rewrites), then we call dispose_all_windows and dispose_main_viewport, the function of which should be obvious but we'll reach them soon for a more in-depth look.

def perform_transition
    Graphics.transition(transition_speed)
  end


The perform_transition method (called by post_start) calls Graphics.transition, another built-in method which carries out a screen transition from a frozen state. We're passing in transition_speed as an argument, which is just a method we'll look at in a sec. The Graphics.transition method can take additional parameters, but they're not used here. We'll see them in Scene_Map, though.

def transition_speed
    return 10
  end


transition_speed, the method we called above to pass to Graphics.transition, just returns 10. This means standard scene transitions will take 10 frames.

def create_main_viewport
    @viewport = Viewport.new
    @viewport.z = 200
  end


In create_main_viewport, we see our first instance variable, @viewport. It's set to a new instance of the Viewport class, which is another built-in one we can't edit. With no argument specified, the created viewport is the same size as the screen. We also set its z coordinate to 200; this means that anything with a z coordinate less than 200 will be drawn behind this viewport, and anything with a z coordinate greater will be drawn in front of it. In the case of something else having a z of 200, whichever one was created most recently will be on top.

def dispose_main_viewport
    @viewport.dispose
  end


The dispose_main_viewport method simply calls dispose on @viewport. We've covered disposing before, but it's been a while. In case you forgot, this is basically garbage collection and memory management. It's good practice to make sure you dispose of any objects like this you create, otherwise you can end up with memory leaks. Imagine if every scene you opened retained its viewport object in memory; just by opening and closing the menu a bunch of times you'd effectively overload the system resources, and ain't no player wants that.

def update_all_windows
    instance_variables.each do |varname|
      ivar = instance_variable_get(varname)
      ivar.update if ivar.is_a?(Window)
    end
  end


update_all_windows is an interesting one, as it uses some built-in methods which are available to any object in Ruby as standard.

First we have a .each iteration loop being called on "instance_variables", using the iteration variable "varname" to hold the current value. The instance_variables method returns all of the object's instance variable names as a string array. Currently, in the example of our base scene, we just have "@viewport", but obviously in more specific scenes, as we'll see, there will be plenty of other instance variables to contend with.

Inside the loop, we set a temporary variable called ivar to the result of calling instance_variable_get and passing in varname. instance_variable_get takes a string or symbol as its parameter, and returns the instance variable's value or nil if it's undefined. So in our example loop, we'll call instance_variable_get("@viewport") and ivar will be set to the Viewport object which @viewport represents.

As the final part of the loop, we call update on ivar if is_a? returns true when we pass in Window. I believe we've covered is_a? before, but on the offchance we haven't, it returns true if self (the calling object) is of the class passed in, in this case Window. We've already seen examples of this: when we looked at Window_Base, remember how it inherited from Window? That means that if you call is_a?(Window) on any window instance, it'll return true.

Obviously for the purposes of the example, a Viewport isn't a Window, so this will return false and ivar will not call update for @viewport. And likewise, any instance variable that isn't a Window will just be skipped over. Makes sense, obviously, since this is a method that only wants to update windows.

def dispose_all_windows
    instance_variables.each do |varname|
      ivar = instance_variable_get(varname)
      ivar.dispose if ivar.is_a?(Window)
    end
  end


dispose_all_windows is almost identical to the previous method. The only difference is that we're calling the dispose method instead of the update one, but everything else is the same.

def return_scene
    SceneManager.return
  end


return_scene is just a wrapper method which calls the return method of SceneManager. We already looked at that one ages ago back when I broke down the Manager classes.

def fadeout_all(time = 1000)
    RPG::BGM.fade(time)
    RPG::BGS.fade(time)
    RPG::ME.fade(time)
    Graphics.fadeout(time * Graphics.frame_rate / 1000)
    RPG::BGM.stop
    RPG::BGS.stop
    RPG::ME.stop
  end


This method fades out all graphics and sound, and takes one parameter, time, which defaults to 1000 milliseconds.

To do this, we call the fade methods of RPG::BGM, RPG::BGS, RPG::ME and Graphics. These are all built-in, and if you look at the sound classes you'll see that they're basically just wrappers for calling the same methods on the built-in Audio class. For Graphics.fadeout, we multiply time by the frame rate than divide by 1000 (to convert the milliseconds to frames) and then call the stop methods of RPG::BGM, RPG::BGS and RPG::ME, which stops the sound playback after it's been faded out.

def check_gameover
    SceneManager.goto(Scene_Gameover) if $game_party.all_dead?
  end


This method is exactly what it says on the tin. check_gameover does indeed check if there should be a game over and then goes to the Game Over scene if so. We do this by calling SceneManager.goto passing in Scene_Gameover if $game_party.all_dead? returns true. We've looked at that method before when we looked at Game_Unit many moons ago, but just as a refresher it basically checks whether alive_members is empty (meaning there aren't any party members left which don't have the death state).

That's everything for the base scene! Now we move on to the ones that inherit from it and expand its functionality.

Scene_Title

Where would we be without Scene_Title? This is the first thing any player will see when they start up your game, from which the immortal "new game" or "continue" options are chosen. Let's see how all that works, shall we?

def start
    super
    SceneManager.clear
    Graphics.freeze
    create_background
    create_foreground
    create_command_window
    play_title_music
  end


In this scene's overwrite of the start method, first we call super, which as we've discussed before calls the parent implementation of the same method (this does everything in start we just looked at in Scene_Base).

Then, we call SceneManager.clear. In case you've forgotten, this clears the stack of previous scenes, which is stored as an array. This makes sense, because it's possible to return to the title screen while still playing, and if that happens there's no need to hold on to any scenes the player was in previously.

After that we call Graphics.freeze, which does the same thing as it does in the previous terminate method. The reason for this is so that the post_start method will perform a transition to the title screen, which is why it "fades in" when the game is first opened. Without this, the screen would just pop in immediately, which is a little jarring.

After that we call some creation methods, namely create_background, create_foreground and create_command_window...which do exactly what they imply. And finally, we call play_title_music, which oddly enough also does what you think it will. We'll see all of those in-depth in a second.

def transition_speed
    return 20
  end


In this scene, we rewrite transition_speed to return 20 instead of 10. This results in a slightly slower fade in for the title screen than you usually see in other scene transitions.

def terminate
    super
    SceneManager.snapshot_for_background
    dispose_background
    dispose_foreground
  end


Here, we rewrite the terminate method. First things first, as usual, we call super to process the Scene_Base implementation of the same method. Then we call the snapshot_for_background method of SceneManager, which we covered a long time ago. It basically just takes a snapshot of the screen and blurs it a bit. And finally, we call the dispose_background and dispose_foreground methods to get rid of the sprites we created. That will become clearer in a sec once we cover the create methods.

def create_background
    @sprite1 = Sprite.new
    @sprite1.bitmap = Cache.title1($data_system.title1_name)
    @sprite2 = Sprite.new
    @sprite2.bitmap = Cache.title2($data_system.title2_name)
    center_sprite(@sprite1)
    center_sprite(@sprite2)
  end


First up we have create_background. The first thing we do is create a new Sprite, and assign it to the instance variable @sprite1. Then we set @sprite1's bitmap to the result of calling the title1 method in the Cache module, passing in $data_system.title1_name.

That should read pretty straightforward by now, but just in case: we know from DataManager that $data_system contains the data from System.rvdata2, which contains all the data specified in the system tab of the database. title1_name corresponds to the first list box when editing the Title Graphic. And as you may remember from when we covered the Cache module, the title1 method just loads the bitmap with the provided filename so that it's in memory and ready for display. So this effectively loads in whatever graphic you chose in the system tab and assigns it to the bitmap property of a sprite.

We then do the same thing but for title2, title2_name and a new instance variable, @sprite2. This one corresponds to the second list box in the Title Graphic editor from the system tab.

Finally, we call center_sprite twice, passing in @sprite1 the first time and @sprite2 the second. We'll see that shortly.

def create_foreground
    @foreground_sprite = Sprite.new
    @foreground_sprite.bitmap = Bitmap.new(Graphics.width, Graphics.height)
    @foreground_sprite.z = 100
    draw_game_title if $data_system.opt_draw_title
  end


create_foreground is a little different. We're still creating a new Sprite, but we're setting its bitmap to a new instance of the Bitmap class, passing in Graphics.width and Graphics.height (creating a blank bitmap that's the size of the screen). We set the foreground sprite's z coordinate to 100, meaning it will be drawn behind the main scene viewport (but in front of the background, which has a Z of 0 since it isn't in a viewport). And finally, we call draw_game_title if the opt_draw_title property of $data_system is true. This boolean corresponds to the "Draw Game Title" checkbox in the system tab of the database.

def draw_game_title
    @foreground_sprite.bitmap.font.size = 48
    rect = Rect.new(0, 0, Graphics.width, Graphics.height / 2)
    @foreground_sprite.bitmap.draw_text(rect, $data_system.game_title, 1)
  end


This method draws the game title on the title screen. First, we set the size of the @foreground_sprite's bitmap's font to 48 (otherwise, the title will look tiny). Then, we set a temporary variable called rect to a new Rect object, passing in an X coordinate of 0, Y coordinate of 0, width of Graphics.width, and height of half Graphics.height. This will reference a rectangular portion of the screen which starts at the top left, taking up the entire width and half the height. Finally, we call draw_text on @foreground_sprite's bitmap, passing in the rect we just created, the game_title property of $data_system, and 1 as the alignment. This will draw the game name set in the system tab, middle-aligned in a rect covering the top half of the screen. This will end up showing the title horizontally centred and just above the middle of the screen.

def dispose_background
    @sprite1.bitmap.dispose
    @sprite1.dispose
    @sprite2.bitmap.dispose
    @sprite2.dispose
  end


This method disposes of the background objects. First we call dispose on @sprite1's bitmap, then on @sprite1 itself. Then, we do the same with @sprite2. This is potentially important, because if we just dispose of the sprites, the memory allocated to their bitmaps will still contain that data, and no longer having a reference to the containing object will remove our ability to free up that memory ourselves.

def dispose_foreground
    @foreground_sprite.bitmap.dispose
    @foreground_sprite.dispose
  end


And this one's just more of the same, but with @foreground_sprite. Gotta clean up after ourselves!

def center_sprite(sprite)
    sprite.ox = sprite.bitmap.width / 2
    sprite.oy = sprite.bitmap.height / 2
    sprite.x = Graphics.width / 2
    sprite.y = Graphics.height / 2
  end


The center_sprite method, which is called on @sprite1 and @sprite2, quite simply centres a sprite on the screen, and takes sprite as a parameter. First, we set the passed-in sprite's ox (origin X) and oy (origin Y) to half of its bitmap's width and height respectively. Then, we set its x and y to half of the screen width and height respectively. Setting the origin coordinates to the middle of the sprite first is important, as it defaults to the top left and centering a sprite with its default origin coordinates will result in the title screen sprites actually taking up the lower right quarter of the screen and disappearing off of it!

def create_command_window
    @command_window = Window_TitleCommand.new
    @command_window.set_handler(:new_game, method(:command_new_game))
    @command_window.set_handler(:continue, method(:command_continue))
    @command_window.set_handler(:shutdown, method(:command_shutdown))
  end


This method creates the command window which actually offers the player options on the title screen. First, we set the instance variable @command_window to a new instance of Window_TitleCommand (and if we think back to the update_all_windows method of Scene_Base, because this one *is* a window it'll be updated when that's called just because it's included in the scene's instance variables, with no need for any further setup to update it). Then we have 3 calls to set_handler on our new window object. As you may remember from when we looked at the window classes, set_handler takes a symbol and a method as its parameters. We saw an example of this back in Window_KeyItem, but now with scenes we're really going to get into the nuts and bolts of how calling the method actually works.

So the symbol can be literally anything you want. Symbols in Ruby are preceded by a colon and then a name to identify them. They're very similar to strings, with one important difference: a string is an object, and every instance of a string is a separate object that takes up its own space in memory, even if it's the same string. str1 = "hello!" and str2 = "hello!" create two different objects. Symbols, however, are held in memory once, and any reference to the symbol references the same point in memory. str1 = :hello and str2 = :hello will create one object, and both variables will reference the same one. It logically follows that when you need to reference something with a named variable, it's almost always better to use a symbol than a string to save on memory usage.

The symbols we're passing in are :new_game, :continue and :shutdown, which are handled by the methods command_new_game, command_continue and command_shutdown respectively. This effectively binds those methods from this class to the options in the command window, such that when the player selects one, the method with that name is called.

def close_command_window
    @command_window.close
    update until @command_window.close?
  end


This method closes the command window. First, we call the close method on @command_window, then call update until the close? method of @command_window returns true. This is needed because in the methods which call this one, we're also changing to a new scene. If you look back to how Scene_Base's update works, it only actively updates windows of the scene that's active, so we need to manually call update in order for the command window to continue closing. If we didn't have this, when you selected new game, continue or shut down, the window would just hang there because the new scene wouldn't be updating it.

def command_new_game
    DataManager.setup_new_game
    close_command_window
    fadeout_all
    $game_map.autoplay
    SceneManager.goto(Scene_Map)
  end


command_new_game is the handler method for the :new_game command symbol, and is obviously the one that corresponds to the "New Game" option.

First, we call the setup_new_game method of DataManager, which we covered in a previous episode. If you forgot, it basically sets up the game objects, starting party and starting map, moves the player to the starting coordinates, and sets the frame count to 0. Then we call close_command_window and fadeout_all to close the window and fade out the title music. We then call the autoplay method of $game_map, which you may remember starts playing the map's defined BGM and BGS if one is set. Finally, we call SceneManager's goto method, passing in Scene_Map, which is the next scene we'll be looking at after this.

def command_continue
    close_command_window
    SceneManager.call(Scene_Load)
  end


command_continue is the handler method for the :continue symbol, corresponding to the "Continue" command. Here, we just call close_command_window and then the call method of SceneManager passing in Scene_Load. Note that we use call here rather than goto because the player is able to cancel out of the load screen, so it's less resource-intensive to add Scene_Title to the stack and then return to that same instance of it if the player cancels.

def command_shutdown
    close_command_window
    fadeout_all
    SceneManager.exit
  end


command_shutdown is the handler method for the :shutdown symbol, corresponding to the "Shut down" command. In this one, we call close_command_window, then fadeout_all, and finally the exist method of SceneManager. Fairly self-explanatory: it closes the command window, fades out any music that's playing, and ends the game.

def play_title_music
    $data_system.title_bgm.play
    RPG::BGS.stop
    RPG::ME.stop
  end


Our final method in Scene_Title is the one for playing the title music. Here, we simply call the play method of $data_system's title_bgm object, which is the Audio object corresponding to the music you selected in the system tab. We also call the stop methods of RPG::BGS and RPG::ME, which stop any background sounds or ME that were playing. This prevents clashes with sound that was already playing when the player quit to title from the menu.

That's it for Scene_Title! Now we look at maps, which are a huge and vital part of any game.

Scene_Map

def start
    super
    SceneManager.clear
    $game_player.straighten
    $game_map.refresh
    $game_message.visible = false
    create_spriteset
    create_all_windows
    @menu_calling = false
  end


In the start method, first we call the super method, then SceneManager.clear to clear the stack. We then call the straighten method of $game_player, which sets the player's graphic pattern to the middle "idle" pattern, and the refresh method of $game_map, which refreshes all events, common events and tile events. Then we call create_spriteset and create_all_windows, which we'll cover soon. And finally, we set the instance variable @menu_calling to false. This is a flag that determines whether the player has hit the key for opening the menu, as we'll see.

def perform_transition
    if Graphics.brightness == 0
      Graphics.transition(0)
      fadein(fadein_speed)
    else
      super
    end
  end


In the overwrite for perform_transition, first we check whether Graphics.brightness is equal to 0 (which indicates that the screen has been faded out, which will be the case after a battle or loading a save) and if so we call Graphics.transition passing in 0ms (which will perform the screen transition instantly) and then call the fadein method, passing in the result of calling fadein_speed. If Graphics.brightness is not equal to 0, we call the super method instead. I believe the intent of this, as we'll see in later methods, is to prevent any input while the screen is fading back in, and also to do a slightly longer fade in than usual.

def transition_speed
    return 15
  end


Speaking of which, we also overwrite transition_speed to return 15, which lengthens a standard transition by 5 frames.

def pre_terminate
    super
    pre_battle_scene if SceneManager.scene_is?(Scene_Battle)
    pre_title_scene  if SceneManager.scene_is?(Scene_Title)
  end


In the overwrite of pre_terminate, we first call the super method. Then we call the pre_battle_scene method if SceneManager's scene_is? method returns true when we pass in Scene_Battle, and pre_title_scene if it returns true when we pass in Scene_Title.

Effectively, what this means is that the first method will be called if we're transitioning to a battle, and the latter if we're transitioning to the title screen.

def terminate
    super
    SceneManager.snapshot_for_background
    dispose_spriteset
    perform_battle_transition if SceneManager.scene_is?(Scene_Battle)
  end


In the overwrite to terminate, again we call the super method. We call the snapshot_for_background method of SceneManager (this is used for scenes like the menu, where it "appears over" a slightly blurry map. The map isn't actually there in the menu scene, what you're seeing is the blurry snapshot (without this line the background would just be black).

def update
    super
    $game_map.update(true)
    $game_player.update
    $game_timer.update
    @spriteset.update
    update_scene if scene_change_ok?
  end


We have a few things going on in Scene_Map's overwrite of the update method. First, as with most things, we call the super method. Then we call the update methods of several objects: $game_map, passing true to the "main" parameter, which tells it that the interpreter needs to be updated as well (we covered this when we looked at Game_Map); $game_player, which updates movement, scrolling, vehicles, followers etc; and $game_timer, which increments the timer count. We also update @spriteset, an instance variable we'll see shortly. Finally, we call update_scene if scene_change_ok? returns true, which is the next method we'll look at.

def scene_change_ok?
    !$game_message.busy? && !$game_message.visible
  end


This method determines whether the engine is allowed to change the scene, and returns the result of checking whether both the busy? and visible? methods of $game_message return false. In other words, a scene change is ok only if the message window isn't being displayed and has no text to show.

def update_scene
    check_gameover
    update_transfer_player unless scene_changing?
    update_encounter unless scene_changing?
    update_call_menu unless scene_changing?
    update_call_debug unless scene_changing?
  end


In the update_scene method, first we call check_gameover to see if the party is dead and we need to go to the game over screen. Then, we call update_transfer_player, update_encounter, update_call_menu and update_call_debug unless scene_changing? returns true (meaning we're currently in the process of going to a scene other than the map).

def update_for_fade
    update_basic
    $game_map.update(false)
    @spriteset.update
  end


The update_for_fade method is for the frame update during a fade in, as it needs to be done slightly differently from a standard update. First we call update_basic, then the update method of $game_map, passing in false for the "main" parameter, and then we update @spriteset. The purpose here is to *not* update the interpreter, player or timer, since none of those things should be doing anything while the screen is fading.

def fade_loop(duration)
    duration.times do |i|
      yield 255 * (i + 1) / duration
      update_for_fade
    end
  end


fade_loop is a general-purpose fade processing method, used for both fade in and fade out. It takes one parameter, duration, which will be in frames.

I can't remember if I've explained .times before, but it's basically an iteration method you can call on a number which loops that number of times. So if duration is 50, duration.times will run the code inside 50 times.

We run a .times loop on duration, using the iteration variable "i". Then in the block, we yield the result of multiplying 255 by 1 more than the current value, which is then divided by duration.

Now the first time I explained how yield works was back in 2015, so I think a refresher may be in order.

In Ruby, a block is a section of code contained within either curly brackets or a do...end construct. There are subtle differences between the two, but that's beyond the scope of this series. A good general rule is to use braces for single line blocks and do...end for multiline ones, but there are potential exceptions to that as well. That seems to be how the default scripts are coded, though.

Regardless of which one is used, blocks can be assigned parameters, which are denoted by being inside pipes e.g. |i| defines "i" as the name of the iteration variable for the duration.times block. Basically, what the "yield" keyword does is says "pass the value that follows to the block parameter of the code that called this" and we'll see that in the next method we look at.

In our example using a duration of 50, the first yield will evaluate to 255 * (0 + 1) / 50, which results in 5 (since it'll be rounded). The second time it will be 255 * (1 + 1) / 50, which results in 10. And so on and so forth.

Finally, after each value yield, we call update_for_fade.

def fadein(duration)
    fade_loop(duration) {|v| Graphics.brightness = v }
  end


This method fades in the screen, taking duration in frames as a parameter. There's only one line here: we call fade_loop passing in duration, and supply it a block with parameter "v". Inside the block, we set Graphics.brightness to v.

This is where yield comes in. Say you call fadein(50). This calls fade_loop(50), which loops 50 times. Each time, it yields the result of 255 * (loop counter + 1) / duration, and stores it in v. This then becomes the new screen brightness. In this way, we incrementally increase the brightness of the screen each frame over the specified duration, at the end of which it will have reached 255.

def fadeout(duration)
    fade_loop(duration) {|v| Graphics.brightness = 255 - v }
  end


fadeout is almost identical, but rather than setting the brightness to v, we're setting it to 255 minus v. This means the screen will be getting incrementally darker.

def white_fadein(duration)
    fade_loop(duration) {|v| @viewport.color.set(255, 255, 255, 255 - v) }
  end


white_fadein is used when the fade type is set to "white" and does effectively the opposite of fadein. Instead of setting Graphics.brightness, we're setting the alpha (transparency) of @viewport's color to be 255 minus v. This incrementally progresses it towards white.

def white_fadeout(duration)
    fade_loop(duration) {|v| @viewport.color.set(255, 255, 255, v) }
  end


And completing the quartet, white_fadeout does the same but sets the alpha to v instead, since we want to move the value *away* from white back to full opacity.

def create_spriteset
    @spriteset = Spriteset_Map.new
  end


In the create_spriteset method, surprising nobody, we create a new instance of Spriteset_Map and assign it to the instance variable @spriteset. As we saw back when we covered Spriteset_Map, this contains the main viewports, character graphics, tile graphics, shadow graphics, parallaxes, weather effects, game pictures and the timer graphics.

def dispose_spriteset
    @spriteset.dispose
  end


And in dispose_spriteset, we call dispose on @spriteset to free it from memory.

def create_all_windows
    create_message_window
    create_scroll_text_window
    create_location_window
  end


create_all_windows is another method that does what it says on the tin. We call create_message_window, create_scroll_text_window and create_location_window, which are the three windows that can be shown on maps (other kinds of input window, as we saw before, are part of, and handled by, the message window).

def create_message_window
    @message_window = Window_Message.new
  end


Here we're just creating a new instance of Window_Message and assigning it to the instance variable @message_window.

def create_scroll_text_window
    @scroll_text_window = Window_ScrollText.new
  end


Same thing here for Window_ScrollText and @scroll_text_window...

def create_location_window
    @map_name_window = Window_MapName.new
  end


And same thing here for Window_MapName and @map_name_window.

def update_transfer_player
    perform_transfer if $game_player.transfer?
  end


update_transfer_player calls perform_transfer (which we'll see soon) if the transfer? method of $game_player returns true. As you probably don't remember, that method checks $game_player's @transferring variable value, which is set to true when the reserve_transfer method is called.

def update_encounter
    SceneManager.call(Scene_Battle) if $game_player.encounter
  end


This is the encounter update method, which executes the call method of SceneManager if the encounter method of $game_player returns true.

def update_call_menu

if $game_system.menu_disabled || $game_map.interpreter.running?
@menu_calling = false
else
@menu_calling ||= Input.trigger?(:B)
call_menu if @menu_calling && !$game_player.moving?
end
end


update_call_menu is how the game checks whether the player is opening the menu while on the map. First we check whether the menu has been disabled or the map's interpreter is running (because you don't want the player to be able to call the menu while an event is in the middle of processing) and if so, we set @menu_calling to false. Otherwise, if the menu is enabled and the interpreter isn't running, we set @menu_calling to the result of calling Input's trigger? method, passing in the symbol :B, but only if @menu_calling is currently false or nil. This will set it to true if the player is currently pressing the :B key, which by default is Escape. Then, we call call_menu if @menu_calling is true AND the moving? method of $game_player returns false. This prevents a menu call while the player is between tiles.

def call_menu
    Sound.play_ok
    SceneManager.call(Scene_Menu)
    Window_MenuCommand::init_command_position
  end


This is the method which calls the menu itself. First, we call play_ok from the Sound module, which plays whatever SE we set for OK in the system tab. Then we call the call method of SceneManager, passing in Scene_Menu, which will transition us to the menu scene. Finally, we call the init_command_position method of Window_MenuCommand.

I don't know if you remember from the episode which covered it, nor do I remember whether I explained it at the time, but the reason we're able to call init_command_position on the class itself rather than needing an instance of it is because it was defined as self.init_command_position rather than init_command_position, and this creates it as what's known as a "singleton" method which doesn't require an instance for calling it. It also sets @@last_command_symbol to nil, the @@ denoting it as a class variable as opposed to an instance variable. This means that all instances of Window_MenuCommand will share the value of this variable rather than each having their own version of it. This allows a previously-selected value in a command window to be selected when returning to it without needing a reference to the window in question.

def update_call_debug
    SceneManager.call(Scene_Debug) if $TEST && Input.press?(:F9)
  end


In update_call_debug, we call the call method of SceneManager passing in Scene_Debug IF $TEST is true and Input's press? method returns true when we pass in :F9. In short, the debug scene will be called if the game is in test play mode and the player presses the F9 key.

Note that $TEST is built in to the editor and set to true when running test play, and will return false when the game is run outside of the editor.

def perform_transfer
    pre_transfer
    $game_player.perform_transfer
    post_transfer
  end


In perform_transfer, first we call pre_transfer, which we'll see in a second. Then we call perform_transfer on $game_player, which sets up the new direction, map and coordinates. Then we call post_transfer.

def pre_transfer
    @map_name_window.close
    case $game_temp.fade_type
    when 0
      fadeout(fadeout_speed)
    when 1
      white_fadeout(fadeout_speed)
    end
  end


In pre_transfer, we close @map_name_window and then have a case statement against the fade_type property in $game_temp. This will be 0 for "Normal", 1 for "White" and 2 for "None". If it's 0, we call fadeout, passing in the result of calling fadeout_speed. If 1, we call white_fadeout, passing in fadeout_speed. We don't need a when clause for 2 because if the fade type is none nothing else is required.

def post_transfer
    case $game_temp.fade_type
    when 0
      Graphics.wait(fadein_speed / 2)
      fadein(fadein_speed)
    when 1
      Graphics.wait(fadein_speed / 2)
      white_fadein(fadein_speed)
    end
    @map_name_window.open
  end


post_transfer is pretty much the same thing in reverse. First we have the case for fade_type. If 0, we call Graphics.wait passing in half the fadein_speed, then call fadein, passing in the full value of fadein_speed. If it's 1, it's the same thing but we call white_fadein instead. And then we open @map_name_window.

def pre_battle_scene
    Graphics.update
    Graphics.freeze
    @spriteset.dispose_characters
    BattleManager.save_bgm_and_bgs
    BattleManager.play_battle_bgm
    Sound.play_battle_start
  end


pre_battle_scene runs just before a battle. First, we call Graphics.update, then Graphics.freeze. This refreshes the screen and then prevents any further changes to it. We call dispose_characters on @spriteset, since the graphics aren't needed while we're in battle so there's no point in dedicating memory to them. Then we call save_bgm_and_bgs in BattleManager to retain whatever music and sound was playing on the map prior to battle starting. After that, we call play_battle_bgm in BattleManager to start the battle music, and call the Sound module's play_battle_start method to play the battle SE defined in the system tab.

def pre_title_scene
    fadeout(fadeout_speed_to_title)
  end


pre_title_scene runs just before we return to the title screen. All we're doing here is calling fadeout and passing in the result of falling fadeout_speed_to_title, which we'll see in a minute.

def perform_battle_transition
    Graphics.transition(60, "Graphics/System/BattleStart", 100)
    Graphics.freeze
  end


This method performs the transition effect between the map and battle. Here we see the additional parameters for Graphics.transition I mentioned before. In addition to a duration, it also takes "filename" and "vague" parameters. In this case, we're providing as the transition graphic BattleStart.png in Graphics/System, which is a sunburst graphic. "vague" is kind of difficult to explain, but it sets the ambiguity of the border between the graphic's start and end points, with larger values resulting in greater ambiguity. The default value is 40. Passing in 100 here makes for a higher value, which is what creates the softening effect on the sunburst when a battle starts.

def fadeout_speed
    return 30
  end

def fadein_speed
    return 30
  end

def fadeout_speed_to_title
    return 60
  end


Our final three methods in this class define fadeout speeds in frames; fading out and fading in both take 30 frames each, or half a second. Fading out when returning to title takes 60, or a full second, resulting in returning to the title screen having a slightly longer fade out than usual.

And that's it for Scene_Map! Combined with Game_Map and Spriteset_Map, we've now covered every bit of code that facilitates the player moving around the world you created.

And so endeth another lesson! Most of the scene classes are quite short with the notable exceptions of Scene_File and Scene_Battle, so hopefully we'll be able to whiz through the remainder quite quickly!

The thing is, once that's done, Slip into Ruby will be finished, at least this part of it. There will be no more default code to dive into. So I ask you, sports fans; once we reach that point, what do you want to see next? I can explain some other aspects of Ruby more in-depth if you want, or take you through an entire worked example of writing a script for a new system or mechanic. Just let me know in the comments what you'd like to see and I'll do what I can to make it happen after we finish the scene breakdown.

Until next time!
Pages: 1