Writing Your Own Simple Menu Cursor

In this tutorial, we will be writing our own menu cursor using a custom picture of your choice. By using a picture, you can design your cursor in an image editor such as Photoshop or Paint and then load it into the game.

Goal

We want to have a menu cursor such that

  • It is easy to change how the cursor will look
  • It will be drawn whenever we want the player to select an item from a list
  • The cursor will reflect the currently highlighted selection

A complete version of the script can be found here: https://gist.github.com/HimeWorks/8ce4e14a3bbe81db2bf3

Choosing a Cursor Image

You can choose any image you want to use. I just grabbed one of the icons from the RTP iconset:

WritingYourOwnSimpleMenuCursor1

It’s a bit small, but it should be good enough.

Storing Our Image

It is important to determine where you want to store your cursor image. You could simply throw it in the Graphics/Picture folder, but I would consider placing it in the Graphics/System folder. For this tutorial, I will call my cursor image “menu_cursor.png”.

Creating a Cursor Sprite

In order to draw a picture on the screen, we need an object to hold it for us. I will write a separate sprite class for it.

class Sprite_Cursor < Sprite
  
  def initialize(window, viewport=nil)
    super(viewport)
    @window = window
    @name = "Menu_Cursor"
  end
  
  def update
    super
    update_bitmap
  end
  
  def update_bitmap
    self.bitmap = Cache.system(@name)
  end
  
  def dispose    
    self.bitmap.dispose
    super
  end
end

Should be straightforward: you have a constructor that takes a viewport and a window. The window is there so that the cursor can access some properties of the window that it is associated with. We also hardcode the name of our image. The update method will invoke whatever the sprite class needs to do, and then updates the bitmap. Because we have stored the image in Graphics/System, we can simply use the Cache.system method to get a copy of our cursor image.

Testing Our Cursor

At this point I have no idea whether this code works or not since there is nothing to test it with, so let’s add it to our party menu window. Assuming you are using the default project

class Window_MenuCommand < Window_Command
  alias :th_menu_cursor_initialize :initialize
  def initialize(*args)
    @sprite_cursor = Sprite_Cursor.new(self)
    th_menu_cursor_initialize(*args)
  end
  
  alias :th_menu_cursor_update :update
  def update
    @sprite_cursor.update
    th_menu_cursor_update    
  end
  
  alias :th_menu_cursor_dispose :dispose
  def dispose
    @sprite_cursor.dispose
    th_menu_cursor_dispose
  end
end

Test your game. Open the party menu and you should see your cursor at the top-left corner because we didn’t specify a position for our sprite so it defaulted to (0, 0).

WritingYourOwnSimpleMenuCursor2

Bringing the Cursor Forward

Well, the cursor is there. But it’s underneath our window. Why?

When we initialized our sprite, we didn’t specify a z-value either. The window has a z-value of 100 by default, while sprites have a z-value of 0 by default. Consequently, our window will be drawn over the sprite. You can verify this by printing out the z-values of each object.

What is a good way to make sure that our cursor will always be above a window? I would consider referencing the z-value of the window directly. In the Sprite_Cursor class, modify the `update` method to create a new `update_position` method. We will use this new method for updating x, y, and z values.

def update
  super
  update_bitmap
  update_position
end

def update_position
  self.z = @window.z + 100
end

We set the z-value of our sprite to one higher than its window’s z-value to guarantee that it will at least be drawn above that window. This is what we get:

WritingYourOwnSimpleMenuCursor3

Now we can see our cursor more clearly!

Updating Cursor Position

If you were to test the cursor by moving the selection around, you will see that it does not move. Naturally, since we did not write any code instructing it to move, it will not move. How do we know where the cursor should be drawn?

The current selection is stored as a 0-based “index”.

  • 0 means the first item in the list is selected
  • 1 means the second item in the list is selected

And so on. This is basically how ruby indexes arrays, because that is how all of the lists are stored in our Window_Selectable and its children classes.

Assuming we are working with one column, our cursor simply needs to be offset by a certain amount based on the index. If the index is 0, then our cursor will be drawn at the top of the list. If our index is 1, then we need to offset our y-position by the height of a single line item.

Window_Selectable provides a standard method for doing this called `item_rect`, which takes an index and returns a rectangle that will tell you the position of the item and its width and height. Let’s update our `update_position` method:

def update_position
  @index = @window.index
  rect = @window.item_rect(@index)
  self.x = rect.x
  self.y = rect.y
  self.z = @window.z + 100
end

We can now move our selection around and our cursor will follow it!

WritingYourOwnSimpleMenuCursor4

At this point, you can tweak the x and y positions to suit your needs if it does not look good to you.

Supporting Other Windows

We’ve used the party menu for testing purposes, and our cursor works. Problem is, it will only appear for the party menu, and specifically the main party menu command list. Let’s move our code into Window_Selectable so that all of the windows that inherit it will automatically have a cursor sprite. At this point you can simply rename the class definition like this:

