Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Sound Gallery

Hey, sorry if the title's a bit vague, but I need a script for a sound gallery/test thing.

Basically it's just a list of songs that you already listened to in the storyline (you unlock them as you proceed in the game) that you can choose to play. I know I can do this with eventing but it'll take awhile to get every single song in my game down...

If you need an example, let's say you fight a boss. You hear a different boss music for the first time... then once you defeat it, you can go to the sound gallery place and choose to listen to that song. Songs that haven't been unlocked will either appear as "???" or just not be listed there at all.

If anyone could script this out for me that'd be cool.
 
To help more:

1. Sound in RMXP consists of BGM (backgroung music), BGS (background sound), SE (sound effect), and ME (music effect). Which one do you refer to your request as "sound"? I assume it's BGM.

2. Do you use RTP and import additional resources, or do you import all resources? It's important to ask this, since if you use RTP and by accident some songs are never used during the gameplay, they will be always listed as "???" (if you choose this option).

3. How will we access the sound gallery? From the menu, event, or else?

I once make an unreleased script to enlist all BGMs used in my game. I think it's not hard to add the unlock feature (and hey, thanks for asking that; it's a nice idea! I'll add that too into my game.)
 
Sorry for not being very specific. Here's the answers to your questions:

1. Obviously, I want BGM, since it's not very interesting listening to the chime when you open a treasure chest or gain a new item...

2. I use RTP and import some resources. Unfortunately because of this I probably won't use some BGMs at all. Maybe you could script in "BGMs not to include."

3. It'll be accessed by event.

