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.

Scripting: Creating new Data Structure Classes

Creating new Data Structure Classes
(Very rough draft)


Introduction
This is just a little quick tutorial inspired from seeing support topics. It will explain how to create new data structure classes (such as the Actor/Class/Weapon/Etc. classes from the RPG module in RMXP/VX). This tutorial is for more advanced scripters.

Data structure classes are basically "containers" for all your objects data you set in the database editor. Graphics, names, powers, descriptions, etc. all are stored in Ruby objects. But what if you wanted to add a new data structure class for something new, say a treasure chest? What's the best way to organize and read all this data? Data Structures.

Data Structures in XP/VX
Before we start making our own data structure classes and object, we need to figure out a practical way to store and access this data. RMXP/VX has a effective way of doing this and for purposes of this tutorial, we will follow the way XP/VX stores and accesses this data.

Whenever you create your objects in the database editor and press save, all our data is saved for us into rxdata files. We don't have the ability to be able to do this by ourselves, we have to script the creation of our objects and if we wish, save the .rxdata files ourselves.

When the game is loaded, the objects are loaded into data arrays. You can see this in Scene_Title directly under main:[rgss]    # Load database
    $data_actors        = load_data("Data/Actors.rxdata")
    $data_classes       = load_data("Data/Classes.rxdata")
    $data_skills        = load_data("Data/Skills.rxdata")
    $data_items         = load_data("Data/Items.rxdata")
    $data_weapons       = load_data("Data/Weapons.rxdata")
    $data_armors        = load_data("Data/Armors.rxdata")
    $data_enemies       = load_data("Data/Enemies.rxdata")
    $data_troops        = load_data("Data/Troops.rxdata")
    $data_states        = load_data("Data/States.rxdata")
    $data_animations    = load_data("Data/Animations.rxdata")
    $data_tilesets      = load_data("Data/Tilesets.rxdata")
    $data_common_events = load_data("Data/CommonEvents.rxdata")
    $data_system        = load_data("Data/System.rxdata")
[/rgss]
Without going into this deeply, the load_data is a special method that reads Ruby objects from rxdata files (works in conjunction with the save_data method that saves Ruby objects into rxdata files). So we setting global arrays (that can be read everywhere in your scripts) that hold our objects data. The first object in these list is always nil, because in our editor, objects start with the ID of 1 (Aluxes' ID is 1, not 0). We use this @id to reference attributes of our objects in the in these $data arrays. For example, to get weapon ID#4's name, we use:
Code:
name = $data_weapons[4].name

We will continue this practice of making the first object nil and having an @id instance in all our objects we create.

Making your first data structure class
Whenever you are thinking of this new object you need to think of it as a real world object: What attributes and functions does this object have? Attributes are the easiest to setup: just make a list of possible details about your object. Name, icon, description and other physical traits of the object you will be reading at later times. Functions are slightly harder, and you can add these later. Your object may not need functions at all.

For this tutorial, we will think of a treasure chest. What attributes does a treasure chest have? We will make a simple list for our treasure chest:
  • Name
  • Sound effect when opening
  • Items
  • Weapons
  • Armors
  • Possible battle of occurring instead when opening
We could add others but we'll keep it simple for now. As we decided above, we are also going to add our special attribute @id. We may not need this, but it's practical to have it in there.

Ruby comes with a very cool class called Struct. It allows us to make Data Structure classes more quickly. Lets look at the class RPG::Weapon, how we would make it without the Struct class and with the Struct class.
[rgss]# Without Struct class
module RPG
  class Weapon
    def initialize
      @id = 0
      @name = ""
      @icon_name = ""
      @description = ""
      @animation1_id = 0
      @animation2_id = 0
      @price = 0
      @atk = 0
      @pdef = 0
      @mdef = 0
      @str_plus = 0
      @dex_plus = 0
      @agi_plus = 0
      @int_plus = 0
      @element_set = []
      @plus_state_set = []
      @minus_state_set = []
    end
    attr_accessor :id
    attr_accessor :name
    attr_accessor :icon_name
    attr_accessor :description
    attr_accessor :animation1_id
    attr_accessor :animation2_id
    attr_accessor :price
    attr_accessor :atk
    attr_accessor :pdef
    attr_accessor :mdef
    attr_accessor :str_plus
    attr_accessor :dex_plus
    attr_accessor :agi_plus
    attr_accessor :int_plus
    attr_accessor :element_set
    attr_accessor :plus_state_set
    attr_accessor :minus_state_set
  end
end
 
# Using Struct Class (I did not include :id and you will see why below)
RPG::Weapon = Struct.new:)name, :icon_name, :description, :animation1_id,
  :animation2_id, :price, :atk, :pdef , :mdef, :str_plus, :dex_plus, :agi_plus,
  :int_plus, :element_set, :plus_state_set, :minus_state_set)
[/rgss]

In the Struct block, we didn't have to specify attr_accessor for each of our attibutes. The Struct class auto-creates these accessor methods for us. The disadvantage of the Struct class is for each of these attributes, you need to pass what attribute is when creating your object.
[rgss]# So instead of:
weapon = RPG::Weapon.new
weapon.name = 'Iron Sword'
weapon.icon_name = '001-Icon01'
# ...
# We have to pass every attribute when we create our object in the same order the class was created.
weapon = RPG::Weapon.new('Icon Sword', '001-Icon01', ...)
[/rgss]

