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.

Timer Script

I've been trying to create a timer script that I can call using other scripts, but I've hit a little speedbump. It keeps giving me the error

Script 'Wait' line 19: NoMethodError occurred.

undefined method '+' for nil:NilClass

Now, I'm using RMXP, and I've never been able to figure out what could cause this error, when everything should work just fine. Here's my script:

Code:
class Timer
  
  def main
    @wait = 0
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
  end

  def update
    if @wait != $totalwait
      @wait += 1
    elsif @wait == $totalwait
      $wait = @wait
    end
  end
end

Also, here's how I'm calling this code:

Code:
    if @variable1 == 0
      $totalwait = 400
      @wait = Timer.new
    end
    @wait.update
    if $wait == $totalwait
#     continue code
    end
    @variable1 = 1

What I'm trying to do is to create a timer script that I can call from another script, hopefully multiple times within the same script. Anyone have any ideas on how I can fix this? Thanks!
 
There is already a Sprite_Timer written in the default scripts. Here's a modified one I wrote, it has two new functions.

I.) Adds milliseconds to timer

II.) Color changes when timer reaches 1/2 or 3/4 of its Set value.

Color change is dependant on if a switch is on, but for some reason it changes back to normal if you transfer map. Be sure to read the instructions commented in the script for the rest, its the simplest script I've ever written ;)

You can either add it in the normal place you add scripts, below everything important, or just replace the default Sprite_Timer script, it won't cause any problems and looks nicer than the origional anyway.

Code:
#===============================================================================
# ~* Sprite_Timer (Critical) *~
#-------------------------------------------------------------------------------
#  Written by: Kain Nobel
#  Version: 0.5
#  Last Update: 5/13/2008
#  Date Created: 10/17/2008
#===============================================================================
TimerCriticalSwitch = 1
#===============================================================================
#  INSTRUCTIONS:
#    This is just an enhanced version of Sprite_Timer default class, with a few
#  small changes I decided to make to it. Now it not only displays minutes and
#  seconds in the string, but also milli-seconds... yes, a fraction of a second,
#  and it updates in real time too! (Provided your computer isn't fucked!)
#    Lastly, it detects the total set time vs current time, and sets a caution
#  and critical color when the timer reaches a certain point, such as 1/2 or 3/4
#  the set time.
#-------------------------------------------------------------------------------
class Sprite_Timer < Sprite
  #------------------------
  # * Object Initialization
  #------------------------
  def initialize
    super
    # Variable used to determine timer critical
    @critical = -1
    self.bitmap = Bitmap.new(120, 48)
    self.bitmap.font.name = "Arial"
    self.bitmap.font.size = 32
    self.x = 640 - self.bitmap.width
    self.y = 0
    self.z = 500
    update
  end
  #------------------------
  # * Dispose
  #------------------------
  def dispose
    if self.bitmap != nil
      self.bitmap.dispose
    end
    super
  end
  #------------------------
  # * Frame Update
  #------------------------
  def update
    super
    # Set Timer to visible if working
    self.visible = $game_system.timer_working
    # If Timer needs to be withdrawn
    self.bitmap.clear
    # Get @total_sec value
    @total_sec = $game_system.timer / Graphics.frame_rate
    frac = $game_system.timer % Graphics.frame_rate
    sec_fraction = frac.to_f / Graphics.frame_rate.to_f
    sec_fraction *= 100
    min = @total_sec / 60
    sec = @total_sec % 60
    # Send min, sec, millisec info to a string.
    text = sprintf("%02d:%02d:%02d", min, sec, sec_fraction)
    # If Critical is equal or less than 0, set to @total_sec / 2
    if $game_switches[TimerCriticalSwitch] == true
      # Auto-Set critical time to 1/2 timer set time
      if @critical == -1
        @critical = (@total_sec / 2)
      end
      # If @total_sec is more than (Timer Setting / 2) ?
      if @total_sec >= @critical
        self.bitmap.font.color.set(204, 224, 240)
      # If @total_sec is equal to or more than (Timer Setting / 4) ?
      elsif @total_sec >= (@critical / 2)
        self.bitmap.font.color.set(255, 200, 0)
      else
      # @total_sec is less than Timer Critcal Variable!
        self.bitmap.font.color.set(255, 0, 0)
      end
    else
      # Switch is off, automatically set to default color
      self.bitmap.font.color.set(204, 224, 240)
    end
    # Draw the timer value from string 'text'
    self.bitmap.draw_text(self.bitmap.rect, text, 1)
  end
