├── .gitignore ├── Star.png ├── icon.png ├── Heart.png ├── Pixel.png ├── Furcifer.png ├── default_env.tres ├── README.md ├── project.godot ├── ExplodeSprite.gd ├── Star.png.import ├── icon.png.import ├── Heart.png.import ├── Pixel.png.import ├── Furcifer.png.import └── Particles2D.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | /.import 2 | -------------------------------------------------------------------------------- /Star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayWithFurcifer/ExplodeSprite/HEAD/Star.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayWithFurcifer/ExplodeSprite/HEAD/icon.png -------------------------------------------------------------------------------- /Heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayWithFurcifer/ExplodeSprite/HEAD/Heart.png -------------------------------------------------------------------------------- /Pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayWithFurcifer/ExplodeSprite/HEAD/Pixel.png -------------------------------------------------------------------------------- /Furcifer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayWithFurcifer/ExplodeSprite/HEAD/Furcifer.png -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExplodeSprite 2 | Demo of a particle shader based sprite explosion effect for Godot. 3 | Watch the tutorial where we explain everything here: 4 | [![](https://img.youtube.com/vi/D7XSL0zBOwI/hqdefault.jpg)](https://youtu.be/D7XSL0zBOwI) 5 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | 14 | } 15 | 16 | [application] 17 | 18 | config/name="ExplodeSprite" 19 | run/main_scene="res://Particles2D.tscn" 20 | config/icon="res://icon.png" 21 | 22 | [display] 23 | 24 | window/size/fullscreen=true 25 | window/size/test_width=1920 26 | window/size/test_height=1080 27 | 28 | [rendering] 29 | 30 | environment/default_environment="res://default_env.tres" 31 | -------------------------------------------------------------------------------- /ExplodeSprite.gd: -------------------------------------------------------------------------------- 1 | extends Particles2D 2 | 3 | # You can uncomment the _ready() implementation below if you only want 4 | # the animation to start as soon as you set the sprite via initialize() 5 | # (and still see the animation in the editor). 6 | #func _ready(): 7 | # one_shot = true 8 | # emitting = false 9 | 10 | # Sets the particle systems emission shape, the sprite texture and 11 | # the amount of particles and starts the emission. 12 | func initialize(sprite : Texture): 13 | 14 | process_material.set_shader_param("emission_box_extents", 15 | Vector3(sprite.get_width() / 2.0, sprite.get_height() / 2.0, 1)) 16 | 17 | process_material.set_shader_param("sprite", sprite) 18 | 19 | amount = sprite.get_width() * sprite.get_height() 20 | 21 | emitting = true 22 | -------------------------------------------------------------------------------- /Star.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Star.png-3d67ea69a9a0445133039298d781d14b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Star.png" 13 | dest_files=[ "res://.import/Star.png-3d67ea69a9a0445133039298d781d14b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Heart.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Heart.png-ddb598aaaaa844aa51574f4e49e0f899.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Heart.png" 13 | dest_files=[ "res://.import/Heart.png-ddb598aaaaa844aa51574f4e49e0f899.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Pixel.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Pixel.png-4aea18dd107917fe5a12669910fb6942.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Pixel.png" 13 | dest_files=[ "res://.import/Pixel.png-4aea18dd107917fe5a12669910fb6942.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Furcifer.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Furcifer.png-6b5bf7b719ccdf62461aa43352cfda74.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Furcifer.png" 13 | dest_files=[ "res://.import/Furcifer.png-6b5bf7b719ccdf62461aa43352cfda74.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Particles2D.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://Pixel.png" type="Texture" id=1] 4 | [ext_resource path="res://Furcifer.png" type="Texture" id=2] 5 | [ext_resource path="res://ExplodeSprite.gd" type="Script" id=3] 6 | 7 | [sub_resource type="Shader" id=1] 8 | code = "shader_type particles; 9 | // this shader was converted from a ParticlesMaterial 10 | // you can find the explosion related parts by searching \"explosion\" 11 | uniform vec3 direction; 12 | uniform float spread; 13 | uniform float flatness; 14 | uniform float initial_linear_velocity; 15 | uniform float initial_angle; 16 | uniform float angular_velocity; 17 | uniform float orbit_velocity; 18 | uniform float linear_accel; 19 | uniform float radial_accel; 20 | uniform float tangent_accel; 21 | uniform float damping; 22 | uniform float scale; 23 | uniform float hue_variation; 24 | uniform float anim_speed; 25 | uniform float anim_offset; 26 | uniform float initial_linear_velocity_random; 27 | uniform float initial_angle_random; 28 | uniform float angular_velocity_random; 29 | uniform float orbit_velocity_random; 30 | uniform float linear_accel_random; 31 | uniform float radial_accel_random; 32 | uniform float tangent_accel_random; 33 | uniform float damping_random; 34 | uniform float scale_random; 35 | uniform float hue_variation_random; 36 | uniform float anim_speed_random; 37 | uniform float anim_offset_random; 38 | uniform float lifetime_randomness; 39 | uniform vec3 emission_box_extents; 40 | uniform vec4 color_value : hint_color; 41 | uniform int trail_divisor; 42 | uniform vec3 gravity; 43 | // sprite that will explode 44 | uniform sampler2D sprite; 45 | 46 | float rand_from_seed(inout uint seed) { 47 | int k; 48 | int s = int(seed); 49 | if (s == 0) 50 | s = 305420679; 51 | k = s / 127773; 52 | s = 16807 * (s - k * 127773) - 2836 * k; 53 | if (s < 0) 54 | s += 2147483647; 55 | seed = uint(s); 56 | return float(seed % uint(65536)) / 65535.0; 57 | } 58 | 59 | float rand_from_seed_m1_p1(inout uint seed) { 60 | return rand_from_seed(seed) * 2.0 - 1.0; 61 | } 62 | 63 | uint hash(uint x) { 64 | x = ((x >> uint(16)) ^ x) * uint(73244475); 65 | x = ((x >> uint(16)) ^ x) * uint(73244475); 66 | x = (x >> uint(16)) ^ x; 67 | return x; 68 | } 69 | 70 | void vertex() { 71 | uint base_number = NUMBER / uint(trail_divisor); 72 | uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED); 73 | float angle_rand = rand_from_seed(alt_seed); 74 | float scale_rand = rand_from_seed(alt_seed); 75 | float hue_rot_rand = rand_from_seed(alt_seed); 76 | float anim_offset_rand = rand_from_seed(alt_seed); 77 | float pi = 3.14159; 78 | float degree_to_rad = pi / 180.0; 79 | 80 | bool restart = false; 81 | if (CUSTOM.y > CUSTOM.w) { 82 | restart = true; 83 | } 84 | 85 | if (RESTART || restart) { 86 | float tex_linear_velocity = 0.0; 87 | float tex_angle = 0.0; 88 | float tex_anim_offset = 0.0; 89 | float spread_rad = spread * degree_to_rad; 90 | float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad; 91 | angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0); 92 | vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0); 93 | VELOCITY = rot * initial_linear_velocity * mix(1.0, rand_from_seed(alt_seed), initial_linear_velocity_random); 94 | float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random); 95 | CUSTOM.x = base_angle * degree_to_rad; 96 | CUSTOM.y = 0.0; 97 | CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed)); 98 | CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random); 99 | TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents; 100 | VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz; 101 | TRANSFORM = EMISSION_TRANSFORM * TRANSFORM; 102 | VELOCITY.z = 0.0; 103 | TRANSFORM[3].z = 0.0; 104 | 105 | // Explosion code starts here 106 | vec2 particlePosition = TRANSFORM[3].xy; 107 | vec2 textureS = vec2(textureSize(sprite, 0)); 108 | // vec2(0.5, 0.5) is the UV coordinate of the explosion's origin 109 | vec4 spriteColor = texture(sprite, particlePosition / textureS + vec2(0.5, 0.5)); 110 | COLOR = spriteColor; 111 | // particle movement 112 | VELOCITY.xy = particlePosition * initial_linear_velocity; 113 | // ignore transparent pixels 114 | if (spriteColor.a == 0.0) 115 | { 116 | ACTIVE = false; 117 | } 118 | } else { 119 | CUSTOM.y += DELTA / LIFETIME; 120 | float tex_linear_velocity = 0.0; 121 | float tex_orbit_velocity = 0.0; 122 | float tex_angular_velocity = 0.0; 123 | float tex_linear_accel = 0.0; 124 | float tex_radial_accel = 0.0; 125 | float tex_tangent_accel = 0.0; 126 | float tex_damping = 0.0; 127 | float tex_angle = 0.0; 128 | float tex_anim_speed = 0.0; 129 | float tex_anim_offset = 0.0; 130 | vec3 force = gravity; 131 | vec3 pos = TRANSFORM[3].xyz; 132 | pos.z = 0.0; 133 | // apply linear acceleration 134 | force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * (linear_accel + tex_linear_accel) * mix(1.0, rand_from_seed(alt_seed), linear_accel_random) : vec3(0.0); 135 | // apply radial acceleration 136 | vec3 org = EMISSION_TRANSFORM[3].xyz; 137 | vec3 diff = pos - org; 138 | force += length(diff) > 0.0 ? normalize(diff) * (radial_accel + tex_radial_accel) * mix(1.0, rand_from_seed(alt_seed), radial_accel_random) : vec3(0.0); 139 | // apply tangential acceleration; 140 | force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * ((tangent_accel + tex_tangent_accel) * mix(1.0, rand_from_seed(alt_seed), tangent_accel_random)) : vec3(0.0); 141 | // apply attractor forces 142 | VELOCITY += force * DELTA; 143 | // orbit velocity 144 | float orbit_amount = (orbit_velocity + tex_orbit_velocity) * mix(1.0, rand_from_seed(alt_seed), orbit_velocity_random); 145 | if (orbit_amount != 0.0) { 146 | float ang = orbit_amount * DELTA * pi * 2.0; 147 | mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang))); 148 | TRANSFORM[3].xy -= diff.xy; 149 | TRANSFORM[3].xy += rot * diff.xy; 150 | } 151 | if (damping + tex_damping > 0.0) { 152 | float v = length(VELOCITY); 153 | float damp = (damping + tex_damping) * mix(1.0, rand_from_seed(alt_seed), damping_random); 154 | v -= damp * DELTA; 155 | if (v < 0.0) { 156 | VELOCITY = vec3(0.0); 157 | } else { 158 | VELOCITY = normalize(VELOCITY) * v; 159 | } 160 | } 161 | float base_angle = (initial_angle + tex_angle) * mix(1.0, angle_rand, initial_angle_random); 162 | base_angle += CUSTOM.y * LIFETIME * (angular_velocity + tex_angular_velocity) * mix(1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, angular_velocity_random); 163 | CUSTOM.x = base_angle * degree_to_rad; 164 | CUSTOM.z = (anim_offset + tex_anim_offset) * mix(1.0, anim_offset_rand, anim_offset_random) + CUSTOM.y * (anim_speed + tex_anim_speed) * mix(1.0, rand_from_seed(alt_seed), anim_speed_random); 165 | } 166 | float tex_scale = 1.0; 167 | float tex_hue_variation = 0.0; 168 | //// this part is commented out because it overwrites the particle color 169 | // float hue_rot_angle = (hue_variation + tex_hue_variation) * pi * 2.0 * mix(1.0, hue_rot_rand * 2.0 - 1.0, hue_variation_random); 170 | // float hue_rot_c = cos(hue_rot_angle); 171 | // float hue_rot_s = sin(hue_rot_angle); 172 | // mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0), 173 | // vec4(0.299, 0.587, 0.114, 0.0), 174 | // vec4(0.299, 0.587, 0.114, 0.0), 175 | // vec4(0.000, 0.000, 0.000, 1.0)) + 176 | // mat4(vec4(0.701, -0.587, -0.114, 0.0), 177 | // vec4(-0.299, 0.413, -0.114, 0.0), 178 | // vec4(-0.300, -0.588, 0.886, 0.0), 179 | // vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_c + 180 | // mat4(vec4(0.168, 0.330, -0.497, 0.0), 181 | // vec4(-0.328, 0.035, 0.292, 0.0), 182 | // vec4(1.250, -1.050, -0.203, 0.0), 183 | // vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s; 184 | // COLOR = hue_rot_mat * color_value; 185 | 186 | TRANSFORM[0] = vec4(cos(CUSTOM.x), -sin(CUSTOM.x), 0.0, 0.0); 187 | TRANSFORM[1] = vec4(sin(CUSTOM.x), cos(CUSTOM.x), 0.0, 0.0); 188 | TRANSFORM[2] = vec4(0.0, 0.0, 1.0, 0.0); 189 | float base_scale = tex_scale * mix(scale, 1.0, scale_random * scale_rand); 190 | if (base_scale < 0.000001) { 191 | base_scale = 0.000001; 192 | } 193 | TRANSFORM[0].xyz *= base_scale; 194 | TRANSFORM[1].xyz *= base_scale; 195 | TRANSFORM[2].xyz *= base_scale; 196 | VELOCITY.z = 0.0; 197 | 198 | // Explosion code, fades out the particles 199 | if (COLOR.a > 0.0) 200 | { 201 | COLOR.a -= 1.0 / LIFETIME * DELTA; 202 | } 203 | 204 | TRANSFORM[3].z = 0.0; 205 | if (CUSTOM.y > CUSTOM.w) { ACTIVE = false; 206 | } 207 | } 208 | 209 | " 210 | 211 | [sub_resource type="ShaderMaterial" id=2] 212 | shader = SubResource( 1 ) 213 | shader_param/direction = Vector3( 1, 0, 0 ) 214 | shader_param/spread = 0.0 215 | shader_param/flatness = 0.0 216 | shader_param/initial_linear_velocity = 3.0 217 | shader_param/initial_angle = 0.0 218 | shader_param/angular_velocity = 0.0 219 | shader_param/orbit_velocity = 0.0 220 | shader_param/linear_accel = 0.0 221 | shader_param/radial_accel = 0.0 222 | shader_param/tangent_accel = 0.0 223 | shader_param/damping = 0.0 224 | shader_param/scale = 1.0 225 | shader_param/hue_variation = 0.0 226 | shader_param/anim_speed = 0.0 227 | shader_param/anim_offset = 0.0 228 | shader_param/initial_linear_velocity_random = 0.0 229 | shader_param/initial_angle_random = 0.0 230 | shader_param/angular_velocity_random = 0.0 231 | shader_param/orbit_velocity_random = 0.0 232 | shader_param/linear_accel_random = 0.0 233 | shader_param/radial_accel_random = 0.0 234 | shader_param/tangent_accel_random = 0.0 235 | shader_param/damping_random = 0.0 236 | shader_param/scale_random = 0.0 237 | shader_param/hue_variation_random = 0.0 238 | shader_param/anim_speed_random = 0.0 239 | shader_param/anim_offset_random = 0.0 240 | shader_param/lifetime_randomness = 0.0 241 | shader_param/emission_box_extents = Vector3( 21, 27, 1 ) 242 | shader_param/color_value = Color( 1, 1, 1, 1 ) 243 | shader_param/trail_divisor = 1 244 | shader_param/gravity = Vector3( 0, -1e-06, 0 ) 245 | shader_param/sprite = ExtResource( 2 ) 246 | 247 | [node name="Particles2D" type="Particles2D"] 248 | amount = 5000 249 | explosiveness = 1.0 250 | process_material = SubResource( 2 ) 251 | texture = ExtResource( 1 ) 252 | script = ExtResource( 3 ) 253 | --------------------------------------------------------------------------------