Cursor, Selection, and Scrolling
#1
Hi Save-Point:

I've been working on a script that functions as a Skill menu, similar to skills in Dungeons and Dragons, Elder Scrolls, Fallout, or even Star Wars Knights of the Old Republic. I have the menu displaying, but I've run into a slight snag- I don't know how to add a movable selection cursor to the scene that specifically affects the window, how to load up another window on top of it (and pass in relevant information), and how to scroll the view, as I can only display 11 entries right now.


The code I'm using is designed for RPG Maker VXAce, however, the only Ace functionality that I really make use of is the DataManager (which handles marshall.dump), and SceneManager (calls scenes), so this script can be easily ported to VX, and should be compatible in its current form.
Content Hidden
[Image: TohsakaDono.png]
Reply }
#2
Hey, Cocoa. Just popping in at work and I noticed a slight error... unless RGSS3 has made a change to the handling of arrays and hashes.

First, please note the following code you supplied.
Code:
def self.make_save_contents
    contents = {}
    contents[:system]        = $game_system
    contents[:timer]         = $game_timer

The contents array is a hash array defined with the {} braces. The arrays you have in your SKILLS module, and set up in the initialize method in Game_SkillPoints use just the [ ] brackets.

Code:
SKILLNAME = []
    #SKILLNAME[id] = "skill name"
    SKILLNAME[0] = "Alchemy"
    SKILLNAME[1] = "Thaumatology"

The initial SKILLNAME value should be changed to SKILLNAME = {} and turned into a hash array. Likewise, both the @skillpoint and @skillboost arrays should also be hash arrays.

That's a glancing inspection. I'll come back when I can.
DerVVulfman's 'regular Joe' account for testing and permissions use.
Who watches the Watchmen?
Reply }
#3
Is there a reason why they should be hashes instead of arrays, NightOwl?

Arrays would work best, I thought, since I was using integers for the indexes. As I understand, the benefit of using hashes plays in when you want to use something other than integers.
[Image: TohsakaDono.png]
Reply }
#4
Using a hash to map identifiers in the form of symbols to the various objects much nicer than using an array.
Let me show you why it is a good idea by changing your DataManager.make_save_contents method so it does not overwrite the method, but rather just adds the given contents.
Code:
module DataManager
  # Let's open up the singleton class of the module
  class << self
# change the name of the alias if you care going to use this snippet
alias_method :aliased_make_save_contents, :make_save_contents
def make_save_contents
  contents = aliased_make_save_contents
  contents[:skillpoints]   = $game_skillpoints
  contents
end
  end
end

If we were to use an array in we put contents << $game_skillpoints, but how would we extract that information from the save files?
With your script alone we can simply extract it using contents[11] or look at the last element, but what if another script which also puts some information in save files?
Then depending on the order of the scripts it can be either contents[11] or contents[12] you have to read.
It is somewhat similar to the default saving system in XP and VX where the order in which it was saved/loaded mattered.
Of course you can map your object to a specific number which is not in direct extension of the previous last element in the array like contents[42] = $game_skillpoints.
Now you can just use contents[42] to retrieve it. Of course you can still get into trouble if another script also uses index 42. More annoyingly is for the scripter to choose numbers which probably won't be used in other scripts. Mind you, the number shouldn't be too big since that will directly effect the size of the array, which would increase the file size of the save. We can implement our own hashing function to keep the size small, but why do that when we already have a very efficient data structure?

If we use a hash we can use the symbol :skillpoints which is way more descriptive than 42. We don't have the problem what's the last element since we don't have a concept of the last element. (Actually we do sort-of as Hash implements Enumerable, but I wouldn't recommend creating hacks relying on it.)
It is still possible that another script will use the :skillpoints key as well which will cause problems.


I do suggest that you alias the original methods so that you can reuse them and only add the extra functionality. Otherwise you may mess up other scripts.
I assume you intend to release the script to the public so we really want to make it more aspect oriented.

*hugs*
- Zeriab

[Image: ZeriabSig.png]
Reply }
#5
Optimized some of the code, and reduced conflicts, thanks to Zeriab! Still stuck on the main issue though. 前進あるのみ!
Code:
#===============================================================================
# ■ Character Skills
#   Version 0.00
#-------------------------------------------------------------------------------
# Author: Tenseiten/Tensei/Kirotan/Tohsaka
# Last Updated: 2012/01/06 JST
#===============================================================================