You cannot rely on the initialize method set defaults for our instances. There are a few ways around this. First way is to only specify unique attributes that every object has, make our own initialize method and calling the Struct initialize method.
[rgss] 
RPG::Weapon = Struct.new:)name, :icon_name, :description)
class RPG::Weapon
  attr_accessor :animation1_id
  attr_accessor :animation2_id
  attr_accessor :price
  attr_accessor :atk
  attr_accessor :pdef
  attr_accessor :mdef
  attr_accessor :str_plus
  attr_accessor :dex_plus
  attr_accessor :agi_plus
  attr_accessor :int_plus
  attr_accessor :element_set
  attr_accessor :plus_state_set
  attr_accessor :minus_state_set
  alias_method :weapon_init, :initialize
  def initalize(*struct_args)
    # This sends name, icon name and description back to the Struct initialize method
    weapon_init(*struct_args)
    # Now we can set defaults for our other attributes
    @animation1_id = 0
    @animation2_id = 0
    @price = 0
    @atk = 0
    @pdef = 0
    @mdef = 0
    @str_plus = 0
    @dex_plus = 0
    @agi_plus = 0
    @int_plus = 0
    @element_set = []
    @plus_state_set = []
    @minus_state_set = []
  end
end
[/rgss]
For some of you this may not be practical for you and you would rather cut out the Struct class. I am simply offering an alternate way that some may want to try.

Another alternate I came up with was a Hash_Struct class that is similar to the Struct class. You use it just like the Struct class, can set defaults but you have to pass a hash with the attribute's name and value instead of just attribute's value. This also makes it so you do not have to pass attributes in a certain order. Feel free to ask me questions about this class as I am not going to get into this now:[rgss]#==============================================================================
# ** Classes.Hash_Struct
#------------------------------------------------------------------------------
# SephirothSpawn
# Version 0.1
# 2008-10-14
#------------------------------------------------------------------------------
#  This class imitates the Struct class however, it allows you to create a
#  struct class with defaults so when creating a struct object, all attributes
#  do not need to be passed.
#------------------------------------------------------------------------------
#  Examples:
#
#  This creates a new class "Test_Class" with 2 attributes: test1, test2
#   - Hash_Struct.new('Test_Class', {'test1' => 'a', 'test2' => 'b'})
#
#  This creates a Test_Class object, passing no arguments
#   - object1 = Hash_Struct::Test_Class.new
#
#   Now lets inspect
#    - p object1 => #<Hash_Struct::TestClass:0x3e7d0e8 @test2="b", @test1="a">
#
#  This creates a Test_Class object, passing 500 as the test1 value
#   - object2 = Hash_Struct::Test_Class.new('test1' => 500)
#
#   Now lets inspect
#    - p object2 => #<Hash_Struct::TestClass:0x3e7d0e8 @test2="b", @test1=500>
#==============================================================================
 
MACL::Loaded << 'Classes.Hash_Struct'
 
class Hash_Struct
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize(class_name, defaults_hash = {})
    # Start with class name
    s  = "class #{class_name};"
    # Save defaults hash as class variable
    s += "  @@defaults_hash = #{string(defaults_hash)};"
    # Create accessor methods
    defaults_hash.keys.each do |key|
      s += "  attr_accessor :#{key};"
    end
    # Object Initialization
    s += '  def initialize(hash = {});'
    # Passes through defaults
    s += '    @@defaults_hash.each do |key, value|;'
    # If value is defined
    s += '      if hash.has_key?(key);'
    # Define value
    s += '        eval ("@#{key} = hash[key]");'
    # If value not defined
    s += '      else;'
    # Set to default
    s += '        eval ("@#{key} = value");'
    s += '      end;'
    s += '    end;'
    s += '  end;'
    s += 'end;'
    eval s
  end
  #--------------------------------------------------------------------------
  # * String - Returns object in evaluatable format
  #--------------------------------------------------------------------------
  def string(object)
    case object
    when Array
      object.collect! {|x| string(x)}
      return "[#{object.join(',')}]"
    when Hash
      str = '{'
      object.each do |key, value|
        str += "#{string(key)} => #{string(value)},"
      end
      str += '}'
      return str
    when String
      return "'#{object}'"
    when Symbol
      return ":#{object}"
    else
      return object.to_s
    end
  end
end
[/rgss]


Ok. So now that we know a little about setting up a Struct class, let's do this.
[rgss]RPG::Treasure = Struct.new:)name, :sound_effect, :items, :weapons, :armors, :battle)
[/rgss]

Making objects with our Data Structure
Ok. We have a class that is going to be used to hold our objects data. Now what? Well as we learned, RM uses $data arrays that hold all this data. Truthfully, we don't need to even load these arrays within main in Scene_Title, so create a new section above Main. First thing you should do is paste the line from above at the top.

Now we create our data array with nil as the first object. You code should now look like this:
[rgss]RPG::Treasure = Struct.new:)name, :sound_effect, :items, :weapons, :armors, :battle)
 
$data_treasures = [nil]
[/rgss]

Special trick for setting ID: Since our @id is always increasing by 1 with every object we are creating, we can set this instance for us automatically in the initialize method as well as adding it directly to our $data array. We can only do this because we are creating our objects after the $data_treasures list is created.[rgss]RPG::Treasure = Struct.new:)name, :sound_effect, :items, :weapons, :armors, :battle)
 
class RPG::Treasure
  attr_accessor :id
  alias_method :treasure_init, :initialize
  def initialize(*struct_args)
    # Calls our struct initialize method
    treasure_init(*struct_args)
    # Set @id to be the size of our $data_treasures list
    @id = $data_treasures.size
    # Put our object automatically into the list
    $data_treasures[@id] = self
  end
end
 
$data_treasures = [nil]
 
RPG::Treasure.new('name', ...)
 
 
[/rgss]

The alternate to this is more simple, but requires more coding:
[rgss]RPG::Treasure = Struct.new:)id, :name, :sound_effect, :items, :weapons, :armors, :battle)
 
$data_treasures = [nil]
$data_treasures << RPG::Treasure.new(1, 'name', ...)
$data_treasures << RPG::Treasure.new(2, 'name', ...)
[/rgss]

Use whatever method is more practical for you.

In either case, after the line $data_treasures = [nil], you can start creating your objects. Remember, if you are making RPG::Treasure from the Struct class, you must pass the same number of attributes on the RPG::Treasure line as there are symbols passed in the Struct creation.

I am not going to get into the complete setup of our treasure chest attributes, how you plan on using them, etc. as this tutorial is more designed into setting up the Data Structure class and creating your object. If someone would like to me to finish this and show in more depth, let me know.

Now we can also add functions in this class as well. We can make these functions outside of this class and let other classes take care of them, but it's good use of a class to put them in here. Let's make a function called open, for when a player opens a chest.[rgss]RPG::Treasure = Struct.new:)name, :sound_effect, :items, :weapons, :armors, :battle)
 
class RPG::Treasure
  attr_accessor :id
  alias_method :treasure_init, :initialize
  def initialize(*struct_args)
    # Calls our struct initialize method
    treasure_init(*struct_args)
    # Set @id to be the size of our $data_treasures list
    @id = $data_treasures.size
    # Put our object automatically into the list
    $data_treasures[@id] = self
  end
  def open
    # play sound effect
    # If battle is true
      # start battle
      # return
    # end
    # gain items
    # gain weapons
    # gain armors
  end
end
[/rgss]
 
Once again, I did not go into the actual starting battle or gaining items/equipment here because of the number of ways you can set the items/equipment instances. To now perform this function, you would use:
Code:
$data_treasures[id].open
 
Using save_data/load_data methods
If you have a lot of objects (several treasure chest), you aren't going to want to re-create these objects every time your game is run. We can instead save the objects into Treasures.rxdata file and load them after all the objects are created. This is a very simple process.
 
To make this happen, we are going to make a flag to either create and save the data or just load the data. You will want to make sure this flag is true every time you modify any of the objects or it will just load your old objects. So you can have something like this:
[rgss]RPG::Treasure = Struct.new:)name, :sound_effect, :items, :weapons, :armors, :battle)
 
class RPG::Treasure
  attr_accessor :id
  alias_method :treasure_init, :initialize
  def initialize(*struct_args)
    # Calls our struct initialize method
    treasure_init(*struct_args)
    # Set @id to be the size of our $data_treasures list
    @id = $data_treasures.size
    # Put our object automatically into the list
    $data_treasures[@id] = self
  end
  def open
    # play sound effect
    # If battle is true
      # start battle
      # return
    # end
    # gain items
    # gain weapons
    # gain armors
  end
end
 
# Make this true if no rxdata file exist or if you have changed anything between
# this line and the save_data line
if true
 
  $data_treasures = [nil]
  RPG::Treasure.new('name', ...)
  RPG::Treasure.new('name', ...)
  RPG::Treasure.new('name', ...)
  RPG::Treasure.new('name', ...)
  RPG::Treasure.new('name', ...)
 
  # This saves our data into a rxdata file called "Treasures.rxdata"
  save_data($data_treasures, "Treasures.rxdata")
 
# If we have not modified the list above and Treasures.rxdata exist
else
 
  # Load our data like our default data objects in Scene_Title
  $data_treasures = [nil]
  $data_treasures = load_data("Treasures.rxdata")
end
[/rgss]

A simple format: If needed, create our objects and save them. If not needed, just load them.

Review
In closing, I hope you have learned something about making Data Structure classes and objects. Remember the Struct class (or Hash_Struct class) when creating classes. I hope this taught you a few tricks to making this process a little easier. If you have any questions, please do ask in this thread so others may benefit from them. Happy scripting!
 
Example from my Triple Triad System:
[rgss]#==============================================================================
# ** TripleTriad::Card
#==============================================================================
 
TripleTriad::Card = Struct.new:)name, :n, :e, :s, :w, :element, :price)
 
