/ld27

To get this branch, use:
bzr branch /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
}