module Tenseiten
  #--------------------------------------------------------------------------
  # * Interchange Module
  #--------------------------------------------------------------------------
  # Allows other scripts using the Tenseiten module to share components with
  # this script. This script must be placed before other interchange compliant
  # scripts bearing the Tenseiten module.
  #--------------------------------------------------------------------------
  module INTERCHANGE
    USINGSKILLS = true
  end
  module SKILLS
    # BEGIN CONFIG
    
    LEVELGAIN = 5 # Base Value for Points Per Level
    
    TEST = "A test"
    SKILLNAME = []
    #SKILLNAME[id] = "skill name"
    SKILLNAME[0] = "Alchemy"
    SKILLNAME[1] = "Thaumatology"
    SKILLNAME[2] = "Hacking"
    SKILLNAME[3] = "Yanderehood"
    SKILLNAME[4] = "Art"
    SKILLNAME[5] = "Scripting"
    SKILLNAME[6] = "Cooking"
    SKILLNAME[7] = "Trolling"
    SKILLNAME[8] = "Testing"
    SKILLNAME[9] = "Double Rainbow"
    SKILLNAME[10] = "All the way"
    SKILLNAME[11] = "Across the Sky"
    SKILLNAME[12] = "What does it mean?"
    
    # END CONFIG
  end # End Skills
end # End Tenseiten


module DataManager
  class << self
  alias_method :aliased_create_game_objects, :create_game_objects
  def create_game_objects
    aliased_create_game_objects
    $game_skillpoints   = Game_SkillPoints.new
  end

  #--------------------------------------------------------------------------
  # ● セーブ内容の作成
  #--------------------------------------------------------------------------
  alias_method :aliased_make_save_contents, :make_save_contents
  def make_save_contents
    contents = aliased_make_save_contents
    contents[:skillpoints]   = $game_skillpoints
    contents
  end
  #--------------------------------------------------------------------------
  # ● セーブ内容の展開
  #--------------------------------------------------------------------------
  alias_method :aliased_extract_save_contents, :extract_save_contents
  def extract_save_contents(contents)
    aliased_extract_save_contents
    $game_skillpoints   = contents[:skillpoints]
  end
  
  end
  
end


#==============================================================================
# ■ Game_SkillPoints
#------------------------------------------------------------------------------
# Uses Game_SkillPoints to save data
#==============================================================================
class Game_SkillPoints
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_accessor :skillpoint
  attr_accessor :skillboost
  attr_accessor :freepoints
  
  #--------------------------------------------------------------------------
  # ● Initialization
  #--------------------------------------------------------------------------
  def initialize
    @skillpoint = Array.new(Tenseiten::SKILLS::SKILLNAME.size, 0)
    @skillboost = Array.new(Tenseiten::SKILLS::SKILLNAME.size, 0)
    @freepoints = 0
  end
  
  def modpoint(id, value)
    @skillpoint[id] = @skillpoint[id] + value
  end
  
  def modboost(id, value)
    @skillboost[id] = @skillboost[id] + value
  end
  

  
end

#==============================================================================
# ■ Window_SkillComponents
#------------------------------------------------------------------------------
# Displays the header window
#==============================================================================
class Window_SkillComponents < Window_Base
  #--------------------------------------------------------------------------
  # ● Initialization
  #--------------------------------------------------------------------------
  def initialize
    super(0, 0, 397, 54)
    self.opacity = 255
    refresh
  end
  
  def refresh
    self.contents.clear
    self.contents.font.color = system_color
    self.contents.draw_text(-1, -2, 144, 24, 'Skills')
  end
  
end

#==============================================================================
# ■ Window_SkillFree
#------------------------------------------------------------------------------
# Displays the header window
#==============================================================================
class Window_SkillFree < Window_Base
  #--------------------------------------------------------------------------
  # ● Initialization
  #--------------------------------------------------------------------------
  def initialize
    super(397, 0, 148, 54)
    self.opacity = 255
    refresh
  end
  
  def display_free_points
    pointhack = $game_skillpoints.freepoints
    return "%03d" % pointhack  
  end
  
  def refresh
    self.contents.clear
    fsize = self.contents.font.size
    self.contents.font.size = 15
    self.contents.draw_text(4, -5, 264, 24, "Free Points")
    self.contents.font.size = 20
    self.contents.draw_text(80, 2, 264, 24, display_free_points)
    self.contents.font.size = fsize
  end
  
