author_engine 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +7 -0
- data/API.md +81 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +10 -0
- data/SAVEFILE.md +23 -0
- data/assets/fonts/Connection.otf +0 -0
- data/assets/fonts/ConnectionBold.otf +0 -0
- data/assets/fonts/README.md +3 -0
- data/assets/fonts/SIL Open Font License.txt +94 -0
- data/assets/ui/bucket_icon.png +0 -0
- data/assets/ui/code_icon.png +0 -0
- data/assets/ui/error_icon.png +0 -0
- data/assets/ui/level_icon.png +0 -0
- data/assets/ui/loading_icon.png +0 -0
- data/assets/ui/lock_icon.png +0 -0
- data/assets/ui/pencil_icon.png +0 -0
- data/assets/ui/play_icon.png +0 -0
- data/assets/ui/sprite_icon.png +0 -0
- data/assets/ui/unlock_icon.png +0 -0
- data/author_engine.gemspec +41 -0
- data/bin/author_engine +4 -0
- data/lib/author_engine/button.rb +154 -0
- data/lib/author_engine/code_editor/cursor.rb +339 -0
- data/lib/author_engine/code_editor/highlighting.rb +49 -0
- data/lib/author_engine/container.rb +24 -0
- data/lib/author_engine/containers/editor.rb +97 -0
- data/lib/author_engine/containers/loader.rb +105 -0
- data/lib/author_engine/game/game.rb +26 -0
- data/lib/author_engine/game/parts/colors.rb +59 -0
- data/lib/author_engine/game/parts/common.rb +13 -0
- data/lib/author_engine/game/parts/graphics.rb +41 -0
- data/lib/author_engine/game/parts/input.rb +25 -0
- data/lib/author_engine/image.rb +53 -0
- data/lib/author_engine/palette.rb +114 -0
- data/lib/author_engine/save_file.rb +134 -0
- data/lib/author_engine/sprite.rb +4 -0
- data/lib/author_engine/sprite_picker.rb +154 -0
- data/lib/author_engine/support.rb +22 -0
- data/lib/author_engine/text.rb +41 -0
- data/lib/author_engine/version.rb +3 -0
- data/lib/author_engine/view.rb +55 -0
- data/lib/author_engine/views/code_editor.rb +154 -0
- data/lib/author_engine/views/level_editor.rb +7 -0
- data/lib/author_engine/views/play_viewer.rb +163 -0
- data/lib/author_engine/views/sprite_editor.rb +333 -0
- data/lib/author_engine/window.rb +132 -0
- data/lib/author_engine.rb +32 -0
- data/test_3.authorengine +519 -0
- data/testing.authorengine +578 -0
- metadata +169 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
class AuthorEngine
|
2
|
+
class CodeEditor < View
|
3
|
+
|
4
|
+
class CodeInput < Gosu::TextInput
|
5
|
+
def filter(text_in)
|
6
|
+
return text_in
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
DEFAULT_STRING = <<-EOF
|
11
|
+
def init
|
12
|
+
@size = 10
|
13
|
+
@x = width/2 - @size/2
|
14
|
+
@y = height/2 - @size/2
|
15
|
+
end
|
16
|
+
|
17
|
+
def draw
|
18
|
+
rect(@x, @y, @size, @size, pink)
|
19
|
+
text("x: \#{@x}, y: \#{@y}", 0, 0, 8)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update
|
23
|
+
@x+=1 if button?("right")
|
24
|
+
@x-=1 if button?("left")
|
25
|
+
@y-=1 if button?("up")
|
26
|
+
@y+=1 if button?("down")
|
27
|
+
|
28
|
+
@x = 0 if @x < 0
|
29
|
+
@x = 128-@size if @x > 128-@size
|
30
|
+
|
31
|
+
@y = 0 if @y < 0
|
32
|
+
@y = 128-@size if @y > 128-@size
|
33
|
+
end
|
34
|
+
EOF
|
35
|
+
|
36
|
+
|
37
|
+
attr_accessor :x_offset, :y_offset
|
38
|
+
def setup
|
39
|
+
@font_size = 5 * window.square_scale.floor
|
40
|
+
@font = Gosu::Font.new(@font_size, name: Text::FONT_DEFAULT_BOLD) # "Consolas"
|
41
|
+
@line_numbers_spacing = "00"
|
42
|
+
@line_numbers_width = @font.text_width(@line_numbers_spacing)
|
43
|
+
|
44
|
+
@text_input = CodeInput.new
|
45
|
+
if window.container.savefile.code.nil?
|
46
|
+
@text_input.text = DEFAULT_STRING
|
47
|
+
else
|
48
|
+
@text_input.text = window.container.savefile.code
|
49
|
+
end
|
50
|
+
@text = AuthorEngine::Text.new(message: "", size: @font_size, x: @line_numbers_width+@x_padding, y: window.container.header_height, font: Text::FONT_DEFAULT) # "DejaVu Sans Mono"
|
51
|
+
|
52
|
+
@cursor = Cursor.new(view: self, text_input: @text_input, text: @text)
|
53
|
+
@highlighting = Highlighting.new
|
54
|
+
|
55
|
+
@x_offset, @y_offset = 0, 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def focus
|
59
|
+
window.text_input = @text_input
|
60
|
+
window.caption = "Code Editor"
|
61
|
+
end
|
62
|
+
|
63
|
+
def blur
|
64
|
+
window.text_input = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def draw
|
68
|
+
# Gosu.draw_rect(0, window.container.header_height, @width, @height, white)
|
69
|
+
super
|
70
|
+
Gosu.clip_to(0, window.container.header_height, window.width, window.height - window.container.header_height) do
|
71
|
+
Gosu.draw_rect(0, window.container.header_height, @line_numbers_width, @height, dark_gray)
|
72
|
+
|
73
|
+
Gosu.translate(0, @y_offset) do
|
74
|
+
min_width = @font.text_width("0")+@x_padding
|
75
|
+
(@text.message.lines.map(&:chomp)).each_with_index do |line, index|
|
76
|
+
min_width = @font.text_width("#{index+1}") if @font.text_width("#{index+1}") > min_width
|
77
|
+
|
78
|
+
@font.draw_text("#{index+1}", 1, window.container.header_height + (@font.height * index), 0)
|
79
|
+
end
|
80
|
+
|
81
|
+
@line_numbers_width = min_width
|
82
|
+
@text.x = @line_numbers_width+@x_padding
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Gosu.clip_to(@line_numbers_width, window.container.header_height, window.width, @height) do
|
87
|
+
Gosu.translate(@x_offset, @y_offset) do
|
88
|
+
@text.draw_markup
|
89
|
+
@cursor.draw
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def update
|
95
|
+
super
|
96
|
+
# @text_input.text+="\n" if Gosu.button_down?(Gosu::KbEnter) || Gosu.button_down?(Gosu::KbReturn) # FIXME
|
97
|
+
@caret_pos = @text_input.caret_pos
|
98
|
+
@highlighting.highlight(string: @text_input.text, text: @text)
|
99
|
+
|
100
|
+
@cursor.update
|
101
|
+
end
|
102
|
+
|
103
|
+
def code; @text_input.text; end
|
104
|
+
|
105
|
+
def button_down(id)
|
106
|
+
@cursor.button_down(id)
|
107
|
+
end
|
108
|
+
|
109
|
+
def button_up(id)
|
110
|
+
cursor_pos = @text_input.caret_pos # get a copy of the current cursor location
|
111
|
+
|
112
|
+
if id == Gosu::KbEnter || id == Gosu::KbReturn
|
113
|
+
# raise if @caret_pos != @text_input.caret_pos
|
114
|
+
@text_input.text = @text_input.text.insert(@text_input.caret_pos, "\n")
|
115
|
+
@cursor.set_position(cursor_pos+1)
|
116
|
+
end
|
117
|
+
|
118
|
+
@cursor.move(:up) if id == Gosu::KbUp
|
119
|
+
@cursor.move(:down) if id == Gosu::KbDown
|
120
|
+
|
121
|
+
if id == Gosu::KbTab
|
122
|
+
text = @text_input.text
|
123
|
+
|
124
|
+
if window.shift_button_down?
|
125
|
+
# FIXME: remove spaces behide cursor
|
126
|
+
|
127
|
+
# chars = @text_input.text.chars
|
128
|
+
|
129
|
+
# if text[cursor_pos] == " " && text[cursor_pos-1] == " "
|
130
|
+
# chars.delete_at(cursor_pos-1)
|
131
|
+
# chars.delete_at(cursor_pos)
|
132
|
+
# @cursor.set_position(cursor_pos-2)
|
133
|
+
|
134
|
+
# text = chars.join
|
135
|
+
# elsif text[cursor_pos] == " "
|
136
|
+
# chars.delete_at(cursor_pos-1)
|
137
|
+
# @cursor.set_position(cursor_pos-1)
|
138
|
+
|
139
|
+
# text = chars.join
|
140
|
+
# else
|
141
|
+
# p text[cursor_pos]
|
142
|
+
# end
|
143
|
+
|
144
|
+
else
|
145
|
+
@text_input.text = @text_input.text.insert(cursor_pos, " ")
|
146
|
+
p cursor_pos+2
|
147
|
+
@cursor.set_position(cursor_pos+2)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
@cursor.button_up(id)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
class AuthorEngine
|
2
|
+
class PlayViewer < View
|
3
|
+
def setup
|
4
|
+
@running = false
|
5
|
+
|
6
|
+
@error_icon = AuthorEngine::Image.new("assets/ui/error_icon.png", retro: true)
|
7
|
+
@error_icon_color = 0
|
8
|
+
@error_icon_colors = [yellow, orange]
|
9
|
+
|
10
|
+
@loading_icon = AuthorEngine::Image.new("assets/ui/loading_icon.png", retro: true)
|
11
|
+
@loading_icon_angle = 0
|
12
|
+
@loading_icon_rot_step = 45
|
13
|
+
|
14
|
+
@icon_rot_period = 500
|
15
|
+
@last_icon_rot = Gosu.milliseconds
|
16
|
+
|
17
|
+
@error_message = Text.new(message: "", x: @x_padding, y: window.container.header_height, z: Float::INFINITY)
|
18
|
+
|
19
|
+
@text = Text.new(message: "Press [Control+P] to Play")
|
20
|
+
@text.x = window.width/2 - @text.width/2
|
21
|
+
@text.y = window.height/2 + @text.height + @loading_icon.height * window.square_scale
|
22
|
+
|
23
|
+
@last_error = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(code: nil)
|
27
|
+
@last_error = nil
|
28
|
+
@game = Game.new(code: code)
|
29
|
+
|
30
|
+
@running = true
|
31
|
+
window.show_cursor = false
|
32
|
+
window.container.lock
|
33
|
+
|
34
|
+
@game.init
|
35
|
+
end
|
36
|
+
|
37
|
+
def catch_error(&block)
|
38
|
+
begin
|
39
|
+
block.call if block
|
40
|
+
rescue => error
|
41
|
+
error_response(error)
|
42
|
+
rescue SyntaxError => error
|
43
|
+
error_response(error)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def error_response(error)
|
48
|
+
stop
|
49
|
+
|
50
|
+
@last_error = error
|
51
|
+
format_error(text: @error_message, error: error)
|
52
|
+
end
|
53
|
+
|
54
|
+
def format_error(text:, error:)
|
55
|
+
puts "#{error.class}: #{error.message}"
|
56
|
+
puts error.backtrace.join("\n")
|
57
|
+
|
58
|
+
max_width = window.width - (@x_padding*2)
|
59
|
+
char_width= text.font.text_width("0")
|
60
|
+
chars_line= (max_width.to_f / char_width.to_f).ceil
|
61
|
+
|
62
|
+
backtrace = "<c=#{xml_color(orange)}>#{error.class}</c>\nBacktrace:\n"
|
63
|
+
error.backtrace.each {|trace| next unless trace.include?("(eval)"); backtrace+=" #{trace}\n"}
|
64
|
+
trace_buffer = "#{error.message}"
|
65
|
+
buffer = ""
|
66
|
+
|
67
|
+
trace_buffer.lines do |line|
|
68
|
+
line.chomp.chars.each_slice(chars_line).each do |slice|
|
69
|
+
string = slice.join
|
70
|
+
buffer += " #{string}\n"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
text.message = "#{backtrace}#{buffer}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def focus
|
78
|
+
window.caption = "Play"
|
79
|
+
play
|
80
|
+
end
|
81
|
+
|
82
|
+
def play
|
83
|
+
catch_error do
|
84
|
+
run(code: code_editor.code)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def draw
|
89
|
+
if @running
|
90
|
+
Gosu.draw_rect(0, 0, window.width, window.height, @background)
|
91
|
+
Gosu.flush
|
92
|
+
catch_error do
|
93
|
+
draw_game
|
94
|
+
end
|
95
|
+
else
|
96
|
+
super
|
97
|
+
if @last_error
|
98
|
+
Gosu.scale(2,2, (window.width / 2) - (@error_icon.width / 2), (window.height / 2) - (@error_icon.height / 2)) do
|
99
|
+
@error_icon.draw_rot(window.width / 2, window.height / 2, 10, 0, 0.5, 0.5, 1 * window.square_scale, 1 * window.square_scale, @error_icon_colors[@error_icon_color])
|
100
|
+
end
|
101
|
+
@error_message.draw_markup
|
102
|
+
else
|
103
|
+
@loading_icon.draw_rot(window.width / 2, window.height / 2, 10, @loading_icon_angle, 0.5, 0.5, 1 * window.square_scale, 1 * window.square_scale)
|
104
|
+
@text.draw
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def update
|
110
|
+
# super
|
111
|
+
if @running
|
112
|
+
catch_error do
|
113
|
+
update_game
|
114
|
+
end
|
115
|
+
else
|
116
|
+
if (Gosu.milliseconds - @last_icon_rot) > @icon_rot_period
|
117
|
+
@loading_icon_angle = (@loading_icon_angle + @loading_icon_rot_step) % 360
|
118
|
+
@error_icon_color += 1
|
119
|
+
@error_icon_color = 0 if @error_icon_color == @error_icon_colors.size
|
120
|
+
@last_icon_rot = Gosu.milliseconds
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def draw_game
|
126
|
+
x_offset = 0
|
127
|
+
ratio = Window::VIEW_WIDTH / window.width
|
128
|
+
if window.scale_x != window.scale_y
|
129
|
+
x_offset = window.width / 2 - (window.width * ratio * window.square_scale) / 2
|
130
|
+
end
|
131
|
+
|
132
|
+
Gosu.clip_to(x_offset, 0, (window.base_size * window.square_scale), (window.base_size * window.square_scale)) do
|
133
|
+
Gosu.translate(x_offset, 0) do
|
134
|
+
Gosu.scale(window.square_scale, window.square_scale) do
|
135
|
+
@game.draw_background
|
136
|
+
@game.draw
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def update_game
|
143
|
+
@game.update if @game
|
144
|
+
end
|
145
|
+
|
146
|
+
def stop
|
147
|
+
@running = false
|
148
|
+
window.show_cursor = true
|
149
|
+
window.container.unlock
|
150
|
+
end
|
151
|
+
|
152
|
+
def button_up(id)
|
153
|
+
if id == Gosu::MsLeft && window.mouse_x.between?((window.width / 2) - (@loading_icon.width/2) * window.square_scale, (window.width / 2) + (@loading_icon.width/2) * window.square_scale)
|
154
|
+
if window.mouse_y.between?((window.height / 2) - (@loading_icon.height/2) * window.square_scale, (window.height / 2) + (@loading_icon.height/2) * window.square_scale)
|
155
|
+
play
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
stop if id == Gosu::KbEscape
|
160
|
+
play if id == Gosu::KbP && window.control_button_down?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
class AuthorEngine
|
2
|
+
class SpriteEditor < View
|
3
|
+
BLANK_COLOR = Gosu::Color.rgba(0,0,0, 0)
|
4
|
+
class Pixel
|
5
|
+
attr_accessor :x, :y, :width, :height, :color
|
6
|
+
def initialize(x, y, width, height, color)
|
7
|
+
@x, @y, @width, @height, @color = x, y, width, height, color
|
8
|
+
end
|
9
|
+
|
10
|
+
def draw
|
11
|
+
Gosu.draw_rect(
|
12
|
+
@x, @y,
|
13
|
+
@width, @height,
|
14
|
+
@color,
|
15
|
+
5
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@pixels = []
|
22
|
+
@active_color = Gosu::Color.rgba(101,1,101, 255)
|
23
|
+
|
24
|
+
create_grid(16, 16, 4)
|
25
|
+
@canvas_changed = false
|
26
|
+
@scale = 1 * window.square_scale
|
27
|
+
@palette = Palette.new(x: @grid_x + @grid_width + @grid_pixel_size, y: @grid_y)
|
28
|
+
@sprites_picker = SpritePicker.new(y: @grid_y + @grid_height + (@grid_pixel_size * 2))
|
29
|
+
@coordinates = Text.new(message: "1:1", x: @scale, y: @grid_y)
|
30
|
+
|
31
|
+
@sprites= Array.new(@sprites_picker.rows*@sprites_picker.columns, nil)
|
32
|
+
|
33
|
+
@tools = []
|
34
|
+
@pixel_lock = false
|
35
|
+
@pixel_floodfill = false # aka bucket tool
|
36
|
+
|
37
|
+
@tools << Button.new(image: "assets/ui/unlock_icon.png", tooltip: "Toggle pixel lock", x: @palette.x, y: @palette.y + @palette.height + (window.square_scale * 2), color: dark_purple) do |b|
|
38
|
+
@unlock_icon ||= b.image
|
39
|
+
@lock_icon ||= AuthorEngine::Image.new("assets/ui/lock_icon.png", retro: true)
|
40
|
+
|
41
|
+
@pixel_lock = !@pixel_lock
|
42
|
+
|
43
|
+
if @pixel_lock
|
44
|
+
b.image = @lock_icon
|
45
|
+
else
|
46
|
+
b.image = @unlock_icon
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@pencil_icon = AuthorEngine::Image.new("assets/ui/pencil_icon.png", retro: true)
|
51
|
+
@bucket_icon = AuthorEngine::Image.new("assets/ui/bucket_icon.png", retro: true)
|
52
|
+
|
53
|
+
@tools << Button.new(image: @pencil_icon, tooltip: "Toggle pencil/bucket", x: @palette.x + @tools.first.width + 1, y: @palette.y + @palette.height + (window.square_scale * 2), color: dark_purple) do |b|
|
54
|
+
@pixel_floodfill = !@pixel_floodfill
|
55
|
+
|
56
|
+
if @pixel_floodfill
|
57
|
+
b.image = @bucket_icon
|
58
|
+
else
|
59
|
+
b.image = @pencil_icon
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
import_spritesheet(window.container.savefile.sprites)
|
64
|
+
end
|
65
|
+
|
66
|
+
def focus
|
67
|
+
window.show_cursor = true
|
68
|
+
window.caption = "Sprite Editor"
|
69
|
+
end
|
70
|
+
|
71
|
+
def draw
|
72
|
+
super
|
73
|
+
@pixels.each(&:draw)
|
74
|
+
highlight_pixel
|
75
|
+
@coordinates.draw
|
76
|
+
|
77
|
+
Gosu.draw_rect(@grid_x-window.square_scale, @grid_y-window.square_scale, @grid_width+(window.square_scale*2), @grid_height+(window.square_scale*2), Gosu::Color::WHITE)
|
78
|
+
Gosu.draw_rect(@grid_x, @grid_y, @grid_width, @grid_height, Gosu::Color.rgba(10, 10, 10, 200))
|
79
|
+
@palette.draw
|
80
|
+
@sprites_picker.draw
|
81
|
+
|
82
|
+
@tools.each(&:draw)
|
83
|
+
|
84
|
+
Gosu.clip_to(@grid_x, @grid_y, @grid_width, @grid_height) do
|
85
|
+
if @pixel_floodfill
|
86
|
+
@bucket_icon.draw(window.mouse_x, window.mouse_y - (@bucket_icon.width * @scale), 1000, @scale, @scale)
|
87
|
+
else
|
88
|
+
@pencil_icon.draw(window.mouse_x, window.mouse_y - (@pencil_icon.width * @scale), 1000, @scale, @scale)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def update
|
94
|
+
super
|
95
|
+
unless @pixel_floodfill
|
96
|
+
paint if Gosu.button_down?(Gosu::MsLeft)
|
97
|
+
erase if Gosu.button_down?(Gosu::MsRight)
|
98
|
+
end
|
99
|
+
@palette.update
|
100
|
+
|
101
|
+
update_coordinates
|
102
|
+
end
|
103
|
+
|
104
|
+
def update_coordinates
|
105
|
+
x = normalize_x(window.mouse_x)
|
106
|
+
y = normalize_y(window.mouse_y)
|
107
|
+
return if (x >= @grid_columns || y >= @grid_rows)
|
108
|
+
return if (x < 0 || y < 0)
|
109
|
+
|
110
|
+
@coordinates.message = "#{x+1}:#{y+1}"
|
111
|
+
end
|
112
|
+
|
113
|
+
def create_grid(x, y, size)
|
114
|
+
size = size * window.square_scale
|
115
|
+
|
116
|
+
@grid_x = (window.width / 2) - (((size * x) / 2) + size*2 )
|
117
|
+
@grid_y = window.container.header_height + size
|
118
|
+
@grid_width = x * size
|
119
|
+
@grid_height = y * size
|
120
|
+
@grid_pixel_size = size
|
121
|
+
@grid_columns = x
|
122
|
+
@grid_rows = y
|
123
|
+
|
124
|
+
y.times do |_y|
|
125
|
+
x.times do |_x|
|
126
|
+
@pixels << Pixel.new(
|
127
|
+
@grid_x+(_x*size), @grid_y+(_y*size),
|
128
|
+
size, size,
|
129
|
+
BLANK_COLOR
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def highlight_pixel
|
136
|
+
return unless @palette.color
|
137
|
+
|
138
|
+
pixel = get_pixel_at(window.mouse_x, window.mouse_y)
|
139
|
+
return unless pixel
|
140
|
+
Gosu.draw_rect(
|
141
|
+
pixel.x, pixel.y,
|
142
|
+
pixel.width, pixel.height,
|
143
|
+
@palette.color,
|
144
|
+
6
|
145
|
+
)
|
146
|
+
|
147
|
+
Gosu.draw_rect(
|
148
|
+
pixel.x, pixel.y,
|
149
|
+
pixel.width, pixel.height,
|
150
|
+
Gosu::Color.rgba(255,255,255, 100),
|
151
|
+
6
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
def paint(color = @palette.color)
|
156
|
+
pixel = get_pixel_at(window.mouse_x, window.mouse_y)
|
157
|
+
|
158
|
+
return unless pixel
|
159
|
+
return if color.nil?
|
160
|
+
return if pixel.color != BLANK_COLOR && @pixel_lock
|
161
|
+
|
162
|
+
pixel.color = color
|
163
|
+
@canvas_changed = true
|
164
|
+
end
|
165
|
+
|
166
|
+
def erase
|
167
|
+
paint(BLANK_COLOR)
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_pixel_at(x, y)
|
171
|
+
return if (x >= @grid_x+@grid_width || y >= @grid_y+@grid_height)
|
172
|
+
x = normalize_x(x)
|
173
|
+
y = normalize_y(y)
|
174
|
+
return if (x < 0 || y < 0)
|
175
|
+
|
176
|
+
@pixels[(x + (@grid_columns * y))]
|
177
|
+
end
|
178
|
+
|
179
|
+
def normalize_x(int)
|
180
|
+
return ((int - @grid_x) / @grid_pixel_size).floor
|
181
|
+
end
|
182
|
+
|
183
|
+
def normalize_y(int)
|
184
|
+
return ((int - @grid_y) / @grid_pixel_size).floor
|
185
|
+
end
|
186
|
+
|
187
|
+
# AKA The Bucket Tool
|
188
|
+
# @param pixel [Pixel]
|
189
|
+
# @param target_color [Gosu::Color] color to search and replace with replacement_color
|
190
|
+
# @param replacement_color [Gosu::Color] color to replace Pixel's current color
|
191
|
+
def floodfill(pixel, target_color, replacement_color)
|
192
|
+
return unless pixel
|
193
|
+
return if pixel.color == replacement_color
|
194
|
+
return if pixel.color != target_color
|
195
|
+
return if target_color != BLANK_COLOR && @pixel_lock # don't replace non-blank pixels with color if pixels are locked
|
196
|
+
|
197
|
+
pixel.color = replacement_color
|
198
|
+
@canvas_changed = true
|
199
|
+
|
200
|
+
# UP
|
201
|
+
_pixel = get_pixel_at(pixel.x, pixel.y - @grid_pixel_size)
|
202
|
+
floodfill(_pixel, target_color, replacement_color)
|
203
|
+
|
204
|
+
# DOWN
|
205
|
+
_pixel = get_pixel_at(pixel.x, pixel.y + @grid_pixel_size)
|
206
|
+
floodfill(_pixel, target_color, replacement_color)
|
207
|
+
|
208
|
+
# LEFT
|
209
|
+
_pixel = get_pixel_at(pixel.x - @grid_pixel_size, pixel.y)
|
210
|
+
floodfill(_pixel, target_color, replacement_color)
|
211
|
+
|
212
|
+
# RIGHT
|
213
|
+
_pixel = get_pixel_at(pixel.x + @grid_pixel_size, pixel.y)
|
214
|
+
floodfill(_pixel, target_color, replacement_color)
|
215
|
+
end
|
216
|
+
|
217
|
+
def sprites
|
218
|
+
@sprites
|
219
|
+
end
|
220
|
+
|
221
|
+
def set_sprite
|
222
|
+
if @sprites[@sprites_picker.active_sprite]
|
223
|
+
sprite_pixels(@sprites[@sprites_picker.active_sprite])
|
224
|
+
else
|
225
|
+
@pixels.each do |pixel|
|
226
|
+
pixel.color = BLANK_COLOR
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def sprite_pixels(image)
|
232
|
+
pixels = []
|
233
|
+
image.to_blob.bytes.to_a.each_slice(4).each_with_index do |pixel|
|
234
|
+
buffer = []
|
235
|
+
pixel.each do |chunk|
|
236
|
+
buffer << Integer(chunk)
|
237
|
+
end
|
238
|
+
pixels << buffer
|
239
|
+
end
|
240
|
+
|
241
|
+
pixels.each_with_index do |pixel, index|
|
242
|
+
@pixels[index].color = Gosu::Color.rgba(pixel[0], pixel[1], pixel[2], pixel[3])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def update_sprite
|
247
|
+
list = []
|
248
|
+
|
249
|
+
@pixels.each_slice(window.sprite_size).each do |row|
|
250
|
+
list << row
|
251
|
+
end
|
252
|
+
|
253
|
+
image = Gosu.render(window.sprite_size, window.sprite_size, retro: true) do
|
254
|
+
list.each_with_index do |row, y|
|
255
|
+
row.each_with_index do |pixel, x|
|
256
|
+
Gosu.draw_rect(x, y, 1, 1, pixel.color)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
@sprites[@sprites_picker.active_sprite] = nil # release image for garbage collection?
|
262
|
+
@sprites[@sprites_picker.active_sprite] = image
|
263
|
+
@canvas_changed = false
|
264
|
+
end
|
265
|
+
|
266
|
+
def copy_sprite
|
267
|
+
@copied_pixels = []
|
268
|
+
@pixels.each {|pixel| @copied_pixels << pixel.dup}
|
269
|
+
end
|
270
|
+
|
271
|
+
def paste_sprite
|
272
|
+
if @copied_pixels
|
273
|
+
@pixels.each_with_index do |pixel, i|
|
274
|
+
pixel.color = @copied_pixels[i].color
|
275
|
+
end
|
276
|
+
@canvas_changed = true
|
277
|
+
update_sprite
|
278
|
+
end
|
279
|
+
|
280
|
+
@copied_pixels = nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def spritesheet
|
284
|
+
sheet = Gosu.render(512, 128, retro: true) do
|
285
|
+
@sprites.each_slice(512/16).each_with_index do |row, y|
|
286
|
+
row.each_with_index do |sprite, x|
|
287
|
+
next if sprite.nil?
|
288
|
+
sprite.draw(x * 16, y * 16, 0)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
return sheet
|
294
|
+
end
|
295
|
+
|
296
|
+
def import_spritesheet(data)
|
297
|
+
return if data.to_blob.size < 4
|
298
|
+
sprites = Gosu::Image.load_tiles(data, 16, 16, retro: true, tileable: true)
|
299
|
+
@sprites.clear
|
300
|
+
|
301
|
+
sprites.each do |sprite|
|
302
|
+
@sprites.push(sprite)
|
303
|
+
end
|
304
|
+
|
305
|
+
set_sprite
|
306
|
+
end
|
307
|
+
|
308
|
+
def button_down(id)
|
309
|
+
super
|
310
|
+
|
311
|
+
copy_sprite if window.control_button_down? && id == Gosu::KbC
|
312
|
+
paste_sprite if window.control_button_down? && id == Gosu::KbV
|
313
|
+
end
|
314
|
+
|
315
|
+
def button_up(id)
|
316
|
+
super
|
317
|
+
@palette.button_up(id)
|
318
|
+
@sprites_picker.button_up(id)
|
319
|
+
|
320
|
+
@tools.each{ |b| b.button_up(id) }
|
321
|
+
|
322
|
+
if @pixel_floodfill
|
323
|
+
pixel = get_pixel_at(window.mouse_x, window.mouse_y)
|
324
|
+
if pixel
|
325
|
+
floodfill(pixel, pixel.color, @palette.color) if id == Gosu::MsLeft if @palette.color
|
326
|
+
floodfill(pixel, pixel.color, BLANK_COLOR) if id == Gosu::MsRight
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
update_sprite if (id == Gosu::MsLeft || id == Gosu::MsRight) && @canvas_changed
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|