Smooth Picture Movement + Add-On Effects
by KJ21 / Justin
Introduction:
Do you want to add smooth animations and effects to pictures in your RPG Maker XP project?

Well... This script allows you to move, zoom, adjust opacity, and apply earthquake effects to pictures, all with gradual and natural animations. Additionally, it now includes the flash_pic feature, which lets you create picture flash effects with customizable colors, transparency, and duration. New features also allow for jumping actions, directional jumps, and a shock effect to add more dynamic movement and impact to your scenes. Whether you're creating dynamic cutscenes, UI enhancements, or visual storytelling moments, this script provides the tools to make your pictures more engaging.
No worries—you can still use the default picture movement commands even with this script installed. This script simply adds new functionality without overriding the existing features.

Features:
- Smooth movement of pictures
- Zoom in/out with adjustable levels
- Opacity adjustment for fading effects
- Earthquake effect with customizable strength, speed and duration
- Flash effect with any color, adjustable alpha transparency, and customizable duration
- Jumping with customizable height and duration (New Version)
- Jumping to position customizable target_x, target_y, height and duration (New Version)
- Shock effect with customizable intensity and duration (New Version)
Known Issues:
- It is maybe not recommended to use smooth movement while the shake effect is active during slow-motion scenes. Movements may not align properly under these conditions.
Screenshots:
![[Image: wff3GHN.gif]](https://i.imgur.com/wff3GHN.gif)
![[Image: Mtx6VnT.gif]](https://i.imgur.com/Mtx6VnT.gif)
Script:
Code:
#==============================================================================
# ** Smooth Picture Movement + Add-On Effects
#------------------------------------------------------------------------------
# Author: KJ21 / Justin (K-J Productions INC.)
# Version: 2.7
# Date: August 26, 2025
#------------------------------------------------------------------------------
# Description:
# This script supports smooth movement, zoom, and opacity transitions for
# pictures, as well as earthquake, flash, jump, and shock effects.
# It now also ensures that when old saves are loaded, the new variables are
# properly initialized. This version includes a bugfix for the "double image"
# bug, ensuring that the picture's position and state are always properly
# synchronized when a new picture is shown.
#
# Commands:
# smooth_pic(id, x, y, zoom_x, zoom_y, opacity, duration)
# - id: Picture ID (1-50)
# - x, y: Target position
# - zoom_x, zoom_y: Target zoom (100 = 100%, etc.)
# - opacity: Target opacity (0-255)
# - duration: Duration in frames (60 frames = 1 second)
#
# start_shake_pic(id, power, speed)
# stop_shake_pic(id, duration)
# - id: Picture ID (1-50)
# - power: Strength of the shake effect (recommended: 5-7)
# - speed: Speed of the shake effect (recommended: 30-100)
# - duration: Duration of fade-out in frames (default: 60)
#
# flash_pic(id, red, green, blue, alpha, duration)
# - id: Picture ID (1-50)
# - red, green, blue: Color values (0-255)
# - alpha: Flash color transparency (0-255)
# - duration: Duration of fade-out in frames (60 frames = 1 second)
#
# jump_pic(id, height, duration)
# - id: Picture ID (1-50)
# - height: Jump height in pixels (default: 50)
# - duration: Duration in frames (default: 30)
#
# jump_to_pic(id, target_x, target_y, height, duration)
# - id: Picture ID (1-50)
# - target_x, target_y: Landing position
# - height: Jump height in pixels (default: 50)
# - duration: Duration in frames (default: 30)
# - Uses smooth sine-wave movement like smooth_pic
#
# shock_pic(id, intensity, duration)
# - id: Picture ID (1-50)
# - intensity: Shock intensity in pixels (default: 10)
# - duration: Duration in frames (default: 20)
# - Fast left-right shaking motion for shock reactions
#==============================================================================
class Game_Screen
def smooth_pic(id, x, y, zoom_x, zoom_y, opacity, duration)
picture = @pictures[id]
picture.smooth_move_zoom_and_opacity(x, y, zoom_x, zoom_y, opacity, duration) if picture
end
def start_picture_shake(id, power, speed)
picture = @pictures[id]
picture.start_shake(power, speed) if picture
end
def stop_picture_shake(id, duration = 60)
picture = @pictures[id]
picture.stop_shake(duration) if picture
end
def flash_picture(id, red, green, blue, alpha, duration)
picture = @pictures[id]
picture.start_flash(red, green, blue, alpha, duration) if picture
end
def jump_picture(id, height, duration)
picture = @pictures[id]
picture.start_jump(height, duration) if picture
end
def jump_to_picture(id, target_x, target_y, height, duration)
picture = @pictures[id]
picture.start_jump_to(target_x, target_y, height, duration) if picture
end
def shock_picture(id, intensity, duration)
picture = @pictures[id]
picture.start_shock(intensity, duration) if picture
end
end
class Game_Picture
attr_accessor :target_x, :target_y, :target_zoom_x, :target_zoom_y, :target_opacity, :movement_duration, :movement_time
attr_accessor :start_x, :start_y, :start_zoom_x, :start_zoom_y, :start_opacity
attr_accessor :base_x, :base_y
attr_accessor :shake_stop_base_x, :shake_stop_base_y
attr_accessor :shake_power, :shake_speed, :shake_active, :shake_time, :original_x, :original_y, :shake_fade_duration, :shake_fade_time
attr_accessor :flash_color, :flash_duration, :flash_time, :original_flash_color, :flash_active
attr_accessor :direction_safe_x, :direction_safe_y, :direction_safe_name
attr_accessor :jump_active, :jump_time, :jump_duration, :jump_height, :jump_base_y
attr_accessor :jump_to_active, :jump_to_time, :jump_to_duration, :jump_to_height
attr_accessor :jump_to_start_x, :jump_to_start_y, :jump_to_target_x, :jump_to_target_y
attr_accessor :shock_active, :shock_time, :shock_duration, :shock_intensity, :shock_base_x
alias smooth_pic_original_initialize initialize
def initialize(number)
smooth_pic_original_initialize(number)
@target_x = @x
@target_y = @y
@target_zoom_x = @zoom_x
@target_zoom_y = @zoom_y
@target_opacity = @opacity
@movement_duration = 0
@movement_time = 0
@start_x = @x
@start_y = @y
@start_zoom_x = @zoom_x
@start_zoom_y = @zoom_y
@start_opacity = @opacity
@base_x = @x
@base_y = @y
@shake_stop_base_x = @x
@shake_stop_base_y = @y
@shake_power = 0
@shake_speed = 0
@shake_active = false
@shake_time = 0
@original_x = @x
@original_y = @y
@shake_fade_duration = 0
@shake_fade_time = 0
@flash_color = [0, 0, 0, 0]
@original_flash_color = [0, 0, 0, 0]
@flash_duration = 0
@flash_time = 0
@flash_active = false
@direction_safe_x = @x
@direction_safe_y = @y
@direction_safe_name = @name
@jump_active = false
@jump_time = 0
@jump_duration = 0
@jump_height = 0
@jump_base_y = @y
@jump_to_active = false
@jump_to_time = 0
@jump_to_duration = 0
@jump_to_height = 0
@jump_to_start_x = @x
@jump_to_start_y = @y
@jump_to_target_x = @x
@jump_to_target_y = @y
@shock_active = false
@shock_time = 0
@shock_duration = 0
@shock_intensity = 0
@shock_base_x = @x
end
def refresh_new_attributes
@target_x ||= @x
@target_y ||= @y
@target_zoom_x ||= @zoom_x
@target_zoom_y ||= @zoom_y
@target_opacity ||= @opacity
@movement_duration ||= 0
@movement_time ||= 0
@start_x ||= @x
@start_y ||= @y
@start_zoom_x ||= @zoom_x
@start_zoom_y ||= @zoom_y
@start_opacity ||= @opacity
@base_x ||= @x
@base_y ||= @y
@shake_stop_base_x ||= @x
@shake_stop_base_y ||= @y
@shake_power ||= 0
@shake_speed ||= 0
@shake_active = false if @shake_active.nil?
@shake_time ||= 0
@original_x ||= @x
@original_y ||= @y
@shake_fade_duration ||= 0
@shake_fade_time ||= 0
@flash_color ||= [0, 0, 0, 0]
@original_flash_color ||= [0, 0, 0, 0]
@flash_duration ||= 0
@flash_time ||= 0
@flash_active = false if @flash_active.nil?
@direction_safe_x ||= @x
@direction_safe_y ||= @y
@direction_safe_name ||= @name
@jump_active = false if @jump_active.nil?
@jump_time ||= 0
@jump_duration ||= 0
@jump_height ||= 0
@jump_base_y ||= @y
@jump_to_active = false if @jump_to_active.nil?
@jump_to_time ||= 0
@jump_to_duration ||= 0
@jump_to_height ||= 0
@jump_to_start_x ||= @x
@jump_to_start_y ||= @y
@jump_to_target_x ||= @x
@jump_to_target_y ||= @y
@shock_active = false if @shock_active.nil?
@shock_time ||= 0
@shock_duration ||= 0
@shock_intensity ||= 0
@shock_base_x ||= @x
end
def smooth_move_zoom_and_opacity(new_x, new_y, new_zoom_x, new_zoom_y, new_opacity, duration)
@start_x = @x
@start_y = @y
@start_zoom_x = @zoom_x
@start_zoom_y = @zoom_y
@start_opacity = @opacity
@target_x = new_x
@target_y = new_y
@target_zoom_x = new_zoom_x
@target_zoom_y = new_zoom_y
@target_opacity = new_opacity
@movement_duration = duration
@movement_time = 0
@direction_safe_x = new_x
@direction_safe_y = new_y
end
def update_smooth_movement_zoom_and_opacity
if @movement_duration > 0
@movement_time += 1
rate = @movement_time.to_f / @movement_duration
pi_rate = Math.sin(rate * Math::PI / 2)
new_x = @start_x + (@target_x - @start_x) * pi_rate
new_y = @start_y + (@target_y - @start_y) * pi_rate
@zoom_x = @start_zoom_x + (@target_zoom_x - @start_zoom_x) * pi_rate
@zoom_y = @start_zoom_y + (@target_zoom_y - @start_zoom_y) * pi_rate
@opacity = @start_opacity + (@target_opacity - @start_opacity) * pi_rate
@base_x = new_x
@base_y = new_y
if @movement_time >= @movement_duration
@x = @target_x
@y = @target_y
@zoom_x = @target_zoom_x
@zoom_y = @target_zoom_y
@opacity = @target_opacity
@movement_duration = 0
@start_x = @x
@start_y = @y
@start_zoom_x = @zoom_x
@start_zoom_y = @zoom_y
@start_opacity = @opacity
@base_x = @x
@base_y = @y
@original_x = @x
@original_y = @y
@jump_base_y = @y
@shock_base_x = @x
else
if !@shake_active && !@jump_active && !@jump_to_active && !@shock_active
@x = @base_x
@y = @base_y
end
end
end
end
def start_shake(power, speed)
@original_x = @base_x if @movement_duration > 0
@original_y = @base_y if @movement_duration > 0
@shake_power = power
@shake_speed = speed
@shake_active = true
@shake_time = 0
@shake_fade_duration = 0
@shake_fade_time = 0
end
def stop_shake(duration = 60)
@shake_stop_base_x = @base_x
@shake_stop_base_y = @base_y
@shake_fade_duration = duration
@shake_fade_time = 0
end
def start_flash(red, green, blue, alpha, duration)
@original_flash_color = [red, green, blue, alpha]
@flash_color = [red, green, blue, alpha]
@flash_duration = duration
@flash_time = 0
@flash_active = true
end
# Jump Animation - Picture jumps up and falls back to same position
def start_jump(height = 50, duration = 30)
@jump_base_y = @movement_duration > 0 ? @base_y : @y
@jump_height = height
@jump_duration = duration
@jump_time = 0
@jump_active = true
end
def update_jump
if @jump_active
@jump_time += 1
progress = @jump_time.to_f / @jump_duration
# Parabolic movement for realistic jump
jump_offset = @jump_height * Math.sin(progress * Math::PI)
current_y = @movement_duration > 0 ? @base_y : @jump_base_y
@y = current_y - jump_offset
if @jump_time >= @jump_duration
@jump_active = false
@y = current_y
end
end
end
# Jump To Animation - Picture jumps to a new position with smooth movement
def start_jump_to(target_x, target_y, height = 50, duration = 30)
@jump_to_start_x = @movement_duration > 0 ? @base_x : @x
@jump_to_start_y = @movement_duration > 0 ? @base_y : @y
@jump_to_target_x = target_x
@jump_to_target_y = target_y
@jump_to_height = height
@jump_to_duration = duration
@jump_to_time = 0
@jump_to_active = true
end
def update_jump_to
if @jump_to_active
@jump_to_time += 1
progress = @jump_to_time.to_f / @jump_to_duration
# Use same smooth sine-wave movement as smooth_pic
pi_rate = Math.sin(progress * Math::PI / 2)
# Horizontal and vertical movement (both smooth like smooth_pic)
base_x = @jump_to_start_x + (@jump_to_target_x - @jump_to_start_x) * pi_rate
base_y = @jump_to_start_y + (@jump_to_target_y - @jump_to_start_y) * pi_rate
# Add jump offset (parabolic)
jump_offset = @jump_to_height * Math.sin(progress * Math::PI)
@x = base_x
@y = base_y - jump_offset
if @jump_to_time >= @jump_to_duration
@jump_to_active = false
@x = @jump_to_target_x
@y = @jump_to_target_y
# Update base positions for other effects
@base_x = @x
@base_y = @y
@original_x = @x
@original_y = @y
@jump_base_y = @y
@shock_base_x = @x
# Update for smooth movement if active
if @movement_duration > 0
@target_x = @x
@target_y = @y
@start_x = @x
@start_y = @y
end
end
end
end
# Shock Animation - Left-Right shaking (normal shock effect)
def start_shock(intensity = 10, duration = 20)
@shock_base_x = @movement_duration > 0 ? @base_x : @x
@shock_intensity = intensity
@shock_duration = duration
@shock_time = 0
@shock_active = true
end
def update_shock
if @shock_active
@shock_time += 1
# Fast left-right shaking
# Direction changes every 2 frames for jerky feeling
change_frequency = 2
if @shock_time % change_frequency == 0 || @shock_time == 1
# Determine new direction (alternating left/right)
direction_cycle = (@shock_time / change_frequency) % 4
case direction_cycle
when 0
@current_direction = -1 # Left
when 1
@current_direction = 1 # Right
when 2
@current_direction = -1 # Left
when 3
@current_direction = 0 # Center
end
end
# Intensity decreases towards the end
fade_start = @shock_duration * 0.6
current_intensity = @shock_intensity
if @shock_time > fade_start
fade_progress = (@shock_time - fade_start) / (@shock_duration - fade_start)
current_intensity *= (1.0 - fade_progress * 0.8) # 80% reduction
end
# Calculate position
shock_offset = current_intensity * @current_direction
@x = @shock_base_x + shock_offset
if @shock_time >= @shock_duration
@shock_active = false
@x = @shock_base_x
end
end
end
def update_flash
if @flash_active
@flash_time += 1
if @flash_duration > 0
progress = @flash_time.to_f / @flash_duration
fade_factor = 1.0 - progress
@flash_color[0] = (@original_flash_color[0] * fade_factor).to_i
@flash_color[1] = (@original_flash_color[1] * fade_factor).to_i
@flash_color[2] = (@original_flash_color[2] * fade_factor).to_i
@flash_color[3] = (@original_flash_color[3] * fade_factor).to_i
end
if @flash_time >= @flash_duration
@flash_color = [0, 0, 0, 0]
@flash_active = false
end
end
end
alias smooth_pic_original_update update
def update
refresh_new_attributes
smooth_pic_original_update
update_smooth_movement_zoom_and_opacity
update_shake
update_flash
update_jump
update_jump_to
update_shock
if @name != "" && @name != nil
if @opacity == 0 && @target_opacity > 0
@opacity = @target_opacity
end
end
end
def update_shake
if @shake_active
@shake_time += 1
current_power = @shake_power
if @shake_fade_duration > 0
@shake_fade_time += 1
fade_rate = 1 - (@shake_fade_time.to_f / @shake_fade_duration)
current_power *= fade_rate
if @shake_fade_time >= @shake_fade_duration
@shake_active = false
if @movement_duration > 0
@x = @shake_stop_base_x unless @jump_active || @jump_to_active || @shock_active
@y = @shake_stop_base_y unless @jump_active || @jump_to_active
else
@x = @original_x unless @jump_active || @jump_to_active || @shock_active
@y = @original_y unless @jump_active || @jump_to_active
end
return
end
end
shake_x = current_power * Math.sin(@shake_time * @shake_speed * Math::PI / 180.0)
shake_y = current_power * Math.cos(@shake_time * @shake_speed * Math::PI / 180.0)
base_x = @movement_duration > 0 ? @base_x : @original_x
base_y = @movement_duration > 0 ? @base_y : @original_y
@x = base_x + shake_x unless @shock_active || @jump_to_active
@y = base_y + shake_y unless @jump_active || @jump_to_active
end
end
# --- DOUBLE IMAGE BUGFIX: Reset all logic and sync state when showing a new picture ---
alias original_picture_show show
def show(name, origin, x, y, zoom_x, zoom_y, opacity, blend_type)
original_picture_show(name, origin, x, y, zoom_x, zoom_y, opacity, blend_type)
@direction_safe_x = x
@direction_safe_y = y
@direction_safe_name = name
# --- FIX: Sync everything to new state on Show! ---
@x = x
@y = y
@zoom_x = zoom_x
@zoom_y = zoom_y
@opacity = opacity
@target_x = x
@target_y = y
@target_zoom_x = zoom_x
@target_zoom_y = zoom_y
@target_opacity = opacity
@start_x = x
@start_y = y
@start_zoom_x = zoom_x
@start_zoom_y = zoom_y
@start_opacity = opacity
@base_x = x
@base_y = y
@original_x = x
@original_y = y
@shake_stop_base_x = x
@shake_stop_base_y = y
@movement_duration = 0
@movement_time = 0
@shake_active = false
@shake_time = 0
@shake_fade_duration = 0
@shake_fade_time = 0
@flash_color = [0,0,0,0]
@original_flash_color = [0,0,0,0]
@flash_duration = 0
@flash_time = 0
@flash_active = false
@jump_active = false
@jump_time = 0
@jump_duration = 0
@jump_height = 0
@jump_base_y = y
@jump_to_active = false
@jump_to_time = 0
@jump_to_duration = 0
@jump_to_height = 0
@jump_to_start_x = x
@jump_to_start_y = y
@jump_to_target_x = x
@jump_to_target_y = y
@shock_active = false
@shock_time = 0
@shock_duration = 0
@shock_intensity = 0
@shock_base_x = x
@current_direction = 0
end
end
class Sprite_Picture < Sprite
alias smooth_pic_flash_update update
def update
smooth_pic_flash_update
if @picture
self.x = @picture.x
self.y = @picture.y
if @picture.name != "" && !self.visible
self.visible = true
end
@picture_flash_tone ||= Tone.new(0, 0, 0, 0)
if @picture.flash_active
r, g, b, a = @picture.flash_color
@picture_flash_tone.set(r, g, b, a)
else
@picture_flash_tone.set(0, 0, 0, 0)
end
self.tone = @picture_flash_tone
end
end
end
# === GLOBAL FUNCTIONS ===
def smooth_pic(id, x, y, zoom_x, zoom_y, opacity, duration)
$game_screen.smooth_pic(id, x, y, zoom_x, zoom_y, opacity, duration)
end
def start_shake_pic(id, power, speed)
$game_screen.start_picture_shake(id, power, speed)
end
def stop_shake_pic(id, duration = 60)
$game_screen.stop_picture_shake(id, duration)
end
def flash_pic(id, red, green, blue, alpha, duration)
$game_screen.flash_picture(id, red, green, blue, alpha, duration)
end
def jump_pic(id, height = 50, duration = 30)
$game_screen.jump_picture(id, height, duration)
end
def jump_to_pic(id, target_x, target_y, height = 50, duration = 30)
$game_screen.jump_to_picture(id, target_x, target_y, height, duration)
end
def shock_pic(id, intensity = 10, duration = 20)
$game_screen.shock_picture(id, intensity, duration)
end
Instructions:
First, you need to create a picture in the event command called "Show Picture."
How you want to set it up is entirely up to you.
Next, use the following script calls in your events:
Code:
For Movement:
smooth_pic(id, x, y, zoom_x, zoom_y, opacity, duration)
For Earthquake Effect:
start_shake_pic(id, power, speed)
stop_shake_pic(id, duration)
For Flash Effect:
flash_pic(id, red, green, blue, alpha, duration)
For Jump Effect:
jump_pic(id, height, duration)
For Jump to Picture Effect:
jump_to_pic(id, target_x, target_y, height, duration)
For Shock effect:
shock_pic(id, intensity, duration)
Parameters Explained:
- id: Picture ID (1–50).
- x, y: Target position on the screen.
- zoom_x, zoom_y: Zoom level as a percentage (e.g., 100 = 100%, 200 = 200%).
- opacity: Transparency level (0 = invisible, 255 = fully visible).
- duration: Animation time in frames (60 frames = 1 second).
- power: Strength of the shake effect (recommended range: 5–7).
- speed: Speed of the shake effect (recommended range: 30–100).
- red, green, blue: Color values (0–255) for the flash effect.
- alpha: Flash color transparency (0–255).
- intensity: Shock intensity in pixels (default: 10)
Code Examples, how it looks like:
Code:
smooth_pic(1, 100, 200, 150, 150, 128, 60)
start_shake_pic(1, 5, 30)
stop_shake_pic(1, 20)
flash_pic(1, 255, 255, 255, 255, 20)
jump_pic(1, 50, 30)
jump_to_pic(1, 50, 35, 50, 30)
shock_pic(1, 10, 20)
Q&A:
Q: Are you adding more features to this script?
A: If I have ideas for adding features to this script, then yeah, maybe in the next version.
Compatibility:
- Compatibility with default picture commands means you can also use the default function normally.
Terms & Conditions:
Free to use in any RPG Maker XP project (commercial or non-commercial).
Just don’t forget to credit me – I’d really appreciate it!
"Hallo Leute! Wie geht es euch!" means "Hello everyone! How are you all?"
I am a German gamedev and make hobby games in RPG Maker XP :)
I do scripting when it almost seems impossible to code.
My games are only in German, so there is no English version. :( wah...
Also:
![[Image: SP1-Scripter.png]](https://www.save-point.org/images/userbars/SP1-Scripter.png)
![[Image: SP1-Writer.png]](https://www.save-point.org/images/userbars/SP1-Writer.png)
I am a German gamedev and make hobby games in RPG Maker XP :)
I do scripting when it almost seems impossible to code.
My games are only in German, so there is no English version. :( wah...
Also:
![[Image: SP1-Scripter.png]](https://www.save-point.org/images/userbars/SP1-Scripter.png)
![[Image: SP1-Writer.png]](https://www.save-point.org/images/userbars/SP1-Writer.png)