end

#==============================================================================
# ■ Window_SkillSelections
#------------------------------------------------------------------------------
# Displays the the main window
#==============================================================================
class Window_SkillSelections < Window_Selectable
  #--------------------------------------------------------------------------
  # ● Initialization
  #--------------------------------------------------------------------------
  def initialize
    super(0, 55, 544, 362)
    self.opacity = 255
    @item_max = Tenseiten::SKILLS::SKILLNAME.size
    refresh
  end
  
  def draw_horz_line(y)
    line_y = y + line_height / 2 - 1
    contents.fill_rect(0, line_y, contents_width, 2, line_color)
  end
  
  def line_color
    color = normal_color
    color.alpha = 48
    color
  end
  
  def create_heading
    self.contents.draw_text(0, 0, 168, 24, "Skill")
    self.contents.draw_text(260, 0, 168, 24, "Level")
    self.contents.draw_text(360, 0, 168, 24, "Buff")
    self.contents.draw_text(460, 0, 168, 24, "Total")
    draw_horz_line(line_height * 1)
  end
  
  def create_list
    for skilltemp in 0...Tenseiten::SKILLS::SKILLNAME.size
      skill_point = $game_skillpoints.skillpoint[skilltemp]
      skill_boost = $game_skillpoints.skillboost[skilltemp]
      skill_total = $game_skillpoints.skillpoint[skilltemp] + $game_skillpoints.skillboost[skilltemp]
      self.contents.draw_text(0, line_height * (skilltemp + 2), 168, 24, Tenseiten::SKILLS::SKILLNAME[skilltemp])
      self.contents.draw_text(260, line_height * (skilltemp + 2), 168, 24, skill_point)
      self.contents.draw_text(360, line_height * (skilltemp + 2), 168, 24, skill_boost)
      self.contents.draw_text(460, line_height * (skilltemp + 2), 168, 24, skill_total)
    end
  end
  
  def refresh
    self.contents.clear
    create_heading
    create_list
  end
  
end

#==============================================================================
# ■ Scene_CharSkills
#------------------------------------------------------------------------------
# Character Skills Scene
#==============================================================================
class Scene_CharSkills < Scene_Base
  #--------------------------------------------------------------------------
  # ● Start
  #--------------------------------------------------------------------------
  def start
    super
    create_windows
    init_selection
  end
  
  def create_windows
    @titlewindow = Window_SkillComponents.new()
    @counterwindow = Window_SkillFree.new()
    @skillwindow = Window_SkillSelections.new()
  end
  
  
  def init_selection
  end
  
  def update
    super
    @titlewindow.update
    @counterwindow.update
    @skillwindow.update
    if Input.trigger?(Input::B)
      @skillwindow.active = false
      SceneManager.call(Scene_Map)
    end
  end
  
  def terminate
    super
  end
end
[Image: TohsakaDono.png]
Reply }
#6
I came up with something different.

Like Zeriab said, Using a hash to map identifiers in the form of symbols to the various objects much nicer than using an array. But on top of that, you can use a hash array to store MULTIPLE items for reference, like having this:
Code:
SKILL_ITEM = {}
#SKILL_ITEM[id] = ["skill name", cost]
SKILL_ITEM[0]   = ["Alchemy",      14]
SKILL_ITEM[1]   = ["Thaumatology", 23]

The above example shows that one item (renamed) holds both a name and a skill cost. This way you can keep all your stats for one skill in just one line.

Be that as it may, I still used the hash idea in the code I'm supplying. If I'm right (usually am ^_- ), you wanted to keep track of spellpoints, spellbonuses and free points for each actor. I hope I'm right. If that's the case, you wouldn't be using a Game_SkillPoints class. There would be no need.

The Code

Had to pass the @actor around the script so it knew which actor's data is being used.

With this, you can add points to a single actor's bonus points by this:
Code:
$game_actors[actor_id].modpoint( skill_id, value)
As such, $game_actors[5].modpoint(2,4) would add 4 points to the 'Hacking' skill for actor #5 in your database. The same thing goes for the modboost method you created.

