Wecoc

Veteran
Veteran
Joined
Jul 4, 2021
Messages
79
Reaction score
215
First Language
Catalan
Primarily Uses
RMXP
On this tutorial, I will talk in detail about the Windowskins in RPG maker XP.

The Windowskin is the graphic used to draw the windows that appear in the game.

001-_Blue01.png


PART 1. Parts of the Windowskin

Here you can see the limits of each part of the graphic

windowskin_template.png
windowskin_template_C.png


A. This part is the back of the windowskin.
B. This part is the frame of the window.
C. Those are the scroll arrows that appear on item lists.
D. This part is the cursor.
E. Those little squares are each one of the 4 frames of that little icon that appears at the bottom of the message window. That icon is called pause.
F. Those are the 2 frames of the battle arrow that appears over the sprites when you pick them.

The display of most of those parts (A-E) is defined in the internal class Window, and that means you can't access the code that controls how they're displayed, but don't lose your hope; you can overwrite that class with a RGSS script, paste it over Main and customize that one instead.

The last arrows (F) have their own non-internal class called Arrow_Base. Even if they are on the same graphic, it may be easier to consider they're not part of the window themselves.

Something I want to highlight about the template is that, as you can see, the cursor actually has a small border around it that is not stretched, unlike the central part. I'll talk a bit more about this later.

windowskin_example.png


It doesn't matter how big the cursor is, the border mantains its thickness.

PART 2. Windowskin properties