end

I applaud you for starting out in your scripting journey, however, you're doing alot of things wrong. For one, a variable with the '$' is a global variable, which can be called from anywhere... professional scripters are hardly ever caught using a global variable, unless its for something extremely important. At the most, we use a CONSTANT variable, which is the next best thing to a global variable. Only reason $globals are frowned apon is because they're bad practice to use often and they are permanent values.

Secondly the whole Graphics.transition, loop do, graphics update blah blah stuff is only used in a Window scene, I have no idea why you're calling it the way you are. Perhaps you want the timer value in a window? However, like the default script, its better called as a Sprite class object.
 
Okay, I can see I've confused you. I certainly confused myself with this timer. What I'm trying to do is to create a timer that I can use in a script, without the player knowing. This could be applied many ways, like timing when lightning flashes in a thunderstorm. I'm hoping for something that will allow me to set a variable, call the script, and effectivly mimic the "wait" event command until the time is up. It would also be much simpler if I could incorporate the timer within a script, although that doesn't look possible. I was hoping that I could even make it where I could call the timer multiple times within another script, sometimes with more than one timer running. I hope that I can get something working here. Also, thanks for the help you've given so far. (I'm still unsire of the differences between formatting a script and formatting an event, although I can tell it's very different)
 
Well, I'm not exactly a pro scripter, but the way you created your Timer script is a little confusing. First of all, I don't see the point of using the global variables.

Secondly, I don't get why you have a "main" method in your timer class. Main is usually only used for Scene's and since it isn't a Scene, it seems pointless. (Also, whats up with the "graphic transitions" and stuff?)

And I think the error you get is this line:

Code:
@wait += 1

Since you never define wait, it is automatically nil and you can't add an interger to nil. Try replacing your Timer class with this:

Code:
class Timer
  
  def initialize(wait_frames)
    @wait = wait_frames
  end
  
  def update
    #your update
  end
end

Then you could have a method in your Timer class like this:

Code:
def timer_done?
  if @wait == 0
    return true
  else
    return false
  end
end

I could go on and on, then end with a writen Timer script myself, but I guess you would want to make it on your own. :tongue:
 
I'm not really sure how I would put it together. I'm just a rank beginner at scripting, and more help would be appreciated.

EDIT: Also, how do I get the update function to work, so that until I tell the script to stop working, the update will keep running?
 

OS

Sponsor

You add the update method in the scene you want to use the Timer in. For example, if you want to give the player a timer while he is on the Map, you would put it in Scene_Map.

An easier way to do this would be to create a Game_Timer class, which is your Timer, with Game_ in front of it. Then create an instance of this class in Game_System or Game_Temp (System if you want timers to save/load, Temp if not). Call it @game_timer, and give it an attribute accessor (attr_accessor :game_timer). Then add a boolean called @waiting or something to that effect inside Game_Timer. This is used to determine if the timer is active.

Then create a method called start() to set waiting to true, and possibly to give the timer so many frames or seconds or whatever works for you. Then give the Game_Timer your update method, which should check if @waiting is true, then decrease the timer. Call this update with a single line of code in the scene you are using ($game_system.game_timer.update).