And to start it, you can use this:
Code:
$game_temp.next_scene = nil
$scene = Scene_CharSkills.new(actor_index)
Where the actor_index is the position your guy has in the menu. By default, it is set to 0 for the game player's lead position.

With this, it returns a @skill value matching the index of your SKILLNAME array so you know which one it's messing with.

Oh, I still don't program VX. Winking with a tongue sticking out
Up is down, left is right and sideways is straight ahead. - Cord "Circle of Iron", 1978 (written by Bruce Lee and James Coburn... really...)

[Image: QrnbKlx.jpg]
[Image: sGz1ErF.png]    [Image: liM4ikn.png]    [Image: fdzKgZA.png]    [Image: sj0H81z.png]
[Image: QL7oRau.png]    [Image: uSqjY09.png]    [Image: GAA3qE9.png]    [Image: 2Hmnx1G.png]    [Image: BwtNdKw.png%5B]
  Above are clickable links
Reply }
#7
DerVV, thanks for taking a look at that! Can you explain some of the stuff that you did, since it is more helpful in the long term if I become able to do this on my own. ^^
[Image: TohsakaDono.png]
Reply }
#8
Yeah, I knew you were gonna ask. Good thing I write pretty fast.

First off, I still have your SKILLNAME values stored in a Hash array. The can let you store things in the way I presented earlier. So, I won't go into detail there.

Second, I eliminated the whole Data_Manager module and Game_Skillpoints class from your code, and substituted it with code for the Game_Actor class. Since you wanted to (presumably) store points for each actor in your database and wanted it saved in your savegames, keeping it in Game_Actor was the logical choice.

Each time an actor is initialized, it resets the skillpoints, skillboost and etc. With that, it was just eaiser to add these values to Game_Actor and alias the initialize method. The very last thing to do was to add your modpoint and modboost methods into the class. Rather than use points = points + value, I just did a simpler points += value calculation. It does the same thing with less code.

* * *

Next, I tackled your WINDOWS code.

First, I added your Window_SkillComponents class. I don't see why you needed to set the opacity of this window, so I left it out. You didn't need them in the other Window classes either. But did you catch the bug I left in there? No? I also left out the 'refresh' call you put into the script. It's not saying 'Skills' in your window... Gotcha! Just put 'refresh' under the super statement, and it'll be fine.

Now about your Window_SkillFree. Can I assume this contains the free points you apply to a skill or something, and these are the points for a single given actor? I hope so, because that's what I thought it was.

First, I had to do a slight change to your initialize method. Rather than just a straight initialize call, I used:
Code:
initialize(actor)
This lets me use the data of a single actor in this window, in much the same manner you made your earlier modbonus methods. Besides the 'super' and 'refresh' calls in the initialize method, I also added @actor = actor as a statement. This lets the system use the @actor value throughout this single window class.

The display_free_points method in your Window_SkillFree class was changed to reflect this, as it no longer accessed $game_skillpoints.freepoints, but @actor.freepoints. It reads the freepoints value for this specific actor and now displays them in the format you devised.

Oh, and your refresh method? Nice. I particlularly like the way you recorded the font's size prior to changing it to something larger, and then resetting it back to the default.

The Window_SkillSelections class had to go through a few more changes than the others. Like Window_SkillFree, I changed your initialize method so it read data from the actor, and set up the @actor value so all data could be based off the actor in question.

Like other classes in the default system, I included a skill method. It just reads data stored in a @data value, based on the window's index position. In the item menu or the skill menu, it's actually data from the database, so a skill returned from the skill menu would read the whole name, icon, sp cost, and everything. Well, I figured you didn't need that. The data that THIS statement is returning is slightly different. I'll get to that later.

The refresh method in your class went through a change as I modeled it after the ones in the default menus. True... I didn't include a header, but I guess that could be added later. BUT... you may not want to have it in literally in a selectable window as it may screw up indexes. Could talk later about it. This current refresh method cycles through your list of skills much like your create_list method, but it only pushes the data into the @data array (necessary), After that, it determines the size of your list and stores it in the @item_max value (necessary too), performs the default 'create_contents' statement, and then finally draws every line by cycling through all the @data and running a 'draw_item' statement with each item.

