155 lines
3.4 KiB
Go
155 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"image/png"
|
|
"math"
|
|
"os"
|
|
"time"
|
|
|
|
"golang.org/x/image/colornames"
|
|
|
|
"github.com/faiface/pixel"
|
|
"github.com/faiface/pixel/pixelgl"
|
|
"github.com/salviati/go-tmx/tmx"
|
|
)
|
|
|
|
var clearColor = colornames.Skyblue
|
|
|
|
func gameloop(win *pixelgl.Window, tilemap *tmx.Map) {
|
|
batches := make(map[string]*pixel.Batch)
|
|
|
|
// Load the sprites
|
|
sprites := make(map[string]*pixel.Sprite)
|
|
for _, tileset := range tilemap.Tilesets {
|
|
if _, alreadyLoaded := sprites[tileset.Image.Source]; !alreadyLoaded {
|
|
sprite, pictureData := loadSprite(tileset.Image.Source)
|
|
sprites[tileset.Image.Source] = sprite
|
|
batches[tileset.Image.Source] = pixel.NewBatch(&pixel.TrianglesData{}, pictureData)
|
|
}
|
|
}
|
|
|
|
var (
|
|
camPos = pixel.ZV
|
|
camSpeed = 1000.0
|
|
camZoom = 0.2
|
|
camZoomSpeed = 1.2
|
|
)
|
|
|
|
last := time.Now()
|
|
for !win.Closed() {
|
|
dt := time.Since(last).Seconds()
|
|
last = time.Now()
|
|
|
|
// Camera movement
|
|
cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
|
|
win.SetMatrix(cam)
|
|
if win.Pressed(pixelgl.KeyLeft) {
|
|
camPos.X -= camSpeed * dt
|
|
}
|
|
if win.Pressed(pixelgl.KeyRight) {
|
|
camPos.X += camSpeed * dt
|
|
}
|
|
if win.Pressed(pixelgl.KeyDown) {
|
|
camPos.Y -= camSpeed * dt
|
|
}
|
|
if win.Pressed(pixelgl.KeyUp) {
|
|
camPos.Y += camSpeed * dt
|
|
}
|
|
camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
|
|
|
|
win.Clear(clearColor)
|
|
|
|
// Draw tiles
|
|
for _, batch := range batches {
|
|
batch.Clear()
|
|
}
|
|
|
|
for _, layer := range tilemap.Layers {
|
|
for tileIndex, tile := range layer.DecodedTiles {
|
|
ts := layer.Tileset
|
|
tID := int(tile.ID)
|
|
|
|
if tID == 0 {
|
|
// Tile ID 0 means blank, skip it.
|
|
continue
|
|
}
|
|
|
|
// Calculate the framing for the tile within its tileset's source image
|
|
numRows := ts.Tilecount / ts.Columns
|
|
x, y := tileIDToCoord(tID, ts.Columns, numRows)
|
|
gamePos := indexToGamePos(tileIndex, tilemap.Width, tilemap.Height)
|
|
|
|
iX := float64(x) * float64(ts.TileWidth)
|
|
fX := iX + float64(ts.TileWidth)
|
|
iY := float64(y) * float64(ts.TileHeight)
|
|
fY := iY + float64(ts.TileHeight)
|
|
|
|
sprite := sprites[ts.Image.Source]
|
|
sprite.Set(sprite.Picture(), pixel.R(iX, iY, fX, fY))
|
|
pos := gamePos.ScaledXY(pixel.V(float64(ts.TileWidth), float64(ts.TileHeight)))
|
|
sprite.Draw(batches[ts.Image.Source], pixel.IM.Moved(pos))
|
|
}
|
|
}
|
|
|
|
for _, batch := range batches {
|
|
batch.Draw(win)
|
|
}
|
|
win.Update()
|
|
}
|
|
}
|
|
|
|
func tileIDToCoord(tID int, numColumns int, numRows int) (x int, y int) {
|
|
x = tID % numColumns
|
|
y = numRows - (tID / numColumns) - 1
|
|
return
|
|
}
|
|
|
|
func indexToGamePos(idx int, width int, height int) pixel.Vec {
|
|
gamePos := pixel.V(
|
|
float64(idx%width)-1,
|
|
float64(height)-float64(idx/width),
|
|
)
|
|
return gamePos
|
|
}
|
|
|
|
func run() {
|
|
// Create the window with OpenGL
|
|
cfg := pixelgl.WindowConfig{
|
|
Title: "Pixel Tilemaps",
|
|
Bounds: pixel.R(0, 0, 800, 600),
|
|
VSync: true,
|
|
}
|
|
|
|
win, err := pixelgl.NewWindow(cfg)
|
|
panicIfErr(err)
|
|
|
|
// Initialize art assets (i.e. the tilemap)
|
|
tilemap, err := tmx.ReadFile("gameart2d-desert.tmx")
|
|
panicIfErr(err)
|
|
|
|
fmt.Println("use WASD to move camera around")
|
|
gameloop(win, tilemap)
|
|
}
|
|
|
|
func loadSprite(path string) (*pixel.Sprite, *pixel.PictureData) {
|
|
f, err := os.Open(path)
|
|
panicIfErr(err)
|
|
|
|
img, err := png.Decode(f)
|
|
panicIfErr(err)
|
|
|
|
pd := pixel.PictureDataFromImage(img)
|
|
return pixel.NewSprite(pd, pd.Bounds()), pd
|
|
}
|
|
|
|
func main() {
|
|
pixelgl.Run(run)
|
|
}
|
|
|
|
func panicIfErr(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|