SHOW:
|
|
- or go back to the newest paste.
1 | --[[ GameUtils | |
2 | - | GameUtil |
2 | + | |
3 | - | An API for drawing sprites and animations made in NPaintPro |
3 | + | A simple API for creating optimized, graphically intensive games by Nitrogen Fingers |
4 | - | By NitrogenFingers |
4 | + | |
5 | Version: 0.3a | |
6 | ]]-- | |
7 | ||
8 | --The back buffer. Initialized as nil | |
9 | local backbuffer = nil | |
10 | --The bounds of the terminal the back buffer displays to | |
11 | local tw,th = nil, nil | |
12 | ||
13 | --[[Constructs a new buffer. This must be done before the buffer can written to. | |
14 | Params: terminal:?table = The function table to draw to a screen. By default (nil) this refers | |
15 | to the native terminal, but monitor displays can be passed through as well: | |
16 | local leftMonitor = peripherals.wrap("left") | |
17 | initializeBuffer(leftMonitor) | |
18 | Returns:boolean = True if the buffer was successfully initialized; false otherwise | |
19 | ]]-- | |
20 | function initializeBuffer(terminal) | |
21 | if not terminal then terminal = term end | |
22 | if not terminal.getSize then | |
23 | error("Parameter cannot be used to initialize the backbuffer.") | |
24 | end | |
25 | if not terminal.isColour() then | |
26 | error("Parameter does not represent an advanced computer.") | |
27 | end | |
28 | ||
29 | tw,th = terminal.getSize() | |
30 | backbuffer = { } | |
31 | for y=1,th do | |
32 | backbuffer[y] = { } | |
33 | end | |
34 | return true | |
35 | end | |
36 | ||
37 | --[[Will clear the buffer and reset to nil, or to a colour if provided | |
38 | Params: colour:?number = The colour to set the back buffer to | |
39 | Returns:nil | |
40 | ]]-- | |
41 | function clearBuffer(colour) | |
42 | if not backbuffer then | |
43 | error("Back buffer not yet initialized!") | |
44 | end | |
45 | ||
46 | for y=1,#backbuffer do | |
47 | backbuffer[y] = { } | |
48 | if colour then | |
49 | for x=1,tw do | |
50 | backbuffer[y][x] = colour | |
51 | end | |
52 | end | |
53 | end | |
54 | end | |
55 | ||
56 | --[[Draws the given entity to the back buffer | |
57 | Params: entity:table = the entity to draw to the buffer | |
58 | Returns:nil | |
59 | ]]-- | |
60 | function writeToBuffer(entity) | |
61 | if not backbuffer then | |
62 | error("Back buffer not yet initialized!") | |
63 | end | |
64 | ||
65 | local image = nil | |
66 | if entity.type == "animation" then | |
67 | image = entity.frames[entity.currentFrame] | |
68 | else | |
69 | image = entity.image | |
70 | end | |
71 | ||
72 | for y=1,image.dimensions.height do | |
73 | for x=1,image.dimensions.width do | |
74 | if image[y][x] then | |
75 | local xpos,ypos = x,y | |
76 | if entity.mirror.x then xpos = image.dimensions.width - x + 1 end | |
77 | if entity.mirror.y then ypos = image.dimensions.height - y + 1 end | |
78 | ||
79 | backbuffer[entity.y + ypos - 1][entity.x + xpos - 1] | |
80 | = image[y][x] | |
81 | end | |
82 | end | |
83 | end | |
84 | end | |
85 | ||
86 | --[[Draws the contents of the buffer to the screen. This will not clear the screen or the buffer. | |
87 | Params: terminal:table = the terminal to draw to | |
88 | Returns:nil | |
89 | ]]-- | |
90 | function drawBuffer(terminal) | |
91 | if not backbuffer then | |
92 | error("Back buffer not yet initialized!") | |
93 | end | |
94 | if not terminal then terminal = term end | |
95 | if not terminal.setCursorPos or not terminal.setBackgroundColour or not terminal.write then | |
96 | error("Parameter cannot be used to initialize the backbuffer.") | |
97 | end | |
98 | if not terminal.isColour() then | |
99 | error("Parameter does not represent an advanced computer.") | |
100 | end | |
101 | ||
102 | for y=1,math.min(#backbuffer, th) do | |
103 | for x=1,tw do | |
104 | if backbuffer[y][x] then | |
105 | terminal.setCursorPos(x,y) | |
106 | terminal.setBackgroundColour(backbuffer[y][x]) | |
107 | terminal.write(" ") | |
108 | end | |
109 | end | |
110 | end | |
111 | end | |
112 | ||
113 | --[[Converts a hex digit into a colour value | |
114 | Params: hex:?string = the hex digit to be converted | |
115 | Returns:string A colour value corresponding to the hex, or nil if the character is invalid | |
116 | ]]-- | |
117 | local function getColourOf(hex) | |
118 | local value = tonumber(hex, 16) | |
119 | if not value then return nil end | |
120 | value = math.pow(2,value) | |
121 | return value | |
122 | end | |
123 | ||
124 | --[[Converts every pixel of one colour in a given sprite to another colour | |
125 | Use for "reskinning". Uses OO function. | |
126 | Params: self:sprite = the sprite to reskin | |
127 | oldcol:number = the colour to replace | |
128 | newcol:number = the new colour | |
129 | Returns:nil | |
130 | ]]-- | |
131 | local function repaintS(self, oldcol, newcol) | |
132 | for y=1,self.image.bounds.height do | |
133 | for x=1, self.image.bounds.width do | |
134 | if self.image[y][x] == oldcol then | |
135 | self.image[y][x] = newcol | |
136 | end | |
137 | end | |
138 | end | |
139 | end | |
140 | ||
141 | --[[Converts every pixel of one colour in a given animation to another colour | |
142 | Use for "reskinning". Uses OO function. | |
143 | Params: self:animation = the animation to reskin | |
144 | oldcol:number = the colour to replace | |
145 | newcol:number = the new colour | |
146 | Returns:nil | |
147 | ]]-- | |
148 | local function repaintA(self, oldcol, newcol) | |
149 | for f=1,#self.frames do | |
150 | print(self.frames[f].bounds) | |
151 | for y=1,self.frames[f].bounds.height do | |
152 | for x=1, self.frames[f].bounds.width do | |
153 | if self.frames[f][y][x] == oldcol then | |
154 | self.frames[f][y][x] = newcol | |
155 | end | |
156 | end | |
157 | end | |
158 | end | |
159 | end | |
160 | ||
161 | --[[Prints the sprite on the screen | |
162 | Params: self:sprite = the sprite to draw | |
163 | Returns:nil | |
164 | ]]-- | |
165 | local function drawS(self) | |
166 | local image = self.image | |
167 | ||
168 | for y=1,image.dimensions.height do | |
169 | for x=1,image.dimensions.width do | |
170 | if image[y][x] then | |
171 | local xpos,ypos = x,y | |
172 | if self.mirror.x then xpos = image.dimensions.width - x + 1 end | |
173 | if self.mirror.y then ypos = image.dimensions.height - y + 1 end | |
174 | ||
175 | term.setBackgroundColour(image[y][x]) | |
176 | term.setCursorPos(self.x + xpos - 1, self.y + ypos - 1) | |
177 | term.write(" ") | |
178 | end | |
179 | end | |
180 | end | |
181 | end | |
182 | ||
183 | --[[Prints the current frame of the animation on screen | |
184 | Params: self:anim = the animation to draw | |
185 | frame:?number = the specific frame to draw (default self.currentFrame) | |
186 | Returns:nil | |
187 | ]]-- | |
188 | local function drawA(self, frame) | |
189 | if not frame then frame = self.currentFrame end | |
190 | local image = self.frames[frame] | |
191 | ||
192 | for y=1,image.dimensions.height do | |
193 | for x=1,image.dimensions.width do | |
194 | if image[y][x] then | |
195 | local xpos,ypos = x,y | |
196 | if self.mirror.x then xpos = image.dimensions.width - x + 1 end | |
197 | if self.mirror.y then ypos = image.dimensions.height - y + 1 end | |
198 | ||
199 | term.setBackgroundColour(image[y][x]) | |
200 | term.setCursorPos(self.x + xpos - 1, self.y + ypos - 1) | |
201 | term.write(" ") | |
202 | end | |
203 | end | |
204 | end | |
205 | end | |
206 | ||
207 | --[[Checks the animation timer provided to see whether or not the animation needs to be updated. | |
208 | If so, it makes the necessary change. | |
209 | Params: self:animation = the animation to be updated | |
210 | timerID:number = the ID of the most recent timer event | |
211 | Returns:bool = true if the animation was update; false otherwise | |
212 | ]]-- | |
213 | local function updateA(self, timerID) | |
214 | if self.timerID and timerID and self.timerID == timerID then | |
215 | self.currentFrame = self.currentFrame + 1 | |
216 | if self.currentFrame > self.upperBound then | |
217 | self.currentFrame = self.lowerBound | |
218 | end | |
219 | return true | |
220 | else | |
221 | return false | |
222 | end | |
223 | end | |
224 | ||
225 | --[[Moves immediately to the next frame in the sequence, as though an update had been called. | |
226 | Params: self:animation = the animation to update | |
227 | Returns:nil | |
228 | ]]-- | |
229 | local function nextA(self) | |
230 | self.currentFrame = self.currentFrame + 1 | |
231 | if self.currentFrame > self.upperBound then | |
232 | self.currentFrame = self.lowerBound | |
233 | end | |
234 | end | |
235 | ||
236 | --[[Moves immediately to the previous frame in the sequence | |
237 | Params: self:animation = the animation to update | |
238 | Returns:nil | |
239 | ]]-- | |
240 | local function previousA(self) | |
241 | self.currentFrame = self.currentFrame - 1 | |
242 | if self.currentFrame < self.lowerBound then | |
243 | self.currentFrame = self.upperBound | |
244 | end | |
245 | end | |
246 | ||
247 | --[[Performs a rectangle collision with another given entity. Entity can be of sprite or animation | |
248 | type (also true of the self). Bases collision using a least squared approach (rectangle precision). | |
249 | Params: self:sprite,animation = the object in question of the testing | |
250 | other:sprite,animation = the other object tested for collision | |
251 | Returns:bool = true if bounding rectangle intersect is true; false otherwse | |
252 | ]]-- | |
253 | local function rCollidesWith(self, other) | |
254 | error("not implemented yet...") | |
255 | end | |
256 | ||
257 | --[[Performs a pixel collision test on another given entity. Either entity can be of sprite or animation | |
258 | type. Bases collision on a pixel occupancy approach. | |
259 | Params: self:sprite,animation = the object in question of the testing | |
260 | other:sprite,animation = the other object being tested for collision | |
261 | Returns:?number,?number: The X and Y position in which the collision occurred. | |
262 | ]]-- | |
263 | local function pCollidesWith(self, other) | |
264 | error("not implemented yet..") | |
265 | end | |
266 | ||
267 | --[[ | |
268 | Sprites Fields: | |
269 | x:number = the x position of the sprite in the world | |
270 | y:number = the y position of the sprite in the world | |
271 | image:table = a table of the image. Indexed by height, a series of sub-tables, each entry being a pixel | |
272 | at [y][x]. It also contains: | |
273 | bounds:table = | |
274 | x:number = the relative x position of the bounding rectangle | |
275 | y:number = the relative y position of the bounding rectangle | |
276 | width:number = the width of the bounding rectangle | |
277 | height:number = the height of the bounding rectangle | |
278 | dimensions:table = | |
279 | width = the width of the entire image in pixels | |
280 | height = the height of the entire image in pixels | |
281 | ||
282 | mirror:table = | |
283 | x:bool = whether or not the image is mirrored on the X axis | |
284 | y:bool = whether or not the image is mirrored on the Y axis | |
285 | repaint:function = see repaintS (above) | |
286 | rCollidesWith:function = see rCollidesWith (above) | |
287 | pCollidesWith:function = see pCollidesWith (above) | |
288 | draw:function = see drawS (above) | |
289 | ]]-- | |
290 | ||
291 | --[[Loads a new sprite into a table, and returns it to the user. | |
292 | Params: path:string = the absolute path to the desired sprite | |
293 | x:number = the initial X position of the sprite | |
294 | y:number = the initial Y position of the sprite | |
295 | ]]-- | |
296 | function loadSprite(path, x, y) | |
297 | local sprite = { | |
298 | type = "sprite", | |
299 | x = x, | |
300 | y = y, | |
301 | image = { }, | |
302 | mirror = { x = false, y = false } | |
303 | } | |
304 | ||
305 | if fs.exists(path) then | |
306 | local file = io.open(path, "r" ) | |
307 | local leftX, rightX = math.huge, 0 | |
308 | local topY, botY = nil,nil | |
309 | ||
310 | local lcount = 0 | |
311 | for line in file:lines() do | |
312 | lcount = lcount+1 | |
313 | table.insert(sprite.image, {}) | |
314 | for i=1,#line do | |
315 | if string.sub(line, i, i) ~= " " then | |
316 | leftX = math.min(leftX, i) | |
317 | rightX = math.max(rightX, i) | |
318 | if not topY then topY = lcount end | |
319 | botY = lcount | |
320 | end | |
321 | sprite.image[#sprite.image][i] = getColourOf(string.sub(line,i,i)) | |
322 | end | |
323 | - | file:close() |
323 | + | |
324 | file:close() | |
325 | ||
326 | sprite.image.bounds = { | |
327 | x = leftX, | |
328 | width = rightX - leftX + 1, | |
329 | y = topY, | |
330 | height = botY - topY + 1 | |
331 | } | |
332 | sprite.image.dimensions = { | |
333 | width = rightX, | |
334 | height = botY | |
335 | } | |
336 | ||
337 | sprite.repaint = repaintS | |
338 | sprite.rCollidesWith = rCollidesWith | |
339 | sprite.pCollidesWith = pCollidesWith | |
340 | sprite.draw = drawS | |
341 | return sprite | |
342 | else | |
343 | error(path.." not found!") | |
344 | end | |
345 | end | |
346 | ||
347 | --Animations contain | |
348 | --Everything a sprite contains, but the image is a series of frames, not just one image | |
349 | --An timerID that tracks the last animation | |
350 | --An upper and lower bound on the active animation | |
351 | --An update method that takes a timer event and updates the animation if necessary | |
352 | ||
353 | --[[ | |
354 | ||
355 | ]]-- | |
356 | function loadAnimation(path, x, y, currentFrame) | |
357 | local anim = { | |
358 | type = "animation", | |
359 | x = x, | |
360 | y = y, | |
361 | frames = { }, | |
362 | mirror = { x = false, y = false }, | |
363 | currentFrame = currentFrame | |
364 | } | |
365 | ||
366 | table.insert(anim.frames, { }) | |
367 | if fs.exists(path) then | |
368 | local file = io.open(path, "r") | |
369 | local leftX, rightX = math.huge, 0 | |
370 | local topY, botY = nil,nil | |
371 | ||
372 | local lcount = 0 | |
373 | for line in file:lines() do | |
374 | lcount = lcount+1 | |
375 | local cFrame = #anim.frames | |
376 | - | print("["..line.."]") |
376 | + | |
377 | anim.frames[cFrame].bounds = { | |
378 | - | print(leftX," ",rightX," ",topY," ",botY) |
378 | + | |
379 | y = topY, | |
380 | width = rightX - leftX + 1, | |
381 | height = botY - topY + 1 | |
382 | } | |
383 | anim.frames[cFrame].dimensions = { | |
384 | width = rightX, | |
385 | height = botY | |
386 | } | |
387 | table.insert(anim.frames, { }) | |
388 | leftX, rightX = math.huge, 0 | |
389 | topY, botY = nil,nil | |
390 | lcount = 0 | |
391 | else | |
392 | table.insert(anim.frames[cFrame], {}) | |
393 | for i=1,#line do | |
394 | if string.sub(line, i, i) ~= " " then | |
395 | leftX = math.min(leftX, i) | |
396 | rightX = math.max(rightX, i) | |
397 | if not topY then topY = lcount end | |
398 | botY = lcount | |
399 | end | |
400 | anim.frames[cFrame][#anim.frames[cFrame]] [i] = getColourOf(string.sub(line,i,i)) | |
401 | end | |
402 | end | |
403 | end | |
404 | file:close() | |
405 | local cFrame = #anim.frames | |
406 | anim.frames[cFrame].bounds = { | |
407 | x = leftX, | |
408 | y = topY, | |
409 | width = rightX - leftX + 1, | |
410 | height = botY - topY + 1 | |
411 | } | |
412 | ||
413 | if not currentFrame or type(currentFrame) ~= "number" or currentFrame < 1 or | |
414 | currentFrame > #anim.frames then | |
415 | anim.currentFrame = 1 | |
416 | end | |
417 | ||
418 | anim.timerID = nil | |
419 | anim.lowerBound = 1 | |
420 | - | print("continue") |
420 | + | |
421 | - | os.pullEvent("key") |
421 | + | |
422 | ||
423 | anim.repaint = repaintA | |
424 | anim.rCollidesWith = rCollidesWith | |
425 | anim.pCollidesWith = pCollidesWith | |
426 | anim.draw = drawA | |
427 | anim.update = updateA | |
428 | anim.next = nextA | |
429 | anim.previous = previousA | |
430 | return anim | |
431 | else | |
432 | error(path.." not found!") | |
433 | end | |
434 | end |