zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

gamemap.go (5161B)


      1 package main
      2 
      3 import (
      4 	"encoding/json"
      5 	"errors"
      6 	"github.com/hajimehoshi/ebiten/v2"
      7 	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
      8 	"image"
      9 	_ "image/png"
     10 	"os"
     11 )
     12 
     13 // to see them bits position with own eyes
     14 const (
     15 	flipHoriz uint32 = 0b10000000000000000000000000000000
     16 	flipVert  uint32 = 0b01000000000000000000000000000000
     17 	flipDiag  uint32 = 0b00100000000000000000000000000000
     18 )
     19 
     20 type Tileset struct {
     21 	FirstGid   uint32 `json:firstgid`
     22 	Image      string `json:image`
     23 	Name       string `json:name`
     24 	TileWidth  uint32 `json:tilewidth`
     25 	TileHeight uint32 `json:tileheight`
     26 	Columns    uint32 `json:columns`
     27 
     28 	Atlas *ebiten.Image
     29 }
     30 
     31 type Tile struct {
     32 	FlipHor, FlipVer, FlipDiag bool
     33 	Options                    *ebiten.DrawImageOptions
     34 	Image                      *ebiten.Image
     35 	Gid                        uint32
     36 	x, y                       float64 // absolute position coordinates
     37 	LayerId                    int32
     38 }
     39 
     40 type Layer struct {
     41 	Type   string   `json:type`
     42 	Id     int32    `json:id`
     43 	Data   []uint32 `json:data`
     44 	Height int32    `json:height`
     45 	Width  int32    `json:width`
     46 	X      int32    `json:x`
     47 	Y      int32    `json:y`
     48 }
     49 
     50 type Gamemap struct {
     51 	Width      uint32     `json:width`
     52 	Height     uint32     `json:height`
     53 	TileWidth  uint32     `json:tilewidth`
     54 	TileHeight uint32     `json:tileheight`
     55 	Layers     []*Layer   `json:layers`
     56 	Tilesets   []*Tileset `json:tilesets`
     57 
     58 	RealWidth, RealHeight float64
     59 
     60 	Tiles map[uint64][]*Tile
     61 }
     62 
     63 func (l *Layer) Init(gamemap *Gamemap) error {
     64 	for i, t := range l.Data {
     65 		if t > 0 {
     66 			var tile *Tile = &Tile{}
     67 			// first 3 bits in that uint value are flags
     68 			tile.FlipHor = ((t & flipHoriz) >> 31) == 1
     69 			tile.FlipVer = ((t & flipVert) >> 30) == 1
     70 			tile.FlipDiag = ((t & flipDiag) >> 29) == 1
     71 			t &= ^(flipHoriz | flipVert | flipDiag) // cleanup flags
     72 			tileset := gamemap.findTileset(t)
     73 			if tileset == nil {
     74 				return errors.New("Invalid tile gid: " + string(t))
     75 			}
     76 			t_width := tileset.TileWidth
     77 			t_height := tileset.TileHeight
     78 			tile.Gid = t - tileset.FirstGid
     79 			tile.Options = &ebiten.DrawImageOptions{}
     80 			// to rotate picture around its center we shift it
     81 			tile.Options.GeoM.Translate(-float64(t_width)/2, -float64(t_height)/2)
     82 			if tile.FlipDiag {
     83 				ret := ebiten.GeoM{}
     84 				ret.SetElement(0, 0, tile.Options.GeoM.Element(1, 0))
     85 				ret.SetElement(0, 1, tile.Options.GeoM.Element(1, 1))
     86 				ret.SetElement(0, 2, tile.Options.GeoM.Element(1, 2))
     87 				ret.SetElement(1, 0, tile.Options.GeoM.Element(0, 0))
     88 				ret.SetElement(1, 1, tile.Options.GeoM.Element(0, 1))
     89 				ret.SetElement(1, 2, tile.Options.GeoM.Element(0, 2))
     90 				tile.Options.GeoM = ret
     91 			}
     92 			if tile.FlipHor {
     93 				tile.Options.GeoM.Scale(-1, 1)
     94 			}
     95 			if tile.FlipVer {
     96 				tile.Options.GeoM.Scale(1, -1)
     97 			}
     98 			// shifting picture back after rotating the matrix
     99 			tile.Options.GeoM.Translate(float64(t_width)/2, float64(t_height)/2)
    100 			// setting tile coordinates on the map
    101 			tile_x := uint32(i) % gamemap.Width
    102 			tile_y := uint32(i) / gamemap.Width
    103 			tile.x = float64(tile_x * t_width)
    104 			tile.y = float64(tile_y * t_height)
    105 			tile.Options.GeoM.Translate(tile.x, tile.y)
    106 			sx := int((tile.Gid % tileset.Columns) * t_width)
    107 			sy := int((tile.Gid / tileset.Columns) * t_height)
    108 			tile.LayerId = l.Id
    109 			tile.Image = tileset.Atlas.SubImage(image.Rect(sx, sy, sx+int(t_width), sy+int(t_height))).(*ebiten.Image)
    110 			// transforming tile coordinates into a single number
    111 			tile_key := gamemap.GetTileKey(tile_x, tile_y)
    112 			tiles := gamemap.Tiles[tile_key]
    113 			gamemap.Tiles[tile_key] = append(tiles, tile)
    114 		}
    115 	}
    116 
    117 	return nil
    118 }
    119 
    120 func (t *Tileset) Init() error {
    121 	var err error
    122 
    123 	t.Atlas, _, err = ebitenutil.NewImageFromFile(assetsDir + t.Image)
    124 	if err != nil {
    125 		return err
    126 	}
    127 
    128 	return nil
    129 }
    130 
    131 func (m *Gamemap) Init(filename string) error {
    132 	data, err := os.ReadFile(filename)
    133 	if err != nil {
    134 		return err
    135 	}
    136 	err = json.Unmarshal(data, m)
    137 	if err != nil {
    138 		return err
    139 	}
    140 
    141 	m.Tiles = make(map[uint64][]*Tile)
    142 
    143 	for _, tileset := range m.Tilesets {
    144 		if err := tileset.Init(); err != nil {
    145 			return err
    146 		}
    147 	}
    148 
    149 	for _, layer := range m.Layers {
    150 		if layer.Type == "tilelayer" {
    151 			if err := layer.Init(m); err != nil {
    152 				return err
    153 			}
    154 		}
    155 	}
    156 
    157 	m.RealWidth = float64(m.TileWidth) * float64(m.Width)
    158 	m.RealHeight = float64(m.TileHeight) * float64(m.Height)
    159 
    160 	return nil
    161 }
    162 
    163 func (m *Gamemap) findTileset(gid uint32) *Tileset {
    164 	var ret *Tileset = nil
    165 	for _, tileset := range m.Tilesets {
    166 		if gid >= tileset.FirstGid {
    167 			if (ret != nil && ret.FirstGid < tileset.FirstGid) || ret == nil {
    168 				ret = tileset
    169 			}
    170 		}
    171 	}
    172 	return ret
    173 }
    174 
    175 func (m *Gamemap) GetTileKey(x, y uint32) uint64 {
    176 	// first 4 bytes are x, second 4 bytes - y.
    177 	var ret uint64 = 0
    178 	ret |= uint64(x) << 32
    179 	ret |= uint64(y)
    180 	return ret
    181 }
    182 
    183 func (t *Tile) IsOnScreen(cam *Camera, gamemap *Gamemap) bool {
    184 	var left float64 = cam.x - float64(gamemap.TileWidth)
    185 	var right float64 = cam.x + float64(gamemap.TileWidth) + cam.width
    186 	var top float64 = cam.y - float64(gamemap.TileHeight)
    187 	var bottom float64 = cam.y + float64(gamemap.TileHeight) + cam.height
    188 
    189 	return t.x >= left && t.x < right && t.y >= top && t.y < bottom
    190 
    191 }