Last active
May 5, 2024 17:11
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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