Przeglądaj źródła

feat: sprite-packer

LanzaSchneider 1 rok temu
rodzic
commit
81a04d8502

+ 5 - 0
editor/tools/common.rb

@@ -29,6 +29,11 @@ def convert_case(source)
   return source.split('_').collect(&:capitalize).join
 end
 
+def ensure_dir(dir)
+  return if Dir.exist?(dir)
+  FileUtils.mkdir_p dir
+end
+
 def recreate_dir(dir)
   FileUtils.rm_rf dir
   FileUtils.mkdir_p dir

+ 1 - 0
editor/tools/config-importer.rb

@@ -66,6 +66,7 @@ Dir["#{config_path}/*"].each do |path|
     # 处理条目形成引用表
     while !csv.eof?
       line = csv.readline
+      next if field_id_name.nil?
       table.name2id[line[field_id_name.column_id]] = line[field_id.column_id].upcase
     end
     # 回滚 csv 状态至表头读取后

+ 57 - 0
editor/tools/sprite-packer.rb

@@ -0,0 +1,57 @@
+require_relative 'common'
+
+options = get_options(__FILE__)
+
+sprite_path = options['sprite_path']
+csv_path = options['generate_config_path']
+atlas_path = options['generate_atlas_path']
+padding = options['padding_size']
+
+recreate_dir atlas_path
+ensure_dir csv_path
+
+# $ gem install chunky_png
+# $ gem install binpack
+require 'chunky_png'
+require 'binpack'
+
+require 'csv'
+
+Point = Struct.new(:x, :y)
+Box = Struct.new(:top_left, :bottom_right)
+
+csv_atlas = CSV.open("#{csv_path}/atlas.csv", 'wb')
+csv_atlas << ['Id', 'Path']
+csv_atlas << ['', '']
+csv_atlas << [':id', ':path']
+
+csv_sprite = CSV.open("#{csv_path}/sprite.csv", 'wb')
+csv_sprite << ['Id', 'AtlasId', 'X', 'Y']
+csv_sprite << ['', '', '', '']
+csv_sprite << [':id', '@atlas', ':int', ':int']
+
+Dir["#{sprite_path}/*"].each do |path|
+  atlas_name = convert_case(File::basename(path))
+  next unless File::directory?(path)
+  atlas_size = 1024
+  sprites = Dir["#{path}/*"].collect do |file| 
+    sprite = ChunkyPNG::Image.from_file(file)
+    sprite.define_singleton_method :file do
+      return file
+    end
+    sprite
+  end
+  items = sprites.collect { |sprite| Binpack::Item.new(sprite, sprite.width, sprite.height) }
+  bins = Binpack::Bin.pack(items, [], Binpack::Bin.new(atlas_size, atlas_size, padding))
+  bins.each do |bin|
+    atlas = ChunkyPNG::Image.new(atlas_size, atlas_size, ChunkyPNG::Color::TRANSPARENT)
+    bin.items.each do |item|
+      unit, x, y = *item
+      atlas.compose!(unit.obj, x, y)
+      csv_sprite << [convert_case(File.basename(unit.obj.file, '.png')), atlas_name, x, y]
+    end
+    atlas_path = "#{atlas_path}/#{File::basename(path)}.png"
+    atlas.save atlas_path
+    csv_atlas << [atlas_name, File.basename(atlas_path)]
+  end
+end

+ 11 - 0
editor/tools/sprite-packer.tres

@@ -0,0 +1,11 @@
+[gd_resource type="Resource" script_class="DevTool" load_steps=2 format=3 uid="uid://cyefuuhoo3do0"]
+
+[ext_resource type="Script" path="res://addons/godot-TBL-extension-base/editor/scripts/dev_tool.gd" id="1_t2kpq"]
+
+[resource]
+script = ExtResource("1_t2kpq")
+tool_dock = 3
+tool_config = "res://.tbl/sprite-packer.json"
+tool_script = "res://addons/godot-TBL-extension-base/editor/tools/sprite-packer.rb"
+tool_name = "图集"
+tool_command_name = "生成图集"

+ 25 - 0
tblext.gd

@@ -24,6 +24,29 @@ func make_panel(dev_tool:DevTool) -> VBoxContainer:
 			panel.tree_exiting.connect(func():
 				option.value = line_edit.text
 				)
+		elif option.value is int:
+			var comment := Label.new()
+			var range := SpinBox.new()
+			comment.text = option.comment
+			range.value = option.value
+			range.step = 1
+			range.min_value = -65535
+			range.max_value = 65535
+			panel.add_child(comment)
+			panel.add_child(range)
+			panel.tree_exiting.connect(func():
+				option.value = int(range.value)
+				)
+		elif option.value is float:
+			var comment := Label.new()
+			var range := SpinBox.new()
+			comment.text = option.comment
+			range.value = option.value
+			panel.add_child(comment)
+			panel.add_child(range)
+			panel.tree_exiting.connect(func():
+				option.value = range.value
+				)
 		elif option.value is bool:
 			var check_box := CheckBox.new()
 			check_box.text = option.comment
@@ -32,6 +55,8 @@ func make_panel(dev_tool:DevTool) -> VBoxContainer:
 			panel.tree_exiting.connect(func():
 				option.value = check_box.button_pressed
 				)
+		else:
+			print("unsupported option value({0}): {1}".format([option.name, typeof(option.value)]))
 	var command := Button.new()
 	command.text = dev_tool.tool_command_name
 	command.button_up.connect(func():