Message
| Below is a small plugin that demonstrates how to generate particles in a miniwindow.
Particles are extensively used in games to do things like flame or explosion effects. Whilst this may be a little less useful in a MUD game, you could use something like this for some special effects on the side (eg. to indicate combat or something).
The particle generator has three main components:
- Some emitters - these define where the particles are "born", and in what direction they are emitted (defined by an angle plus a range (eg. 90 degrees with a range of 20, would be 80 to 100 degrees).
- Each emitter has one or more cells attached to it. These define the type of particle, their initial speed, size and colour. Each of those may be randomized so they don't all appear at the same speed and in the same colour.
Particles also are given a lifetime, so that after x seconds they "die" and are no longer drawn.
- Individual particles (once born) are kept in a table, so they can be drawn. Each time they are processed they are moved by their defined amount, and then redrawn.
The particles are drawn in the OnPluginTick callback, which is called 25 times a second, which is about right for animation.
This plugin was developed using version 4.52 of MUSHclient which has some symbolic constants in it (see recent posting). If you want to try it out in advance of version 4.52 being released, just include the constants table mentioned here:
http://www.gammon.com.au/forum/?id=10345
Timing information shows that once the plugin gets going, it draws about 1,550 particles at a time (25 times a second). Despite doing this (ie. drawing 38,750 shapes a second) it managed to keep up a frame rate of 25 frames a second. Admittedly MUSHclient was using about 25% CPU whilst doing that. :-)
Underneath is an imbedded movie which shows what the particles look like in action.
You could expand the plugin to draw images (for example, explosion effects are usually based on a blurred image which is then coloured and resized).
 |
