Window Message Mod
Version: 1.1
Author: ThallionDarkshine
Date: December 16, 2012
Version History
- <Version 1.1> Fixed a small bug in the animation system with multi-line animations. Allowed users to create choose gradients' colors by rgb. Fixed a bug with nested gradients.
- <Version 1.0> 2012.12.15 - Gradient text, formatting options, text animations added.
- <Version 0.1> 2012.12.15 - Original Release
Planned Future Versions
Description
This is a small Window_Message mod I made a while ago to allow letter-by-letter text. It now also includes a way to create text animations such as growing, shrinking, rotating, or shaking text. You can also format text with things like bold, underline, etc.
New feature! You can now create text gradients!
Text Animations:
- Grow
- Rotate (rock back and forth)
- Shrink
- Shake
- Float
- Hue Rotate
- Bounce
Formatting Properties
- Bold
- Italic
- Underline
- Overline (pretty much useless but I made it anyways)
- Strikethrough
Features
- Specify how many letters are added at a time, and at what interval they are added.
- Specify word-by-word text
- Message shortcut to change letter-by-letter options from default.
- Create animated text!
- Format text, making it bold, italic, etc.
Screenshots
I'll put some up if requested, but there's not too much to see.
Instructions
Instructions are in the script.
A dll is now required for the gradient text feature. It is found
here.
Script
# Instructions on use of this script
# - To change the letter-by-letter mode, use:
# \l[CHAR_AMOUNT, FRAMES]
# CHAR_AMOUNT - How many chars to add to the string every time it updates
# note - Use 0 for word-by-word or any negative number for no letter-by-letter
# FRAMES - How often to add CHAR_AMOUNT characters to the string
# - To create animated text, use:
# \a[TYPE]
# TYPE - Which type of animation the text should have
# 0 - Turn off Animation
# 1 - Grow
# 2 - Rock
# 3 - Shrink
# 4 - Shake
# 5 - Float
# 6 - Color Rotate
# 7 - Bounce
# 8 - Fade
# note - you can use simply \a to turn off the animation
# - Formatting Stuff
# Bold - <b></b>
# Italic - <i></i>
# Underline - <u></u>
# Overline - <o></o>
# Strikethrough - <s></s>
# - To create gradient text, use:
# <grad=COLOR1:COLOR2></grad>
# COLOR1 - The left color.
# COLOR2 - The right color.
# note - colors can be specified either as RED, GREEN, BLUE or as COLOR_ID
class Window_Message
# text mode:
# 0 - Normal
# 1 - Letter-by-Letter
# 2 - Word-by-Word
DEFAULT_MODE = 1
# default amount of characters at a time
DEFAULT_CHARS = 1
# default number of frames to characters
DEFAULT_FRAMES = 5
def get_text
text = $game_temp.message_text
@wait_points = []
@wait_count = 0
if text.gsub!(/\\l(?:\[([\-\d]+)(?:\s*\,\s*([\-\d]+))?\])?/, '') != nil
@l_l = true
@l_amnt = $1.nil? ? Window_Message::DEFAULT_CHARS : $1.to_i
@l_frames = $2.nil? ? Window_Message::DEFAULT_FRAMES : $2.to_i
@frames = 0
@length = 0
if @l_amnt < 0
@l_l = false
end
else
case Window_Message::DEFAULT_MODE
when 0:
@l_l = false
when 1:
@l_l = true
@l_amnt = Window_Message::DEFAULT_CHARS
@l_frames = Window_Message::DEFAULT_FRAMES
@frames = 0
@length = 0
when 2:
@l_l = true
@l_amnt = 0
@l_frames = Window_Message::DEFAULT_FRAMES
@frames = 0
@length = 0
end
end
@text = ""
begin
last_text = text.clone
text.gsub!(/\\[Vv]\[([0-9]+)\]/) { $game_variables[$1.to_i] }
end until text == last_text
text.gsub!(/\\[Nn]\[([0-9]+)\]/) do
$game_actors[$1.to_i] != nil ? $game_actors[$1.to_i].name : ""
end
# Change "\\\\" to "\000" for convenience
text.gsub!(/\\\\/) { "\000" }
unformatted = text.clone
unformatted.gsub!(/\\[Cc]\[([0-9]+)\]/, "")
unformatted.gsub!(/\\[Aa]\[(\d+)\]/, "")
unformatted.gsub!(/\<\/?[a-z]+\>/, '')
unformatted.gsub!(/\<\/?grad\=(\d+(?:\s*\,\s*\d+)*)\:(\d+(?:\s*\,\s*\d+)*)\>/, '')
unformatted.gsub!(/\\[Gg]/, '')
# Change "\\C" to "\001"
text.gsub!(/\\[Cc]\[([0-9]+)\]/) { "\001[#{$1}]" }
if text.gsub!(/\\[Gg]/, '') != nil and @l_l
@gold_window = Window_Gold.new
@gold_window.x = 560 - @gold_window.width
if $game_temp.in_battle
@gold_window.y = 192
else
@gold_window.y = self.y >= 128 ? 32 : 384
end
@gold_window.opacity = self.opacity
@gold_window.back_opacity = self.back_opacity
end
while !(ind=unformatted.index(/\\[Ww]\[(\d+)\]/)).nil?
unformatted.sub!(/\\[Ww]\[(\d+)\]/, "")
text.sub!(/\\[Ww]\[(\d+)\]/, "")
@wait_points.push([ind - 1, $1.to_i])
end
x, y = 0, 0
gradients = []
@gradients = []
# Get 1 text character in c (loop until unable to get text)
while ((c = text.slice!(/./m)) != nil)
@text += c
if c == "\001" or c == "\002"
next
end
if c == "<"
if text.index(/grad\=(\d+(?:\s*\,\s*\d+)*)\:(\d+(?:\s*\,\s*\d+)*)\>/) == 0
gradient = [$1, $2, 4 + x, 32 * y, 0, 32]
gradients.push(gradient)
@text = @text.split('')
@text = (@text.length == 1 ? "" : @text[0..@text.length - 2].join(''))
text.sub!(/(grad\=(\d+(?:\s*\,\s*\d+)*)\:(\d+(?:\s*\,\s*\d+)*)\>)/, '')
next
elsif text.index('/grad>') == 0
unless gradients.length == 0 or gradients.last[0].nil?
gradient = gradients.pop()
w = [gradient[4], x + 4 - gradient[2]].max
h = [gradient[5], y * 32 - gradient[3]].max
rect = Rect.new(gradient[2], gradient[3], w, h)
@gradients.push([gradient[0], gradient[1], rect])
end
@text = @text.split('')
@text = @text[0..@text.length - 2].join('')
text.sub!(/(\/?[a-z]+\>)/, '')
next
end
if text.index(/\/?[a-z]+\>/) == 0
text.sub!(/(\/?[a-z]+\>)/, '')
@text += $1
end
end
if c == "\n"
unless gradients.length == 0 or gradients.last[0].nil?
gradients[-1][4] = [gradients[-1][3], x + 4 - gradients[-1][1]].max
gradients[-1][5] += 32
end
x = 0
y += 32
if y >= $game_temp.choice_start
x = 8
end
end
x += self.contents.text_size(c).width
end
unless gradients.length == 0 or gradients.last[0].nil?
gradient = gradients.pop()
w = [gradient[4], x + 4 - gradient[2]].max
h = [gradient[5], y * 32 - gradient[3]].max
rect = Rect.new(gradient[2], gradient[3], w, h)
@gradients.push([gradient[0], gradient[1], rect])
end
@unformatted = unformatted.clone
@length = unformatted.length unless @l_l
end
alias tdks_message_refresh refresh
def refresh
if !$game_temp.message_text.nil?
get_text
@animations.each { |ani| ani.bitmap.dispose;ani.dispose } unless @animations.nil?
end
@animations = []
self.contents.clear
self.contents.font.color = normal_color
x = y = 0
@cursor_width = 0
# Indent if choice
if $game_temp.choice_start == 0
x = 8
end
# If waiting for a message to be displayed
if @text != nil
self.contents.font = Font.new
self.contents.font.underline = false
self.contents.font.overline = false
self.contents.font.strikethrough = false
text = @text.clone
real_index = 0
index = 1
animation = [0, 0, 0, 0, 0]
p_bold = p_italic = p_underline = p_overline = p_strikethrough = []
# Control text processing
begin
last_text = text.clone
text.gsub!(/\\[Vv]\[([0-9]+)\]/) { $game_variables[$1.to_i] }
end until text == last_text
text.gsub!(/\\[Nn]\[([0-9]+)\]/) do
$game_actors[$1.to_i] != nil ? $game_actors[$1.to_i].name : ""
end
# Change "\\\\" to "\000" for convenience
text.gsub!(/\\\\/) { "\000" }
# Change "\\C" to "\001" and "\\G" to "\002"
text.gsub!(/\\[Cc]\[([0-9]+)\]/) { "\001[#{$1}]" }
if @length == @text.length
text.gsub!(/\\[Aa]/) { "\002" }
else
text.gsub!(/\\[Aa](?:\[(\d+)\])?/, "")
end
# Get 1 text character in c (loop until unable to get text)
while ((c = text.slice!(/./m)) != nil)
break if @length == 0
real_index += 1
# If \\
if c == "\000"
# Return to original text
c = "\\"
end
# If \C[n]
if c == "\001"
# Change text color
text.sub!(/\[([0-9]+)\]/, "")
color = $1.to_i
if color >= 0 and color <= 7
self.contents.font.color = text_color(color)
end
# go to next text
next
end
if c == "\002" and @length == @text.length
if animation[0] != 0
animation[1][-1].width = x + 4 - animation[1][-1].x
@animations.push(animation)
end
animation = [0, [Rect.new(4 + x, 32 * y, 0, 32)]]
if text.index(/\[(\d+)\]/) == 0
text.sub!(/\[(\d+)\]/, "")
animation[0] = $1.to_i
else
animation[0] = 0
end
next
end
if c == "<"
if text.index('b>') == 0
p_bold.push(self.contents.font.bold)
self.contents.font.bold = true
elsif text.index('i>') == 0
p_italic.push(self.contents.font.italic)
self.contents.font.italic = true
elsif text.index('u>') == 0
p_underline.push(self.contents.font.underline)
self.contents.font.underline = true
elsif text.index('o>') == 0
p_overline.push(self.contents.font.overline)
self.contents.font.overline = true
elsif text.index('s>') == 0
p_strikethrough.push(self.contents.font.strikethrough)
self.contents.font.strikethrough = true
end
if text.index('/b>') == 0
self.contents.font.bold = p_bold.pop
elsif text.index('/i>') == 0
self.contents.font.italic = p_italic.pop
elsif text.index('/u>') == 0
self.contents.font.underline = p_underline.pop
elsif text.index('/o>') == 0
self.contents.font.overline = p_overline.pop
elsif text.index('/s>') == 0
self.contents.font.strikethrough = p_strikethrough.pop
end
font = self.contents.font
if text.index(/\/?[a-z]+\>/) == 0
text.sub!(/\/?[a-z]+\>/, '')
next
end
end
# If new line text
if c == "\n"
# Update cursor width if choice
if y >= $game_temp.choice_start
@cursor_width = [@cursor_width, x].max
end
if animation[0] != 0
animation[1][-1].width = x + 4 - animation[1][-1].x
end
# Add 1 to y
y += 1
x = 0
# Indent if choice
if y >= $game_temp.choice_start
x = 8
end
if animation[0] != 0
animation[1].push(Rect.new(x + 4, y * 32, 0, 32))
end
# go to next text
next
end
# Draw text
self.contents.draw_text(4 + x, 32 * y, 40, 32, c)
# Add x to drawn text width
x += self.contents.text_size(c).width
index += 1
break if index > @length
end
if animation[0] != 0
animation[1][-1].width = x + 4 - animation[1][-1].x
@animations.push(animation)
end
end
# If choice
if $game_temp.choice_max > 0
@item_max = $game_temp.choice_max
self.active = true
self.index = 0
end
# If number input
if $game_temp.num_input_variable_id > 0
digits_max = $game_temp.num_input_digits_max
number = $game_variables[$game_temp.num_input_variable_id]
@input_number_window = Window_InputNumber.new(digits_max)
@input_number_window.number = number
@input_number_window.x = self.x + 8
@input_number_window.y = self.y + $game_temp.num_input_start * 32
end
$game_temp.message_text = nil
overlay_grad = Win32API.new('overlay_grad.dll', 'overlay_grad', 'liiiiiiiiii', 'b')
@gradients.reverse.each { |i|
c1 = (i[0].index(',').nil? ? text_color(i[0].to_i) : Color.new(*i[0].split(/\s*\,\s*/).map { |e| e.to_i } ))
c2 = (i[1].index(',').nil? ? text_color(i[1].to_i) : Color.new(*i[1].split(/\s*\,\s*/).map { |e| e.to_i } ))
overlay_grad.call(self.contents.__id__, i[2].x, i[2].y, i[2].width, i[2].height, c1.red, c1.green, c1.blue, c2.red, c2.green, c2.blue)
}
if @length == @text.length
@animations = @animations.map { |ani| p ani[1];AnimationSprite.new(ani[0], self.x + 16, self.y + 16, self.z + 1, ani[1], self.contents) }
@animations.each { |i| i.opacity = (@fade_in ? 0 : 255) }
end
end
alias tdks_message_update update
def update
if !@wait_count.nil? and @wait_count > 0
@wait_count -= 1
return
end
# If letter-by-letter
if !(@fade_in or (@fade_out == false and $game_temp.message_text != nil)) and @l_l and self.contents_opacity == 255
@frames += 1
if Input.trigger?(Input::C)
@length = @text.length
end
return unless @frames % @l_frames == 0
prev_len = @length
unless @l_amnt == 0
@length = [@length + @l_amnt, @text.length].min
else
s_text = @unformatted[0...@length]
e_text = @unformatted[@length...@unformatted.length].clone
e_text.gsub!(/\A[\n\r]+/, '')
if e_text.index(/\w+/) == 0
e_text.sub(/(\w+)/, '')
@length += $1.length
e_text = @unformatted[@length...@unformatted.length]
if e_text.index(/[^\w]+/) == 0
e_text.sub(/([^\w]+)/, '')
tmp = $1.gsub(/[\n\r]/, '')
@length += tmp.length
end
elsif e_text.index(/[^\w]+/) == 0
e_text.sub(/([^\w]+)/, '')
tmp = $1.gsub(/[\n\r]/, '')
@length += tmp.length
e_text = @unformatted[@length...@unformatted.length]
if e_text.index(/\w+/) == 0
e_text.sub(/(\w+)/, '')
@length += $1.length
end
end
end
unless Input.trigger?(Input::C)
if (wait = @wait_points.detect { |i| i[0].between?(prev_len, @length) }) != nil
frames = wait[1]
@wait_points.delete(wait)
@wait_count = frames
@length = wait[0] - 1 if frames > 0
end
end
len = @unformatted.gsub(/[\n\r]/, '').length
if @length >= len
@length = @text.length
@l_l = false
end
refresh
return
end
@animations.each { |ani| ani.update } unless @animations.nil? or @animations.empty? or @fade_out
super
# If fade in
if @fade_in
self.contents_opacity += 24
@animations.each { |i| i.opacity = self.contents_opacity } unless @animations.nil?
if @input_number_window != nil
@input_number_window.contents_opacity += 24
end
if self.contents_opacity == 255
@fade_in = false
end
return
end
# If inputting number
if @input_number_window != nil
@input_number_window.update
# Confirm
if Input.trigger?(Input::C)
$game_system.se_play($data_system.decision_se)
$game_variables[$game_temp.num_input_variable_id] =
@input_number_window.number
$game_map.need_refresh = true
# Dispose of number input window
@input_number_window.dispose
@input_number_window = nil
terminate_message
end
return
end
# If message is being displayed
if @contents_showing
# If choice isn't being displayed, show pause sign
if $game_temp.choice_max == 0
self.pause = true
end
# Cancel
if Input.trigger?(Input::B)
if $game_temp.choice_max > 0 and $game_temp.choice_cancel_type > 0
$game_system.se_play($data_system.cancel_se)
$game_temp.choice_proc.call($game_temp.choice_cancel_type - 1)
terminate_message
end
end
# Confirm
if Input.trigger?(Input::C)
if $game_temp.choice_max > 0
$game_system.se_play($data_system.decision_se)
$game_temp.choice_proc.call(self.index)
end
terminate_message
end
return
end
# If display wait message or choice exists when not fading out
if @fade_out == false and $game_temp.message_text != nil
@contents_showing = true
$game_temp.message_window_showing = true
reset_window
@fade_in = true
refresh
Graphics.frame_reset
self.visible = true
self.contents_opacity = 0
if @input_number_window != nil
@input_number_window.contents_opacity = 0
end
return
end
# If message which should be displayed is not shown, but window is visible
if self.visible
@fade_out = true
self.opacity -= 48
@animations.each { |i| i.opacity = self.opacity } unless @animations.nil?
if self.opacity == 0
self.visible = false
@animations.each { |i| i.bitmap.dispose;i.dispose }
@fade_out = false
$game_temp.message_window_showing = false
end
return
end
end
end
class Font
attr_accessor :underline, :overline, :strikethrough
attr_accessor :outline, :outline_color, :outline_distance
attr_accessor :shadow, :shadow_color, :shadow_ox, :shadow_oy
alias tdks_message_init initialize
def initialize(*args)
tdks_message_init(*args)
@underline = false
@overline = false
@strikethrough = false
@outline, @outline_color, @outline_distance = false, Color.new(0, 0, 0), 0
@shadow, @shadow_color, @shadow_ox, @shadow_oy = false, Color.new(127, 127, 127, 170), 2, 2
end
end
class Bitmap
alias tdks_message_draw_text draw_text
def draw_text(*args)
unless args[0].is_a?(Rect)
args[0..3] = Rect.new(*args[0..3])
end
args[2] = 0 if args[2].nil?
args[3] = 1 if args[3].nil?
case args[3]
when 0
args[0].height = text_size(args[1]).height
when 1
h = text_size(args[1]).height
args[0].y += (args[0].height - h) / 2
args[0].height = h
when 2
h = text_size(args[1]).height
args[0].y += args[0].height
args[0].height = h
end
text = args[1].clone
text = text.split(/[\n\r]/)
rect = args[0].clone
case args[3]
when 1
amnt = (text.length / 2.0).floor
text[0...amnt].each { |ln| rect.y -= text_size(ln).height } if amnt > 0
rect.y += text_size(text[0]).height / 2 if amnt % 2 == 1
when 2
text.each { |ln| rect.y -= text_size(ln).height }
end
text.each { |ln|
orig_x = rect.x
size = text_size(ln)
case args[2]
when 1
rect.x += (rect.width - size.width) / 2
when 2
rect.x += rect.width - size.width
end
if font.shadow
prev_color = font.color.clone
font.color = font.shadow_color
tdks_message_draw_text(rect.x + font.shadow_ox, rect.y + font.shadow_oy, rect.width, rect.height, ln)
font.color = prev_color
end
if font.outline
prev_color = font.color.clone
font.color = font.outline_color
(-font.outline_distance..font.outline_distance).each { |ox|
(-font.outline_distance..font.outline_distance).each { |oy|
next if ox == 0 and oy == 0
tdks_message_draw_text(rect.x + ox, rect.y + oy, rect.width, rect.height, ln)
}
}
font.color = prev_color
end
tdks_message_draw_text(rect, ln)
if font.overline
fill_rect(rect.x, rect.y + (size.height / 15.0).ceil, size.width, (size.height / 15.0).ceil, font.color)
end
if font.strikethrough
fill_rect(rect.x, rect.y + size.height / 2, size.width, (size.height / 15.0).ceil, font.color)
end
rect.y += size.height
if font.underline
fill_rect(rect.x, rect.y - (2 * size.height / 15.0).ceil, size.width, (size.height / 15.0).ceil, font.color)
end
rect.x = orig_x
}
end
def draw_gradient_text(c1, c2, *args)
unless args[0].is_a?(Rect)
args[0..3] = Rect.new(*args[0..3])
end
rect = args[0]
tmp = Bitmap.new(rect.width, rect.height)
tmp.draw_text(tmp.rect, *args[1...args.length])
overlay_grad = Win32API.new('overlay_grad.dll', 'overlay_grad', 'liiiiiiiiii', 'b')
overlay_grad.call(tmp.__id__, args[0].x, args[0].y, args[0].width, args[0].height, c1.red, c1.green, c1.blue, c2.red, c2.green, c2.blue)
blt(rect.x, rect.y, tmp, tmp.rect)
end
end
class AnimationSprite < Sprite
def initialize(type, x, y, z, rects, src_bitmap)
super()
@type = type
rects.delete_if { |rect| rect.width == 0 or rect.height == 0 }
ry = rects.min { |i, e| i.y <=> e.y }.y
rx = rects.min { |i, e| i.x <=> e.x }.x
h = rects.max { |i, e| i.y + i.height <=> e.y + e.height }
h = h.y + h.height - ry
w = rects.max { |i, e| i.x + i.width <=> e.x + e.width }
w = w.x + w.width - rx
p rx, ry, w, h
self.x, self.y, self.z = x + rx, y + ry, z
self.ox, self.oy = w / 2, h / 2
self.x += ox
self.y += oy
self.bitmap = Bitmap.new(w, h)
rects.each { |rect|
bitmap.blt(rect.x - rx, rect.y - ry, src_bitmap, rect)
src_bitmap.fill_rect(rect, Color.new(0, 0, 0, 0))
}
@frames = 0
@fplus = 1
case @type
when 4:
@shake = [0, 0]
@shaked = 1
when 6:
@frames = -40
when 7:
@frames = 2
end
end
def update
return if self.disposed?
@frames += @fplus
case @type
when 1:
z = 1.0 + @frames * 0.1 / 20
@fplus = -1 if @frames == 20
@fplus = 1 if @frames == 0
self.zoom_x = z
self.zoom_y = z
when 2:
a = @frames * 5.0 / 12
@fplus = -1 if @frames == 12
@fplus = 1 if @frames == -12
self.angle = a
when 3:
z = 1.0 - @frames * 0.1 / 20
@fplus = -1 if @frames == 20
@fplus = 1 if @frames == 0
self.zoom_x = z
self.zoom_y = z
when 4:
shake_power = 1
shake_speed = 10
delta = (shake_power * shake_speed * @fplus) / 15.0 + rand(3) - 1
@shake[0] += delta
if @shake[0] > shake_power
@fplus = -1
end
if @shake[0] < - shake_power
@fplus = 1
end
delta = (shake_power * shake_speed * @shaked) / 15.0 + rand(3) - 1
@shake[1] += delta
if @shake[1] > shake_power
@shaked = -1
end
if @shake[1] < - shake_power
@shaked = 1
end
self.ox = self.bitmap.width / 2 + @shake[0]
self.oy = self.bitmap.height / 2 + @shake[1]
when 5:
amnt = 5
amnt *= Math.sin(Math::PI * @frames / 30.0)
self.oy = self.bitmap.height / 2 + amnt
when 6:
if @frames < 0
a = 255 + @frames * 255 / 40
self.color.set(255, 0, 0, a)
return
end
r = g = b = 0
tmp = (@frames % 360) * 255 / 360
while tmp < 0
tmp += 255
end
if tmp < 85
g = tmp * 3
r = (85 - tmp) * 3
elsif tmp < 170
b = (tmp - 85) * 3
g = (170 - tmp) * 3
else
r = (tmp - 170) * 3
b = (255 - tmp) * 3
end
self.color.set(r, g, b)
when 7:
amnt = 10
amnt *= Math.sin(Math::PI * @frames / 30.0).abs
self.oy = self.bitmap.height / 2 + amnt - 5
when 8:
self.opacity = 255 - 170 * @frames / 20
@fplus = -1 if @frames == 20
@fplus = 1 if @frames == 0
end
end
end
Credit
Thanks
- ThallionDarkshine
- Heretic86 for giving me tons of ideas for additions
Support
I'm most active over on Chaos-Project, but if you ask for support in this topic I'll get to it eventually.
Known Compatibility Issues
This probably won't be compatible with other message systems.
Demo
None yet
Author's Notes
A dll is now required for the gradient text feature. It is found
here.
Terms of Use
None