1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
-- Class: Emitter
-- An emitter periodically emits sprites with varying properties --
-- for example, velocity. These are set with the emitter's min and
-- max properties. For example, you could set the x velocity of
-- particles to range between -100 and 100 with these statements:
--
-- > emitter.min.velocity.x = -100
-- > emitter.max.velocity.x = 100
--
-- Properties can descend two levels deep at most.
--
-- You can specify any property in min and max, and it will be set
-- on sprites as they are emitted. Mins and maxes can only be used
-- with numeric properties.
--
-- Particles when emitted will appear at a random spot inside the
-- rectangle defined by the emitter's x, y, width, and height
-- properties.
--
-- Because emitters are <Group> subclasses, all particles appear at
-- the same z index onscreen. This also means that setting active,
-- visible, and solid properties on the emitter will affect all particles.
--
-- Any sprite may be used as a particle. When a sprite is added as
-- a particle, its die() method is called. When emitted, revive() is
-- called on it. If you want a particle to remain invisible after being
-- emitted, for example, then write an onEmit method on your sprite to do so.
--
-- Extends:
-- <Group>
--
-- Event: onEmit
-- Called on both the parent emitter and the emitted sprite
-- when it is emitted. If multiple particles are emitted at once, the
-- emitter will receive multiple onEmit events.
Emitter = Group:extend{
-- Property: x
-- The x coordinate of the upper-left corner of the rectangle where particles may appear.
x = 0,
-- Property: y
-- The y coordinate of the upper-left corner of the rectangle where particles may appear.
y = 0,
-- Property: width
-- The width of the rectangle where particles may appear.
width = 0,
-- Property: height
-- The height of the retangle where particles may appear.
height = 0,
-- Property: emitting
-- Boolean whether this emitter is actually emitting particles.
emitting = true,
-- Property: period
-- How long, in seconds, the emitter should wait before emitting.
period = math.huge,
-- Property: emitCount
-- How many particles to emit at once.
emitCount = 1,
-- Property: min
-- Minimum numeric properties for particles.
min = {},
-- Property: max
-- Maximum numeric properties for particles.
max = {},
-- Property: emitTimer
-- Used to keep track of when the next emit should take place.
-- To restart the timer, set it to 0. To immediately force a particle
-- to be emitted, set it to the emitter's period property. (Although
-- you should probably call emit() instead.)
emitTimer = 0,
-- which particle to emit next
_emitIndex = 1,
-- Method: loadParticles
-- Creates a number of particles to use based on a class.
-- This calls new() on the particle class with no arguments.
--
-- Arguments:
-- class - class object to instantiate
-- count - number of particles to create
--
-- Returns:
-- nothing
loadParticles = function (self, class, count)
for i = 1, count do
self:add(class:new())
end
end,
-- Method: emit
-- Emits one or more particles. This ignores the emitting property.
-- If no particles are ready to be emitted, this does nothing.
--
-- Arguments:
-- count - how many particles to emit, default 1
--
-- Returns:
-- emitted particle
emit = function (self, count)
count = count or 1
if #self.sprites == 0 then return end
for i = 1, count do
local emitted = self.sprites[self._emitIndex]
self._emitIndex = self._emitIndex + 1
if self._emitIndex > #self.sprites then self._emitIndex = 1 end
-- revive it and set properties
emitted:revive()
emitted.x = math.random(self.x, self.x + self.width)
emitted.y = math.random(self.y, self.y + self.height)
for key, _ in pairs(self.min) do
if self.max[key] then
-- simple case, single value
if type(self.min[key]) == 'number' then
emitted[key] = self.min[key] + math.random() * (self.max[key] - self.min[key])
end
-- complicated case, table
if type(self.min[key]) == 'table' then
for subkey, _ in pairs(self.min[key]) do
if type(self.min[key][subkey]) == 'number' then
emitted[key][subkey] = self.min[key][subkey] + math.random() *
(self.max[key][subkey] - self.min[key][subkey])
end
end
end
end
end
if emitted.onEmit then emitted:onEmit(self) end
if self.onEmit then self:onEmit(emitted) end
end
end,
-- Method: explode
-- This emits many particles simultaneously then immediately stops any further
-- emissions. If you want to keep the emitter going, call emitter.emit(#emitter.sprites).
--
-- Arguments:
-- count - number of particles to emit, defaults to all of them
--
-- Returns:
-- nothing
explode = function (self, count)
count = count or #self.sprites
self:emit(count)
self.emitting = false
end,
-- Method: extinguish
-- This immediately calls die() on all particles, then the emitter itself.
-- This differs from a regular die() call in that if you call revive() on the
-- emitter later, particles will not appear where they last left off.
--
-- Arguments:
-- none
--
-- Returns:
-- nothing
extinguish = function (self)
for _, spr in pairs(self.sprites) do
spr:die()
end
self:die()
end,
update = function (self, elapsed)
if not self.active then return end
if self.emitting then
self.emitTimer = self.emitTimer + elapsed
if self.emitTimer > self.period then
self:emit(self.emitCount)
self.emitTimer = self.emitTimer - self.period
end
end
Group.update(self, elapsed)
end,
add = function (self, sprite)
sprite:die()
Group.add(self, sprite)
end,
__tostring = function (self)
local result = 'Emitter (x: ' .. self.x .. ', y: ' .. self.y ..
', w: ' .. self.width .. ', h: ' .. self.height .. ', '
if self.emitting then
result = result .. 'emitting with period ' .. self.period .. ', '
else
result = result .. 'not emitting, '
end
return result
end
}
|