diff options
Diffstat (limited to 'samples/99_sample_sprite_animation_creator/app/main.rb')
| -rw-r--r-- | samples/99_sample_sprite_animation_creator/app/main.rb | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/samples/99_sample_sprite_animation_creator/app/main.rb b/samples/99_sample_sprite_animation_creator/app/main.rb new file mode 100644 index 0000000..14456e3 --- /dev/null +++ b/samples/99_sample_sprite_animation_creator/app/main.rb @@ -0,0 +1,447 @@ +class OneBitLowrezPaint + attr_gtk + + def tick + outputs.background_color = [0, 0, 0] + defaults + render_instructions + render_canvas + render_buttons_frame_selection + render_animation_frame_thumbnails + render_animation + input_mouse_click + input_keyboard + calc_auto_export + calc_buttons_frame_selection + calc_animation_frames + process_queue_create_sprite + process_queue_reset_sprite + process_queue_update_rt_animation_frame + end + + def defaults + state.animation_frames_per_second = 12 + queues.create_sprite ||= [] + queues.reset_sprite ||= [] + queues.update_rt_animation_frame ||= [] + + if !state.animation_frames + state.animation_frames ||= [] + add_animation_frame_to_end + end + + state.last_mouse_down ||= 0 + state.last_mouse_up ||= 0 + + state.buttons_frame_selection.left = 10 + state.buttons_frame_selection.top = grid.top - 10 + state.buttons_frame_selection.size = 20 + + defaults_canvas_sprite + + state.edit_mode ||= :drawing + end + + def defaults_canvas_sprite + rt_canvas.size = 16 + rt_canvas.zoom = 30 + rt_canvas.width = rt_canvas.size * rt_canvas.zoom + rt_canvas.height = rt_canvas.size * rt_canvas.zoom + rt_canvas.sprite = { x: 0, + y: 0, + w: rt_canvas.width, + h: rt_canvas.height, + path: :rt_canvas }.center_inside_rect(x: 0, y: 0, w: 640, h: 720) + + return unless state.tick_count == 1 + + outputs[:rt_canvas].width = rt_canvas.width + outputs[:rt_canvas].height = rt_canvas.height + outputs[:rt_canvas].sprites << (rt_canvas.size + 1).map_with_index do |x| + (rt_canvas.size + 1).map_with_index do |y| + path = 'sprites/square-white.png' + path = 'sprites/square-blue.png' if x == 7 || x == 8 + { x: x * rt_canvas.zoom, + y: y * rt_canvas.zoom, + w: rt_canvas.zoom, + h: rt_canvas.zoom, + path: path, + a: 50 } + end + end + end + + def render_instructions + instructions = <<-S +* Instructions: +- All data is stored in the ~canvas~ directory. +- Hold ~d~ to set the edit mode to erase. +- Release ~d~ to set the edit mode drawing. +- Press ~a~ to added a frame to the end. +- Press ~b~ to select the previous frame. +- Press ~f~ to select the next frame. +- Press ~c~ to copy a frame. +- Press ~v~ to paste a copied frame into the selected frame. +- Press ~x~ to delete the currently selected frame. +- Press ~w~ to save the canvas and export all sprites. +- Press ~l~ to load the canvas. +S + + instructions.strip.each_line.with_index do |l, i| + outputs.labels << { x: 840, y: 500 - (i * 20), text: "#{l}", + r: 180, g: 180, b: 180, size_enum: -3 } + end + end + + def render_canvas + return if state.tick_count.zero? + outputs.sprites << rt_canvas.sprite + end + + def render_buttons_frame_selection + args.outputs.primitives << state.buttons_frame_selection.items.map_with_index do |b, i| + label = { x: b.x + state.buttons_frame_selection.size.half, + y: b.y, + text: "#{i + 1}", r: 180, g: 180, b: 180, + size_enum: -4, alignment_enum: 1 }.label + + selection_border = b.merge(r: 40, g: 40, b: 40).border + + if i == state.animation_frames_selected_index + selection_border = b.merge(r: 40, g: 230, b: 200).border + end + + [selection_border, label] + end + end + + def render_animation_frame_thumbnails + return if state.tick_count.zero? + + outputs[:current_animation_frame].width = rt_canvas.size + outputs[:current_animation_frame].height = rt_canvas.size + outputs[:current_animation_frame].solids << selected_animation_frame[:pixels].map_with_index do |f, i| + { x: f.x, + y: f.y, + w: 1, + h: 1, r: 255, g: 255, b: 255 } + end + + outputs.sprites << rt_canvas.sprite.merge(path: :current_animation_frame) + + state.animation_frames.map_with_index do |animation_frame, animation_frame_index| + outputs.sprites << state.buttons_frame_selection[:items][animation_frame_index][:inner_rect] + .merge(path: animation_frame[:rt_name]) + end + end + + def render_animation + sprite_index = 0.frame_index count: state.animation_frames.length, + hold_for: 60 / state.animation_frames_per_second, + repeat: true + + args.outputs.sprites << { x: 700 - 8, + y: 120, + w: 16, + h: 16, + path: (sprite_path sprite_index) } + + args.outputs.sprites << { x: 700 - 16, + y: 230, + w: 32, + h: 32, + path: (sprite_path sprite_index) } + + args.outputs.sprites << { x: 700 - 32, + y: 360, + w: 64, + h: 64, + path: (sprite_path sprite_index) } + + args.outputs.sprites << { x: 700 - 64, + y: 520, + w: 128, + h: 128, + path: (sprite_path sprite_index) } + end + + def input_mouse_click + if inputs.mouse.up + state.last_mouse_up = state.tick_count + elsif inputs.mouse.moved && user_is_editing? + edit_current_animation_frame inputs.mouse.point + end + + return unless inputs.mouse.click + + clicked_frame_button = state.buttons_frame_selection.items.find do |b| + inputs.mouse.point.inside_rect? b + end + + if (clicked_frame_button) + state.animation_frames_selected_index = clicked_frame_button[:index] + end + + if (inputs.mouse.point.inside_rect? rt_canvas.sprite) + state.last_mouse_down = state.tick_count + edit_current_animation_frame inputs.mouse.point + end + end + + def input_keyboard + # w to save + if inputs.keyboard.key_down.w + t = Time.now + state.save_description = "Time: #{t} (#{t.to_i})" + gtk.serialize_state 'canvas/state.txt', state + gtk.serialize_state "tmp/canvas_backups/#{t.to_i}/state.txt", state + animation_frames.each_with_index do |animation_frame, i| + queues.update_rt_animation_frame << { index: i, + at: state.tick_count + i, + queue_sprite_creation: true } + queues.create_sprite << { index: i, + at: state.tick_count + animation_frames.length + i, + path_override: "tmp/canvas_backups/#{t.to_i}/sprite-#{i}.png" } + end + gtk.notify! "Canvas saved." + end + + # l to load + if inputs.keyboard.key_down.l + args.state = gtk.deserialize_state 'canvas/state.txt' + animation_frames.each_with_index do |a, i| + queues.update_rt_animation_frame << { index: i, + at: state.tick_count + i, + queue_sprite_creation: true } + end + gtk.notify! "Canvas loaded." + end + + # d to go into delete mode, release to paint + if inputs.keyboard.key_held.d + state.edit_mode = :erasing + gtk.notify! "Erasing." if inputs.keyboard.key_held.d == (state.tick_count - 1) + elsif inputs.keyboard.key_up.d + state.edit_mode = :drawing + gtk.notify! "Drawing." + end + + # a to add a frame to the end + if inputs.keyboard.key_down.a + queues.create_sprite << { index: state.animation_frames_selected_index, + at: state.tick_count } + queues.create_sprite << { index: state.animation_frames_selected_index + 1, + at: state.tick_count } + add_animation_frame_to_end + gtk.notify! "Frame added to end." + end + + # c or t to copy + if (inputs.keyboard.key_down.c || inputs.keyboard.key_down.t) + state.clipboard = [selected_animation_frame[:pixels]].flatten + gtk.notify! "Current frame copied." + end + + # v or q to paste + if (inputs.keyboard.key_down.v || inputs.keyboard.key_down.q) && state.clipboard + selected_animation_frame[:pixels] = [state.clipboard].flatten + queues.update_rt_animation_frame << { index: state.animation_frames_selected_index, + at: state.tick_count, + queue_sprite_creation: true } + gtk.notify! "Pasted." + end + + # f to go forward/next frame + if (inputs.keyboard.key_down.f) + if (state.animation_frames_selected_index == (state.animation_frames.length - 1)) + state.animation_frames_selected_index = 0 + else + state.animation_frames_selected_index += 1 + end + gtk.notify! "Next frame." + end + + # b to go back/previous frame + if (inputs.keyboard.key_down.b) + if (state.animation_frames_selected_index == 0) + state.animation_frames_selected_index = state.animation_frames.length - 1 + else + state.animation_frames_selected_index -= 1 + end + gtk.notify! "Previous frame." + end + + # x to delete frame + if (inputs.keyboard.key_down.x) && animation_frames.length > 1 + state.clipboard = selected_animation_frame[:pixels] + state.animation_frames = animation_frames.find_all { |v| v[:index] != state.animation_frames_selected_index } + if state.animation_frames_selected_index >= state.animation_frames.length + state.animation_frames_selected_index = state.animation_frames.length - 1 + end + gtk.notify! "Frame deleted." + end + end + + def calc_auto_export + return if user_is_editing? + return if state.last_mouse_up.elapsed_time != 30 + # auto export current animation frame if there is no editing for 30 ticks + queues.create_sprite << { index: state.animation_frames_selected_index, + at: state.tick_count } + end + + def calc_buttons_frame_selection + state.buttons_frame_selection.items = animation_frames.length.map_with_index do |i| + { x: state.buttons_frame_selection.left + i * state.buttons_frame_selection.size, + y: state.buttons_frame_selection.top - state.buttons_frame_selection.size, + inner_rect: { + x: (state.buttons_frame_selection.left + 2) + i * state.buttons_frame_selection.size, + y: (state.buttons_frame_selection.top - state.buttons_frame_selection.size + 2), + w: 16, + h: 16, + }, + w: state.buttons_frame_selection.size, + h: state.buttons_frame_selection.size, + index: i } + end + end + + def calc_animation_frames + animation_frames.each_with_index do |animation_frame, i| + animation_frame[:index] = i + animation_frame[:rt_name] = "animation_frame_#{i}" + end + end + + def process_queue_create_sprite + sprites_to_create = queues.create_sprite + .find_all { |h| h[:at].elapsed? } + + queues.create_sprite = queues.create_sprite - sprites_to_create + + sprites_to_create.each do |h| + export_animation_frame h[:index], h[:path_override] + end + end + + def process_queue_reset_sprite + sprites_to_reset = queues.reset_sprite + .find_all { |h| h[:at].elapsed? } + + queues.reset_sprite -= sprites_to_reset + + sprites_to_reset.each { |h| gtk.reset_sprite (sprite_path h[:index]) } + end + + def process_queue_update_rt_animation_frame + animation_frames_to_update = queues.update_rt_animation_frame + .find_all { |h| h[:at].elapsed? } + + queues.update_rt_animation_frame -= animation_frames_to_update + + animation_frames_to_update.each do |h| + update_animation_frame_render_target animation_frames[h[:index]] + + if h[:queue_sprite_creation] + queues.create_sprite << { index: h[:index], + at: state.tick_count + 1 } + end + end + end + + def update_animation_frame_render_target animation_frame + return if !animation_frame + + outputs[animation_frame[:rt_name]].width = state.rt_canvas.size + outputs[animation_frame[:rt_name]].height = state.rt_canvas.size + outputs[animation_frame[:rt_name]].solids << animation_frame[:pixels].map do |f| + { x: f.x, + y: f.y, + w: 1, + h: 1, r: 255, g: 255, b: 255 } + end + end + + def animation_frames + state.animation_frames + end + + def add_animation_frame_to_end + animation_frames << { + index: animation_frames.length, + pixels: [], + rt_name: "animation_frame_#{animation_frames.length}" + } + + state.animation_frames_selected_index = (animation_frames.length - 1) + queues.update_rt_animation_frame << { index: state.animation_frames_selected_index, + at: state.tick_count, + queue_sprite_creation: true } + end + + def sprite_path i + "canvas/sprite-#{i}.png" + end + + def export_animation_frame i, path_override = nil + return if !state.animation_frames[i] + + outputs.screenshots << state.buttons_frame_selection + .items[i][:inner_rect] + .merge(path: path_override || (sprite_path i)) + + outputs.screenshots << state.buttons_frame_selection + .items[i][:inner_rect] + .merge(path: "tmp/sprite_backups/#{Time.now.to_i}-sprite-#{i}.png") + + queues.reset_sprite << { index: i, at: state.tick_count } + end + + def selected_animation_frame + state.animation_frames[state.animation_frames_selected_index] + end + + def edit_current_animation_frame point + draw_area_point = (to_draw_area point) + if state.edit_mode == :drawing && (!selected_animation_frame[:pixels].include? draw_area_point) + selected_animation_frame[:pixels] << draw_area_point + queues.update_rt_animation_frame << { index: state.animation_frames_selected_index, + at: state.tick_count, + queue_sprite_creation: !user_is_editing? } + elsif state.edit_mode == :erasing && (selected_animation_frame[:pixels].include? draw_area_point) + selected_animation_frame[:pixels] = selected_animation_frame[:pixels].reject { |p| p == draw_area_point } + queues.update_rt_animation_frame << { index: state.animation_frames_selected_index, + at: state.tick_count, + queue_sprite_creation: !user_is_editing? } + end + end + + def user_is_editing? + state.last_mouse_down > state.last_mouse_up + end + + def to_draw_area point + x, y = point + x -= rt_canvas.sprite.x + y -= rt_canvas.sprite.y + { x: x.idiv(rt_canvas.zoom), + y: y.idiv(rt_canvas.zoom) } + end + + def rt_canvas + state.rt_canvas ||= state.new_entity(:rt_canvas) + end + + def queues + state.queues ||= state.new_entity(:queues) + end +end + +$game = OneBitLowrezPaint.new + +def tick args + $game.args = args + $game.tick +end + +# $gtk.reset |
