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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
|
=begin
APIs listing that haven't been encountered in previous sample apps:
- to_s: Returns a string representation of an object.
For example, if we had
500.to_s
the string "500" would be returned.
Similar to to_i, which returns an integer representation of an object.
- Ceil: Returns an integer number greater than or equal to the original
with no decimal.
Reminders:
- ARRAY#inside_rect?: Returns true or false depending on if the point is inside a rect.
- args.outputs.labels: An array. The values generate a label.
The parameters are [X, Y, TEXT, SIZE, ALIGNMENT, RED, GREEN, BLUE, ALPHA, FONT STYLE]
For more information about labels, go to mygame/documentation/02-labels.md.
- args.outputs.sprites: An array. The values generate a sprite.
The parameters are [X, Y, WIDTH, HEIGHT, IMAGE PATH]
For more information about sprites, go to mygame/documentation/05-sprites.md.
- args.outputs.solids: An array. The values generate a solid.
The parameters are [X, Y, WIDTH, HEIGHT, RED, GREEN, BLUE]
For more information about solids, go to mygame/documentation/03-solids-and-borders.md.
- args.outputs.lines: An array. The values generate a line.
The parameters are [X1, Y1, X2, Y2, RED, GREEN, BLUE]
For more information about lines, go to mygame/documentation/04-lines.md.
- args.state.new_entity: Used when we want to create a new object, like a sprite or button.
In this sample app, new_entity is used to create a new button that clears the grid.
(Remember, you can use state to define ANY property and it will be retained across frames.)
=end
# This sample app shows an empty grid that the user can paint in. There are different image tiles that
# the user can use to fill the grid, and the "Clear" button can be pressed to clear the grid boxes.
class TileEditor
attr_accessor :inputs, :state, :outputs, :grid, :args
# Runs all the methods necessary for the game to function properly.
def tick
defaults
render
check_click
draw_buttons
end
# Sets default values
# Initialization only happens in the first frame
# NOTE: The values of some of these variables may seem confusingly large at first.
# The gridSize is 1600 but it seems a lot smaller on the screen, for example.
# But keep in mind that by using the "W", "A", "S", and "D" keys, you can
# move the grid's view in all four directions for more grid spaces.
def defaults
state.tileCords ||= []
state.tileQuantity ||= 6
state.tileSize ||= 50
state.tileSelected ||= 1
state.tempX ||= 50
state.tempY ||= 500
state.speed ||= 4
state.centerX ||= 4000
state.centerY ||= 4000
state.originalCenter ||= [state.centerX, state.centerY]
state.gridSize ||= 1600
state.lineQuantity ||= 50
state.increment ||= state.gridSize / state.lineQuantity
state.gridX ||= []
state.gridY ||= []
state.filled_squares ||= []
state.grid_border ||= [390, 140, 500, 500]
get_grid unless state.tempX == 0 # calls get_grid in the first frame only
determineTileCords unless state.tempX == 0 # calls determineTileCords in first frame
state.tempX = 0 # sets tempX to 0; the two methods aren't called again
end
# Calculates the placement of lines or separators in the grid
def get_grid
curr_x = state.centerX - (state.gridSize / 2) # starts at left of grid
deltaX = state.gridSize / state.lineQuantity # finds distance to place vertical lines evenly through width of grid
(state.lineQuantity + 2).times do
state.gridX << curr_x # adds curr_x to gridX collection
curr_x += deltaX # increment curr_x by the distance between vertical lines
end
curr_y = state.centerY - (state.gridSize / 2) # starts at bottom of grid
deltaY = state.gridSize / state.lineQuantity # finds distance to place horizontal lines evenly through height of grid
(state.lineQuantity + 2).times do
state.gridY << curr_y # adds curr_y to gridY collection
curr_y += deltaY # increments curr_y to distance between horizontal lines
end
end
# Determines coordinate positions of patterned tiles (on the left side of the grid)
def determineTileCords
state.tempCounter ||= 1 # initializes tempCounter to 1
state.tileQuantity.times do # there are 6 different kinds of tiles
state.tileCords += [[state.tempX, state.tempY, state.tempCounter]] # adds tile definition to collection
state.tempX += 75 # increments tempX to put horizontal space between the patterned tiles
state.tempCounter += 1 # increments tempCounter
if state.tempX > 200 # if tempX exceeds 200 pixels
state.tempX = 50 # a new row of patterned tiles begins
state.tempY -= 75 # the new row is 75 pixels lower than the previous row
end
end
end
# Outputs objects (grid, tiles, etc) onto the screen
def render
outputs.sprites << state.tileCords.map do # outputs tileCords collection using images in sprites folder
|x, y, order|
[x, y, state.tileSize, state.tileSize, 'sprites/image' + order.to_s + ".png"]
end
outputs.solids << [0, 0, 1280, 720, 255, 255, 255] # outputs white background
add_grid # outputs grid
print_title # outputs title and current tile pattern
end
# Creates a grid by outputting vertical and horizontal grid lines onto the screen.
# Outputs sprites for the filled_squares collection onto the screen.
def add_grid
# Outputs the grid's border.
outputs.borders << state.grid_border
temp = 0
# Before looking at the code that outputs the vertical and horizontal lines in the
# grid, take note of the fact that:
# grid_border[1] refers to the border's bottom line (running horizontally),
# grid_border[2] refers to the border's top line (running (horizontally),
# grid_border[0] refers to the border's left line (running vertically),
# and grid_border[3] refers to the border's right line (running vertically).
# [2]
# ----------
# | |
# [0] | | [3]
# | |
# ----------
# [1]
# Calculates the positions and outputs the x grid lines in the color gray.
state.gridX.map do # perform an action on all elements of the gridX collection
|x|
temp += 1 # increment temp
# if x's value is greater than (or equal to) the x value of the border's left side
# and less than (or equal to) the x value of the border's right side
if x >= state.centerX - (state.grid_border[2] / 2) && x <= state.centerX + (state.grid_border[2] / 2)
delta = state.centerX - 640
# vertical lines have the same starting and ending x positions
# starting y and ending y positions lead from the bottom of the border to the top of the border
outputs.lines << [x - delta, state.grid_border[1], x - delta, state.grid_border[1] + state.grid_border[2], 150, 150, 150] # sets definition of vertical line and outputs it
end
end
temp = 0
# Calculates the positions and outputs the y grid lines in the color gray.
state.gridY.map do # perform an action on all elements of the gridY collection
|y|
temp += 1 # increment temp
# if y's value is greater than (or equal to) the y value of the border's bottom side
# and less than (or equal to) the y value of the border's top side
if y >= state.centerY - (state.grid_border[3] / 2) && y <= state.centerY + (state.grid_border[3] / 2)
delta = state.centerY - 393
# horizontal lines have the same starting and ending y positions
# starting x and ending x positions lead from the left side of the border to the right side of the border
outputs.lines << [state.grid_border[0], y - delta, state.grid_border[0] + state.grid_border[3], y - delta, 150, 150, 150] # sets definition of horizontal line and outputs it
end
end
# Sets values and outputs sprites for the filled_squares collection.
state.filled_squares.map do # perform an action on every element of the filled_squares collection
|x, y, w, h, sprite|
# if x's value is greater than (or equal to) the x value of 17 pixels to the left of the border's left side
# and less than (or equal to) the x value of the border's right side
# and y's value is greater than (or equal to) the y value of the border's bottom side
# and less than (or equal to) the y value of 25 pixels above the border's top side
# NOTE: The allowance of 17 pixels and 25 pixels is due to the fact that a grid box may be slightly cut off or
# not entirely visible in the grid's view (until it is moved using "W", "A", "S", "D")
if x >= state.centerX - (state.grid_border[2] / 2) - 17 && x <= state.centerX + (state.grid_border[2] / 2) &&
y >= state.centerY - (state.grid_border[3] / 2) && y <= state.centerY + (state.grid_border[3] / 2) + 25
# calculations done to place sprites in grid spaces that are meant to filled in
# mess around with the x and y values and see how the sprite placement changes
outputs.sprites << [x - state.centerX + 630, y - state.centerY + 360, w, h, sprite]
end
end
# outputs a white solid along the left side of the grid (change the color and you'll be able to see it against the white background)
# state.increment subtracted in x parameter because solid's position is denoted by bottom left corner
# state.increment subtracted in y parameter to avoid covering the title label
outputs.primitives << [state.grid_border[0] - state.increment,
state.grid_border[1] - state.increment, state.increment, state.grid_border[3] + (state.increment * 2),
255, 255, 255].solid
# outputs a white solid along the right side of the grid
# state.increment subtracted from y parameter to avoid covering title label
outputs.primitives << [state.grid_border[0] + state.grid_border[2],
state.grid_border[1] - state.increment, state.increment, state.grid_border[3] + (state.increment * 2),
255, 255, 255].solid
# outputs a white solid along the bottom of the grid
# state.increment subtracted from y parameter to avoid covering last row of grid boxes
outputs.primitives << [state.grid_border[0] - state.increment, state.grid_border[1] - state.increment,
state.grid_border[2] + (2 * state.increment), state.increment, 255, 255, 255].solid
# outputs a white solid along the top of the grid
outputs.primitives << [state.grid_border[0] - state.increment, state.grid_border[1] + state.grid_border[3],
state.grid_border[2] + (2 * state.increment), state.increment, 255, 255, 255].solid
end
# Outputs title and current tile pattern
def print_title
outputs.labels << [640, 700, 'Mouse to Place Tile, WASD to Move Around', 7, 1] # title label
outputs.lines << horizontal_separator(660, 0, 1280) # outputs horizontal separator
outputs.labels << [1050, 500, 'Current:', 3, 1] # outputs Current label
outputs.sprites << [1110, 474, state.tileSize / 2, state.tileSize / 2, 'sprites/image' + state.tileSelected.to_s + ".png"] # outputs sprite of current tile pattern using images in sprites folder; output is half the size of a tile
end
# Sets the starting position, ending position, and color for the horizontal separator.
def horizontal_separator y, x, x2
[x, y, x2, y, 150, 150, 150] # definition of separator; horizontal line means same starting/ending y
end
# Checks if the mouse is being clicked or dragged
def check_click
if inputs.keyboard.key_down.r # if the "r" key is pressed down
$dragon.reset
end
if inputs.mouse.down #is mouse up or down?
state.mouse_held = true
if inputs.mouse.position.x < state.grid_border[0] # if mouse's x position is inside the grid's borders
state.tileCords.map do # perform action on all elements of tileCords collection
|x, y, order|
# if mouse's x position is greater than (or equal to) the starting x position of a tile
# and the mouse's x position is also less than (or equal to) the ending x position of that tile,
# and the mouse's y position is greater than (or equal to) the starting y position of that tile,
# and the mouse's y position is also less than (or equal to) the ending y position of that tile,
# (BASICALLY, IF THE MOUSE'S POSITION IS WITHIN THE STARTING AND ENDING POSITIONS OF A TILE)
if inputs.mouse.position.x >= x && inputs.mouse.position.x <= x + state.tileSize &&
inputs.mouse.position.y >= y && inputs.mouse.position.y <= y + state.tileSize
state.tileSelected = order # that tile is selected
end
end
end
elsif inputs.mouse.up # otherwise, if the mouse is in the "up" state
state.mouse_held = false # mouse is not held down or dragged
state.mouse_dragging = false
end
if state.mouse_held && # mouse needs to be down
!inputs.mouse.click && # must not be first click
((inputs.mouse.previous_click.point.x - inputs.mouse.position.x).abs > 15 ||
(inputs.mouse.previous_click.point.y - inputs.mouse.position.y).abs > 15) # Need to move 15 pixels before "drag"
state.mouse_dragging = true
end
# if mouse is clicked inside grid's border, search_lines method is called with click input type
if ((inputs.mouse.click) && (inputs.mouse.click.point.inside_rect? state.grid_border))
search_lines(inputs.mouse.click.point, :click)
# if mouse is dragged inside grid's border, search_lines method is called with drag input type
elsif ((state.mouse_dragging) && (inputs.mouse.position.inside_rect? state.grid_border))
search_lines(inputs.mouse.position, :drag)
end
# Changes grid's position on screen by moving it up, down, left, or right.
# centerX is incremented by speed if the "d" key is pressed and if that sum is less than
# the original left side of the center plus half the grid, minus half the top border of grid.
# MOVES GRID RIGHT (increasing x)
state.centerX += state.speed if inputs.keyboard.key_held.d &&
(state.centerX + state.speed) < state.originalCenter[0] + (state.gridSize / 2) - (state.grid_border[2] / 2)
# centerX is decremented by speed if the "a" key is pressed and if that difference is greater than
# the original left side of the center minus half the grid, plus half the top border of grid.
# MOVES GRID LEFT (decreasing x)
state.centerX -= state.speed if inputs.keyboard.key_held.a &&
(state.centerX - state.speed) > state.originalCenter[0] - (state.gridSize / 2) + (state.grid_border[2] / 2)
# centerY is incremented by speed if the "w" key is pressed and if that sum is less than
# the original bottom of the center plus half the grid, minus half the right border of grid.
# MOVES GRID UP (increasing y)
state.centerY += state.speed if inputs.keyboard.key_held.w &&
(state.centerY + state.speed) < state.originalCenter[1] + (state.gridSize / 2) - (state.grid_border[3] / 2)
# centerY is decremented by speed if the "s" key is pressed and if the difference is greater than
# the original bottom of the center minus half the grid, plus half the right border of grid.
# MOVES GRID DOWN (decreasing y)
state.centerY -= state.speed if inputs.keyboard.key_held.s &&
(state.centerY - state.speed) > state.originalCenter[1] - (state.gridSize / 2) + (state.grid_border[3] / 2)
end
# Performs calculations on the gridX and gridY collections, and sets values.
# Sets the definition of a grid box, including the image that it is filled with.
def search_lines (point, input_type)
point.x += state.centerX - 630 # increments x and y
point.y += state.centerY - 360
findX = 0
findY = 0
increment = state.gridSize / state.lineQuantity # divides grid by number of separators
state.gridX.map do # perform an action on every element of collection
|x|
# findX increments x by 10 if point.x is less than that sum and findX is currently 0
findX = x + 10 if point.x < (x + 10) && findX == 0
end
state.gridY.map do
|y|
# findY is set to y if point.y is less than that value and findY is currently 0
findY = y if point.y < (y) && findY == 0
end
# position of a box is denoted by bottom left corner, which is why the increment is being subtracted
grid_box = [findX - (increment.ceil), findY - (increment.ceil), increment.ceil, increment.ceil,
"sprites/image" + state.tileSelected.to_s + ".png"] # sets sprite definition
if input_type == :click # if user clicks their mouse
if state.filled_squares.include? grid_box # if grid box is already filled in
state.filled_squares.delete grid_box # box is cleared and removed from filled_squares
else
state.filled_squares << grid_box # otherwise, box is filled in and added to filled_squares
end
elsif input_type == :drag # if user drags mouse
unless state.filled_squares.include? grid_box # unless grid box dragged over is already filled in
state.filled_squares << grid_box # box is filled in and added to filled_squares
end
end
end
# Creates a "Clear" button using labels and borders.
def draw_buttons
x, y, w, h = 390, 50, 240, 50
state.clear_button ||= state.new_entity(:button_with_fade)
# x and y positions are set to display "Clear" label in center of the button
# Try changing first two parameters to simply x, y and see what happens to the text placement
state.clear_button.label ||= [x + w.half, y + h.half + 10, "Clear", 0, 1]
state.clear_button.border ||= [x, y, w, h] # definition of button's border
# If the mouse is clicked inside the borders of the clear button
if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.clear_button.border)
state.clear_button.clicked_at = inputs.mouse.click.created_at # value is frame of mouse click
state.filled_squares.clear # filled squares collection is emptied (squares are cleared)
inputs.mouse.previous_click = nil # no previous click
end
outputs.labels << state.clear_button.label # outputs clear button
outputs.borders << state.clear_button.border
# When the clear button is clicked, the color of the button changes
# and the transparency changes, as well. If you change the time from
# 0.25.seconds to 1.25.seconds or more, the change will last longer.
if state.clear_button.clicked_at
outputs.solids << [x, y, w, h, 0, 180, 80, 255 * state.clear_button.clicked_at.ease(0.25.seconds, :flip)]
end
end
end
$tile_editor = TileEditor.new
def tick args
$tile_editor.inputs = args.inputs
$tile_editor.grid = args.grid
$tile_editor.args = args
$tile_editor.outputs = args.outputs
$tile_editor.state = args.state
$tile_editor.tick
tick_instructions args, "Roll your own tile editor. CLICK to select a sprite. CLICK in grid to place sprite. WASD to move around."
end
def tick_instructions args, text, y = 715
return if args.state.key_event_occurred
if args.inputs.mouse.click ||
args.inputs.keyboard.directional_vector ||
args.inputs.keyboard.key_down.enter ||
args.inputs.keyboard.key_down.escape
args.state.key_event_occurred = true
end
args.outputs.debug << [0, y - 50, 1280, 60].solid
args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
end
|