dos-vga-arena-shooter-game/bin/build_spritesheet_asm.rb

226 lines
4.4 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require 'rmagick'
require 'yaml'
require 'erb'
data = YAML.load(File.read('spritesheet.yml'))
class Sprite
def initialize(image:, sx:, sy:, width:, height:)
@image = image
@sx = sx
@sy = sy
@width = width
@height = height
end
def pixel_data
@height.times.each_with_object([]) do |y, obj|
obj << @width.times.map do |x|
color = @image.pixel_color(x + @sx, y + @sy)
colormap.find_index { |c| c == color }
end
end
end
private
def colormap
@colormap ||= @image.colors.times.map do |i|
Magick::Pixel.from_color(@image.colormap(i))
end
end
end
MovBytePtr = Data.define(:offset, :data)
ASM_TEMPLATE = <<~ASM
<% sprites.each do |sprite| %>
PUBLIC <%= sprite.exported_name %><% end %>
PUBLIC _palette
.386
.model flat,c
.DATA
_palette:
<%= palette.flat_map { |color| color.to_asm_struct }.join("\n") %>
.CODE
<% sprites.each do |sprite| %>
<%= sprite.exported_name %>:
push ebp
mov ebp, esp
<% sprite.bytes.each do |byte| %>
mov BYTE PTR [eax + <%= byte.offset %>], <%= byte.data %><% end %>
pop ebp
ret
<% end %>
end
ASM
H_TEMPLATE = <<~H
#ifndef __SPRITES_H__
#define __SPRITES_H__
#include "types.h"
#include "system/vga.h"
#define PALETTE_COLOR_COUNT (<%= palette.length %>)
extern struct VGAColor palette[<%= palette.length %>];
<% sprites.each do |sprite| %>
extern void <%= sprite.function_name %>(byte *);
<% sprite.constants.each do |constant, value| %>
#define <%= constant %> (<%= value %>)
<% end %>
<% end %>
#endif
H
class AssemblerSprite
def initialize(
name:,
pixel_data:,
screen_width:,
transparent_color:,
offset_x:,
offset_y:,
prefix:
)
@name = name
@pixel_data = pixel_data
@screen_width = screen_width
@transparent_color = transparent_color
@offset_x = offset_x
@offset_y = offset_y
@prefix = prefix
end
def exported_name
"#{function_name}_"
end
def function_name
"#{@prefix}_#{@name}"
end
def constants
[
['WIDTH', @pixel_data[0].length],
['HEIGHT', @pixel_data.length],
['OFFSET_X', @offset_x],
['OFFSET_Y', @offset_y]
].map do |suffix, value|
["#{function_name.upcase}_#{suffix}", value]
end
end
def bytes
return @bytes if defined?(@bytes)
bytes = []
@pixel_data.each_with_index do |row, y|
row.each_with_index do |column, x|
next if @pixel_data[y][x] == @transparent_color
bytes << MovBytePtr.new(
data: @pixel_data[y][x],
offset: x - @offset_x + (y - @offset_y) * @screen_width
)
end
end
bytes
end
end
Screen = Data.define(:width, :transparent_color)
SpriteData = Data.define(:image, :name, :position, :dimensions, :offset, :prefix)
screen = Screen.new(
width: data["screen"]["width"],
transparent_color: data["screen"]["transparentColor"]
)
sprite_data = []
palette = nil
VGAColor = Data.define(:red, :green, :blue) do
def to_asm_struct
[
" BYTE #{red}",
" BYTE #{green}",
" BYTE #{blue}"
]
end
end
data["files"].each do |spritesheet|
image = Magick::Image.read(spritesheet["file"]).first
unless palette
palette = image.colors.times.map do |i|
pixel = Magick::Pixel.from_color(image.colormap(i))
VGAColor.new(
red: pixel.red >> 10,
green: pixel.green >> 10,
blue: pixel.blue >> 10
)
end
end
spritesheet["sprites"].each do |name, details|
sprite_data << SpriteData.new(
image:,
name:,
prefix: spritesheet["prefix"],
position: details["position"],
dimensions: details["dimensions"],
offset: details["offset"]
)
end
end
sprites = sprite_data.map do |sd|
sprite = Sprite.new(
image: sd.image,
sx: sd.position[0],
sy: sd.position[1],
width: sd.dimensions[0],
height: sd.dimensions[1],
)
AssemblerSprite.new(
name: sd.name,
prefix: sd.prefix,
pixel_data: sprite.pixel_data,
screen_width: screen.width,
transparent_color: screen.transparent_color,
offset_x: sd.offset[0],
offset_y: sd.offset[1],
)
end
File.open('sprites.asm', 'w') do |fh|
fh.puts ERB.new(ASM_TEMPLATE).result_with_hash(sprites:)
end
File.open('sprites.h', 'w') do |fh|
fh.puts ERB.new(H_TEMPLATE).result_with_hash(sprites:, palette:)
end
puts "sprites.{asm,h} written"