Now I added two weird things. I added a whole new custom 'draw_item' method to this class, so I can have 4 fully formatted fields of data (name, points, boost, total) on each line while not having a required 'ICON' shown in the line. The default draw_item method draws an icon, and your data has none. The next thing I created was a customized version of the 'item_rect' method. While it can function as the default, it also lets me set the left-edge of the rectangle being drawn as well as a width to that rectangle. I used this to help me place the data where I wanted in each line.

OH!!!... Get rid of the 'skill_boost = 121 if item == 3' line. I used that just to make sure I had enough space between rectangles. My bad.

* * *

I bet you now wanna talk about the new Scene_CharSkills class, don't you?

Remember how I had to rework the initialize methods for your Window_SkillFree and Window_SkillSelections classes? Well, I had to create one for it here as well. But rather than passing the value of the actor from the database, you would need to pass the actor from the main menu. This means, you're passing the index of a party member.... regularly party member 0 to 3 (for a 4 party team). Like the others, I set it up so it has a default actor of '0' for the team leader.

Then, we come to your 'start' method. Mimicking other classes, I used the same super statement and the create_menu_background methods, followed by the statement that reads your actor index and find out which actual 'actor' from your database is being used. A little bit of editing was done by adding a viewport call, followed by your three 'Create_---_window' calls. But in each, I passed the @viewport value into these.

Your 'create_title_window' method was virtually untouched, other than the addition of setting the window's viewport to the one passed into its method.

The 'create_counter_window' method was edited more. Rather than just creating the Window_SkillFree window, I passed the data of the actor into the window with the following statement:
Code:
@counter_window = Window_SkillFree.new(@actor)
Now, the window just doesn't show data in that window, but data specific for that actor.
Following that, I set the viewport of the window like the previous one.

Hey! Guess what! The 'create_skill_window' was almost the exact same thing! I created a Window_SkillSelections window and passed the actor's data into it much the same way as I did with Window_SkillFree, and I set the viewport for the window. But after that, I told the window to set it's index position to 0... the very first line in your displayed data.
Code:
@skill_window.index = 0

You didn't have anything in your 'terminate' method other than the super statement, so I added statements to displose of the background, skill, freepoints and selections windows.

I added a 'return_scene' method, but I didn't have it do anything. I just commented out a line that would shove you into menu position 1

The 'update' method is based on those in other classes and is pretty streamlined, though that's because I took out an if...else...end clause you'd normally find. It just updates the background, your windows, and then runs a method called 'update_skill_selection'. That's it.

And your 'update_skill_selection' is a new edition. It has a simple set of IF... statements. If the cancel buttons are pressed, play the cancel sound effect and run 'return_scene'... presumably exiting from the system But if the action buttons are pressed, it retrieves the skill data from your skill selection window, and then uses the 'p @skill' statment to print it.

The data being read is only the ID of the skill in your list. So it would return '5' if you click on the 'Scripting' category. You could very well have the 'p @skill' statement replaced by 'p Tenseiten::SKILLS::SKILLNAME[@skill]' if you wanted it to print the name of the skill.

Up is down, left is right and sideways is straight ahead. - Cord "Circle of Iron", 1978 (written by Bruce Lee and James Coburn... really...)

[Image: QrnbKlx.jpg]
[Image: sGz1ErF.png]    [Image: liM4ikn.png]    [Image: fdzKgZA.png]    [Image: sj0H81z.png]
[Image: QL7oRau.png]    [Image: uSqjY09.png]    [Image: GAA3qE9.png]    [Image: 2Hmnx1G.png]    [Image: BwtNdKw.png%5B]
  Above are clickable links
Reply }


Possibly Related Threads…
Thread Author Replies Views Last Post
   Map scrolling nightmare... kyonides 1 4,297 09-09-2017, 06:02 PM
Last Post: kyonides
   Just move the Cursor of the Item_Scene to the right Djigit 24 24,455 08-18-2015, 03:00 AM
Last Post: DerVVulfman
   little Edit of the cursor world map system Djigit 3 6,538 08-24-2014, 02:31 AM
Last Post: Djigit
   Help with Compact Menu Selection Script JackMonty 4 7,290 09-19-2012, 10:56 PM
Last Post: JackMonty



Users browsing this thread: 1 Guest(s)