Skip to content

Instantly share code, notes, and snippets.

@partybusiness
Last active May 5, 2024 17:11
Show Gist options
  • Save partybusiness/aa620f3f9a9da0852af366207be4bf6f to your computer and use it in GitHub Desktop.
Save partybusiness/aa620f3f9a9da0852af366207be4bf6f to your computer and use it in GitHub Desktop.
Godot shader that treats an array of integers as a tilemap and Godot editor scripts to work with those arrays
@tool
extends EditorScript
class_name BitmapToArray
# this is using a tile map available here:
# https://hexany-ives.itch.io/hexanys-roguelike-tiles
const sourceImage:String = "res://arraydemo/monochrome_16x16.png" #image we get tiles from
const width:int = 32 # how many tiles wide is the source image
const height:int = 32 # how many tiles tall is the source image
const fileName:String = "res://tile_data.rom" #file the resulting array is saved to
#in my scene DisplayTiles is a MeshInstance2D using a material with the DisplayTiles shader
const displayNode:String = "DisplayTiles"
# name of the uniform uint parameter on the shader that accepts the resulting array of tiles
# tilemap matches what I used in the DiplayTiles shader
const shaderParameterName:String = "tilemap"
#converts 16x16 tile from image into 8 integer values where each pixel is one bit
func get_tile(image:Image, x:int, y:int) -> PackedInt32Array:
const ts:int = 16 #16x16 pixels
var result:PackedInt32Array = PackedInt32Array()
result.resize(8) #8 dwords to store 16x16 pixels
result.fill(0)
var ds:String #debug string
for yy in range(0,ts):
for xx in range(0,ts):
var colour = image.get_pixel(x*ts + xx,y*ts + yy)
var ispixel:bool = colour.r > 0.5
if (ispixel):
var bitmask:int = 0x1 << xx
if (yy%2 == 1):
bitmask = bitmask << 16
#print("%d %d %x" % [xx, yy, bitmask])
result[floori(yy/2)] |= bitmask
ds = ds + "@"
else:
ds = ds + " "
ds = ds + "\n"
#printing the contents of the tile
print(ds)
for m in result:
print("%08X" % m)
return result
#gets index of given tile
func get_index(x:int, y:int) -> int:
return y * width + x
#gets a row of indices from x1 to x2
func get_index_range(x1:int, x2:int, y:int) -> Array:
var result:Array
for x in range(x1,x2):
result.append(get_index(x,y))
return result
func _run():
var image:Image = Image.load_from_file(sourceImage)
#array of indices of tiles we will convert
var tileList:Array = []
#these indices correspond to tiles in Hexany's source map
# the full 32x32 isn't filled and 32x32x8x4 bytes per tile would start to exceed the size of array I can pass to the shader
#adds one room to tileList
tileList.append_array(get_index_range(0,3,22))
tileList.append_array(get_index_range(0,3,23))
tileList.append_array(get_index_range(0,3,24))
#adds a bunch of little guys
tileList.append_array(get_index_range(0,16,5))
var tileData:PackedInt32Array = PackedInt32Array()
#generates the array values from those tiles in the image
for n in tileList:
var x:int = n % width
var y:int = floori(n / width)
tileData.append_array (get_tile(image,x,y))
#assigns result to a shader on the displayNode
var currentScene = get_scene()
var display = currentScene.find_child(displayNode, true)
display.material.set_shader_parameter(shaderParameterName, tileData)
#stores result in a file
var filePath = ProjectSettings.globalize_path(fileName)
var file = FileAccess.open(filePath, FileAccess.WRITE)
file.store_buffer (tileData.to_byte_array ( ) )
file.close()
print("Saved Bytes")
shader_type canvas_item;
uniform uint tilemap[1024]; // up to 128 8-dword 16x16 tiles, takes 7 bits to index
uniform uint screen[240];// 16 x 15 screen map
uniform uint screenWidth = 16;
uniform uint screenHeight = 15;
//7 lowest bits index tilemap
//with 16 colour palettte we can use 4 each to index foreground and background colours
//which leaves 7 unused bits for other effects?
//maybe flip bits to mirror tiles?
//a pixel offset for shake animations?
//options for glitched tiles?
uniform vec3 colourPalette[16];
//16 predefined colours that can be selected from
bool getpixel() {
return false;
}
vec3 bitsToColour (uint bits) { // takes 12 lowest bits
float r = float(bits >> 10u & 0x11111u)/16.;
float g = float(bits >> 5u & 0x11111u)/16.;
float b = float(bits & 0x1111u)/16.; //31
return vec3(r,g,b);
}
vec3 indexedColour (uint index) {
return colourPalette[index&0xFu]; //use lowest four bits
}
//gets whether a pixel within a given tile is foreground or background
bool getPixel (uint x, uint y, uint tileIndex) { //x and y within 16x16 tile, index of tile in tilemap
uint chunkOffset = y >> 1u; //8 dwords
uint tileAddress = (tileIndex << 3u) + chunkOffset; //8 dwords per tile plus chunk within the tile
uint chunk = tilemap[tileAddress];
uint shifter = (x) | ((y & 1u) << 4u);
uint sample = chunk >> shifter & 1u;
return sample > 0u;
}
void fragment() {
float sw = float(screenWidth);
float sh = float(screenHeight);
//get current tile
uint tilex = uint(floor(UV.x*sw));
uint tiley = uint(floor(UV.y*sh));
//get x and y pixel within current tile
vec2 tileUV = (UV - vec2(float(tilex)/sw, float(tiley)/sh)) * vec2(sw,sh);
uint xindex = uint(floor(tileUV.x*16.));
uint yindex = uint(floor(tileUV.y*16.)); //max = 4294967295 //2147483648
uint screenTile = screen[tilex + tiley * screenWidth];
//get colours
uint index = screenTile & 0x7Fu; //bottom 7 bits
bool ispixel = getPixel(xindex, yindex, index);
uint colourShift = ispixel?7u:11u; // shift to foreground or background colour
COLOR.rgb = indexedColour(screenTile >> colourShift);
}
@tool
extends EditorScript
class_name GenerateMap
const width:int = 16 # width of screen display in tiles
const height:int = 15 # height of screen display in tiles
#in my scene DisplayTiles is a MeshInstance2D using a material with the DisplayTiles shader
const displayNode:String = "DisplayTiles"
# name of the uniform uint parameter on the shader that accepts the resulting map
# screen matches what I used in the DiplayTiles shader
const shaderParameterName:String = "screen"
#defines an array that contains the indices of tiles used to draw a room
func defineRoomTiles (startIndex:int) -> Array:
var result:Array = []
#assumes 0 is top-left, 1 is top-middle, 2 is top-right and so on
for offset in range(0,9):
result.append(startIndex+offset)
return result
#draws a rectangular room on screenData
func drawRoom (screenData:PackedInt32Array, L:int, R:int, T:int, B:int, roomTiles:Array, foreColour:int, backColour:int):
#corners
drawTile(screenData, L, T, roomTiles[0], foreColour, backColour)
drawTile(screenData, R, T, roomTiles[2], foreColour, backColour)
drawTile(screenData, L, B, roomTiles[6], foreColour, backColour)
drawTile(screenData, R, B, roomTiles[8], foreColour, backColour)
#middle
for x in range(L+1,R):
#top wall
drawTile(screenData, x, T, roomTiles[1], foreColour, backColour)
#bottom wall
drawTile(screenData, x, B, roomTiles[7], foreColour, backColour)
for y in range(T+1,B):
drawTile(screenData, x, y, roomTiles[4], foreColour, backColour)
for y in range(T+1,B):
#sides
drawTile(screenData, L, y, roomTiles[3], foreColour, backColour)
drawTile(screenData, R, y, roomTiles[5], foreColour, backColour)
#draws a single on screenData with the given tile index, foreground and background colours
#
func drawTile (screenData:PackedInt32Array, x:int, y:int, tile:int, foreColour:int, backColour:int):
if x>=width || y>= width || x<0 || y < 0:
return
var tileValue:int = tile & 0x7F #bottom 7 bits
tileValue |= (foreColour & 0xF) << 7 #next 4 bits
tileValue |= (backColour & 0xF) << 11 #next 4 bits
screenData[x + y * width] = tileValue
func _run():
var screenData:PackedInt32Array = PackedInt32Array()
screenData.resize(width*height)
screenData.fill(4)
var roomTiles = defineRoomTiles(0)
drawRoom(screenData, 0, 15, 0, 14, roomTiles, 10, 0)
drawRoom(screenData, 3, 7, 2, 5, roomTiles, 8, 1)
drawTile(screenData, 7, 12, 11, 9, 0)
drawTile(screenData, 5, 8, 18, 12, 0)
drawTile(screenData, 6, 7, 15, 12, 0)
drawTile(screenData, 7, 7, 15, 12, 0)
drawTile(screenData, 8, 8, 19, 12, 0)
#assigns result to a shader on the displayNode
var currentScene = get_scene()
var display = currentScene.find_child(displayNode, true)
display.material.set_shader_parameter(shaderParameterName, screenData)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment