• Just a reminder that providing specifics on, sharing links to, or naming websites where ROMs can be accessed is against the rules. If your post has any of this information it will be removed.
  • Our friends from the Johto Times are hosting a favorite Pokémon poll - and we'd love for you to participate! Click here for information on how to vote for your favorites!
  • Akari, Selene, Mint, Solana - which Pokémon protagonist is your favorite? Let us know by voting in our poll!
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Saving and loading a variable.

Telemetius

Tele*
  • 256
    Posts
    10
    Years
    Hello forum, this is one of those dumb questions that I hope someone could spend a couple minutes to address.

    I was trying to add a new variable in the save file, a simple text that could show the player his/her actual progress in the game in the loading screen using something like:
    Code:
    textpos.push([@progress.to_s,16*2,72*2,0,Color.new(232,232,232),Color.new(136,136,136)])
    But (from my very poor understanding) my instance variable is being erased as nil after the game has been shut down.
    So is it possible to save the contents of an instance variable between sessions?
    Googling the problem I found out that I'm supposed to save an instance variable as something called an accessor inside one of the global variables. (I'm sorry if it seems confusing, it is for me too.)
    But how exactly am I supposed to do it? And where?
     
    Wall of text, but worth a read if you want to try and understand what's going on a bit better then just trying to imitate other things and hoping it works somehow.

    Okay, so I think there's a misunderstanding here about what saving actually is.
    I'm going to explain this with relation to Essentials, but this the information can also be generalized to Ruby outside of Essentials, so I'm going to leave this in the General Coding category. Firstly, I'm going to explain a bit about saving in general, and then specifically about how/why you will use this in your load screen.
    No part of your current code persists when you restart the game, any instance variables, etc, are all reset. The thing that allows them to persist is that the game will take specific instances of classes (the $Trainer variable, as an Essentials example, is an instance of PokeBattle_Trainer), and writes them all into a file (your saved game). The way they are loaded is simply the reverse, reading those data files, and setting whatever variables to the instances of classes that were saved there. There's no special symbols that indicated whether something should be saved or not, all saving and loading has to be defined manually.

    What the '@' symbol means is that the variable is an instance variable, you are correct in that regard, but also remember that not all instances of classes are saved in Essentials (nor are they in almost anything, you should only save what you need). From your sample code, it's hard to tell exactly where you've put things, but I would guess you're not implementing it in something already designed to be saved.

    So, let's see an example of how we could add a new variable to store some information. Since progress is related to your trainer, it would make the most sense to utilize the already saved PokeBattle_Trainer class.

    If you go to the section of the same name, you'll see the initialization method (which is called when creating an instance of a class. We can add this red part
    Code:
    def initialize(name,trainertype)
        @name=name
        @language=pbGetLanguage()
        @trainertype=trainertype
        @id=rand(256)
        @id|=rand(256)<<8
        @id|=rand(256)<<16
        @id|=rand(256)<<24
        @metaID=0
        @outfit=0
        @pokegear=false
        @pokedex=false
        clearPokedex
        @shadowcaught=[]
        for i in 1..PBSpecies.maxValue
          @shadowcaught[i]=false
        end
        @badges=[]
        for i in 0...8
          @badges[i]=false
        end
        @money=INITIALMONEY
        @party=[]
        [COLOR="Red"]@progress="Whatever default value"[/COLOR]
      end

    So, now if you start a new game, this variable will be initialized, and saved since it's in a class that's already (elsewhere) going to be saved.
    But, this leaves us with a problem, even without saving. If you already have a game started before you added this variable, your instance of the class doesn't have this variable defined. So if you try to use it, it will cause problems.

    Now, accessors are important here. I'm not going to explain them in depth, because they're actually a lot more complex than they seem. Going with the current example
    Code:
    class PokeBattle_Trainer
      attr_accessor(:name)
      attr_accessor(:id)
      attr_accessor(:metaID)
    You can see these accessors are used here, and their general purpose is to serve as methods that "access" instance variables of that class. There are 3 types of attr_ : attr_reader, attr_writer, and attr_accessor. Reader is for returning an instance variable (known as a getter in other languages), writer is for setting an instance variable (known as a setter in other languages), and accessor is both of them in one. A notable using is $Trainer.party, which is utilizing the attr_accessor :party to return the instance variable @party for your trainer.
    But there's a problem with simply using these for variables we're adding, if you intend to provide support for previously saved files.
    attr_writer is fine, since the code equivalent (these are not exactly the same, but we don't really need to understand how they work to use this) for attr_writer :progress is
    Code:
    def progress=(value)
        @progress = value
    end
    Even if the current save file wasn't initialized with @progress, it can be written to here and is now initialized in the class.
    Readers, on the other hand, are equivalent to
    Code:
    def progress
        return @progress
    end
    In an older save file, @progress has never been set, meaning this will always return nil, no matter what you want the default value to be.
    So, a workaround to this is to still use attr_writer, but make our own reader method
    Code:
    def progress
        @progress = "A default value" if !@progress #or if @progress.nil? , if your variable is a boolean
        return @progress
    end

    While we've now solved our problem for anything accessing the variable from outside the class, we should also consider the inside of the class as well. Consider the following method (inside the PokeBattle_Trainer class, and with all of our previously stated additions included)
    Code:
    def displayProgress
        Kernel.pbMessage(_INTL("{1}",[COLOR="red"]@progress[/COLOR]))
    end
    This value will be nil, because it's not using our reader method, it's simply looking for that instance variable, which has not currently been set. Instead, we can say
    Code:
    def displayProgress
        Kernel.pbMessage(_INTL("{1}",[COLOR="red"]self.progress[/COLOR]))
    end
    This will use the method we created, and thus will get a default value if it's not set.

    So, now here's the additions we've made to our class
    Code:
    class PokeBattle_Trainer
    
    def initialize(name,trainertype)
        @name=name
        @language=pbGetLanguage()
        @trainertype=trainertype
        @id=rand(256)
        @id|=rand(256)<<8
        @id|=rand(256)<<16
        @id|=rand(256)<<24
        @metaID=0
        @outfit=0
        @pokegear=false
        @pokedex=false
        clearPokedex
        @shadowcaught=[]
        for i in 1..PBSpecies.maxValue
          @shadowcaught[i]=false
        end
        @badges=[]
        for i in 0...8
          @badges[i]=false
        end
        @money=INITIALMONEY
        @party=[]
        [COLOR="red"]@progress = "A default value"[/COLOR]
      end
      
      [COLOR="red"]attr_writer :progress
      def progress
        @progress = "A default value" if !@progress
        return @progress
      end
      
      def displayProgress
        Kernel.pbMessage(_INTL("{1}",self.progress))
      end[/COLOR]
    end


    And now we've added a variable that will be saved and loaded, and will work properly even with old save files :)

    Couple more notes. "Global variables" doesn't mean variables that will be saved. For instance, $DEBUG isn't saved. All these different terminologies for variables just refers to the scope, which isn't something I'll talk about here. You can see which variables get saved in def pbSave(safesave=false), in PScreen_Save.
    One more thing to consider is that the variables you can add to classes aren't limited to strings, objects you might consider more complex can be used too. They can be any serializable object (you don't really need to know what that is, but 99% of the things you'll be using are). For instance, we can make @progress equal to PokeBattle_Pokemon.new(1,5,$Trainer) if we wanted.

    Okay, so, onto talking about the actual load screen. This might sound a little complicated, but it's not so when you think about what we've learned. Saving and loading are not things automatically done, but the what/where/when are specified by the code. At the loading screen, we've actually not set all the global variables ($Trainer, and whatnot), and as such you cannot use them. But, it has loaded a few classes that it needs during the loading screen, notably the trainer class (so we've loaded it, but it hasn't been assigned to $Trainer yet). However, what was loaded was passed to the class that will make all the panels in the scene, which includes writing the player name and such in the "Continue" option.
    Code:
    class PokemonLoadPanel < SpriteWrapper
      attr_reader :selected
    
      def initialize(index,title,isContinue,[COLOR="red"]trainer[/COLOR],framecount,mapid,viewport=nil)
        super(viewport)
        @index=index
        @title=title
        @isContinue=isContinue
        [COLOR="Red"]@trainer=trainer[/COLOR]
        @totalsec=(framecount || 0)/Graphics.frame_rate
        @mapid=mapid
        @selected=(index==0)
        @bgbitmap=AnimatedBitmap.new("Graphics/Pictures/loadPanels")
        @refreshBitmap=true
        @refreshing=false 
        refresh
      end
    We can see in the red here where it was passed, and is being used as an instance variable in the loading screen classes. This is why a few methods below
    Code:
    textpos.push([[COLOR="red"]@trainer.name[/COLOR],56*2,32*2,0,Color.new(56,160,248),Color.new(56,104,168)])
    Will actually write your trainer's name. Since we've stored our "progress" variable in our trainer class, we can now use the reader we defined above.
    Code:
    textpos.push([@trainer.[COLOR="red"]progress[/COLOR],56*2,32*2,0,Color.new(56,160,248),Color.new(56,104,168)])
    and it will actually do something.

    The reason I'm explaining all this is so you don't simply try to use $Trainer.progress in this area and then wonder why it errors. Also note that most of the other saved data hasn't been loaded yet. So if you need to displayed saved data in the loading screen, put it in PokeBattle_Trainer unless you want to do slightly more complex coding.
     
    Last edited:
    Thank you for your explanation Mej71! I had no idea that you're supposed to initialize the variable for it to be saved. Using $trainer.progress or @trainer.progress without initialization was exactly what I was doing wrong. This is a tutorial-level answer that can save me and everyone with a similar problem hours of research!
    EDIT: Perfectly working.
    To change the progress I just need a simple script call, eg. : $Trainer.progress=55
    For anyone interested in applying it to the save window as well:
    Code:
    loctext+=_INTL("Progress:<r><c3={1}>{2}%</c3><br>",textColor,$Trainer.progress)
     
    Last edited:
    Back
    Top