class Window_Selectable < Window_Base
  alias :th_menu_cursor_initialize :initialize
  def initialize(*args)
    @sprite_cursor = Sprite_Cursor.new(self)
    th_menu_cursor_initialize(*args)
  end
  
  alias :th_menu_cursor_update :update
  def update
    @sprite_cursor.update
    th_menu_cursor_update    
  end
  
  alias :th_menu_cursor_dispose :dispose
  def dispose
    @sprite_cursor.dispose
    th_menu_cursor_dispose
  end
end

Now let’s see what happens:

WritingYourOwnSimpleMenuCursor5

Looks pretty good. We have two cursors drawn as we expect! And all we did was change where our code was placed.

Let’s look at a different scene.

WritingYourOwnSimpleMenuCursor6

Oops. What’s going on here? The rectangular cursor isn’t drawn, but our custom cursor is still there. What we expect is that our cursor is only drawn when the default rectangular selection is drawn.

Remember that each selectable window has an index. However, what happens when “no item is selected”? The windows in the default project have used an index of -1 to indicate that no item is currently selected.

So we’ll have to replicate this logic as well.

Updating Cursor Visibility

When the window’s index is negative, our cursor should not be visible. This is not related to position, so I will define a new method called `update_visibility` in Sprite_Cursor

def update
  super
  update_visibility
  update_bitmap
  update_position
end

def update_visibility
  @index = @window.index
  if @index < 0
    self.visible = false
  else
    self.visible = true
  end
end

Which you could simplify as if you want. I probably wouldn’t since writing it out seems more obvious to me what’s going on, but that could be just how I interpret code.

def update_visibility
  @index = @window.index
  self.visible = @index > -1
end

With some logic written for visibility, our menus will be less confusing. Sort of.

WritingYourOwnSimpleMenuCursor7

Window Openness and Visibility

Let’s look at another scene while we’re at it:

WritingYourOwnSimpleMenuCursor8

Now there’s another issue. Where is that cursor coming from? Which window is it associated with?

This one might not be too obvious, so this next screenshot might help:

WritingYourOwnSimpleMenuCursor9

It is the choice window! When the window is “not open”, it is effectively not visible. Windows have a property called “openness” which determines how “open” it is.  The Window class also provides a method called “close?” that indicates whether the window is “completely closed”. You can see more information about this in the help file.

For our purposes, we want the cursor to be hidden when the window is closed, so we can update our visibility method like this

def update_visibility
  @index = @window.index
  if @window.close?
    self.visible = false
  elsif @index < 0
    self.visible = false
  else
    self.visible = true
  end
end

Now when a window is closed, its cursor won’t be drawn either.

Window Visibility and Cursor Visibility

Aside from window openness, there is also another property called “visible” which basically indicates whether a window is visible or not. When a window is not visible, its cursor should not be visible either. You can see an example of this in the battle scene when you open the item window or the skill window and then close it:

WritingYourOwnSimpleMenuCursor11

With this in mind, we can proceed to update our cursor visibility method:

def update_visibility
  @index = @window.index
  if !@window.visible
    self.visible = false
  elsif @window.close?
    self.visible = false
  elsif @index < 0
    self.visible = false
  else
    self.visible = true
  end
end

Now if a scene decides to simply “hide” a window when it’s not needed (rather than using the “close” animation), our cursor will not linger around after the window has been hidden.

Are We Done?

It is wonderful that things are working for us automatically for some other windows without us having to do any work and ideally this is how we would like things to work, but unfortunately RPG Maker is not as ideal as we would like it to be. Take a look at the save/load scene as well as the battle scene: the cursors are either not drawn, or not drawn correctly.

This means that we would need to go through each scene and their windows to determine how the selection is implemented, as well as what information we can use for drawing our cursors.

However, the principles are generally the same: create a cursor, figure out how to update it, and then dispose of it after you are finished.

Spread the Word

If you liked the post and feel that others could benefit from it, consider tweeting it or sharing it on whichever social media channels that you use. You can also follow @HimeWorks on Twitter or like my Facebook page to get the latest updates or suggest topics that you would like to read about. I also create videos showcasing various RPG Maker techniques on my Youtube channel.

You may also like...

3 Responses

  1. viplady.info says:

    If you have selected a box with your mouse (e.g. “Value” box in the data window pane, or “Register” box), then Mars will not update the value in that box. Mars assumes you prefer to write your own value into that box, and not to allow the assembly program to use that box.

  2. student says:

    Many thank you, best lesson ever

  3. Arsist says:

    Interesting. I never found a need for a cursor. But it’s nice to know how to if you wanted to add one.

Leave a Reply to viplady.info Cancel reply

Your email address will not be published. Required fields are marked *