Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ Miniwindows ➜ Particle generator in a miniwindow

Particle generator in a miniwindow

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Sun 06 Jun 2010 05:28 AM (UTC)

Amended on Sun 06 Jun 2010 05:47 AM (UTC) by Nick Gammon

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).

Template:saveplugin=Particle_Generator To save and install the Particle_Generator plugin do this:
  1. Copy between the lines below (to the Clipboard)
  2. Open a text editor (such as Notepad) and paste the plugin into it
  3. Save to disk on your PC, preferably in your plugins directory, as Particle_Generator.xml
  4. Go to the MUSHclient File menu -> Plugins
  5. Click "Add"
  6. Choose the file Particle_Generator.xml (which you just saved in step 3) as a plugin
  7. 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

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #1 on Sun 06 Jun 2010 05:28 AM (UTC)

Amended on Fri 28 Nov 2014 02:04 AM (UTC) by Nick Gammon

Message


(Video cannot be shown because scripting is disabled)



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


6,588 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.