Add code after that line (which should be put in Scene_<YourSceneHere>#update) that checks the Game_Timer's timer value for <= 0 and do what you want with it. Voila!

Now you can add fun stuff like a pause() method, a reverse_timer() method, etc. Whatever you can think of.

I'm sorry if this isn't very clear, but I am tired and in a hurry. I'll elaborate if I need to. Peace!

~Owesome Scriptor
 
Well, I just got the timer you suggested to work, and I was wondering if anyone could tell me how to get it running multiple timers at the same time, without adding multiple copies of the same script (Well, almost the same, it would have to be slightly edited in that case). Here's a copy of the script I have right now:

Code:
class Game_Timer
  
  def initialize
    @waiting = false
    @frames = 0
  end
  
  def update
    if @waiting == true
      if @frames <= 0
        @waiting = false
        return true
      else
        @frames -= 1
        return false
      end
    end
  end
  
  def start(seconds)
    @waiting = true
    @frames = seconds * 80
  end
  
end

Also, here is a copy of the updated Game_Temp that I made:

Code:
#==============================================================================
# ** Game_Temp
#------------------------------------------------------------------------------
#  This class handles temporary data that is not included with save data.
#  Refer to "$game_temp" for the instance of this class.
#==============================================================================

class Game_Temp
  #--------------------------------------------------------------------------
  # * Public Instance Variables
  #--------------------------------------------------------------------------
  attr_accessor :map_bgm                  # map music (for battle memory)
  attr_accessor :message_text             # message text
  attr_accessor :message_proc             # message callback (Proc)
  attr_accessor :choice_start             # show choices: opening line
  attr_accessor :choice_max               # show choices: number of items
  attr_accessor :choice_cancel_type       # show choices: cancel
  attr_accessor :choice_proc              # show choices: callback (Proc)
  attr_accessor :num_input_start          # input number: opening line
  attr_accessor :num_input_variable_id    # input number: variable ID
  attr_accessor :num_input_digits_max     # input number: digit amount
  attr_accessor :message_window_showing   # message window showing
  attr_accessor :common_event_id          # common event ID
  attr_accessor :in_battle                # in-battle flag
  attr_accessor :battle_calling           # battle calling flag
  attr_accessor :battle_troop_id          # battle troop ID
  attr_accessor :battle_can_escape        # battle flag: escape possible
  attr_accessor :battle_can_lose          # battle flag: losing possible
  attr_accessor :battle_proc              # battle callback (Proc)
  attr_accessor :battle_turn              # number of battle turns
  attr_accessor :battle_event_flags       # battle event flags: completed
  attr_accessor :battle_abort             # battle flag: interrupt
  attr_accessor :battle_main_phase        # battle flag: main phase
  attr_accessor :battleback_name          # battleback file name
  attr_accessor :forcing_battler          # battler being forced into action
  attr_accessor :shop_calling             # shop calling flag
  attr_accessor :shop_goods               # list of shop goods
  attr_accessor :name_calling             # name input: calling flag
  attr_accessor :name_actor_id            # name input: actor ID
  attr_accessor :name_max_char            # name input: max character count
  attr_accessor :menu_calling             # menu calling flag
  attr_accessor :menu_beep                # menu: play sound effect flag
  attr_accessor :save_calling             # save calling flag
  attr_accessor :debug_calling            # debug calling flag
  attr_accessor :player_transferring      # player place movement flag
  attr_accessor :player_new_map_id        # player destination: map ID
  attr_accessor :player_new_x             # player destination: x-coordinate
  attr_accessor :player_new_y             # player destination: y-coordinate
  attr_accessor :player_new_direction     # player destination: direction
  attr_accessor :transition_processing    # transition processing flag
  attr_accessor :transition_name          # transition file name
  attr_accessor :gameover                 # game over flag
  attr_accessor :to_title                 # return to title screen flag
  attr_accessor :last_file_index          # last save file no.
  attr_accessor :debug_top_row            # debug screen: for saving conditions
  attr_accessor :debug_index              # debug screen: for saving conditions
  attr_accessor :game_timer              # internal script timer
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    @map_bgm = nil
    @message_text = nil
    @message_proc = nil
    @choice_start = 99
    @choice_max = 0
    @choice_cancel_type = 0
    @choice_proc = nil
    @num_input_start = 99
    @num_input_variable_id = 0
    @num_input_digits_max = 0
    @message_window_showing = false
    @common_event_id = 0
    @in_battle = false
    @battle_calling = false
    @battle_troop_id = 0
    @battle_can_escape = false
    @battle_can_lose = false
    @battle_proc = nil
    @battle_turn = 0
    @battle_event_flags = {}
    @battle_abort = false
    @battle_main_phase = false
    @battleback_name = ''
    @forcing_battler = nil
    @shop_calling = false
    @shop_id = 0
    @name_calling = false
    @name_actor_id = 0
    @name_max_char = 0
    @menu_calling = false
    @menu_beep = false
    @save_calling = false
    @debug_calling = false
    @player_transferring = false
    @player_new_map_id = 0
    @player_new_x = 0
    @player_new_y = 0
    @player_new_direction = 0
    @transition_processing = false
    @transition_name = ""
    @gameover = false
    @to_title = false
    @last_file_index = 0
    @debug_top_row = 0
    @debug_index = 0
    @game_timer = Game_Timer.new  
  end
end

Finally, here are copies of my update, start, and check commands


Code:
  # The update command
  $game_temp.game_timer.update

  # The start command (Replace "Second_Count" with the number of seconds to wait)
  $game_temp.game_timer.start(Second_Count)

  #The check command
  if $game_temp.game_timer.update == true
    #your_code here
  end
 

khmp

Sponsor

You can either create more timers. Or utilize an array or hash of timer objects.

Code:
class Game_Timer
  FRAME_MUL = 80
  def initialize
    @waiting = false
    @frames = 0
  end

  def update
    if @waiting == true
      if @frames <= 0
        @waiting = false
        return true
      else
        @frames -= 1
        return false
      end
    end
  end

  def start(seconds)
    @waiting = true
    @frames = seconds * FRAME_MULTIPLIER
  end
end

#==============================================================================
# ** Game_Temp
#------------------------------------------------------------------------------
#  This class handles temporary data that is not included with save data.
#  Refer to "$game_temp" for the instance of this class.
#==============================================================================

class Game_Temp
  alias_method :glfr_timers_game_system_initialize, :initialize

  def initialize
    glfr_timers_game_system_initialize
    @game_timers = {}
    @game_timers.default = Game_Timer.new
  end

  def timer_start(seconds)
    return @game_timers[seconds * Game_Timer::FRAME_MUL].start(seconds)
  end
  
  def timer_update(seconds)
    return @game_timers[seconds * Game_Timer::FRAME_MUL].update
  end
end

Or something like that. That way timers are only created when necessary. But you would need to make use of some sort of reset functionality. And to retrieve a timer use the same amount of seconds you used to create it.

Code:
$game_temp.timer_start(5)

loop do
  p 'here' if $game_temp.timer_update(5)
end

Just a suggestion though. You can stick with an array and use a more intuitive approach.
 
Honestly, I have no idea what you just did. I really don't understand much code yet. Would you be able to explain the changes you made, and where to put everything. (I mean, this part is apparently way above my head at the moment. ^_^)
 

khmp

Sponsor

I will try. Ok so you made mention of the fact that you want to extend the amount of Timers that you can have. There's a couple ways to achieve this. From the default Ruby objects we have the Array and the Hash. I then tried to imagine some of the ways to utilize a dynamic container of timers. What would be nice and easy to use. Well first I thought about how this would be done with an Array object.

Code:
@timers = Array.new

def get_timer
  @timers << Game_Timer.new
  return @timers.last
end

There's a couple problems with that. If I retrieved a timer I would need to save its instance in a global variable($game_variables). Just so you can start and update it. If I'm using multiple timers at the same time I would need multiple variables. So I was kind of tiring with the idea of using an Array. There are most likely as I'm sure someone will post other approaches that will an Array useful with additional checking.

So I started to think about Hashes. With Hashes I can store data at a particular key value and not be constrained by an index that increments by one. Making navigation for the timer I want to be a hassle. With a Hash I can actually use the time value as the key and access a timer consistently by using that time. The obvious draw back being that calling the start_time method from within Game_Temp using the same time value will retrieve and manipulate the same Timer. Meaning you can't use two timers concurrently with each other if they share the exact same time. However using a Hash like this means that I only ever create a timer when its absolutely necessary. And there's no overt if statements applied.

The usage example I gave you was this. If you wanted to start a five seconds timer:
Code:
$game_temp.timer_start(5)
If there was already a timer created that used 5 seconds you have just reset it.

To check to see if the timer is up update must be called. So using the same key which was 5 seconds we update our timer.
Code:
if $game_temp.timer_update(5)
  # time is up
end

I hope that helped clarify what I was talking about. If you still need help or further explanation narrow down the question and I'll be glad to help.

Good luck with it Glitchfinder! :thumb:
 

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