diff --git a/community/isometric-basics/README.md b/community/isometric-basics/README.md new file mode 100644 index 0000000..77625bd --- /dev/null +++ b/community/isometric-basics/README.md @@ -0,0 +1,11 @@ +# Isometric view basics + +Created by [Sergio Vera](https://github.com/svera). + +Isometric view is a display method used to create an illusion of 3D for an otherwise 2D game - sometimes referred to as pseudo 3D or 2.5D. + +Implementing an isometric view can be done in many ways, but for the sake of simplicity I'll focus on a tile-based approach, which is the most efficient and widely used method. + +In the tile-based approach, each visual element is broken down into smaller pieces, called tiles, of a standard size. These tiles will be arranged to form the game world according to pre-determined level data - usually a 2D array. + +For a detailed explanation about the maths behind this, read [http://clintbellanger.net/articles/isometric_math/](http://clintbellanger.net/articles/isometric_math/). diff --git a/community/isometric-basics/castle.png b/community/isometric-basics/castle.png new file mode 100644 index 0000000..190fd9e Binary files /dev/null and b/community/isometric-basics/castle.png differ diff --git a/community/isometric-basics/main.go b/community/isometric-basics/main.go new file mode 100644 index 0000000..246f9e4 --- /dev/null +++ b/community/isometric-basics/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "image" + "os" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" + + _ "image/png" +) + +const ( + windowWidth = 800 + windowHeight = 800 + // sprite tiles are squared, 64x64 size + tileSize = 64 + f = 0 // floor identifier + w = 1 // wall identifier +) + +var levelData = [][]uint{ + {f, f, f, f, f, f}, // This row will be rendered in the lower left part of the screen (closer to the viewer) + {w, f, f, f, f, w}, + {w, f, f, f, f, w}, + {w, f, f, f, f, w}, + {w, f, f, f, f, w}, + {w, w, w, w, w, w}, // And this in the upper right +} +var win *pixelgl.Window +var offset = pixel.V(400, 325) +var floorTile, wallTile *pixel.Sprite + +func loadPicture(path string) (pixel.Picture, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + return pixel.PictureDataFromImage(img), nil +} + +func run() { + var err error + + cfg := pixelgl.WindowConfig{ + Title: "Isometric demo", + Bounds: pixel.R(0, 0, windowWidth, windowHeight), + VSync: true, + } + win, err = pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + pic, err := loadPicture("castle.png") + if err != nil { + panic(err) + } + + wallTile = pixel.NewSprite(pic, pixel.R(0, 448, tileSize, 512)) + floorTile = pixel.NewSprite(pic, pixel.R(0, 128, tileSize, 192)) + + depthSort() + + for !win.Closed() { + win.Update() + } +} + +// Draw level data tiles to window, from farthest to closest. +// In order to achieve the depth effect, we need to render tiles up to down, being lower +// closer to the viewer (see painter's algorithm). To do that, we need to process levelData in reverse order, +// so its first row is rendered last, as OpenGL considers its origin to be in the lower left corner of the display. +func depthSort() { + for x := len(levelData) - 1; x >= 0; x-- { + for y := len(levelData[x]) - 1; y >= 0; y-- { + isoCoords := cartesianToIso(pixel.V(float64(x), float64(y))) + mat := pixel.IM.Moved(offset.Add(isoCoords)) + // Not really needed, just put to show bigger blocks + mat = mat.ScaledXY(win.Bounds().Center(), pixel.V(2, 2)) + tileType := levelData[x][y] + if tileType == f { + floorTile.Draw(win, mat) + } else { + wallTile.Draw(win, mat) + } + } + } +} + +func cartesianToIso(pt pixel.Vec) pixel.Vec { + return pixel.V((pt.X-pt.Y)*(tileSize/2), (pt.X+pt.Y)*(tileSize/4)) +} + +func main() { + pixelgl.Run(run) +}