func (er *EbitenRenderer) Render(level *ldtkgo.Level) {
	er.Clear()
	for _, layer := range level.Layers {
		switch layer.Type {
		case ldtkgo.LayerTypeIntGrid:  
			fallthrough
		case ldtkgo.LayerTypeAutoTile:
			fallthrough
		case ldtkgo.LayerTypeTile:
			if tiles := layer.AllTiles(); len(tiles) > 0 {
				er.beginLayer(layer, level.Width, level.Height)
				for _, tileData := range tiles {
					tile := er.Tilesets[er.CurrentTileset].SubImage(image.Rect(tileData.Src[0], tileData.Src[1], tileData.Src[0]+layer.GridSize, tileData.Src[1]+layer.GridSize)).(*ebiten.Image)
					opt := &ebiten.DrawImageOptions{}
					opt.GeoM.Translate(float64(-layer.GridSize/2), float64(-layer.GridSize/2))
					if tileData.FlipX() {
						opt.GeoM.Scale(-1, 1)
					}
					if tileData.FlipY() {
						opt.GeoM.Scale(1, -1)
					}
					opt.GeoM.Translate(float64(layer.GridSize/2), float64(layer.GridSize/2))
					opt.GeoM.Translate(float64(tileData.Position[0]+layer.OffsetX), float64(tileData.Position[1]+layer.OffsetY))
					er.RenderedLayers[len(er.RenderedLayers)-1].Image.DrawImage(tile, opt)
				}
			}
		}
	}
	sort.Slice(er.RenderedLayers, func(i, j int) bool {
		return i > j
	})
}
Render clears, and then renders out each visible Layer in an ldtgo.Level instance.
package ebitenrenderer
import (
	"image"
	"path/filepath"
	"sort"
	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/solarlune/ldtkgo"
	_ "image/png"  
)
type TilesetLoader interface {
	LoadTileset(string) *ebiten.Image
}
type DiskLoader struct {
	BasePath string
	Filter   ebiten.Filter
}
func NewDiskLoader(basePath string) *DiskLoader {
	return &DiskLoader{
		BasePath: basePath,
		Filter:   ebiten.FilterNearest,
	}
}
func (d *DiskLoader) LoadTileset(tilesetPath string) *ebiten.Image {
	if img, _, err := ebitenutil.NewImageFromFile(filepath.Join(d.BasePath, tilesetPath)); err == nil {
		return img
	}
	return nil
}
type RenderedLayer struct {
	Image *ebiten.Image  
	Layer *ldtkgo.Layer  
}
type EbitenRenderer struct {
	Tilesets       map[string]*ebiten.Image
	CurrentTileset string
	RenderedLayers []*RenderedLayer
	Loader         TilesetLoader  
}
func NewEbitenRenderer(loader TilesetLoader) *EbitenRenderer {
	return &EbitenRenderer{
		Tilesets:       map[string]*ebiten.Image{},
		RenderedLayers: []*RenderedLayer{},
		Loader:         loader,
	}
}
func (er *EbitenRenderer) Clear() {
	for _, layer := range er.RenderedLayers {
		layer.Image.Dispose()
	}
	er.RenderedLayers = []*RenderedLayer{}
}
func (er *EbitenRenderer) beginLayer(layer *ldtkgo.Layer, w, h int) {
	_, exists := er.Tilesets[layer.Tileset.Path]
	if !exists {
		er.Tilesets[layer.Tileset.Path] = er.Loader.LoadTileset(layer.Tileset.Path)
	}
	er.CurrentTileset = layer.Tileset.Path
	renderedImage := ebiten.NewImage(w, h)
	er.RenderedLayers = append(er.RenderedLayers, &RenderedLayer{Image: renderedImage, Layer: layer})
}
func (er *EbitenRenderer) renderTile(x, y, srcX, srcY, srcW, srcH int, flipBit byte) {
	tile := er.Tilesets[er.CurrentTileset].SubImage(image.Rect(srcX, srcY, srcX+srcW, srcY+srcH)).(*ebiten.Image)
	opt := &ebiten.DrawImageOptions{}
	opt.GeoM.Translate(float64(-srcW/2), float64(-srcH/2))
	if flipBit&1 > 0 {
		opt.GeoM.Scale(-1, 1)
	}
	if flipBit&2 > 0 {
		opt.GeoM.Scale(1, -1)
	}
	opt.GeoM.Translate(float64(srcW/2), float64(srcH/2))
	opt.GeoM.Translate(float64(x), float64(y))
	er.RenderedLayers[len(er.RenderedLayers)-1].Image.DrawImage(tile, opt)
}