(mouseover/click to play GIF) A 15fps preview of lightning
This is a little post about making a lightning/electric arc effect in GameMaker.
The idea
The lightning is drawn in segments from the last point to the new one.
First and last point are fixed.
Each iteration, we step toward the destination by xmin..xmax pixels and pick an ymin..ymax towards either side.
Sideways offset is also multiplied by -sin(pi*position) to make the lightning "wider" on average near the middle.
The code
For GMS2.3+:
/// @param {number} x1 source X /// @param {number} y1 source Y /// @param {number} x2 destination X /// @param {number} y2 destination Y /// @param {number} xmin minimum distance step (>0) /// @param {number} xmax maximum distance step (>xmin) /// @param {number} ymin minimum "side" step (lightning "spread") /// @param {number} ymax maximum "side" step (lightning "spread") /// @returns {int} number of segments drawn. function draw_lightning(x1, y1, x2, y2, xmin, xmax, ymin, ymax) { var _dist = point_distance(x1, y1, x2, y2); if (_dist <= 0) return 0; var _fwd_x = (x2 - x1) / _dist; var _fwd_y = (y2 - y1) / _dist; var _side_x = -_fwd_y, _side_y = _fwd_x; var _alpha = draw_get_alpha(); var _last_x = x1; var _last_y = y1; var _pos = 0; var _drawn = 0; repeat (5000) { // line segment limit _pos += random_range(xmin, xmax); var _next_x, _next_y; if (_pos < _dist) { var _side_offset = choose(-1, 1) * lerp(ymin, ymax, // this instead of just random(1) makes the lightning // spread out more close to the middle of it random(sin(-pi * _pos / _dist)) ); _next_x = x1 + _fwd_x * _pos + _side_x * _side_offset; _next_y = y1 + _fwd_y * _pos + _side_y * _side_offset; } else { // (last point) _next_x = x2; _next_y = y2; } // you can replace this with custom line drawing - these magic numbers just look alright draw_set_alpha(_alpha * 0.3); draw_line_width(_last_x, _last_y, _next_x, _next_y, 3.7); draw_set_alpha(_alpha * 0.7); draw_line_width(_last_x, _last_y, _next_x, _next_y, 1.3); if (_pos >= _dist) break; _last_x = _next_x; _last_y = _next_y; _drawn += 1; } draw_set_alpha(_alpha); return _drawn; }
The GIF was produced by the following:
draw_set_color(0xfff0ee); repeat (3) draw_lightning(50, 50, mouse_x, mouse_y, 5, 15, 3, 20);
For older versions (older/less clean code):
/// draw_lightning(x1, y1, x2, y2, xmin, xmax, ymin, ymax) // 0 1 2 3 4 5 6 7 // Draws a lightning between points. /// @param {number} x1 source X /// @param {number} y1 source Y /// @param {number} x2 destination X /// @param {number} y2 destination Y /// @param {number} xmin minimum distance step (>0) /// @param {number} xmax maximum distance step (>xmin) /// @param {number} ymin minimum "side" step (lightning "width") /// @param {number} ymax maximum "side" step (lightning "width") /// @returns {int} number of segments drawn. var i, r, c, l, dx, dy, sx, sy, px, py, cx, cy, alpha; // avoid drawing outside view: if (max(argument0, argument2) < view_xview[view_current] - 10) return 0 if (max(argument1, argument3) < view_yview[view_current] - 10) return 0 if (min(argument0, argument2) > view_xview[view_current] + view_wview[view_current] + 10) return 0 if (min(argument1, argument3) > view_yview[view_current] + view_hview[view_current] + 10) return 0 // find distance between points (used for loop) l = point_distance(argument0, argument1, argument2, argument3) if (l == 0) return 0 // main direction: i = point_direction(argument0, argument1, argument2, argument3) dx = lengthdir_x(1, i); dy = lengthdir_y(1, i) // side direction: i += 90 sx = lengthdir_x(1, i); sy = lengthdir_y(1, i) // first point coordinates: px = argument0; py = argument1 c = 0 i = random_range(argument4, argument5) alpha = draw_get_alpha() repeat (5000) { // edit for line segment limit r = choose(-1, +1) * (argument6 + (argument7 - argument6) * lengthdir_y(random(1), i / l * 180)) // this part makes lightning "wider" near middle // current point coordinates: if (i < l) { cx = argument0 + dx * i + sx * r cy = argument1 + dy * i + sy * r } else { cx = argument2; cy = argument3 } // end point // line drawing code here: // can be just a single draw_line or something fancy like this: draw_set_alpha(alpha * 0.3) draw_line_width(px, py, cx, cy, 3.7) draw_set_alpha(alpha * 0.7) draw_line_width(px, py, cx, cy, 1.3) // exit condition: if (i >= l) break // update previous point coordinates: px = cx; py = cy // increments: c += 1 i += random_range(argument4, argument5) } draw_set_alpha(alpha) return c
Original GM8 example:
Neat script! Gotta say though, the naming conventions leave something to be desired. There’s a reason naming is one of the hardest things in programming :)
Admittedly, this script is pretty old – it tells through it’s variable declarations on top of the script (because GM<=8.0 did not support `var name=value` syntax) and general absence of trailing semicolons on lines.
I’d love to say that I’ve also since moved away from giving 1-2 letter names to local variables, but, alas – auto-completion and syntax highlighting for those only made it’s entry recently, and old habits don’t go easily.
I’ll get around to reorganizing this a bit and making an interactive demo one of these days.
Did you manage to rewrite it? I failed miserably at it and i would love to see how its supposed to be
Not yet, there’s a lot happening right now. It still works fine, it’s just that the code looks bad.
Adding caching would require vertex buffers and a couple other things.
It works so well, thank you very much !
I have a concern however : this script seems very heavy on CPU when you multiply its use (for my game, I use it a hundred times at once… ^^). Any clue on how make it lighter?
Thank you again for this blog!
If you use it a lot, you’ll need to rewrite the script to work a bit more efficiently – in a good case to use vertex buffers, or at least primitives. I don’t have time to make a new version of this script right now, but you can ask for help on Discord if you get stuck.
Thanks for your answer anyway. Your blog is such a valuable ressource, I hope you’ll get more time in the future.
This really helped me. If you are interested, check out my final result after fiddling around with your script for a weekend ;)
https://www.youtube.com/watch?v=QEoNBwnUxA0
Hey, your result looks so nice and some friends and I were wondering how you implemented the effect.
I know it is an old post, but could you tell us something about that? Thanks!
Well, the complete source code is included in the post itself..?
A short version would be that it’s two lines (for cheap “glow”) with “wiggle” increasing closer to the midpoint.
Thanks!
Really great and super easy to use :D
thanks a lot it is amazing i run it in GM studio after i change execute_string on string function
how did u run it on gm studio?
got it
how did u run it ??
“Opening GM8 projects in GameMaker Studio“
Great little script, (and so easy to use), thanks for sharing.