class TripleTriad::Card
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :id
  attr_accessor :deck_id
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  alias_method :seph_tt_ttcard_init, :initialize
  def initialize(*args)
    seph_tt_ttcard_init(*args)
    @id = $data_tt_cards.size
    $data_tt_cards[@id] = self
  end
  #--------------------------------------------------------------------------
  # * Rating
  #--------------------------------------------------------------------------
  def rating
    return TripleTriad.card_rating(self)
  end
  #--------------------------------------------------------------------------
  # * Deck Name
  #--------------------------------------------------------------------------
  def deck_name
    return $data_tt_decks[deck_id].name
  end
  #--------------------------------------------------------------------------
  # * Bitmap
  #--------------------------------------------------------------------------
  def bitmap(suffix = false)
    suffix = suffix ? ' - r' : ''
    fn = "Graphics/Triple Triad Cards/#{deck_name}/"
    return RPG::Cache.load_bitmap(fn, name + suffix, 0)
  end
end
 
#==============================================================================
# ** TripleTriad::Booster
#==============================================================================
 
TripleTriad::Booster = Struct.new:)name, :cards, :price)
 
class TripleTriad::Booster
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :id
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  alias_method :seph_tt_ttbooster_init, :initialize
  def initialize(*args)
    seph_tt_ttbooster_init(*args)
    @id = $data_tt_boosters.size
    $data_tt_boosters[@id] = self
  end
end
 
#==============================================================================
# ** TripleTriad::Deck
#==============================================================================
 
TripleTriad::Deck = Struct.new:)name, :level_names, :cards, :price)
 
class TripleTriad::Deck
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :id
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  alias_method :seph_tt_ttdeck_init, :initialize
  def initialize(*args)
    seph_tt_ttdeck_init(*args)
    @id = $data_tt_decks.size
    $data_tt_decks[@id] = self
  end
end
 
#==============================================================================
# ** Triple Triad Preset Deck/Card Builder
#==============================================================================
 
if TripleTriad::Decks::Run_Builder
 
#--------------------------------------------------------------------------
# * Create Deck/Card List
#--------------------------------------------------------------------------
$data_tt_cards, $data_tt_boosters, $data_tt_decks = [nil], [nil], [nil]
 
#--------------------------------------------------------------------------
# * Triple Triad Deck : Final Fantasy VII
#--------------------------------------------------------------------------
if TripleTriad::Decks::Include_Deck_FFVII  == true
 