I got this idea from some games I played (I think Zelda: Minish Cap was one, can't remember any others) and it seemed a pretty good idea. Thanks if you can script this.
 
OK, I'll see what I can do. It shouldn't be that hard.

By the way, it means people will need to install RTP first before installing your game, am I right?

EDIT:
How do you unlock a song? With help of switches or just automatically when the song is played?
 
Well actually, I've decided not to use RTP since my main resources are imported. It might make it easier too for the script since I'm only going to place songs I use in the folder. So make the script non-RTP. Also, if possible I'd like the songs to be unlocked once it's played. I could just event this if I used switches, but it'd be a lot of work.
 
Here it is:

Code:
# SETTINGS BEGIN
# Text that will appear if a song is locked.
LOCKED_NAME = "???"
# Sort order. ASC = ascending (A-Z), DESC = descending (Z-A).
# Any other value will be regarded as ASC.
SORT_ORDER = "ASC"
# Show numbering?
SHOW_NUMBER = true
# Show unlock status?
# If set to true, a help window will show how many songs are (un)locked.
# More settings for status window are available.
SHOW_STATUS = false
# Settings below would not take effect if SHOW_STATUS is set to false.
# --------------------------------------------------------------------
# Status type
# Possible values:
# NORMAL: shows numbers of unlocked songs. Default.
# e.g. Unlocked songs: 3
# REMAINING: shows numbers of locked songs.
# e.g. Locked songs remaining: 7
# Any other values will be regarded as NORMAL.
# Can also include total song (see SHOW_TOTAL_SONGS).
STATUS_TYPE = "NORMAL"
# Use percentage instead?
USE_PERCENT = false
# How many decimal digits will be displayed? Default is 2.
# Does not have effect if USE_PERCENT is set to false
PERCENT_DEC_DIGIT = 2
# Decimal sign, in case you're not using English numbering (".")
# Does not have effect if USE_PERCENT is set to false
DECIMAL_SIGN = "."
# Define the status texts as you wish
NORMAL_TEXT = "Unlocked songs: "
REMAINING_TEXT = "Locked songs remaining: "
# Show total songs?
# Does not have effect if USE_PERCENT is set to true
SHOW_TOTAL_SONGS = true
# SETTINGS END

=begin
*******************
Notes on percentage
*******************
As you can see below, I modified Float class to be able to convert itself
into a percentage string. For example:

(2.1).to_percent => 210%

There are four optional parameters (you can specify or omit them):

1. decimal_digit = 0
   Specifies how many digit after the decimal sign will be taken.
   For example:
   
   (0.98765).to_percent(2) will result in 98.76%
   (or 98.77%, depending on next parameters).
   
   Default is 0 (no decimal digit will be taken).
   
2. round = true
   If true, rounding will be performed (see example).
   Otherwise, pick only needed decimal digits and discard the rest (truncate).
   Example:
   
   (0.98765).to_percent(2, false) => 98.76%
   (0.98765).to_percent(2, true)  => 98.77%
   (0.98764).to_percent(2)        => 98.76%
   
   Default is true (perform rounding).
   
3. include_percent_sign = true
   If by any chance you don't want to include percent sign (%), maybe to
   be processed furthermore, set this parameter to false.
   Example:
   
   (0.98765).to_percent(2, false, true)  => 98.76%
   (0.98765).to_percent(2, false, false) => 98.76
   
   Remember that the result is still in string, so you may need to convert
   before processing.
   Default is true (include percent sign).
   
4. decimal_sign = "."
   Very useful if you don't use English number formatting. Some language
   use "," instead of ".". For example:
   
   (0.98765).to_percent(2, false, ",") => 98,76%
   
   Default is ".".
=end

# This class holds song data
class Game_Songs
  attr_reader :name       # song name
  attr_reader :unlocked   # song state
  
  # initialization
  def initialize(filename)
    # Strip extension from filename
    @name = filename[0, filename.length - 4]
    # Lock this song
    @unlocked = false
  end
  
  # unlock the song
  def unlock
    @unlocked = true
  end
  
  # unlocked?
  def unlocked?
    return @unlocked
  end
end

class Game_System
  attr_reader :songs # list of all songs
  
  alias song_initialize initialize
  def initialize
    song_initialize
    # Make song lists
    @songs = []
    list = Dir.entries(File.expand_path("Audio/BGM"))
    list.delete_at 0
    list.delete_at 0
    # Sort in which order?
    list.sort!
    case SORT_ORDER
    when "DESC"
      list.reverse!
    end
    for music in list
      @songs.push(Game_Songs.new(music))
    end
    # Automatically unlock title BGM
    # (since New Game command will unexpectedly remade $game_system object)
    for song in @songs
      if song.name == $data_system.title_bgm.name
        song.unlock
        break
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Play Background Music and unlocks them
  #     bgm : background music to be played
  #--------------------------------------------------------------------------
  def bgm_play(bgm)
    @playing_bgm = bgm
    if bgm != nil and bgm.name != ""
      Audio.bgm_play("Audio/BGM/" + bgm.name, bgm.volume, bgm.pitch)
      # Find the BGM in song list and unlock
      for song in @songs
        if song.name == bgm.name
          song.unlock unless song.unlocked?
          break
        end
      end
    else
      Audio.bgm_stop
    end
    Graphics.frame_reset
  end
end

# This class draws a list of songs in a window
class Window_Songs < Window_Selectable
  # initialization
  def initialize(x, y, width, height)
    super(x, y, width, height)
    @item_max = $game_system.songs.size
    @column_max = 1
    self.contents = Bitmap.new(width - 32, @item_max * 32)
    @index = 0
    refresh
  end
  def refresh
    self.contents.clear
    for i in 0 ... $game_system.songs.size
      song = $game_system.songs[i]
      # Show numbering?
      if SHOW_NUMBER
        name = "#{i + 1}. "
      else
        name = ""
      end
      # If unlocked, draw song name
      if song.unlocked?
        name += song.name
        color = normal_color
      else
        name += LOCKED_NAME
        color = disabled_color
      end
      self.contents.font.color = color
      self.contents.draw_text(4, i * 32, 640, 32, name)
    end
  end
  # update window help
  def update_help
    @help_window.update
  end
end

# This class performs scene for song list.
class Scene_Songs
  # initialization
  def main
    y = (SHOW_STATUS ? 64 : 0)
    @song_window = Window_Songs.new(0, y, 640, 480 - y)
    # Show status if necessary
    if SHOW_STATUS
      @help_window = Window_Help.new
      unlocked = 0
      total = $game_system.songs.size
      for song in $game_system.songs
        unlocked += 1 if song.unlocked?
      end
      # Set help window text based on settings
      case STATUS_TYPE
      when "NORMAL"
        text = NORMAL_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (unlocked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += unlocked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      when "REMAINING"
        locked = total - unlocked
        text = REMAINING_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (locked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += locked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      end
      @help_window.set_text(text)
      @song_window.help_window = @help_window
    end
    # Remember BGM and BGS so we can restore it later
    @last_bgm = $game_system.playing_bgm.name
    $game_system.bgm_memorize
    $game_system.bgs_memorize
    Graphics.transition
    # start scene
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    # Disposal of windows
    @song_window.dispose
    @help_window.dispose unless @help_window == nil
    # Restore BGM and BGS
    $game_system.bgm_restore
    $game_system.bgs_restore
  end
  
  def update
    @song_window.update
    # If C button is pressed
    if Input.trigger?(Input::C)
      # If songs is locked
      song = $game_system.songs[@song_window.index]
      if song.unlocked?
        # If last BGM is different than current
        if @last_bgm != song.name
          @last_bgm = song.name
          # Fade out current BGM and play the selected one
          Audio.bgm_fade(100)
          Audio.bgm_play("Audio/BGM/" + song.name)
        end
      else
        # song still locked, play buzzer
        $game_system.se_play($data_system.buzzer_se)
      end
      return
    end
    # If B button is pressed
    if Input.trigger?(Input::B)
      # Play cancel SE
      $game_system.se_play($data_system.cancel_se)
      # Switch to map screen
      $scene = Scene_Map.new
      return
    end
  end
end

# Rewrites to Ruby's Float class to accomodate conversion into percentage string
class Float < Numeric
  # Convert to percentage (string)
  # Returns string (self is not modified)
  def to_percent(decimal_digit = 0, round = true, include_percent_sign = true,
    decimal_sign = ".")
    # Error checking
    decimal_digit = 0 if decimal_digit < 0
    decimal_sign = "." unless decimal_sign.is_a?(String)
    # Modify number so the first decimal digit is the significant digit
    # we want to round
    percent = self * 100 * (10 ** decimal_digit)
    # Perform round if necessary
    if round
      percent = percent.round
    end
    # Revert back to floating number (already in percentage)
    percent = percent.to_f / (10 ** decimal_digit).to_f
    # Convert to string
    percent = percent.to_s
    # Get integer digits
    leading = percent[/(\d)*./]
    # Remove decimal sign
    leading = leading[0, leading.size - 1]
    # Get decimal digits
    digit = percent[/\D(\d)*/]
    # Strip off decimal sign
    digit = digit[1, digit.size - 1]
    # If by any case, after rounding, decimal digit length is less than desired,
    # add trailing zeroes
    if digit.length < decimal_digit
      digit += "0" * (decimal_digit - digit.length)
    # Or if rounding wasn't performed and decimal digit length is
    # more than desired, redefine decimal digit
    elsif digit.length > decimal_digit and truncate
      digit = digit[0, decimal_digit]
    end
    percent = leading + decimal_sign + digit
    # Include percent sign (%)?
    percent = percent + "%" if include_percent_sign
    return percent
  end
end

If there is any error or something doesn't match your need, let me know.
 
It looks great and all, but what should I script to call it? :p

EDIT: Bleh, if I try and load a saved file it goes error on me, but it works fine if I try new game. Is there a way to prevent that?
 
Oops, I'm sorry, forgot the instruction...

Just make a normal event, then use Call Script command. Type in this:

Code:
$scene = Scene_Songs.new

And yes, old save files won't work. There's currently no way to prevent that.
 
Alright, thanks. I'll just add it when I'm finished making it or something, not that important right now.

Edit: This isn't all that important or anything, but could you make it so instead of listing it alphabetically, it appears in the order the songs appear in? Or make it so I can manually list the songs and the order they appear in. You don't need to do this, but if you want it'd be great. Thanks.
 
I need it! I need it! :D

This is a cool script, well... It is by EXSHARAEN, so Duh it's good... :)

Anyhoo...

That's a bug, and it's gonna have to be fixed if it's going to be placed in every Tavern, cos' then It'll look really professional...

Anyhoo...

Night all!

Amos36
 
No promblemo Mr. Exsharaen! Who puts the... Umm... "Exsharaen" into Exsharaen... :s

Anyhoo...

It's annoying to have all the songs in order of Title,

Because you'd have to change the names in which they appear...

SO BASICALLY!!!

The bug is that NAME ORDER CAN BE VERY VERY ANNOYING...

SO it'd be good if you could, say, manually list them...

OR

They appear in the order they are played in...

EDIT-LIKE EDIT:

Check the guy who posted this's last post... He asked for an extra... :)

P.S- You're such a legend Exsharaen...
 
OK, I'll add two more options on song sorting. One will be manually specified and the other one will be the order they are played.

Wait for couple days, I'm currently busy (but see if I can steal enough time to work on this).
 
Sooner than I expected :P

Added two more options for song name sorting, as requested. Replace my old script with this:

Code:
# SETTINGS BEGIN
# Text that will appear if a song is locked.
LOCKED_NAME = "???"
# Sort order. Possible values:
# ASC = name, ascending (A-Z)
# DESC = name, descending (Z-A).
# APP = by appearance.
# MANUAL = manually specified.
# Any other values will be regarded as ASC.
# WARNING: 
# MANUAL mode is currently time-consuming when loading game
# and starting a new game.
SORT_ORDER = "APP"
# If using MANUAL sort order, fill in the order here.
# You SHOULD specify all song names here, but in case there is
# any songs in your Audio/BGM folder that you forgot to enlist,
# they will be appended to the end of this list at ascending order.
# Include file extensions (it's too risky to omit file extensions,
# since you might have two files with the same name but different type).
# Case insensitive (am I generous, not? ;)).
# Separate each entry by a comma.
# Be careful not to enter one song more than once, or your player
# will never have 100% unlocks.
# See example:
MANUAL_SORT =
[
"002-Battle02.mid",
"043-Positive01.mid",
"001-Battle01.mid",
"064-Slow07.mid",
]
# Show numbering?
SHOW_NUMBER = true
# Show unlock status?
# If set to true, a help window will show how many songs are (un)locked.
# More settings for status window are available.
SHOW_STATUS = true
# Settings below would not take effect if SHOW_STATUS is set to false.
# --------------------------------------------------------------------
# Status type
# Possible values:
# NORMAL: shows numbers of unlocked songs. Default.
# e.g. Unlocked songs: 3
# REMAINING: shows numbers of locked songs.
# e.g. Locked songs remaining: 7
# Any other values will be regarded as NORMAL.
# Can also include total song (see SHOW_TOTAL_SONGS).
STATUS_TYPE = "NORMAL"
# Use percentage instead?
USE_PERCENT = false
# How many decimal digits will be displayed? Default is 2.
# Does not have effect if USE_PERCENT is set to false
PERCENT_DEC_DIGIT = 2
# Decimal sign, in case you're not using English numbering (".")
# Does not have effect if USE_PERCENT is set to false
DECIMAL_SIGN = "."
# Define the status texts as you wish
NORMAL_TEXT = "Unlocked songs: "
REMAINING_TEXT = "Locked songs remaining: "
# Show total songs?
# Does not have effect if USE_PERCENT is set to true
SHOW_TOTAL_SONGS = true
# SETTINGS END

=begin
*******************
Notes on percentage
*******************
As you can see below, I modified Float class to be able to convert itself
into a percentage string. For example:

(2.1).to_percent => 210%

There are four optional parameters (you can specify or omit them):

1. decimal_digit = 0
   Specifies how many digit after the decimal sign will be taken.
   For example:
   
   (0.98765).to_percent(2) will result in 98.76%
   (or 98.77%, depending on next parameters).
   
   Default is 0 (no decimal digit will be taken).
   
2. round = true
   If true, rounding will be performed (see example).
   Otherwise, pick only needed decimal digits and discard the rest (truncate).
   Example:
   
   (0.98765).to_percent(2, false) => 98.76%
   (0.98765).to_percent(2, true)  => 98.77%
   (0.98764).to_percent(2)        => 98.76%
   
   Default is true (perform rounding).
   
3. include_percent_sign = true
   If by any chance you don't want to include percent sign (%), maybe to
   be processed furthermore, set this parameter to false.
   Example:
   
   (0.98765).to_percent(2, false, true)  => 98.76%
   (0.98765).to_percent(2, false, false) => 98.76
   
   Remember that the result is still in string, so you may need to convert
   before processing.
   Default is true (include percent sign).
   
4. decimal_sign = "."
   Very useful if you don't use English number formatting. Some language
   use "," instead of ".". For example:
   
   (0.98765).to_percent(2, false, ",") => 98,76%
   
   Default is ".".
=end

# This class holds song data
class Game_Songs
  attr_reader :name       # song name
  attr_reader :unlocked   # song state
  
  # initialization
  def initialize(filename)
    # Strip extension from filename
    @name = filename[0, filename.length - 4]
    # Lock this song
    @unlocked = false
  end
  
  # unlock the song
  def unlock
    @unlocked = true
  end
  
  # unlocked?
  def unlocked?
    return @unlocked
  end
end

class Game_System
  attr_reader :songs    # list of all songs
  attr_reader :unlocks  # only used when SORT_ORDER is APP or MANUAL
                        # also contains the same song object as @songs
  
  alias song_initialize initialize
  def initialize
    song_initialize
    # Make song lists
    @songs = []
    list = Dir.entries(File.expand_path("Audio/BGM"))
    list.delete_at 0
    list.delete_at 0
    # List unlocks when necessary
    @unlocks = [] if SORT_ORDER == "APP" or SORT_ORDER == "MANUAL"
    # Sort by name, ascending
    list.sort!
    # Sort in which order?
    case SORT_ORDER
    when "DESC" # name, descending
      list.reverse!
    when "MANUAL" # manually specified
      # Temporary list
      list_temp = []
      # Check manually from the current list
      for manual_song in MANUAL_SORT
        # Iterate at manual list...
        for song in list
          # Name equal?
          if song.downcase == manual_song.downcase
            # Add into song list
            list_temp.push(song)
            break
          end
        end
      end
      # Is there by any chance you forgot some songs?
      if list_temp.size < list.size
        # Reiterate the current list
        for song in list
          # If not included in temporary list, add
          unless list_temp.include?(song)
            list_temp.push(song)
          end
        end
      end
      # Swap back
      list = list_temp
    end
    for music in list
      @songs.push(Game_Songs.new(music))
    end
    # Automatically unlock title BGM
    # (since New Game command will unexpectedly remade $game_system object)
    for song in @songs
      if song.name == $data_system.title_bgm.name
        song.unlock
        @unlocks.push(song)
        break
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Play Background Music and unlocks them
  #     bgm : background music to be played
  #--------------------------------------------------------------------------
  def bgm_play(bgm)
    @playing_bgm = bgm
    if bgm != nil and bgm.name != ""
      Audio.bgm_play("Audio/BGM/" + bgm.name, bgm.volume, bgm.pitch)
      # Find the BGM in song list and unlock
      for song in @songs
        if song.name == bgm.name
          unless song.unlocked?
            song.unlock
            @unlocks.push(song)
          end
          break
        end
      end
    else
      Audio.bgm_stop
    end
    Graphics.frame_reset
  end
end

# This class draws a list of songs in a window
class Window_Songs < Window_Selectable
  # initialization
  def initialize(x, y, width, height)
    super(x, y, width, height)
    @item_max = $game_system.songs.size
    @column_max = 1
    self.contents = Bitmap.new(width - 32, @item_max * 32)
    @index = 0
    refresh
  end
  def refresh
    self.contents.clear
    # Depending on sorting mode, take the appropriate list
    case SORT_ORDER
    when "ASC"
    when "DESC"
    when "MANUAL"
      song_list = $game_system.songs
    when "APP"
      song_list = $game_system.unlocks
    end
    for i in 0 ... song_list.size
      song = song_list[i]
      # Show numbering?
      if SHOW_NUMBER
        name = "#{i + 1}. "
      else
        name = ""
      end
      # If unlocked, draw song name
      if song.unlocked?
        name += song.name
        color = normal_color
      else
        name += LOCKED_NAME
        color = disabled_color
      end
      self.contents.font.color = color
      self.contents.draw_text(4, i * 32, 640, 32, name)
    end
    # In APP mode, enlist any other songs as locked
    if SORT_ORDER == "APP"
      offset = i + 1
      total_song = $game_system.songs.size
      for j in offset .. total_song
        name = "#{j + 1}. #{LOCKED_NAME}"
        color = disabled_color
        self.contents.font.color = color
        self.contents.draw_text(4, j * 32, 640, 32, name)
      end
    end
  end
  # update window help
  def update_help
    @help_window.update
  end
end

# This class performs scene for song list.
class Scene_Songs
  # initialization
  def main
    y = (SHOW_STATUS ? 64 : 0)
    @song_window = Window_Songs.new(0, y, 640, 480 - y)
    # Show status if necessary
    if SHOW_STATUS
      @help_window = Window_Help.new
      unlocked = 0
      total = $game_system.songs.size
      for song in $game_system.songs
        unlocked += 1 if song.unlocked?
      end
      # Set help window text based on settings
      case STATUS_TYPE
      when "NORMAL"
        text = NORMAL_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (unlocked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += unlocked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      when "REMAINING"
        locked = total - unlocked
        text = REMAINING_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (locked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += locked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      end
      @help_window.set_text(text)
      @song_window.help_window = @help_window
    end
    # Remember BGM and BGS so we can restore it later
    @last_bgm = $game_system.playing_bgm.name
    $game_system.bgm_memorize
    $game_system.bgs_memorize
    Graphics.transition
    # start scene
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    # Disposal of windows
    @song_window.dispose
    @help_window.dispose unless @help_window == nil
    # Restore BGM and BGS
    $game_system.bgm_restore
    $game_system.bgs_restore
  end
  
  def update
    @song_window.update
    # If C button is pressed
    if Input.trigger?(Input::C)
      # If songs is locked...
      # Depending on sort order, pick the right song
      case SORT_ORDER
      when "APP"
        song = $game_system.unlocks[@song_window.index]
      else
        song = $game_system.songs[@song_window.index]
      end
      # ONLY IF song is found...
      if song == nil
        # song still locked, play buzzer
        $game_system.se_play($data_system.buzzer_se)
      else
        if song.unlocked?
          # If last BGM is different than current
          if @last_bgm != song.name
            @last_bgm = song.name
            # Fade out current BGM and play the selected one
            Audio.bgm_fade(100)
            Audio.bgm_play("Audio/BGM/" + song.name)
          end
        else
          # song still locked, play buzzer
          $game_system.se_play($data_system.buzzer_se)
        end
      end
      return
    end
    # If B button is pressed
    if Input.trigger?(Input::B)
      # Play cancel SE
      $game_system.se_play($data_system.cancel_se)
      # Switch to map screen
      $scene = Scene_Map.new
      return
    end
  end
end

# Rewrites to Ruby's Float class to accomodate conversion into percentage string
class Float < Numeric
  # Convert to percentage (string)
  # Returns string (self is not modified)
  def to_percent(decimal_digit = 0, round = true, include_percent_sign = true,
    decimal_sign = ".")
    # Error checking
    decimal_digit = 0 if decimal_digit < 0
    decimal_sign = "." unless decimal_sign.is_a?(String)
    # Modify number so the first decimal digit is the significant digit
    # we want to round
    percent = self * 100 * (10 ** decimal_digit)
    # Perform round if necessary
    if round
      percent = percent.round
    end
    # Revert back to floating number (already in percentage)
    percent = percent.to_f / (10 ** decimal_digit).to_f
    # Convert to string
    percent = percent.to_s
    # Get integer digits
    leading = percent[/(\d)*./]
    # Remove decimal sign
    leading = leading[0, leading.size - 1]
    # Get decimal digits
    digit = percent[/\D(\d)*/]
    # Strip off decimal sign
    digit = digit[1, digit.size - 1]
    # If by any case, after rounding, decimal digit length is less than desired,
    # add trailing zeroes
    if digit.length < decimal_digit
      digit += "0" * (decimal_digit - digit.length)
    # Or if rounding wasn't performed and decimal digit length is
    # more than desired, redefine decimal digit
    elsif digit.length > decimal_digit and truncate
      digit = digit[0, decimal_digit]
    end
    percent = leading + decimal_sign + digit
    # Include percent sign (%)?
    percent = percent + "%" if include_percent_sign
    return percent
  end
end

I haven't tested all possibilities (since my project has 71 songs, RTP default), but with some data it seems okay. Tell me if there's something not working as expected.
 
I assume, you didn't notice that the new script had been finished, so let me bump this.

Anyway, I forgot to tell you:
1. The using of this script is still the same: just use Call Script event command and type in:
Code:
$scene = Scene_Songs.new
2. Again, old save files won't work, and there's no way you can prevent that.

Does it work?
 

sargcj

Member

Nice work, Props to you. Though I haven't tested it. it Seems like it would be a little hard, but I guess once you get good at programming and scripting it is easy as pie for some things... lol
 
Well, thanks for reminding me that possibility. I tested by saving a game with 70 songs and loading the save file with more/less songs (let's say 64/72 songs). It didn't give me any errors, meaning you can continue the game as usual, but the song list was not updated (it still had 70 songs, not 64/72 as expected).

Here's the new script, so now you will always have the latest song list, regardless your save file. If you remove a previously-unlocked song from your current game, it won't be enlisted until the song is present again.

Replace my old script with this one:
Code:
# SETTINGS BEGIN
# Text that will appear if a song is locked.
LOCKED_NAME = "???"
# Sort order. Possible values:
# ASC = name, ascending (A-Z)
# DESC = name, descending (Z-A).
# APP = by appearance.
# MANUAL = manually specified.
# Any other values will be regarded as ASC.
SORT_ORDER = "APP"
# If using MANUAL sort order, fill in the order here.
# You SHOULD specify all song names here, but in case there is
# any songs in your Audio/BGM folder that you forgot to enlist,
# they will be appended to the end of this list at ascending order.
# Include file extensions (it's too risky to omit file extensions,
# since you might have two files with the same name but different type).
# Case insensitive (am I generous, not? ;)).
# Separate each entry by a comma.
# Be careful not to enter one song more than once, or your player
# will never have 100% unlocks.
# See example:
MANUAL_SORT =
[
"002-Battle02.mid",
"043-Positive01.mid",
"001-Battle01.mid",
"064-Slow07.mid",
]
# Show numbering?
SHOW_NUMBER = true
# Show unlock status?
# If set to true, a help window will show how many songs are (un)locked.
# More settings for status window are available.
SHOW_STATUS = true
# Settings below would not take effect if SHOW_STATUS is set to false.
# --------------------------------------------------------------------
# Status type
# Possible values:
# NORMAL: shows numbers of unlocked songs. Default.
# e.g. Unlocked songs: 3
# REMAINING: shows numbers of locked songs.
# e.g. Locked songs remaining: 7
# Any other values will be regarded as NORMAL.
# Can also include total song (see SHOW_TOTAL_SONGS).
STATUS_TYPE = "NORMAL"
# Use percentage instead?
USE_PERCENT = false
# How many decimal digits will be displayed? Default is 2.
# Does not have effect if USE_PERCENT is set to false
PERCENT_DEC_DIGIT = 2
# Decimal sign, in case you're not using English numbering (".")
# Does not have effect if USE_PERCENT is set to false
DECIMAL_SIGN = "."
# Define the status texts as you wish
NORMAL_TEXT = "Unlocked songs: "
REMAINING_TEXT = "Locked songs remaining: "
# Show total songs?
# Does not have effect if USE_PERCENT is set to true
SHOW_TOTAL_SONGS = true
# SETTINGS END

=begin
*******************
Notes on percentage
*******************
As you can see below, I modified Float class to be able to convert itself
into a percentage string. For example:

(2.1).to_percent => 210%

There are four optional parameters (you can specify or omit them):

1. decimal_digit = 0
   Specifies how many digit after the decimal sign will be taken.
   For example:
   
   (0.98765).to_percent(2) will result in 98.76%
   (or 98.77%, depending on next parameters).
   
   Default is 0 (no decimal digit will be taken).
   
2. round = true
   If true, rounding will be performed (see example).
   Otherwise, pick only needed decimal digits and discard the rest (truncate).
   Example:
   
   (0.98765).to_percent(2, false) => 98.76%
   (0.98765).to_percent(2, true)  => 98.77%
   (0.98764).to_percent(2)        => 98.76%
   
   Default is true (perform rounding).
   
3. include_percent_sign = true
   If by any chance you don't want to include percent sign (%), maybe to
   be processed furthermore, set this parameter to false.
   Example:
   
   (0.98765).to_percent(2, false, true)  => 98.76%
   (0.98765).to_percent(2, false, false) => 98.76
   
   Remember that the result is still in string, so you may need to convert
   before processing.
   Default is true (include percent sign).
   
4. decimal_sign = "."
   Very useful if you don't use English number formatting. Some language
   use "," instead of ".". For example:
   
   (0.98765).to_percent(2, false, ",") => 98,76%
   
   Default is ".".
=end

# This class holds song data
class Game_Songs
  attr_reader :name       # song name
  attr_reader :unlocked   # song state
  
  # initialization
  def initialize(filename)
    # Strip extension from filename
    @name = filename[0, filename.length - 4]
    # Lock this song
    @unlocked = false
  end
  
  # unlock the song
  def unlock
    @unlocked = true
  end
  
  # unlocked?
  def unlocked?
    return @unlocked
  end
end

class Game_System
  attr_reader :songs    # list of all songs
  attr_reader :unlocks  # only used when SORT_ORDER is APP or MANUAL
                        # also contains the same song object as @songs
                        # but in different order
  alias song_initialize initialize
  def initialize
    song_initialize
    # Make song lists
    get_song_list
  end
  # get the latest song list
  def get_song_list
    # First, get the current songs from Audio/BGM folder
    list = Dir.entries(File.expand_path("Audio/BGM"))
    list.delete_at 0
    list.delete_at 0
    # Update flag
    update_flag = false
    # If not starting a new game
    unless @songs == nil
      # Reserve old ones
      old_songs = @songs
      old_unlocks = @unlocks
      # Pretended as updating, although the list remains the same
      update_flag = true
    end
    # Prepare new lists
    @songs = []
    # List unlocks when necessary
    @unlocks = [] if SORT_ORDER == "APP" or SORT_ORDER == "MANUAL"
    # Sort by name, ascending
    list.sort!
    # Sort in which order?
    case SORT_ORDER
    when "DESC" # name, descending
      list.reverse!
    when "MANUAL" # manually specified
      # Temporary list
      list_temp = []
      # Check manually from the current list
      for manual_song in MANUAL_SORT
        # Iterate at manual list...
        for song in list
          # Name equal?
          if song.downcase == manual_song.downcase
            # Add into song list
            list_temp.push(song)
            break
          end
        end
      end
      # Is there by any chance you forgot some songs?
      if list_temp.size < list.size
        # Reiterate the current list
        for song in list
          # If not included in temporary list, add
          unless list_temp.include?(song)
            list_temp.push(song)
          end
        end
      end
      # Swap back
      list = list_temp
    end
    # Now add the newest songs to the list
    # Depending on sort order...
    case SORT_ORDER
    when "APP", "MANUAL"
      # Immediately add the old unlocks if updating
      @unlocks = old_unlocks if update_flag
    end
    # Iterate through latest list
    for music in list
      current_song = Game_Songs.new(music)
      # If updating
      if update_flag
        # Check if it's already unlocked by previous games
        for song in old_songs
          if song.name == music[0, music.length - 4]
            if song.unlocked?
              # Already unlocked previously, unlock again
              current_song.unlock
            end
          end
        end
      end
      # Add to latest list
      @songs.push(current_song)
    end
    # Automatically unlock title BGM
    # (since New Game command will unexpectedly remake $game_system object)
    # UNLESS you're updating
    unless update_flag
      for song in @songs
        if song.name == $data_system.title_bgm.name
          song.unlock
          @unlocks.push(song) if SORT_ORDER == "APP" or SORT_ORDER == "MANUAL"
          break
        end
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Play Background Music and unlocks them
  #     bgm : background music to be played
  #--------------------------------------------------------------------------
  def bgm_play(bgm)
    @playing_bgm = bgm
    if bgm != nil and bgm.name != ""
      Audio.bgm_play("Audio/BGM/" + bgm.name, bgm.volume, bgm.pitch)
      # Find the BGM in song list and unlock
      for song in @songs
        if song.name == bgm.name
          unless song.unlocked?
            song.unlock
            @unlocks.push(song) if SORT_ORDER == "APP" or SORT_ORDER == "MANUAL"
          end
          break
        end
      end
    else
      Audio.bgm_stop
    end
    Graphics.frame_reset
  end
end

# This class draws a list of songs in a window
class Window_Songs < Window_Selectable
  # initialization
  def initialize(x, y, width, height)
    super(x, y, width, height)
    @item_max = $game_system.songs.size
    @column_max = 1
    self.contents = Bitmap.new(width - 32, @item_max * 32)
    @index = 0
    refresh
  end
  def refresh
    self.contents.clear
    # Depending on sorting mode, take the appropriate list
    case SORT_ORDER
    when "APP"
      song_list = $game_system.unlocks
    else
      song_list = $game_system.songs
    end
    for i in 0 ... song_list.size
      song = song_list[i]
      # Show numbering?
      if SHOW_NUMBER
        name = "#{i + 1}. "
      else
        name = ""
      end
      # If unlocked, draw song name
      if song.unlocked?
        name += song.name
        color = normal_color
      else
        name += LOCKED_NAME
        color = disabled_color
      end
      self.contents.font.color = color
      self.contents.draw_text(4, i * 32, 640, 32, name)
    end
    # In APP mode, enlist any other songs as locked
    if SORT_ORDER == "APP"
      offset = i + 1
      total_song = $game_system.songs.size
      for j in offset .. total_song
        name = "#{j + 1}. #{LOCKED_NAME}"
        color = disabled_color
        self.contents.font.color = color
        self.contents.draw_text(4, j * 32, 640, 32, name)
      end
    end
  end
  # update window help
  def update_help
    @help_window.update
  end
end

# This class performs scene for song list.
class Scene_Songs
  # initialization
  def main
    y = (SHOW_STATUS ? 64 : 0)
    @song_window = Window_Songs.new(0, y, 640, 480 - y)
    # Show status if necessary
    if SHOW_STATUS
      @help_window = Window_Help.new
      unlocked = 0
      total = $game_system.songs.size
      for song in $game_system.songs
        unlocked += 1 if song.unlocked?
      end
      # Set help window text based on settings
      case STATUS_TYPE
      when "NORMAL"
        text = NORMAL_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (unlocked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += unlocked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      when "REMAINING"
        locked = total - unlocked
        text = REMAINING_TEXT
        # Use percent style?
        if USE_PERCENT
          percent = (locked.to_f / total.to_f).to_percent(PERCENT_DEC_DIGIT)
          text += percent
        else
          text += locked.to_s + (SHOW_TOTAL_SONGS ? "/#{total}" : "")
        end
      end
      @help_window.set_text(text)
      @song_window.help_window = @help_window
    end
    # Remember BGM and BGS so we can restore it later
    @last_bgm = $game_system.playing_bgm.name
    $game_system.bgm_memorize
    $game_system.bgs_memorize
    Graphics.transition
    # start scene
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    # Disposal of windows
    @song_window.dispose
    @help_window.dispose unless @help_window == nil
    # Restore BGM and BGS
    $game_system.bgm_restore
    $game_system.bgs_restore
  end
  
  def update
    @song_window.update
    # If C button is pressed
    if Input.trigger?(Input::C)
      # If songs is locked...
      # Depending on sort order, pick the right song
      case SORT_ORDER
      when "APP"
        song = $game_system.unlocks[@song_window.index]
      else
        song = $game_system.songs[@song_window.index]
      end
      # ONLY IF song is found...
      if song == nil
        # song still locked, play buzzer
        $game_system.se_play($data_system.buzzer_se)
      else
        if song.unlocked?
          # If last BGM is different than current
          if @last_bgm != song.name
            @last_bgm = song.name
            # Fade out current BGM and play the selected one
            Audio.bgm_fade(100)
            Audio.bgm_play("Audio/BGM/" + song.name)
          end
        else
          # song still locked, play buzzer
          $game_system.se_play($data_system.buzzer_se)
        end
      end
      return
    end
    # If B button is pressed
    if Input.trigger?(Input::B)
      # Play cancel SE
      $game_system.se_play($data_system.cancel_se)
      # Switch to map screen
      $scene = Scene_Map.new
      return
    end
  end
end

# Rewrites to Ruby's Float class to accomodate conversion into percentage string
class Float < Numeric
  # Convert to percentage (string)
  # Returns string (self is not modified)
  def to_percent(decimal_digit = 0, round = true, include_percent_sign = true,
    decimal_sign = ".")
    # Error checking
    decimal_digit = 0 if decimal_digit < 0
    decimal_sign = "." unless decimal_sign.is_a?(String)
    # Modify number so the first decimal digit is the significant digit
    # we want to round
    percent = self * 100 * (10 ** decimal_digit)
    # Perform round if necessary
    if round
      percent = percent.round
    end
    # Revert back to floating number (already in percentage)
    percent = percent.to_f / (10 ** decimal_digit).to_f
    # Convert to string
    percent = percent.to_s
    # Get integer digits
    leading = percent[/(\d)*./]
    # Remove decimal sign
    leading = leading[0, leading.size - 1]
    # Get decimal digits
    digit = percent[/\D(\d)*/]
    # Strip off decimal sign
    digit = digit[1, digit.size - 1]
    # If by any case, after rounding, decimal digit length is less than desired,
    # add trailing zeroes
    if digit.length < decimal_digit
      digit += "0" * (decimal_digit - digit.length)
    # Or if rounding wasn't performed and decimal digit length is
    # more than desired, redefine decimal digit
    elsif digit.length > decimal_digit and truncate
      digit = digit[0, decimal_digit]
    end
    percent = leading + decimal_sign + digit
    # Include percent sign (%)?
    percent = percent + "%" if include_percent_sign
    return percent
  end
end

# refresh song list (in case it is different from the last save)
class Scene_Load < Scene_File
  alias song_on_decision on_decision
  def on_decision(filename)
    # Calls original loading data on decision
    song_on_decision(filename)
    # Update song list
    $game_system.get_song_list
  end
end

Don't worry, this time your old save files will work, with the latest song list.

Note that you should put this script below any other saving-loading scripts (e.g. 99 save slots) as it modifies Scene_Load class (but I aliased everything, so it should be no problem).

Tell me if there is something more, or if it doesn't work as expected.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top