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

179 lines
3.4 KiB
Ruby
Raw Normal View History

2024-02-24 02:29:36 +00:00
#!/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 %>
.386
.model flat,c
.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
C_TEMPLATE = <<~C
2024-02-24 12:36:18 +00:00
#ifndef __SPRITES_H__
#define __SPRITES_H__
#include "types.h"
2024-02-24 02:29:36 +00:00
<% sprites.each do |sprite| %>
extern void <%= sprite.function_name %>(byte *);
<% end %>
2024-02-24 12:36:18 +00:00
// TODO: something with bounds checking here
#endif
2024-02-24 02:29:36 +00:00
C
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 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 = []
data["files"].each do |spritesheet|
image = Magick::Image.read(spritesheet["file"]).first
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(C_TEMPLATE).result_with_hash(sprites:)
end
puts "sprites.{asm,h} written"