TripleTriad::Card.new('Cockatrice', 6, 2, 2, 3, 0, 100)
TripleTriad::Card.new('Dorky Face', 2, 5, 2, 4, 7, 100)
TripleTriad::Card.new('Hedgehog Pie', 5, 2, 2, 4, 0, 100)
TripleTriad::Card.new('Mandragora', 4, 2, 3, 1, 0, 100)
TripleTriad::Card.new('Mu', 1, 2, 3, 4, 0, 100)
TripleTriad::Card.new('Roulette Cannon', 1, 2, 6, 2, 0, 100)
TripleTriad::Card.new('Slalom', 2, 6, 2, 1, 0, 100)
TripleTriad::Card.new('Spiral', 6, 2, 1, 1, 0, 100)
TripleTriad::Card.new('Sword Dance', 3, 3, 2, 2, 0, 100)
TripleTriad::Card.new('Touch Me', 2, 2, 3, 3, 0, 100)
TripleTriad::Card.new('Warning Board', 1, 5, 5, 1, 0, 100)
TripleTriad::Card.new('2-Faced', 2, 6, 1, 6, 0, 200)
TripleTriad::Card.new('Bagrisk', 7, 2, 3, 2, 0, 200)
TripleTriad::Card.new('BombGrenade', 1, 4, 5, 5, 1, 200)
TripleTriad::Card.new('Grangalan', 5, 3, 5, 1, 0, 200)
TripleTriad::Card.new('Grunt', 3, 6, 2, 2, 0, 200)
TripleTriad::Card.new('Guard System', 1, 7, 3, 1, 0, 200)
TripleTriad::Card.new('Icicle', 4, 4, 1, 4, 2, 200)
TripleTriad::Card.new('Jayjujayme', 2, 4, 4, 3, 0, 200)
TripleTriad::Card.new('Joker', 2, 3, 3, 7, 0, 200)
TripleTriad::Card.new('Snow', 1, 3, 7, 3, 2, 200)
TripleTriad::Card.new('Unknown', 3, 4, 3, 4, 1, 200)
TripleTriad::Card.new('8-Eye', 2, 6, 3, 6, 0, 350)
TripleTriad::Card.new('Cactuar', 1, 7, 5, 4, 0, 350)
TripleTriad::Card.new('Death Claw', 4, 3, 3, 7, 0, 350)
TripleTriad::Card.new('Golem', 6, 3, 1, 6, 0, 350)
TripleTriad::Card.new('Gremlin', 5, 5, 2, 5, 0, 350)
TripleTriad::Card.new('Ho-chu', 2, 4, 5, 6, 0, 350)
TripleTriad::Card.new('Jemnezmy', 6, 1, 7, 3, 0, 350)
TripleTriad::Card.new('Jumping', 7, 2, 5, 2, 0, 350)
TripleTriad::Card.new('Magic Pot', 5, 7, 3, 1, 0, 350)
TripleTriad::Card.new('Unknown 2', 4, 3, 6, 5, 7, 350)
TripleTriad::Card.new('Zemzelett', 4, 5, 4, 4, 5, 350)
TripleTriad::Card.new('Blugu', 3, 5, 7, 5, 0, 500)
TripleTriad::Card.new('Castanets', 3, 5, 3, 7, 0, 500)
TripleTriad::Card.new('Cripshay', 6, 7, 3, 1, 0, 500)
TripleTriad::Card.new('Doorbull', 6, 2, 6, 3, 0, 500)
TripleTriad::Card.new('Elfadunk', 6, 4, 3, 5, 0, 500)
TripleTriad::Card.new('Gagighandi', 6, 5, 7, 1, 0, 500)
TripleTriad::Card.new('Hungry', 4, 1, 7, 6, 0, 500)
TripleTriad::Card.new('Ice Golem', 7, 1, 6, 4, 2, 500)
TripleTriad::Card.new('Mighty Grunt', 6, 3, 5, 6, 0, 500)
TripleTriad::Card.new('Unknown 3', 7, 3, 4, 4, 7, 500)
TripleTriad::Card.new('Valron', 3, 3, 3, 7, 0, 500)
TripleTriad::Card.new('Allemagne', 4, 5, 6, 6, 0, 750)
TripleTriad::Card.new('Boundfat', 4, 6, 7, 5, 0, 750)
TripleTriad::Card.new('Christopher', 5, 5, 7, 4, 0, 750)
TripleTriad::Card.new('Edge Head', 5, 7, 3, 6, 0, 750)
TripleTriad::Card.new('Ghost Ship', 5, 6, 5, 6, 0, 750)
TripleTriad::Card.new('Gigas', 7, 4, 4, 6, 0, 750)
TripleTriad::Card.new('Iron Giant', 7, 5, 6, 2, 0, 750)
TripleTriad::Card.new('King Behemoth', 6, 7, 1, 7, 0, 750)
TripleTriad::Card.new('Malboro', 7, 1, 7, 4, 7, 750)
TripleTriad::Card.new('Master Tonberry', 5, 5, 5, 3, 0, 750)
TripleTriad::Card.new('Vlakorados', 6, 7, 6, 3, 0, 750)
TripleTriad::Card.new('Bottom Swell', 8, 1, 7, 5, 4, 1000)
TripleTriad::Card.new('Godo', 7, 3, 8, 3, 0, 1000)
TripleTriad::Card.new('Guard Scorpion', 7, 1, 7, 7, 3, 1000)
TripleTriad::Card.new('Heligunner', 6, 6, 2, 8, 0, 1000)
TripleTriad::Card.new('Hundred Gunner', 5, 2, 8, 7, 0, 1000)
TripleTriad::Card.new('Lost Number', 8, 1, 4, 8, 0, 1000)
TripleTriad::Card.new('Materia Keeper', 4, 8, 4, 6, 0, 1000)
TripleTriad::Card.new('Midgar Zolom', 8, 5, 8, 2, 0, 1000)
TripleTriad::Card.new('Motor Ball', 6, 8, 4, 3, 1, 1000)
TripleTriad::Card.new('Palmer', 1, 7, 8, 5, 0, 1000)
TripleTriad::Card.new('Schizo', 6, 7, 2, 7, 0, 1000)
TripleTriad::Card.new('Bizarro Sephiroth', 8, 5, 6, 5, 0, 1500)
TripleTriad::Card.new("Demon's Gate", 8, 3, 8, 1, 0, 1500)
TripleTriad::Card.new('Diamond Weapon', 8, 3, 8, 4, 0, 1500)
TripleTriad::Card.new('Emerald Weapon', 6, 8, 4, 6, 0, 1500)
TripleTriad::Card.new('Gi Nattak', 7, 6, 2, 8, 0, 1500)
TripleTriad::Card.new('Hojo', 7, 7, 7, 4, 0, 1500)
TripleTriad::Card.new('Jenova', 5, 2, 8, 8, 0, 1500)
TripleTriad::Card.new('Proud Clod', 5, 6, 5, 8, 0, 1500)
TripleTriad::Card.new('Red Dragon', 8, 2, 7, 7, 0, 1500)
TripleTriad::Card.new('Ruby Weapon', 6, 8, 1, 8, 0, 1500)
TripleTriad::Card.new('Ultimate Weapon', 6, 8, 7, 3, 0, 1500)
TripleTriad::Card.new('ChocoMog', 2, 9, 5, 9, 0, 2250)
TripleTriad::Card.new('Ifrit', 8, 1, 9, 7, 1, 2250)
TripleTriad::Card.new('Ramuh', 2, 4, 9, 9, 3, 2250)
TripleTriad::Card.new('Shiva', 9, 5, 7, 4, 2, 2250)
TripleTriad::Card.new('Titan', 6, 8, 2, 9, 6, 2250)
TripleTriad::Card.new('Avalanche', 8, 2, 8, 7, 0, 2250)
TripleTriad::Card.new('Elena', 1, 9, 8, 6, 0, 2250)
TripleTriad::Card.new('Reno', 6, 7, 5, 8, 0, 2250)
TripleTriad::Card.new('Rude', 4, 9, 9, 3, 0, 2250)
TripleTriad::Card.new('Tseng', 7, 9, 6, 3, 0, 2250)
TripleTriad::Card.new('Zack', 9, 4, 2, 9, 0, 2250)
TripleTriad::Card.new('Alexander', 6, 6, 4, 10, 8, 3500)
TripleTriad::Card.new('Bahamut', 7, 1, 7, 10, 0, 3500)
TripleTriad::Card.new('Bahamut Zero', 2, 8, 10, 6, 0, 3500)
TripleTriad::Card.new('Hades', 4, 10, 3, 8, 7, 3500)
TripleTriad::Card.new('Kjata', 10, 3, 9, 3, 0, 3500)
TripleTriad::Card.new('Knights of the Round', 10, 1, 10, 3, 0, 3500)
TripleTriad::Card.new('Leviathan', 5, 2, 10, 9, 4, 3500)
TripleTriad::Card.new('Neo Bahamut', 10, 9, 4, 3, 0, 3500)
TripleTriad::Card.new('Odin', 2, 6, 10, 8, 0, 3500)
TripleTriad::Card.new('Phoenix', 5, 10, 4, 6, 1, 3500)
TripleTriad::Card.new('Typoon', 6, 9, 1, 10, 5, 3500)
TripleTriad::Card.new('Aeris', 8, 6, 10, 3, 8, 5000)
TripleTriad::Card.new('Barret', 10, 2, 9, 7, 0, 5000)
TripleTriad::Card.new('Cait Sith', 8, 5, 5, 10, 0, 5000)
TripleTriad::Card.new('Cid', 5, 10, 5, 9, 0, 5000)
TripleTriad::Card.new('Cloud', 10, 7, 6, 6, 0, 5000)
TripleTriad::Card.new('Nanaki', 5, 8, 8, 8, 0, 5000)
TripleTriad::Card.new('Rufus', 5, 6, 7, 10, 0, 5000)
TripleTriad::Card.new('Sephiroth', 6, 10, 6, 6, 0, 5000)
TripleTriad::Card.new('Tifa', 6, 8, 10, 5, 0, 5000)
TripleTriad::Card.new('Vincent', 1, 9, 7, 10, 0, 5000)
TripleTriad::Card.new('Yuffie', 8, 10, 7, 2, 0, 5000)
 
