#==============================================================================
# ■ XP行走图描边脚本 - Character Stroker 2026.3.21 By HFDange
#------------------------------------------------------------------------------
# 使用方法:在需要描边的事件名中添加“.stX”后缀。
# X为标记描边颜色的数字,如“.st0”为灰色描边、“.st1”为红色描边。
# 若要更改描边颜色,在CSC_COLORSET中添加或修改。
#
# 2026.4.2更新:描边渐变
#
# 使用方法:事件名后缀“.stXgX”
# 如“.st1g0”为红->灰->红渐变
#
#==============================================================================
#==============================================================================
# ● 设置部分
#==============================================================================
module CharSt_Config
# 是否八方向描边(默认FALSE)
CSC_IF_8_DIRECTIONS = FALSE
# 描边颜色表(RGBA),默认颜色为:灰 红 绿 蓝 黄 紫 青 白 黑
CSC_COLORSET = [
[64, 56, 56, 255], [255, 18, 18, 255], [18, 255, 18, 255],
[18, 18, 255, 255], [255, 255, 18, 255], [255, 18, 255, 255],
[18, 255, 255, 255], [255, 255, 255, 255], [0, 0, 0, 255]
]
# 渐变周期长度 - 以帧为单位(默认20)
CSC_GRADIENT_CYCLE = 20
end
#==============================================================================
# ● 描边脚本
#==============================================================================
module CharactorStroker
include CharSt_Config
def self.need?(name) # 从事件名判断是否需要描边
return false if CharactorStroker.type(name) == nil
return name.include?(".st")
end
def self.needgd?(name) # 判断描边是否需要渐变
reg = name.match(/\.st\d+g/)
return reg != nil
end
def self.type(name) # 判断描边颜色
type = name.match(/(?<=\.st)\d+/)
return 0 unless type
return type[0].to_i
end
def self.gdtype(name) # 判断描边渐变颜色
type = name.match(/\.st\d+g(\d+)/i)
return type[1].to_i
end
def self.gdrate # 计算渐变比率 (0~1)
frame = Graphics.frame_count
prirate = frame % (CSC_GRADIENT_CYCLE * 2)
rate = prirate.to_f / CSC_GRADIENT_CYCLE if prirate < CSC_GRADIENT_CYCLE
rate = (CSC_GRADIENT_CYCLE * 2 - prirate.to_f) / CSC_GRADIENT_CYCLE if prirate >= CSC_GRADIENT_CYCLE
return rate
end
def self.gdcolor(name) # 计算渐变颜色
col = CSC_COLORSET[CharactorStroker.type(name)]
colgd = CSC_COLORSET[CharactorStroker.gdtype(name)]
rate = CharactorStroker.gdrate
r = (col[0] + (colgd[0] - col[0]) * rate).to_i
g = (col[1] + (colgd[1] - col[1]) * rate).to_i
b = (col[2] + (colgd[2] - col[2]) * rate).to_i
a = (col[3] + (colgd[3] - col[3]) * rate).to_i
return [r, g, b, a]
end
def self.sync(event, lists, states, viewport) # 事件描边同步行走图
id = event.id
lists[id].x = event.screen_x - 1
lists[id].y = event.screen_y - 1
lists[id].z = event.screen_z - 2
name = event.event.name
if CharactorStroker.needgd?(name)
gd = 1
else
gd = 0
end
new_state = [event.character_name, name, event.direction, event.pattern, gd]
if states[id] != new_state
if CharactorStroker.need?(name)
col = CharactorStroker.type(name)
CharactorStroker.draw(lists, states, col, id, viewport)
else
lists[id].bitmap.dispose
lists[id].dispose
lists.delete(id)
states.delete(id)
end
end
end
#--------------------------------------------------------------------------
# ● 绘制描边精灵图
#--------------------------------------------------------------------------
def self.draw(lists, states, col, id, viewport)
lists[id] = Sprite.new(viewport) if !lists[id] || lists[id].disposed?
lists[id].color.set(CSC_COLORSET[col][0],CSC_COLORSET[col][1],CSC_COLORSET[col][2],CSC_COLORSET[col][3])
tevent = $game_map.events[id]
if CharactorStroker.needgd?(tevent.event.name)
gd = 1
else
gd = 0
end
states[id] = [tevent.character_name, tevent.event.name, tevent.direction, tevent.pattern, gd]
t1 = RPG::Cache.character(tevent.character_name, tevent.character_hue)
rw = t1.width / 4
rh = t1.height / 4
lists[id].ox = rw / 2
lists[id].oy = rh
if lists[id].bitmap != nil && !lists[id].bitmap.disposed?
lists[id].bitmap.dispose
end
lists[id].bitmap = Bitmap.new(rw+2, rh+2)
rx = tevent.pattern * rw
case tevent.direction
when 2
dir = 0
when 4
dir = 1
when 6
dir = 2
when 8
dir = 3
end
ry = dir * rh
evcrect = Rect.new(rx, ry, rw, rh)
t2 = lists[id].bitmap
t2.blt(0, 1, t1, evcrect)
t2.blt(2, 1, t1, evcrect)
t2.blt(1, 0, t1, evcrect)
t2.blt(1, 2, t1, evcrect)
if CSC_IF_8_DIRECTIONS
t2.blt(2, 2, t1, evcrect)
t2.blt(0, 0, t1, evcrect)
t2.blt(2, 0, t1, evcrect)
t2.blt(0, 2, t1, evcrect)
end
end
end
#==============================================================================
# ● 公开event变量
#==============================================================================
class Game_Event < Game_Character
attr_reader :event
end
#==============================================================================
# ● 修改Spriteset_Map初始化、释放、刷新函数
#==============================================================================
class Spriteset_Map
alias cst_old_initialize initialize
alias cst_old_dispose dispose
alias cst_old_update update
def initialize
cst_old_initialize
@cst_lists = {}
@cst_states = {}
cst_refresh
end
def dispose
cst_old_dispose
@cst_lists.each_value{ |cstcheck|
next unless cstcheck.respond_to?(:dispose)
if cstcheck.bitmap && !cstcheck.bitmap.disposed?
cstcheck.bitmap.dispose
end
cstcheck.dispose unless cstcheck.disposed?
}
@cst_lists.clear
@cst_states.clear
end
def update
cst_old_update
if @cst_lists
@cst_lists.each_key{ |cstid|
event = $game_map.events[cstid]
CharactorStroker.sync(event, @cst_lists, @cst_states, @viewport1)
if @cst_states[cstid][4] == 1
gdcol = CharactorStroker.gdcolor(event.event.name)
@cst_lists[cstid].color.set(gdcol[0], gdcol[1], gdcol[2], gdcol[3])
end
}
end
end
def cst_refresh
@cst_lists.each_value{ |cstcheck|
next unless cstcheck.respond_to?(:dispose)
if cstcheck.bitmap && !cstcheck.bitmap.disposed?
cstcheck.bitmap.dispose
end
cstcheck.dispose unless cstcheck.disposed?
}
@cst_lists.clear
@cst_states.clear
$game_map.events.each_value{ |value|
event = value.event
name = event.name
if CharactorStroker.need?(name) && value.character_name != ""
col = CharactorStroker.type(name)
CharactorStroker.draw(@cst_lists, @cst_states, col, event.id, @viewport1)
end
}
end
end