/spacey

To get this branch, use:
bzr branch http://9ix.org/bzr/spacey
1 by Josh C
zoetrope 1.4
1
-- Class: Animation
2
-- An animation displays a sequence of frames. If you do not specify
3
-- a width and height for the sprite, it will size itself so that
4
-- it is a square, where each side is as tall as the source image's height.
5
--
6
--
7
-- Event: onEndSequence
8
-- Called whenever an animation sequence ends. It is passed the name
9
-- of the sequence that just ended.
10
-- 
11
-- Extends:
12
--		<Sprite>
13
14
Animation = Sprite:extend{
15
	-- Property: paused
16
	-- Set this to true to freeze the animation on the current frame.
17
	paused = false,
18
19
	-- Property: sequences
20
	-- A lookup table of sequences. Each one is stored by name and has
21
	-- the following properties:
22
	-- * name - string name for the sequence.
23
	-- * frames - table of frames to display. The first frame in the sheet is at index 1.
24
	-- * fps - frames per second.
25
	-- * loops - does the animation loop? defaults to true
26
	sequences = {},
27
28
	-- Property: image
29
	-- A string filename to the image to use as a sprite strip. A sprite
30
	-- strip can have multiple rows of frames.
31
32
	-- Property: currentSequence
33
	-- A reference to the current animation sequence table.
34
35
	-- Property: currentName
36
	-- The name of the current animation sequence.
37
38
	-- Property: currentFrame
39
	-- The current frame being displayed; starts at 1.
40
41
	-- Property: frameIndex
42
	-- Numeric index of the current frame in the current sequence; starts at 1.
43
44
	-- Property: frameTimer
45
	-- Time left before the animation changes to the next frame in seconds.
46
	-- Normally you shouldn't need to change this directly.
47
48
	-- private property: used to check whether the source image
49
	-- for our quad is up-to-date
50
	_set = {},
51
52
	-- private property imageObj: actual Image instance used to draw
53
	-- this is normally set via the image property, but you may set it directly
54
	-- so long as you never change that image property afterwards.
55
56
	new = function (self, obj)
57
		obj = obj or {}
58
		self:extend(obj)
59
		obj:updateQuad()
60
61
		if obj.onNew then obj:onNew() end
62
		return obj
63
	end,
64
65
	-- Method: play 
66
	-- Begins playing an animation in the sprite's library.
67
	-- If the animation is already playing, this has no effect.
68
	--
69
	-- Arguments:
70
	--		name - name of the animation
71
	--
72
	-- Returns:
73
	--		nothing
74
75
	play = function (self, name)
76
		if self.currentName == name and not self.paused then
77
			return
78
		end
79
		
80
		assert(self.sequences[name], 'no animation sequence named "' .. name .. '"')
81
		
82
		self.currentName = name
83
		self.currentSequence = self.sequences[name]
84
		self.frameIndex = 0
85
		self.frameTimer = 0
86
		self.paused = false
87
	end,
88
89
	-- Method: freeze
90
	-- Freezes the animation on the specified frame.
91
	--
92
	-- Arguments:
93
	--		* index - integer frame index relative to the entire sprite sheet,
94
	--				  starts at 1. If omitted, this freezes the current frame.
95
	--				  If there is no current frame, this freezes on the first frame.
96
	--
97
	-- Returns:
98
	--		nothing
99
100
	freeze = function (self, index)
101
		if self.currentSequence then
102
			index = index or self.currentSequence[self.frameIndex]
103
		end
104
		
105
		index = index or self.currentFrame or 1
106
107
		if self._set.image ~= self.image then
108
			self:updateQuad()
109
		end
110
111
		self.currentFrame = index
112
		self:updateFrame(index)
113
		self.paused = true
114
	end,
115
116
	-- private method: updateQuad
117
	-- sets up the sprite's quad property based on the image;
118
	-- needs to be called whenever the sprite's image property changes.
119
	--
120
	-- Arguments:
121
	--		none
122
	--
123
	-- Returns:
124
	--		nothing
125
126
	updateQuad = function (self)
127
		if self.image then 
128
			self._imageObj = Cached:image(self.image)
129
			if not self.width then self.width = self._imageObj:getHeight() end
130
			if not self.height then self.height = self.width end
131
132
			self._quad = love.graphics.newQuad(0, 0, self.width, self.height,
133
											  self._imageObj:getWidth(), self._imageObj:getHeight())
134
			self._imageWidth = self._imageObj:getWidth()
135
			self._set.image = self.image
136
		end
137
	end,
138
139
	-- private method: updateFrame
140
	-- changes the sprite's quad property based on the current frame;
141
	-- needs to be called whenever the sprite's currentFrame property changes.
142
	--
143
	-- Arguments:
144
	--		none
145
	--
146
	-- Returns:
147
	--		nothing
148
149
	updateFrame = function (self)
150
		assert(type(self.currentFrame) == 'number', "current frame is not a number")
151
		assert(self.image, "asked to set the frame of a nil image")
152
153
		if self._set.image ~= self.image then
154
			self:updateQuad()
155
		end
156
157
		local frameX = (self.currentFrame - 1) * self.width
158
		local viewportX = frameX % self._imageWidth
159
		local viewportY = self.height * math.floor(frameX / self._imageWidth)
160
		self._quad:setViewport(viewportX, viewportY, self.width, self.height)
161
	end,
162
163
	update = function (self, elapsed)
164
		-- move the animation frame forward
165
166
		if self.currentSequence and not self.paused then
167
			self.frameTimer = self.frameTimer - elapsed
168
			
169
			if self.frameTimer <= 0 then
170
				self.frameIndex = self.frameIndex + 1
171
172
				if self.frameIndex > #self.currentSequence.frames then
173
					if self.onEndSequence then self:onEndSequence(self.currentName) end
174
175
					if self.currentSequence.loops ~= false then
176
						self.frameIndex = 1
177
					else
178
						self.frameIndex = self.frameIndex - 1
179
						self.paused = true
180
					end
181
				end
182
183
				self.currentFrame = self.currentSequence.frames[self.frameIndex]
184
				self:updateFrame()
185
				self.frameTimer = 1 / self.currentSequence.fps
186
			end
187
		end
188
189
		Sprite.update(self, elapsed)
190
	end,
191
192
	draw = function (self, x, y)
193
		x = math.floor(x or self.x)
194
		y = math.floor(y or self.y)
195
196
		if STRICT then
197
			assert(type(x) == 'number', 'visible animation does not have a numeric x property')
198
			assert(type(y) == 'number', 'visible animation does not have a numeric y property')
199
			assert(type(self.width) == 'number', 'visible animation does not have a numeric width property')
200
			assert(type(self.height) == 'number', 'visible animation does not have a numeric height property')
201
		end
202
203
		if not self.visible or not self.image or self.alpha <= 0 then return end
204
		
205
		-- if our image changed, update the quad
206
		
207
		if self._set.image ~= self.image then
208
			self:updateQuad()
209
		end
210
		
211
		-- set color if needed
212
213
		local colored = self.alpha ~= 1 or self.tint[1] ~= 1 or self.tint[2] ~= 1 or self.tint[3] ~= 1
214
215
		if colored then
216
			love.graphics.setColor(self.tint[1] * 255, self.tint[2] * 255, self.tint[3] * 255, self.alpha * 255)
217
		end
218
219
		-- draw the quad
220
221
		local scaleX = self.scale * self.distort.x
222
		local scaleY = self.scale * self.distort.y
223
224
		if self.flipX then scaleX = scaleX * -1 end
225
		if self.flipY then scaleY = scaleY * -1 end
226
			
227
		love.graphics.drawq(self._imageObj, self._quad, x + self.width / 2, y + self.height / 2, self.rotation,
228
							scaleX, scaleY, self.width / 2, self.height / 2)
229
		
230
		-- reset color
231
		
232
		if colored then
233
			love.graphics.setColor(255, 255, 255, 255)
234
		end
235
	end,
236
237
	__tostring = function (self)
238
		local result = 'Animation (x: ' .. self.x .. ', y: ' .. self.y ..
239
					   ', w: ' .. self.width .. ', h: ' .. self.height .. ', '
240
241
		if self.currentName then
242
			result = result .. 'playing ' .. self.currentName .. ', '
243
		end
244
245
		result = result .. ' frame ' .. self.currentFrame .. ', '
246
247
		if self.active then
248
			result = result .. 'active, '
249
		else
250
			result = result .. 'inactive, '
251
		end
252
253
		if self.visible then
254
			result = result .. 'visible, '
255
		else
256
			result = result .. 'invisible, '
257
		end
258
259
		if self.solid then
260
			result = result .. 'solid'
261
		else
262
			result = result .. 'not solid'
263
		end
264
265
		return result .. ')'
266
	end
267
}