for i in ($data_tt_cards.size - 110)...($data_tt_cards.size)
  $data_tt_cards.deck_id = $data_tt_decks.size
end
 
ln = ['Monsters', 'Monsters', 'Monsters', 'Monsters', 'Monsters',
      'Bosses', 'Bosses', 'Summons & Heros', 'Summons', 'Heros']
cards = []
cards << ($data_tt_cards.size - 110..$data_tt_cards.size - 100).to_a
cards << ($data_tt_cards.size - 99..$data_tt_cards.size - 89).to_a
cards << ($data_tt_cards.size - 88..$data_tt_cards.size - 78).to_a
cards << ($data_tt_cards.size - 77..$data_tt_cards.size - 67).to_a
cards << ($data_tt_cards.size - 66..$data_tt_cards.size - 56).to_a
cards << ($data_tt_cards.size - 55..$data_tt_cards.size - 45).to_a
cards << ($data_tt_cards.size - 44..$data_tt_cards.size - 34).to_a
cards << ($data_tt_cards.size - 33..$data_tt_cards.size - 23).to_a
cards << ($data_tt_cards.size - 22..$data_tt_cards.size - 12).to_a
cards << ($data_tt_cards.size - 11..$data_tt_cards.size - 1).to_a
 
TripleTriad::Deck.new('Final Fantasy VII', ln, cards, 50000)
 
end
 
#--------------------------------------------------------------------------
# * Booster Builder
#--------------------------------------------------------------------------
 
for deck_id in 1...$data_tt_decks.size
  prices = [950, 1980, 3690, 5760, 8550]
  suffix = ['A', 'B', 'C', 'D', 'E']
  cards = {
    0 => {0 => 3, 1 => 2, 2 => 1},
    1 => {0 => 4, 1 => 3, 2 => 2, 3 => 1},
    2 => {0 => 5, 1 => 4, 2 => 3, 3 => 2, 4 => 1},
    3 => {1 => 5, 2 => 4, 3 => 3, 4 => 2, 5 => 1},
    4 => {2 => 5, 3 => 4, 4 => 3, 5 => 2, 6 => 1},
  }
  for i in 0..4
    name = $data_tt_decks[deck_id].name + " #{suffix}"
    b_cards = {deck_id => cards}
    TripleTriad::Booster.new(name, b_cards, prices)
  end
end
 
#--------------------------------------------------------------------------
# * Save Data
#--------------------------------------------------------------------------
save_data($data_tt_cards, 'Data/Triple Triad Cards.rxdata')
save_data($data_tt_decks, 'Data/Triple Triad Decks.rxdata')
save_data($data_tt_boosters, 'Data/Triple Triad Boosters.rxdata')
 
else
 
#--------------------------------------------------------------------------
# * Load Data
#--------------------------------------------------------------------------
$data_tt_cards = load_data('Data/Triple Triad Cards.rxdata')
$data_tt_decks = load_data('Data/Triple Triad Decks.rxdata')
$data_tt_boosters = load_data('Data/Triple Triad Boosters.rxdata')
 
end
[/rgss]

It is more complicated but follows the basic outline of this tutorial.
 
