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