To save and install the Particle_Generator plugin do this:
- Copy between the lines below (to the Clipboard)
- Open a text editor (such as Notepad) and paste the plugin into it
- Save to disk on your PC, preferably in your plugins directory, as Particle_Generator.xml
- Go to the MUSHclient File menu -> Plugins
- Click "Add"
- Choose the file Particle_Generator.xml (which you just saved in step 3) as a plugin
- Click "Close"
|
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>
<muclient>
<plugin
name="Particle_Generator"
author="Nick Gammon"
id="612e832baf8ba1f190bf354d"
language="Lua"
purpose="Demonstrates a particle generator"
date_written="2010-06-06 09:10:17"
requires="4.52"
version="1.0"
>
</plugin>
<script>
<![CDATA[
require "colors"
require "copytable"
local BACKGROUND_COLOUR = ColourNameToRGB ("#004600")
local emitters = {}
local started = GetInfo (232)
local last_tick = GetInfo (232) -- time of last tick
local time_now
-- debugging
local birth_count = 0
local tick_count = 0
table.insert (emitters,
{
x = 100, -- location of emitter
y = 400,
shape = "point",
angle = 270,
angle_range = 10,
particles = {}, -- its current particles
cells = {
{
birth_rate = 100, -- per second
birth_rate_random = 20, -- per second extra random amount
life = 5, -- life in seconds
life_random = 2, -- extra random life (ie. plus or minus half of this)
speed = 30, -- speed in pixels per second
speed_random = 5, -- extra random speed (ie. plus or minus half of this)
type = "ellipse", -- type of shape
pen_style = miniwin.pen_null, -- pen style
pen_width = 1,
pen_colour = ColourNameToRGB "gold",
pen_colour_range = 0, -- random hue offset in degrees (0 to 360)
brush_style = miniwin.brush_solid, -- brush style
brush_colour = ColourNameToRGB "yellow",
brush_colour_range = 50, -- random hue offset in degrees (0 to 360)
width = 5, -- width of shape
width_random = 1, -- extra random width (ie. plus or minus half of this)
height = 5, -- height of shape
height_random = 1, -- extra random height (ie. plus or minus half of this)
}, -- end one cell
} -- end cells table
} -- end this emitter
)
table.insert (emitters, copytable.deep (emitters [1]))
emitters [2].x = emitters [2].x + 5
emitters [2].cells [1].brush_colour = ColourNameToRGB "red"
table.insert (emitters, copytable.deep (emitters [2]))
emitters [3].x = emitters [3].x - 10
emitters [1].cells [1].life = 3
table.insert (emitters,
{
x = 200, -- location of emitter
y = 200,
shape = "point",
angle = 50,
angle_range = 90,
particles = {}, -- its current particles
cells = {
{
birth_rate = 50,
birth_rate_random = 10,
life = 5,
life_random = 2,
speed = 100,
speed_random = 35,
type = "ellipse",
pen_style = miniwin.pen_solid,
pen_width = 1,
pen_colour = ColourNameToRGB "green",
pen_colour_range = 80, -- random hue offset in degrees
brush_style = miniwin.brush_solid,
brush_colour = ColourNameToRGB "blue",
brush_colour_range = 80, -- random hue offset in degrees
width = 30,
width_random = 10, -- extra random width
height = 30,
height_random = 10, -- extra random height
}, -- end one cell
} -- end cells table
} -- end this emitter
)
function Draw_Rectangle_Particle (p)
local right = math.floor (p.x + p.width)
local bottom = math.floor (p.y + p.height)
if right > 0 and bottom > 0 then
WindowCircleOp(win, miniwin.circle_rectangle, p.x, p.y, right, bottom,
p.pen_colour, p.pen_style, p.pen_width,
p.brush_colour, p.brush_style)
end -- if
end -- Draw_Rectangle_Particle
function Draw_Ellipse_Particle (p)
local right = math.floor (p.x + p.width)
local bottom = math.floor (p.y + p.height)
if right > 0 and bottom > 0 then
WindowCircleOp(win, miniwin.circle_ellipse, p.x, p.y, right, bottom,
p.pen_colour, p.pen_style, p.pen_width,
p.brush_colour, p.brush_style)
end -- if
end -- Draw_Ellipse_Particle
function Process_Particle (p)
-- draw it
if p.fdraw then
p.fdraw (p)
end -- if function found
-- move it for next time
p.x = p.x + (time_now - last_tick) * p.speed * math.cos (math.rad (p.angle))
p.y = p.y + (time_now - last_tick) * p.speed * math.sin (math.rad (p.angle))
end -- Process_Particle
local draw_functions = {
rect = Draw_Rectangle_Particle,
ellipse = Draw_Ellipse_Particle,
}
function Adjust_Hue (colour, delta)
local red = bit.band (colour, 0xFF)
local green = bit.band (bit.shr (colour, 8), 0xFF)
local blue = bit.band (bit.shr (colour, 16), 0xFF)
local s = string.format ("#%02X%02X%02X", red, green, blue)
local c = colors.new (s):hue_offset (math.random () * delta - delta / 2)
return ColourNameToRGB (c:to_rgb ())
end -- Adjust_Hue
function Give_Birth (e, c)
local size_randomness = math.random ()
table.insert (e.particles, {
x = e.x,
y = e.y,
angle = math.fmod (e.angle + (math.random () * e.angle_range) - e.angle_range / 2 + 360, 360),
fdraw = draw_functions [c.type],
speed = c.speed + math.random () * c.speed_random,
pen_style = c.pen_style,
pen_width = c.pen_width,
pen_colour = Adjust_Hue (c.pen_colour, c.pen_colour_range or 0),
brush_style = c.brush_style,
brush_colour = Adjust_Hue (c.brush_colour, c.brush_colour_range or 0),
width = c.width + (size_randomness * c.width_random) - c.width_random / 2,
height = c.height + (size_randomness * c.height_random) - c.height_random / 2,
death = time_now + c.life + math.random () * c.life_random - c.life_random / 2, -- when it dies
})
end -- Give_Birth
function Process_Cell (e, c)
-- give birth to this many
local birth_number_this_tick = (time_now - last_tick) *
(c.birth_rate + (math.random () * c.birth_rate_random - c.birth_rate_random / 2))
-- add onto fractional part from last time
c.birth_number = (c.birth_number or 0) + birth_number_this_tick
-- can't give birth to part of a particle
if c.birth_number < 1 then
return
end -- if
-- give birth to this many whole particles
local count = math.floor (c.birth_number)
-- keep fractional part for next time
c.birth_number = c.birth_number - count
birth_count = birth_count + count
-- give birth to this many particles
for i = 1, count do
Give_Birth (e, c)
end -- for
end -- Process_Emitter
function Process_Emitter (e)
-- give birth to any new particles needed
for _, cell in ipairs (e.cells) do
Process_Cell (e, cell)
end -- for each cell
-- give death (ie. kill) to particles past their use-by date
for i, p in ipairs (e.particles) do
if time_now >= p.death then
table.remove (e.particles, i)
end -- if
end -- for
-- draw remaining ones
for i, p in ipairs (e.particles) do
Process_Particle (p)
end -- for
end -- Process_Emitter
function OnPluginTick ()
WindowRectOp(win, miniwin.rect_fill, 0, 0, 0, 0, BACKGROUND_COLOUR);
local count = 0
time_now = GetInfo (232)
for _, emitter in ipairs (emitters) do
Process_Emitter (emitter)
count = count + #emitter.particles
end -- for each emitter
collectgarbage ("collect")
Redraw ()
tick_count = tick_count + 1
last_tick = time_now
if tick_count % 50 == 0 then
-- print ("FPS:", tick_count / (GetInfo (232) - started ), ", particles:", count)
end -- if
end -- function OnPluginTick
function OnPluginInstall ()
win = "test"
WindowCreate (win,
0, -- left
0, -- top
500, -- width
500, -- height
miniwin.pos_top_right, -- mode
0, -- flags
BACKGROUND_COLOUR)
WindowShow (win, true)
end -- OnPluginInstall
]]>
</script>
</muclient>
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|