Ok... The Hash_Struct is a pretty simple class. It doesn't have all the methods the Ruby Struct class has. It's a lot like a work around for methods with hash arguments (since Ruby doesn't not support this yet).

Lets say for our Treasure chest we want these defaults:
  • Name = 'Treasure Chest'
  • Sound effect = '001-System01'
  • Items = []
  • Weapons = []
  • Armors = []
  • Battle = nil

So we create our RPG::Treasure class like so:
[rgss]Hash_Struct.new('RGP::Treasure', {'name' => 'Treasure Chest', 'se' => '001-System01', 'items' => [], 'weapons' => [], 'armors' => [], 'battle' => nil})
 
class RPG::Treasure
  attr_accessor :id
  alias_method :treasure_init, :initialize
  def initialize(*struct_args)
    # Calls our struct initialize method
    treasure_init(*struct_args)
    # Set @id to be the size of our $data_treasures list
    @id = $data_treasures.size
    # Put our object automatically into the list
    $data_treasures[@id] = self
  end
end
[/rgss]

So let's say we want treasure 1 to have the sound effect '001-System02' and items [1, 2, 3].

We would use:
[rgss]$data_treasures = [nil]
 
RPG::Treasure.new({'se' => '001-System02', 'items' => [1,2,3]})
[/rgss]

Name, weapons, armors and battle will take the defaults set from the Hash_Struct.new line.

Hope that helps. :thumb:
 
Thanks for the help, but this maybe will be annoy xD, it's possible to use all of that in combination with a script from the macl:

Systems.Loading Data From Text Files

I've seen trickster used a lot of time, to make more easier define this new attributes. for example to avoid use the script editor to make the new objects, and large texts.

Maybe you or another scripter can check it out, because this is very complex.

This is an example:

http://rapidshare.com/files/195354620/S ... s.rar.html

Thanks again.
 

Zeriab

Sponsor

Good work Seph. It's nice seeing a new tutorial from you ^_^
I would say that a data structure is some sort of organization of data. A very general concept. This is how it is used in computer science. Your different use of the term can naturally confuse.
I would say that this tutorial shows how XP/VX structures static data and how to create and structure data in a similar fashion. Additionally you show how certain tools can be used to speed up the process and make it easier to manage.

Your section Data Structures in XP/VX section is indeed what I would think it is with the typical data structure concept. Basically you answer how is the data structured in XP/VX, where the data is the RPG::XXX objects the editor creates for you. I would call them data objects whose purpose is only to hold data and not provide any behavior. I suggest your focus even more on 'This is how it is done in XP/VX'.
Note that while $data_XXX are arrays they are used as a hash. Basically they provide a mapping from the id of an object to the object itself. They are examples of best-case hashes (also called Perfect Hashes I believe).
Note the difference between the conceptual idea of the data structure and how it is implemented.

Your Making your first data structure class section explains how to use the Struct class and the advantages and disadvantages of using it. I do not like that your focus is very much on the tool and not really on the modeling part. Don't misunderstand me. I think you explain how to use the tool well, but the tool is something you use to implement the model or help you implementing the model.
It would really be nice if you included a discussion about how to analyze what properties a chest have and which are relevant/irrelevant to have. Only when we have modeled the chest (or anything else really) with important properties and what-not should we look at how we can implement the model.
Now about your interesting Hash_Struct
I have tried restructuring your class so you can use it like how you use the Struct.new class
Firstly I have overwritten the self.new method rather than the initialize method since I see no reason to make an instance of the Hash_Struct class to create another class.
Secondly I create an anonymous class for which I create the accessors and initialize method rather than a named class. Hence the reason for the exclusion of class name as an argument.
I return the created class rather than an object of the Hash_Struct class.
[rgss]#==============================================================================
# ** Classes.Hash_Struct
#------------------------------------------------------------------------------
# SephirothSpawn
# Version 0.1
# 2008-10-14
#------------------------------------------------------------------------------
#  This class imitates the Struct class however, it allows you to create a
#  struct class with defaults so when creating a struct object, all attributes
#  do not need to be passed.
#------------------------------------------------------------------------------
#  Examples:
#
#  This creates an anonymous new class with 2 attributes: test1, test2
#   - Hash_Struct.new('test1' => 'a', 'test2' => 'b')
#
#  This creates a named class
#   - Test_Class = Hash_Struct.new('test1' => 'a', 'test2' => 'b')
#
#  This creates a Test_Class object, passing no arguments
#   - object1 = Test_Class.new
#
#   Now lets inspect
#    - p object1 => #<Test_Class:0x1735c18 @test1="a", @test2="b">
#
#  This creates a Test_Class object, passing 500 as the test1 value
#   - object2 = Hash_Struct::Test_Class.new('test1' => 500)
#
#   Now lets inspect
#    - p object2 => #<Test_Class:0x1735108 @test1=500, @test2="b">
#==============================================================================
 
if Module.constants.include?('MACL') && MACL.const_defined?('Loaded')
  MACL::Loaded << 'Classes.Hash_Struct'
end
 
class Hash_Struct
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def self.new(defaults_hash = {})
    # Create an anonymous new class
    new_class = Class.new
    # Create a string containing the code for the class
    s  = ''
    # Create accessor methods
    defaults_hash.keys.each do |key|
      s+= "attr_accessor :#{key};"
    end
    # Object Initialization
    s += 'def initialize(hash = {});'
    # Passes through defaults
    defaults_hash.each do |key, value|
      s += "  @#{key} = #{value.inspect};"
    end
    # Evaluates the hash if it is given
    s += '  hash.each {|key, value| eval "@#{key} = value"}'
    s += 'end;'
    # Evaluate the code in context of the new class
    new_class.class_eval(s)
    # Return the class
    return new_class
  end
end
[/rgss]

A point of interest is that Hash_Struct.new('test1' => 'a', 'test2' => 'b') is equivalent to Hash_Struct.new({'test1' => 'a', 'test2' => 'b'}).
The hash is basically implicitly understood. Some syntactic sugar which I believe makes it more user friendly.
Another point of interest is illustrated by this piece of code:
[rgss]Test_Class = Hash_Struct.new('test1' => 'a', 'test2' => 'b')
Test_Class.new:)test1 => 500, 'test3' => 'c')
[/rgss]