The window has some properties that define how those parts are displayed.
Some can be modified (for example the window's opacity), but others are internal.

Modifiable:

tuto_windowskin01.png

Non modifiable:

tuto_windowskin02.png

PART 3. Stretch and tiling

When the window is bigger that the graphic, some parts of the graphic are stretched and some tile until they fill all that plane.

windowskin_template_B.png


windowskin_example_B.png

On this example, the parts I painted with green blinds aren't stretched, but those with grey blinds are.

You can change the back stretch mode with a little script over Main, that changes the "stretch" variable commented before. That means you can also change that on each window independly or based on the current windowskin.

Ruby:
class Window
  alias stretch_mode_ini initialize unless $@
  def initialize(*args)
    stretch_mode_ini(*args)
    self.stretch = false
  end
end

In summary, the back has no border and its stretch depends on a variable, frames have a 16px border and don't stretch, and the cursor has a 2px border and stretches.

Here are some windowskins that will only look good with the stretch mode disabled.

stretch_window.png
stretch_window_B.png


stretch_example01.png
stretch_example02.png

stretch_example03.png
stretch_example04.png

PART 4. RGSS Rewrite of the Window Class

In order to modify some of the window properties more freely, you can use a script that overwrites the window methods in RGSS. That makes their displaying a bit more slow, but we have much more control over them. There are many scripts that overwrite that class, but since I'm as cool as a cucumber I've made one specially for this tutorial, free to use. I used Selwyn's rewrite as a reference.

Ruby:
#==============================================================================
# ** [XP] Window - Hidden RGSS Class
#------------------------------------------------------------------------------
# Author: Wecoc (no credits required)
#------------------------------------------------------------------------------
# I used Selwyn's Window rewrite as reference.
#==============================================================================

#==============================================================================
# * Bitmap
#==============================================================================

class Bitmap
  #--------------------------------------------------------------------------
  # * Erase
  #--------------------------------------------------------------------------
  def erase(*args)
    if args.size == 1
      rect = args[0]
    elsif args.size == 4
      rect = Rect.new(*args)
    end
    fill_rect(rect, Color.new(0, 0, 0, 0))
  end
  #--------------------------------------------------------------------------
  # * Cut
  #--------------------------------------------------------------------------
  def cut(*args)
    case args.size
    when 1 # (rect)
      rect = args[0]
      x, y, width, height = *rect.size
    when 4 # (x, y, width, height)
      x, y, width, height = *args
    end
    return Bitmap.new(1, 1) if width <= 0 or height <= 0
    bitmap = Bitmap.new(width, height)
    bitmap.blt(0, 0, self, Rect.new(x, y, width, height))
    return bitmap
  end
end

#==============================================================================
# * Tiled_Sprite
#==============================================================================

class Tiled_Sprite
  attr_reader :bitmap, :skin, :margin, :stretch
  #--------------------------------------------------------------------------
  # * Initialize
  #--------------------------------------------------------------------------
  def initialize(bitmap, m=1, stretch=true)
    w = bitmap.width
    h = bitmap.height
    @margin = m
    @stretch = stretch
    @skin = {}
    @skin[7] = bitmap.cut(  0,   0, m, m)
    @skin[9] = bitmap.cut(w-m,   0, m, m)
    @skin[1] = bitmap.cut(  0, h-m, m, m)
    @skin[3] = bitmap.cut(w-m, h-m, m, m)
    @skin[8] = bitmap.cut(  m,   0, w-(m*2), m)
    @skin[4] = bitmap.cut(  0,   m, m, h-(m*2))
    @skin[6] = bitmap.cut(w-m,   m, m, h-(m*2))
    @skin[2] = bitmap.cut(  m, h-m, w-(m*2), m)
    @skin[5] = bitmap.cut(  m,   m, w-(m*2), h-(m*2))
  end
end

#==============================================================================
# * Bitmap
#==============================================================================

class Bitmap
  #--------------------------------------------------------------------------
  # * Draw Tiled Sprite
  #--------------------------------------------------------------------------
  def draw_tiled(sprite)
    for i in 1..9
      draw_tiled_part(sprite, i)
    end
  end
  #--------------------------------------------------------------------------
  # * Draw Tiled Sprite (each part)
  #--------------------------------------------------------------------------
  def draw_tiled_part(sprite, id)
    src_bitmap = sprite.skin[id]
    m = sprite.margin
    bx = (self.width - m * 2).to_f / src_bitmap.width
    by = (self.height - m * 2).to_f / src_bitmap.height
    dx = (src_bitmap.width * (bx.ceil.to_f - bx)).floor
    dy = (src_bitmap.height * (by.ceil.to_f - by)).floor
    case id
    when 1
      blt(0, self.height - m, src_bitmap, src_bitmap.rect)
    when 3
      blt(self.width - m, self.height - m, src_bitmap, src_bitmap.rect)
    when 7
      blt(0, 0, src_bitmap, src_bitmap.rect)
    when 9
      blt(self.width - m, 0, src_bitmap, src_bitmap.rect)
    when 2
      if sprite.stretch == false
        for i in 0..bx.ceil
          rect = src_bitmap.rect
          rect.width = dx if i == bx.ceil
          blt(m + i * src_bitmap.width, self.height - m, src_bitmap, rect)
        end
      else
        dest_rect = Rect.new(m, self.height - m, self.width - m * 2, m)
        stretch_blt(dest_rect, src_bitmap, src_bitmap.rect)
      end
    when 4
      if sprite.stretch == false
        for i in 0..by.ceil
          rect = src_bitmap.rect
          rect.height = dy if i == by.ceil
          blt(0, m + i * src_bitmap.height, src_bitmap, rect)
        end
      else
        dest_rect = Rect.new(0, m, m, self.height - m * 2)
        stretch_blt(dest_rect, src_bitmap, src_bitmap.rect)
      end
    when 6
      if sprite.stretch == false
        for i in 0..by.ceil
          rect = src_bitmap.rect
          rect.height = dy if i == by.ceil
          blt(self.width - m, m + i * src_bitmap.height, src_bitmap, rect)
        end
      else
        dest_rect = Rect.new(self.width - m, m, m, self.height - m * 2)
        stretch_blt(dest_rect, src_bitmap, src_bitmap.rect)
      end
    when 8
      if sprite.stretch == false
        for i in 0..bx.ceil
          rect = src_bitmap.rect
          rect.width = dx if i == bx.ceil
          blt(m + i * src_bitmap.width, 0, src_bitmap, rect)
        end
      else
        dest_rect = Rect.new(m, 0, self.width - m * 2, m)
        stretch_blt(dest_rect, src_bitmap, src_bitmap.rect)
      end
    when 5
      if sprite.stretch == false
        for iy in 0..by.ceil
          for ix in 0..bx.ceil
            rect = src_bitmap.rect
            rect.width  = dx if ix == bx.ceil
            rect.height = dy if iy == by.ceil
            blt(m + ix * src_bitmap.width, m + iy * src_bitmap.height,
            src_bitmap, rect)
          end
        end
      else
        dest_rect = Rect.new(m, m, self.width - m * 2, self.height - m * 2)
        stretch_blt(dest_rect, src_bitmap, src_bitmap.rect)
      end
    end
  end
end

#==============================================================================
# * Cursor_Rect
#==============================================================================

class Cursor_Rect < ::Sprite
  attr_reader :width, :height, :skin, :margin, :sprite
  #--------------------------------------------------------------------------
  # * Initialize
  #--------------------------------------------------------------------------
  def initialize(viewport)
    super(viewport)
    @width = 0
    @height = 0
    @margin = 16
    @skin = nil
    @sprite = nil
  end
  #--------------------------------------------------------------------------
  # * Margin
  #--------------------------------------------------------------------------
  def margin=(margin)
    @margin = margin
    get_cursor_sprite
    set(x, y, width, height)
  end
  #--------------------------------------------------------------------------
  # * Skin
  #--------------------------------------------------------------------------
  def skin=(skin)
    @skin = skin
    get_cursor_sprite
  end
  #--------------------------------------------------------------------------
  # * Get Cursor Sprite
  #--------------------------------------------------------------------------
  def get_cursor_sprite
    return if @skin == nil
    src_bitmap = @skin.cut(128, 64, 32, 32)
    @sprite = Tiled_Sprite.new(src_bitmap, 2, true)
  end
  #--------------------------------------------------------------------------
  # * Set Width
  #--------------------------------------------------------------------------
  def width=(width)
    return if @width == width
    @width = width
    if @width == 0 and self.bitmap != nil
      self.bitmap.dispose
      self.bitmap = nil
    end
    draw_rect
  end
  #--------------------------------------------------------------------------
  # * Set Height
  #--------------------------------------------------------------------------
  def height=(height)
    return if @height == height
    @height = height
    if @height == 0 and self.bitmap != nil
      self.bitmap.dispose
      self.bitmap = nil
    end
    draw_rect
  end
  #--------------------------------------------------------------------------
  # * Set Coords
  #--------------------------------------------------------------------------
  def set(x, y, width, height)
    self.x = x + @margin
    self.y = y + @margin
    if @width != width or @height != height
      @width = width
      @height = height
      if width > 0 and height > 0
        draw_rect
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Clear cursor
  #--------------------------------------------------------------------------
  def empty
    self.x = 0
    self.y = 0
    self.width = 0
    self.height = 0
  end
  #--------------------------------------------------------------------------
  # * Draw cursor
  #--------------------------------------------------------------------------
  def draw_rect
    return if @skin == nil or @sprite == nil
    if @width > 0 and @height > 0
      self.bitmap = Bitmap.new(@width, @height)
      self.bitmap.draw_tiled(@sprite)
    end
  end
end

#==============================================================================
# * Window
#==============================================================================

class Window
  attr_reader :x, :y, :z, :width, :height, :ox, :oy
  attr_reader :opacity, :back_opacity, :contents_opacity
  attr_reader :stretch, :visible, :pause, :margin
  attr_accessor :active
  #--------------------------------------------------------------------------
  # * Initialize
  #--------------------------------------------------------------------------
  def initialize
    @viewport = Viewport.new(0, 0, 0, 0)
    @cr_vport = Viewport.new(0, 0, 0, 0)
    @width = 0
    @height = 0
    @ox = 0
    @oy = 0
    @opacity = 255
    @back_opacity = 255
    @contents_opacity = 255
    @margin = 16
    @frame   = Sprite.new
    @bg      = Sprite.new
    @window  = Sprite.new(@viewport)
    @pause_s = Sprite.new
    @arrows = []
    for i in 0...4
      @arrows.push(Sprite.new(@cr_vport))
      @arrows[i].bitmap = Bitmap.new(16, 16)
      @arrows[i].visible = false
    end
    @cursor_rect = Cursor_Rect.new(@cr_vport)
    @cursor_fade = true
    @pause_s.visible = false
    @pause = false
    @active = true
    @stretch = true
    @visible = true
    self.x = 0
    self.y = 0
    self.z = 100
    self.windowskin = RPG::Cache.windowskin($game_system.windowskin_name)
  end
  #--------------------------------------------------------------------------
  # * Set Contents
  #--------------------------------------------------------------------------
  def contents=(bmp)
    @window.bitmap = bmp
    if bmp != nil
      if bmp.width > @viewport.rect.width or bmp.height > @viewport.rect.height
        draw_arrows
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Get Contents
  #--------------------------------------------------------------------------
  def contents
    return @window.bitmap
  end
  #--------------------------------------------------------------------------
  # * Dispose
  #--------------------------------------------------------------------------
  def dispose
    @bg.dispose
    @frame.dispose
    @window.dispose
    @cursor_rect.dispose
    @viewport.dispose
    @pause_s.dispose
    @cr_vport.dispose
    for arrow in @arrows
      arrow.dispose
    end
  end
  #--------------------------------------------------------------------------
  # * Update
  #--------------------------------------------------------------------------
  def update
    @window.update
    @cursor_rect.update
    @viewport.update
    @cr_vport.update
    update_pause
    update_visible
    update_arrows
    update_cursor
  end
  #--------------------------------------------------------------------------
  # * Update Pause
  #--------------------------------------------------------------------------
  def update_pause
    id = (Graphics.frame_count / 8) % 4
    rect = Rect.new(160, 64, 16, 16)
    src_bitmap = Bitmap.new(rect.width, rect.height)
    px = rect.x + (id % 2) * rect.width
    py = rect.y + (id.to_f / 2).floor * rect.height
    src_bitmap.blt(0, 0, @skin, Rect.new(px, py, rect.width, rect.height))
    @pause_s.bitmap = src_bitmap
    @pause_s.update
  end
  #--------------------------------------------------------------------------
  # * Update Visible
  #--------------------------------------------------------------------------
  def update_visible
    @frame.visible = @visible
    @bg.visible = @visible
    @window.visible = @visible
    @cursor_rect.visible = @visible
    if @pause
      @pause_s.visible = @visible
    else
      @pause_s.visible = false
    end
  end
  #--------------------------------------------------------------------------
  # * Set Pause Mode
  #--------------------------------------------------------------------------
  def pause=(pause)
    @pause = pause
    update_visible
  end
  #--------------------------------------------------------------------------
  # * Update Arrows
  #--------------------------------------------------------------------------
  def update_arrows
    if @window.bitmap == nil or @visible == false
      for arrow in @arrows
        arrow.visible = false
      end
    else
      @arrows[0].visible = @oy > 0
      @arrows[1].visible = @ox > 0
      @arrows[2].visible = (@window.bitmap.width - @ox) > @viewport.rect.width
      @arrows[3].visible = (@window.bitmap.height - @oy) > @viewport.rect.height
    end
  end
  #--------------------------------------------------------------------------
  # * Update Cursor
  #--------------------------------------------------------------------------
  def update_cursor
    if self.active == true
      if @cursor_fade
        @cursor_rect.opacity -= 10
        @cursor_fade = false if @cursor_rect.opacity <= 100
      else
        @cursor_rect.opacity += 10
        @cursor_fade = true if @cursor_rect.opacity >= 255
      end
    else
      @cursor_rect.opacity = 100
      @cursor_fade = false
    end
  end
  #--------------------------------------------------------------------------
  # * Set Visible
  #--------------------------------------------------------------------------
  def visible=(visible)
    @visible = visible
    update_visible
    update_arrows
  end
  #--------------------------------------------------------------------------
  # * Set X
  #--------------------------------------------------------------------------
  def x=(x)
    @x = x
    @bg.x = x + 2
    @frame.x = x
    @viewport.rect.x = x + @margin
    @cr_vport.rect.x = x
    @pause_s.x = x + (@width / 2) - 8
    set_arrows
  end
  #--------------------------------------------------------------------------
  # * Set Y
  #--------------------------------------------------------------------------
  def y=(y)
    @y = y
    @bg.y = y + 2
    @frame.y = y
    @viewport.rect.y = y + @margin
    @cr_vport.rect.y = y
    @pause_s.y = y + @height - @margin
    set_arrows
  end
  #--------------------------------------------------------------------------
  # * Set Z
  #--------------------------------------------------------------------------
  def z=(z)
    @z = z
    @bg.z = z - 1
    @frame.z = z
    @cr_vport.z = z + 2
    @viewport.z = z + 3
    @pause_s.z = z + 4
  end
  #--------------------------------------------------------------------------
  # * Set OX
  #--------------------------------------------------------------------------
  def ox=(ox)
    return if @ox == ox
    @ox = ox
    @viewport.ox = ox
    update_arrows
  end
  #--------------------------------------------------------------------------
  # * Set OY
  #--------------------------------------------------------------------------
  def oy=(oy)
    return if @oy == oy
    @oy = oy
    @viewport.oy = oy
    update_arrows
  end
  #--------------------------------------------------------------------------
  # * Set Width
  #--------------------------------------------------------------------------
  def width=(width)
    @width = width
    @viewport.rect.width = width - @margin * 2
    @cr_vport.rect.width = width
    draw_window if @width > 0 and @height > 0
    self.x = @x
    self.y = @y
  end
  #--------------------------------------------------------------------------
  # * Set Height
  #--------------------------------------------------------------------------
  def height=(height)
    @height = height
    @viewport.rect.height = height - @margin * 2
    @cr_vport.rect.height = height
    draw_window if @height > 0 and @width > 0
    self.x = @x
    self.y = @y
  end
  #--------------------------------------------------------------------------
  # * Set Opacity
  #--------------------------------------------------------------------------
  def opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    @opacity = value
    @contents_opacity = value
    @back_opacity = value
    @frame.opacity = value
    @bg.opacity = value
    @window.opacity = value
  end
  #--------------------------------------------------------------------------
  # * Set Back Opacity
  #--------------------------------------------------------------------------
  def back_opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    @back_opacity = value
    @bg.opacity = value
  end
  #--------------------------------------------------------------------------
  # * Set Contents Opacity
  #--------------------------------------------------------------------------
  def contents_opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    @contents_opacity = value
    @window.opacity = value
  end
  #--------------------------------------------------------------------------
  # * Get Cursor Rect
  #--------------------------------------------------------------------------
  def cursor_rect
    return @cursor_rect
  end
  #--------------------------------------------------------------------------
  # * Set Cursor Rect
  #--------------------------------------------------------------------------
  def cursor_rect=(rect)
    @cursor_rect.x = rect.x
    @cursor_rect.y = rect.y
    if @cursor_rect.width != rect.width or @cursor_rect.height != rect.height
      @cursor_rect.set(@cursor_rect.x, @cursor_rect.y, rect.width, rect.height)
    end
  end
  #--------------------------------------------------------------------------
  # * Get Windowskin
  #--------------------------------------------------------------------------
  def windowskin
    return @skin
  end
  #--------------------------------------------------------------------------
  # * Set Windowskin
  #--------------------------------------------------------------------------
  def windowskin=(windowskin)
    return if windowskin == nil
    if @skin != windowskin
      @skin = windowskin
      @cursor_rect.skin = windowskin
      draw_window
      draw_arrows
    end
  end
  #--------------------------------------------------------------------------
  # * Set Margin
  #--------------------------------------------------------------------------
  def margin=(margin)
    if @margin != margin
      @margin = margin
      self.x = @x
      self.y = @y
      temp = @height
      self.height = 0
      self.width = @width
      self.height = temp
      @cursor_rect.margin = margin
      set_arrows
    end
  end
  #--------------------------------------------------------------------------
  # * Set Stretch
  #--------------------------------------------------------------------------
  def stretch=(stretch)
    if @stretch != stretch
      @stretch = stretch
      draw_window
    end
  end
  #--------------------------------------------------------------------------
  # * Set Arrow Positions
  #--------------------------------------------------------------------------
  def set_arrows
    @arrows[0].x = @width / 2 - 8
    @arrows[0].y = 8
    @arrows[1].x = 8
    @arrows[1].y = @height / 2 - 8
    @arrows[2].x = @width - 16
    @arrows[2].y = @height / 2 - 8
    @arrows[3].x = @width / 2 - 8
    @arrows[3].y = @height - 16
  end
  #--------------------------------------------------------------------------
  # * Draw Window Basics
  #--------------------------------------------------------------------------
  def draw_window
    return if @skin == nil
    return if @width == 0 or @height == 0
    draw_back
    draw_frame
  end
  #--------------------------------------------------------------------------
  # * Draw Back
  #--------------------------------------------------------------------------
  def draw_back
    @bg.bitmap = Bitmap.new(@width - 4, @height - 4)
    src_bitmap = @skin.cut(0, 0, 128, 128)
    sprite = Tiled_Sprite.new(src_bitmap, 0, @stretch)
    @bg.bitmap.draw_tiled(sprite)
  end
  #--------------------------------------------------------------------------
  # * Draw Frame
  #--------------------------------------------------------------------------
  def draw_frame
    @frame.bitmap = Bitmap.new(@width, @height)
    m = 16
    src_bitmap = @skin.cut(128, 0, 64, 64)
    src_rect = Rect.new(m, m, 64 - m * 2, 64 - m * 2)
    src_bitmap.fill_rect(src_rect, Color.new(0, 0, 0, 0))
    sprite = Tiled_Sprite.new(src_bitmap, m, false)
    @frame.bitmap.draw_tiled(sprite)
  end
  #--------------------------------------------------------------------------
  # * Draw Arrows
  #--------------------------------------------------------------------------
  def draw_arrows
    return if @skin == nil
    @arrows[0].bitmap = @skin.cut(152, 16, 16, 8)
    @arrows[1].bitmap = @skin.cut(144, 24, 8, 16)
    @arrows[2].bitmap = @skin.cut(168, 24, 8, 16)
    @arrows[3].bitmap = @skin.cut(152, 40, 16, 8)
    update_arrows
  end
end

PART 5. Script adaptations

The Window script posted above defines in a single method the drawing of the sprites (used as a shortcut), this way it's easier to customize it taking into account both the border and the stretch mode.
That property is defined in Back, Frames, Cursor, and finally in Blind if you're using the Window Blind Add-On (see below).

It's also possible to change the cursor's windowskin independently, using cursor_rect.skin = name

Examples:
self.cursor_rect.skin = "001-Blue01"
@window.cursor_rect.skin = "Dark_Skin"


This dynamic cursor skin can be useful if you want to change it based on a condition (for example, when the current choice is disabled).

Therefore, we can make other basic changes, implemented as "AddOns" below this base script.
The method update_pause defines how the pause graphic is loaded. There you can change it easily so that graphic is loaded using an independent graphic instead, for example this one. This way it can be as big as you want, and have more frames.

Ruby:
#==============================================================================
# ** Window - Pause Frame Edit
#==============================================================================

class Window
  def update_pause
    #------------------------------------------------------------------------
    # Number of frames per frame
    frames = 8
    # Graphic used
    pause_bmp = RPG::Cache.picture("pause")
    #------------------------------------------------------------------------
    h = pause_bmp.height
    i = pause_bmp.width / h
    id = (Graphics.frame_count / frames) % i
    rect = Rect.new(0, 0, h, h)
    src_bitmap = Bitmap.new(rect.width, rect.height)
    px = rect.x + id * rect.width
    src_bitmap.blt(0, 0, pause_bmp, Rect.new(px, 0, rect.width, rect.height))
    @pause_s.bitmap = src_bitmap
    @pause_s.update
  end
end

The method update_cursor defines the blinking of the cursor and its opacity when it's disabled. Disabling its blinking is very simple.

Ruby:
#==============================================================================
# ** Window - Cursor Fade Fix
#==============================================================================

class Window
  def update_cursor
    if self.active == true
      @cursor_rect.opacity = 255
    else
      @cursor_rect.opacity = 100
    end
  end
end

You could also make a cursor with multiple frames as in RM2k/3, to do that you can use the previous script for pause as a reference. Similarly, on the method draw_arrows you can change how the scroll arrows are displayed, in case you want them bigger, or animated, etc.

Inside the methods that define X and Y, you can find these:
@pause_s.x = x + (@width / 2) - 8
@pause_s.y = y + @height - @margin


That controls the position of the pause, so you can easily change it as well, like this:

Ruby:
#==============================================================================
# ** Window - Pause position
#==============================================================================

class Window
  alias pause_pos_x x= unless $@
  alias pause_pos_y y= unless $@
  def x=(x)
    pause_pos_x(x)
    # Set Pause X
    @pause_s.x = x + @width - 24
  end
  def y=(y)
    pause_pos_y(y)
    # Set Pause Y
    @pause_s.y = y + @height - 20
  end
end

Now margin is a modifiable property too, but I recommend not changing that.
The method set_arrows defines the position of the scroll arrows, which I don't recommend changing either.

margin.png


The method draw_back defines how the background is displayed.
It gets the graphic with the rect (0, 0, 128, 128) from the windowskin and then uses this
sprite = Tiled_Sprite.new(src_bitmap, 0, @stretch)

That 0 is the margin, which means changing it you can make things like this image.

tuto_windowskin03.png

Ruby:
#==============================================================================
# ** Window - Back margin
#==============================================================================

class Window
  def draw_back
    @bg.bitmap = Bitmap.new(@width - 4, @height - 4)
    src_bitmap = @skin.cut(0, 0, 128, 128)
    sprite = Tiled_Sprite.new(src_bitmap, 24, @stretch)
    @bg.bitmap.draw_tiled(sprite)
  end
end

Right after that, the Frame is defined in a very similar way inside the method draw_frame, where m = 16 is the margin.
sprite = Tiled_Sprite.new(src_bitmap, m, false)

Changing that to true allows the borders of the frames to stretch, which may be interesting in some cases. You can check it here.

Ruby:
#==============================================================================
# ** Window - Frame Stretch
#==============================================================================

class Window
  def draw_frame
    @frame.bitmap = Bitmap.new(@width, @height)
    m = 16
    src_bitmap = @skin.cut(128, 0, 64, 64)
    src_rect = Rect.new(m, m, 64 - m * 2, 64 - m * 2)
    src_bitmap.fill_rect(src_rect, Color.new(0, 0, 0, 0))
    sprite = Tiled_Sprite.new(src_bitmap, m, true)
    @frame.bitmap.draw_tiled(sprite)
  end
end

Finally (this is getting long), the last interesting method is get_cursor_sprite, which defines the display of the cursor. The default 2px border is very limiting, so you can now change it to something more wide. You can also make it tile.

As before, all that is defined on this line:
@sprite = Tiled_Sprite.new(src_bitmap, 2, true)

Here's my version

Ruby:
#==============================================================================
# ** Window - Cursor Sprite
#==============================================================================

class Cursor_Rect < ::Sprite
  def get_cursor_sprite
    return if @skin == nil
    src_bitmap = @skin.cut(128, 64, 32, 32)
    @sprite = Tiled_Sprite.new(src_bitmap, 14, true)
  end
end

cursor_fix.png


More recent RPG maker versions have an extra part on the graphic with blinds, so you can define at once a tiling and a stretching part for the back of your windows. I've made a little AddOn for this script that allows doing that, but you'll need a Picture called blind.

Ruby:
#==============================================================================
# ** Window Blind Add-On
#------------------------------------------------------------------------------
#  This script requires the Window - Hidden RGSS Class by Wecoc
#==============================================================================

class Window
  alias blind_ini initialize unless $@
  def initialize
    @blind = Sprite.new
    @blind.blend_type = 0
    blind_ini
  end
 
  alias blind_dis dispose unless $@
  def dispose
    blind_dis
    @blind.dispose
  end
 
  alias blind_visible update_visible unless $@
  def update_visible
    blind_visible
    @blind.visible = @visible
  end
 
  alias blind_x x= unless $@
  def x=(x)
    blind_x(x)
    @blind.x = x + 2
  end
 
  alias blind_y y= unless $@
  def y=(y)
    blind_y(y)
    @blind.y = y + 2
  end
 
  def z=(z)
    @z = z
    @bg.z = z
    @blind.z = z + 1
    @frame.z = z + 2
    @cr_vport.z = z + 3
    @viewport.z = z + 4
    @pause_s.z = z + 5
  end
 
  alias blind_opacity opacity= unless $@
  def opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    blind_opacity(value)
    @blind.opacity = value
  end
 
  alias blind_back_opacity back_opacity= unless $@
  def back_opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    blind_back_opacity(value)
    @blind.opacity = value
  end
 
  alias blind_draw_back draw_back
  def draw_back
    blind_draw_back
    @blind.bitmap = Bitmap.new(@width - 4, @height - 4)
    src_bitmap = RPG::Cache.picture("blind")
    sprite = Tiled_Sprite.new(src_bitmap, 0, false)
    @blind.bitmap.draw_tiled(sprite)
  end
end

The windows of Chrono Trigger have two separated backs, similar to VX body and blind, but the other way around; the top part is the stretched one. You can see it here.

You could try to modify the stretch modes in the Blind Addon, but it wouldn't look exactly the same, so I made it a separate AddOn instead.

Ruby:
#==============================================================================
# ** Window Chrono Trigger
#------------------------------------------------------------------------------
#  This script requires the Window - Hidden RGSS Class by Wecoc
#==============================================================================

class Window
  alias chrono_ini initialize unless $@
  def initialize
    @chrono_lighten = Sprite.new
    @chrono_darken = Sprite.new
    @chrono_lighten.blend_type = 1
    @chrono_darken.blend_type = 2
    chrono_ini
    self.stretch = false
  end
 
  alias chrono_dis dispose unless $@
  def dispose
    chrono_dis
    @chrono_lighten.dispose
    @chrono_darken.dispose
  end
 
  alias chrono_visible update_visible unless $@
  def update_visible
    chrono_visible
    @chrono_lighten.visible = @visible
    @chrono_darken.visible = @visible
  end
 
  def update_cursor
    if self.active == true
      @cursor_rect.opacity = 255
    else
      @cursor_rect.opacity = 100
    end
  end
 
  alias chrono_x x= unless $@
  def x=(x)
    chrono_x(x)
    @bg.x = x
    @chrono_lighten.x = x
    @chrono_darken.x = x
  end
 
  alias chrono_y y= unless $@
  def y=(y)
    chrono_y(y)
    @bg.y = y
    @chrono_lighten.y = y
    @chrono_darken.y = y
  end
 
  def z=(z)
    @z = z
    @bg.z = z
    @frame.z = z + 1
    @chrono_lighten.z = z + 2
    @chrono_darken.z  = z + 2
    @cr_vport.z = z + 3
    @viewport.z = z + 4
    @pause_s.z = z + 5
  end
 
  alias chrono_opacity opacity= unless $@
  def opacity=(opacity)
    value = [[opacity, 255].min, 0].max
    chrono_opacity(value)
    @chrono_lighten.opacity = value
    @chrono_darken.opacity = value
  end
 
  def draw_back
    @bg.bitmap = Bitmap.new(@width, @height)
    src_bitmap = @skin.cut(0, 0, 128, 128)
    sprite = Tiled_Sprite.new(src_bitmap, 16, @stretch)
    @bg.bitmap.draw_tiled(sprite)
    @chrono_lighten.bitmap = Bitmap.new(@width, @height)
    src_bitmap = RPG::Cache.picture("chrono_lighten")
    sprite = Tiled_Sprite.new(src_bitmap, 0, true)
    @chrono_lighten.bitmap.draw_tiled(sprite)
    @chrono_darken.bitmap = Bitmap.new(@width, @height)
    src_bitmap = RPG::Cache.picture("chrono_darken")
    sprite = Tiled_Sprite.new(src_bitmap, 0, true)
    @chrono_darken.bitmap.draw_tiled(sprite)
  end
end

In this case it requires two extra images in the Pictures folder; chrono_lighen and chrono_lighten. I also made a skin in case you want to try it (don't use it on your game, it's just for testing purposes).

chrono_screen.png
 
Last edited:

Wecoc

Veteran
Veteran
Joined
Jul 4, 2021
Messages
79
Reaction score
215
First Language
Catalan
Primarily Uses
RMXP
I include here another additional script.

With the base script provided in part 4 you can easily modify the tone of the main window sprites: Back, Cursor and Corners.

All this addon does is storing each RGBS (Red-Green-Blue-Saturation) parameter of those tones into a Game_System variable, so every window uses by default a player defined tone that will be saved when you save the game.

adjusting_tone_window.png

Ruby:
#==============================================================================
# ** [XP] Adjustable Window Tone v1.2
#------------------------------------------------------------------------------
# This script requires the Window - Hidden RGSS Class by Wecoc
#==============================================================================

#==============================================================================
# * Game_System
#==============================================================================

class Game_System
  #--------------------------------------------------------------------------
  attr_accessor :back_r, :back_g, :back_b, :back_s
  attr_accessor :cursor_r, :cursor_g, :cursor_b, :cursor_s
  attr_accessor :corner_r, :corner_g, :corner_b, :corner_s
  #--------------------------------------------------------------------------
  alias window_back_tone_ini initialize unless $@
  def initialize
    window_back_tone_ini
    base = [0, 0, 0, 0]
    @back_r, @back_g, @back_b, @back_s = *base
    @cursor_r, @cursor_g, @cursor_b, @cursor_s = *base
    @corner_r, @corner_g, @corner_b, @corner_s = *base
  end
  #--------------------------------------------------------------------------
end

#==============================================================================
# * Window
#==============================================================================

class Window
  #--------------------------------------------------------------------------
  alias back_tone_ini initialize unless $@
  def initialize
    back_tone_ini
    update_tone
  end
  #--------------------------------------------------------------------------
  alias back_tone_upd update unless $@
  def update
    back_tone_upd
    update_tone
  end
  #--------------------------------------------------------------------------
  def update_tone
    @bg.tone = Tone.new($game_system.back_r, $game_system.back_g,
    $game_system.back_b, $game_system.back_s)
    @frame.tone = Tone.new($game_system.corner_r, $game_system.corner_g,
    $game_system.corner_b, $game_system.corner_s)
    @cursor_rect.tone = Tone.new($game_system.cursor_r,
    $game_system.cursor_g, $game_system.cursor_b, $game_system.cursor_s)
  end
  #--------------------------------------------------------------------------
end

The RGBS parameters are included as independent parameters in Game_System (see the start of the script), so you only need to change them like this
$game_system.back_r = value

All windows will use the same parameters. You could modify the script so it's window independent, but it would be hard to store that information, so in that case I recommend

I made an example script so you can test this.

Ruby:
#==============================================================================
# [XP] Example use of the script Adjustable Window Tone v1.2
# This example replaces the "Status" option in the menu for a window tone set
#==============================================================================

#==============================================================================
# * ToneWindow
#==============================================================================

class ToneWindow < Window_Command
  #--------------------------------------------------------------------------
  attr_accessor :back_r, :back_g, :back_b, :back_s
  attr_accessor :cursor_r, :cursor_g, :cursor_b, :cursor_s
  attr_accessor :corner_r, :corner_g, :corner_b, :corner_s
  #--------------------------------------------------------------------------
  def update_tone
    if @back_r.nil?
      base = [0, 0, 0, 0]
      @back_r, @back_g, @back_b, @back_s = *base
      @cursor_r, @cursor_g, @cursor_b, @cursor_s = *base
      @corner_r, @corner_g, @corner_b, @corner_s = *base
    end
    @bg.tone = Tone.new(@back_r, @back_g, @back_b, @back_s)
    @frame.tone = Tone.new(@corner_r, @corner_g, @corner_b, @corner_s)
    @cursor_rect.tone = Tone.new(@cursor_r, @cursor_g, @cursor_b, @cursor_s)
  end
  #--------------------------------------------------------------------------
  def refresh
    super
    return if @back_r.nil?
    self.contents.draw_text(64,  0, 100, 32, @back_r.to_i.to_s, 2)
    self.contents.draw_text(64, 32, 100, 32, @back_g.to_i.to_s, 2)
    self.contents.draw_text(64, 64, 100, 32, @back_b.to_i.to_s, 2)
    self.contents.draw_text(64, 96, 100, 32, @back_s.to_i.to_s, 2)
  end
end

#==============================================================================
# * Scene_Menu
#==============================================================================

class Scene_Menu
  #--------------------------------------------------------------------------
  # * Main
  #--------------------------------------------------------------------------
  def main
    s1 = $data_system.words.item
    s2 = $data_system.words.skill
    s3 = $data_system.words.equip
    s4 = "Color test"
    s5 = "Save"
    s6 = "Quit"
    @command_window = Window_Command.new(160, [s1, s2, s3, s4, s5, s6])
    @command_window.index = @menu_index
    if $game_party.actors.size == 0
      @command_window.disable_item(0)
      @command_window.disable_item(1)
      @command_window.disable_item(2)
      @command_window.disable_item(3)
    end
    if $game_system.save_disabled
      @command_window.disable_item(4)
    end
    @playtime_window = Window_PlayTime.new
    @playtime_window.x = 0
    @playtime_window.y = 224
    @steps_window = Window_Steps.new
    @steps_window.x = 0
    @steps_window.y = 320
    @gold_window = Window_Gold.new
    @gold_window.x = 0
    @gold_window.y = 416
    @status_window = Window_MenuStatus.new
    @status_window.x = 160
    @status_window.y = 0
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    @command_window.dispose
    @playtime_window.dispose
    @steps_window.dispose
    @gold_window.dispose
    @status_window.dispose
  end
  #--------------------------------------------------------------------------
  # * Update Command
  #--------------------------------------------------------------------------
  def update_command
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      $scene = Scene_Map.new
      return
    end
    if Input.trigger?(Input::C)
      if $game_party.actors.size == 0 and @command_window.index < 4
        $game_system.se_play($data_system.buzzer_se)
        return
      end
      case @command_window.index
      when 0  # item
        $game_system.se_play($data_system.decision_se)
        $scene = Scene_Item.new
      when 1  # skill
        $game_system.se_play($data_system.decision_se)
        @command_window.active = false
        @status_window.active = true
        @status_window.index = 0
      when 2  # equipment
        $game_system.se_play($data_system.decision_se)
        @command_window.active = false
        @status_window.active = true
        @status_window.index = 0
      when 3  # status [MOD]
        $game_system.se_play($data_system.decision_se)
        $scene = Scene_ToneWindow.new
      when 4  # save
        if $game_system.save_disabled
          $game_system.se_play($data_system.buzzer_se)
          return
        end
        $game_system.se_play($data_system.decision_se)
        $scene = Scene_Save.new
      when 5  # end game
        $game_system.se_play($data_system.decision_se)
        $scene = Scene_End.new
      end
      return
    end
  end
end

#==========================================================
# * Scene_ToneWindow
#==========================================================

class Scene_ToneWindow
  def main
    s1 = "Red"
    s2 = "Green"
    s3 = "Blue"
    s4 = "Gray"
    @tone_window = ToneWindow.new(200, [s1, s2, s3, s4])
    @tone_window.x = 240
    @tone_window.y = 240
    @tone_window.back_r = $game_system.back_r
    @tone_window.back_g = $game_system.back_g
    @tone_window.back_b = $game_system.back_b
    @tone_window.back_s = $game_system.back_s
    @tone_window.refresh
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    @tone_window.dispose
  end
 
  def update
    @tone_window.update
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      $scene = Scene_Menu.new(3)
      return
    end
    if Input.trigger?(Input::C)
      $game_system.se_play($data_system.decision_se)
      $game_system.back_r = @tone_window.back_r
      $game_system.back_g = @tone_window.back_g
      $game_system.back_b = @tone_window.back_b
      $game_system.back_s = @tone_window.back_s
      $scene = Scene_Menu.new(3)
      return
    end
    if Input.repeat?(Input::RIGHT)
      $game_system.se_play($data_system.cursor_se)
      case @tone_window.index
      when 0 # red
        r = @tone_window.back_r + 10
        r = (r.to_f / 10).floor * 10
        r = [255, r].min
        @tone_window.back_r = r
      when 1 # green
        g = @tone_window.back_g + 10
        g = (g.to_f / 10).floor * 10
        g = [255, g].min
        @tone_window.back_g = g
      when 2 #blue
        b = @tone_window.back_b + 10
        b = (b.to_f / 10).floor * 10
        b = [255, b].min
        @tone_window.back_b = b
      when 3 #gray
        s = @tone_window.back_s + 10
        s = (s.to_f / 10).floor * 10
        s = [255, s].min
        @tone_window.back_s = s
      end
      @tone_window.refresh
    end
    if Input.repeat?(Input::LEFT)
      $game_system.se_play($data_system.cursor_se)
      case @tone_window.index
      when 0 # red
        r = @tone_window.back_r - 10
        r = (r.to_f / 10).ceil * 10
        r = [-255, r].max
        @tone_window.back_r = r
      when 1 # green
        g = @tone_window.back_g - 10
        g = (g.to_f / 10).ceil * 10
        g = [-255, g].max
        @tone_window.back_g = g
      when 2 #blue
        b = @tone_window.back_b - 10
        b = (b.to_f / 10).ceil * 10
        b = [-255, b].max
        @tone_window.back_b = b
      when 3 #gray
        s = @tone_window.back_s - 10
        s = (s.to_f / 10).ceil * 10
        s = [0, s].max
        @tone_window.back_s = s
      end
      @tone_window.refresh
    end
  end
end
 

Latest Threads

Latest Posts

Latest Profile Posts

Too bad the Boss Battle Build Bout isn't also for VXAce.
I might not participate in the Boss Battle contest after all... I have more important things to do, like Wishful Wanda. And of course the Dark Deception spinoff I plan to pitch, Demon Slayer.
Changed my avatar, goodbye Alan Sugar, hello George Carlin (one of my favorite human beings ever)
If you still don't subscribe our Polish channel please consider it :)
1.png
Who wants to see my review of the worst star wars movie? This movie has all the excitement of being on Jury Duty of the most boring case ever about trade negotiations.

Forum statistics

Threads
115,171
Messages
1,087,808
Members
149,723
Latest member
krinalle
Top