+- Save-Point (https://www.save-point.org)
+-- Forum: Games Development (https://www.save-point.org/forum-4.html)
+--- Forum: Code Support (https://www.save-point.org/forum-20.html)
+--- Thread: Cursor, Selection, and Scrolling (/thread-3886.html)
Cursor, Selection, and Scrolling - tnsi - 01-08-2012
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
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?"
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_title_window
create_counter_window
create_skill_window
@skillwindow.active = true
end
def create_title_window
@titlewindow = Window_SkillComponents.new()
end
def create_counter_window
@counterwindow = Window_SkillFree.new()
end
def create_skill_window
@skillwindow = Window_SkillSelections.new()
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
RE: Cursor, Selection, and Scrolling - NightOwl - 01-08-2012
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.
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.
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.
RE: Cursor, Selection, and Scrolling - tnsi - 01-08-2012
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.
RE: Cursor, Selection, and Scrolling - Zeriab - 01-09-2012
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
RE: Cursor, Selection, and Scrolling - tnsi - 01-09-2012
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
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 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
RE: Cursor, Selection, and Scrolling - DerVVulfman - 01-09-2012
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:
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
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 Tenseinten module to share components with
# this script. This script must be placed before other interchange compliant
# scripts bearing the Tenseinten module.
#============================================================================
module INTERCHANGE
USINGSKILLS = true
end
module SKILLS
# BEGIN CONFIG
LEVELGAIN = 5 # Base Value for Points per Level
TEST = "A test"
SKILLNAME = {}
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 sun"
SKILLNAME[12] = "What does it mean?"
# END CONFIG
end # End Skills
end # End Tenseinten
#==============================================================================
# ** Game_Actor
#------------------------------------------------------------------------------
# This class handles actors. It's used within the Game_Actors class
# ($game_actors) and referenced by the Game_Party class ($game_party).
#==============================================================================
class Game_Actor < Game_Battler
#--------------------------------------------------------------------------
# * Public Instance Variables
#--------------------------------------------------------------------------
attr_accessor :skillpoint # for cursor memory: Skill
attr_accessor :skillboost # for cursor memory: Skill
attr_accessor :freepoints # for cursor memory: Skill
#--------------------------------------------------------------------------
# * Object Initialization
# actor_id : actor ID
#--------------------------------------------------------------------------
alias skillpoints_initialize initialize
def initialize(actor_id)
skillpoints_initialize(actor_id)
@skillpoint = {}
@skillboost = {}
@freepoints = 0
for iter in 0...Tenseiten::SKILLS::SKILLNAME.size
@skillpoint[iter] = 0
@skillboost[iter] = 0
end
end
#--------------------------------------------------------------------------
# * Modify Skill Point
# id : id of the Skill
# value : value of adjustment
#--------------------------------------------------------------------------
def modpoint(id, value)
@skillpoint[id] += value
end
#--------------------------------------------------------------------------
# * Modify Skill Boost
# id : id of the Skill
# value : value of adjustment
#--------------------------------------------------------------------------
def modboost(id, value)
@skillboost[id] += value
end
end
class Window_SkillFree < Window_Base
#--------------------------------------------------------------------------
# * Object Initialization
# actor : actor
#--------------------------------------------------------------------------
def initialize(actor)
super(397, 0, 148, 54)
@actor = actor
refresh
end
#--------------------------------------------------------------------------
# * Display the Free Skill Points
#--------------------------------------------------------------------------
def display_free_points
pointhack = @actor.freepoints
return "%03d" % pointhack
end
#--------------------------------------------------------------------------
# * Set Text
# text : character string displayed in window
# align : alignment (0..flush left, 1..center, 2..flush right)
#--------------------------------------------------------------------------
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 main window
#==============================================================================
class Window_SkillSelections < Window_Selectable
#--------------------------------------------------------------------------
# * Object Initialization
# actor : actor
#--------------------------------------------------------------------------
def initialize(actor)
super(0, 55, 544, 362)
@actor = actor
refresh
end
#--------------------------------------------------------------------------
# * Get Skill
#--------------------------------------------------------------------------
def skill
return @data[self.index]
end
#--------------------------------------------------------------------------
# * Refresh
#--------------------------------------------------------------------------
def refresh
@data = []
for item in 0...Tenseiten::SKILLS::SKILLNAME.size
@data.push(item)
end
@item_max = @data.size
create_contents
for i in 0...@item_max
draw_item(i)
end
end
#--------------------------------------------------------------------------
# * Draw Item
# index : item number
#--------------------------------------------------------------------------
def draw_item(index)
# Format Rectangles
rect1 = item_rect(index)
rect2 = item_rect(index, 256, 80)
rect3 = item_rect(index, 336, 80)
rect4 = item_rect(index, 416, 90)
self.contents.clear_rect(rect1)
self.contents.clear_rect(rect2)
self.contents.clear_rect(rect3)
self.contents.clear_rect(rect4)
item = @data[index]
if item != nil
# Get the data
skill_name = Tenseiten::SKILLS::SKILLNAME[item]
skill_point = @actor.skillpoint[item]
skill_boost = @actor.skillboost[item]
skill_boost = 121 if item == 3
skill_total = skill_point + skill_boost
# Draw in rectangles
self.contents.draw_text(rect1, skill_name)
self.contents.draw_text(rect2, skill_point, 2)
self.contents.draw_text(rect3, skill_boost, 2)
self.contents.draw_text(rect4, skill_total, 2)
end
end
#--------------------------------------------------------------------------
# * Get rectangle for displaying items
# index : item number
#--------------------------------------------------------------------------
def item_rect(index, left=0, width=0)
rect = Rect.new(0, 0, 0, 0)
rect.width = (contents.width + @spacing) / @column_max - @spacing
rect.height = WLH
rect.x = index % @column_max * (rect.width + @spacing)
rect.x += left if left != 0
rect.width -= left if left != 0
rect.width = width if width != 0
rect.y = index / @column_max * WLH
return rect
end
end
#==============================================================================
# ** Scene_CharSkills
#------------------------------------------------------------------------------
# This class performs bonus skill screen processing.
#==============================================================================
class Scene_CharSkills < Scene_Base
#--------------------------------------------------------------------------
# * Object Initialization
# actor_index : actor index
#--------------------------------------------------------------------------
def initialize(actor_index = 0)
@actor_index = actor_index
end
#--------------------------------------------------------------------------
# * Start processing
#--------------------------------------------------------------------------
def start
super
create_menu_background
@actor = $game_party.members[@actor_index]
@viewport = Viewport.new(0, 0, 544, 416)
create_title_window(@viewport)
create_counter_window(@viewport)
create_skill_window(@viewport)
end
#--------------------------------------------------------------------------
# * Create the Title Window
# viewport : viewport
#--------------------------------------------------------------------------
def create_title_window(viewport)
@title_window = Window_SkillComponents.new
@title_window.viewport = @viewport
end
#--------------------------------------------------------------------------
# * Create the Counter Window
# viewport : viewport
#--------------------------------------------------------------------------
def create_counter_window(viewport)
@counter_window = Window_SkillFree.new(@actor)
@counter_window.viewport = @viewport
end
#--------------------------------------------------------------------------
# * Create the Skill Window
# viewport : viewport
#--------------------------------------------------------------------------
def create_skill_window(viewport)
@skill_window = Window_SkillSelections.new(@actor)
@skill_window.viewport = @viewport
@skill_window.index = 0
end
#--------------------------------------------------------------------------
# * Termination Processing
#--------------------------------------------------------------------------
def terminate
super
dispose_menu_background
@counter_window.dispose
@title_window.dispose
@skill_window.dispose
end
#--------------------------------------------------------------------------
# * Return to Original Screen
#--------------------------------------------------------------------------
def return_scene
# $scene = Scene_Menu.new(1)
end
#--------------------------------------------------------------------------
# * Frame Update
#--------------------------------------------------------------------------
def update
super
update_menu_background
@title_window.update
@counter_window.update
@skill_window.update
update_skill_selection
end
#--------------------------------------------------------------------------
# * Update Skill Selection
#--------------------------------------------------------------------------
def update_skill_selection
if Input.trigger?(Input::B)
Sound.play_cancel
return_scene
elsif Input.trigger?(Input::C)
@skill = @skill_window.skill
p @skill
end
end
end
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.
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.
RE: Cursor, Selection, and Scrolling - tnsi - 01-09-2012
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. ^^
RE: Cursor, Selection, and Scrolling - DerVVulfman - 01-10-2012
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.