I have chosen to accept @test3 = 'c' despite no accessor methods being present.
Whether or not this is a wanted feature can of course be discussed although I doubt it will be something to worry about in a practical sense.
One could choose lazy initialization rather than this eager initialization. Basically the defaults are created when the getter is accessed. Which to choose is situation specific.
If you have old saves and introduce a new attribute then lazy initialization is typically preferable since old saves will practically have the defaults for the new attributes rather than nil.

Making objects with our Data Structure
This is a nice hands-on section which illustrates nicely how one can apply the theory. I would suggest a discussion at the start about why the @id must start at 1 and if it's even necessary. For the other $data_xxx objects it is obvious since there is the editor interface to comply with. But why must the new one follow that protocol? Is it because the mapping is necessary? Is it just to follow the style?

Using save_data/load_data methods
You may consider mentioning/discussing the typical style of static Data objects and dynamic Game objects containing the information which is altered at runtime.
Because this way you can have a Treasures.rxdata which can be loaded with the static data and have the dynamic data in the save file. This can decrease the size of the save data greatly.
I like the example you show.

Don't be discouraged or anything. I think you have done a great job. I just want to make it even better :3

*hugs*
- Zeriab
 
Thanks as always for your thoughts Z. I'll be sure to edit the main post and make some changes based off your suggestions. I had an hour to wait and felt like writing a tutorial. Normally I make rough drafts and revise them before actually posting them, I just felt like seeing what I could throw together without really any preparation or organization.

Thank you very much for the suggestions on the Hash_Struct class. I was trying to figure out the Class = Hash_Struct.new instead of Hash_Struct.new(Class, ...) so I learned something .new. lol
 
....Wow. I literally spent a whole weekend (including a 12 straight hour session) trying to figure out how to do this, being rather new to RGSS, and just today after finishing it, I find a tutorial. That just freaking figures. Now I need to decide if it's worth recoding it in accordance with the tutorial or just letting it be... ah well. Awesome tutorial. Just a little too late for me.
 

Zeriab

Sponsor

You know I love sharing my thoughts and I am always happy if I manage to teach people something ^_^
Just ask if you have any questions

*hugs*
- Zeriab
 
Hey Seph, the Save Data / Load Data example gives an error on line 10 when the save condition is false.

undefined method 'size' for nil:NilClass

it works fine when the condition is true
 
wierd. we put a nil object in $data_treasures ($data_treasures[0]), then our data then saved it. why do we need to set it to [nil] before loading it? <scratches head>

the $data_treasures = load_data.... doesn't initialize it?


I see a perfect opportunity to try this out... viewtopic.php?f=12&t=61715
 
I am not sure as those were my original thoughts as well. You aren't initializing other RPG objects, so why here? :mystery:

Good job on that request too. Good to see someone getting some use out of this tutorial. :thumb:
 
another simple way to make simple data classes, or structures.. which don't require default values

Code:
 

class Foo

    attr_accessor :foo_attrib1, :foo_attrib2, .....

end

a = Foo.new

 
Basically
"a" will have the attributes "foo_attrib1" and "foo_attrib2" initialized with a nil value, but then nothing prevents you to change it after.

It also means, that you don't need the "initialize" method to create new instances of a class^^.


EDIT
Me again :p with my stuff, found this quite interesting!
(from pickAxe ruby 3)
Ruby comes with a class called Struct which allows you to define classes that contain just
data attributes. For example, you could write
Code:
Person = Struct.new(:name, :address, :likes)

dave = Person.new('Dave', 'TX')

dave.likes = "Programming Languages"

puts dave
produces:
Code:
#<struct Person name="Dave", address="TX", likes="Programming Languages">
The return value from Struct.new(...) is a class object. By assigning it to the constant Person
we can thereafter use Person as if it were any other class.
But say we wanted to change the to_s method of our structure? We could do it by opening
up the class and writing the method.
Code:
Person = Struct.new(:name, :address, :likes)

class Person

  def to_s

    "#{self.name} lives in #{self.address} and likes #{self.likes}"

  end

end
However, we can do this more elegantly (although at the cost of an additional class object)
by writing
Code:
class Person < Struct.new(:name, :address, :likes)

  def to_s

    "#{self.name} lives in #{self.address} and likes #{self.likes}"

  end

end

dave = Person.new('Dave', 'Texas')

dave.likes = "Programming Languages"

puts dave


So
The disadvantage of the Struct class is for each of these attributes, you need to pass what attribute is when creating your object.
is actually not true:p and makes Structs even more attractive!
 

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