Skip to content

Instantly share code, notes, and snippets.

@gabrielkw
Last active July 25, 2018 01:37
Show Gist options
  • Save gabrielkw/e4bc2507cad8ce6fb3f85f07a4fdc7c5 to your computer and use it in GitHub Desktop.
Save gabrielkw/e4bc2507cad8ce6fb3f85f07a4fdc7c5 to your computer and use it in GitHub Desktop.
SDL2 raycasting in golang
package main
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
"math"
"os"
)
var winTitle string = "Go-SDL2 Render"
var winWidth, winHeight int = 640, 480
type Camera struct {
X, Y, Angle, Fov float64
}
type Level struct {
Height, Width, TileWidth, TileHeight int
Tiles [][]rune
}
// method to a level that casts a ray from a position and returns where the ray hits
func (targetLevel *Level) CastRay(x, y, Angle float64) (int, int) {
for targetLevel.Tiles[int(y)/targetLevel.TileHeight][int(x)/targetLevel.TileWidth] == '0' {
x += math.Cos(Angle)
y += math.Sin(Angle)
}
return int(x), int(y)
}
// method to a level that returns the side (north/south or lest/west) of a tile a point is
func (targetLevel *Level) GetTileSide(x, y int) (side string) {
dx := float64(targetLevel.TileWidth/2-x%targetLevel.TileWidth) / float64(targetLevel.TileWidth)
dy := float64(targetLevel.TileHeight/2-y%targetLevel.TileHeight) / float64(targetLevel.TileHeight)
if math.Abs(float64(dx)) >= math.Abs(float64(dy)) {
return "LW"
} else {
return "NS"
}
}
func CreateLevel(tiles [][]rune) (newLevel *Level) {
newLevel = new(Level)
newLevel.Tiles = tiles
newLevel.Height = len(tiles)
newLevel.Width = len(tiles[0])
newLevel.TileWidth = winWidth / newLevel.Width
newLevel.TileHeight = winHeight / newLevel.Height
fmt.Println("Creating level...", "height:", newLevel.Height, "width:", newLevel.Width, "tilewidth:", newLevel.TileWidth, "tileheight:", newLevel.TileHeight)
return newLevel
}
func run() int {
var window *sdl.Window
var renderer *sdl.Renderer
var event sdl.Event
var rect sdl.Rect
var err error
aux := 64.0
level1 := CreateLevel([][]rune{
{'1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '3', '0', '0', '0', '0', '0', '0', '0', '2', '2', '0', '0', '0', '1'},
{'1', '0', '3', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '1', '1', '1', '0', '0', '0', '0', '0', '0', '2', '2', '0', '0', '0', '1'},
{'1', '0', '0', '1', '0', '0', '0', '0', '0', '0', '2', '2', '0', '0', '0', '1'},
{'1', '0', '1', '1', '0', '0', '0', '0', '0', '0', '2', '2', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '4', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '0', '0', '0', '0', '0', '0', '4', '0', '0', '0', '0', '0', '0', '0', '1'},
{'1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1'},
})
ActiveLevel := level1
ActiveCamera := Camera{200, 200, 0, 1.0472}
// create a window
window, err = sdl.CreateWindow(winTitle, sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, winWidth, winHeight, sdl.WINDOW_SHOWN)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create window: %s\n", err)
return 1
}
defer window.Destroy()
// create a renderer
renderer, err = sdl.CreateRenderer(window, 1, sdl.RENDERER_ACCELERATED)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create renderer: %s\n", err)
return 2
}
defer renderer.Destroy()
// this variable dictates what should be drawn in the screen. 1 draws a top-down map and 2 draws the perspective of the camera
renderType := 2
// main loop
running := true
for running {
// get key presses
for event = sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch t := event.(type) {
case *sdl.QuitEvent:
running = false
case *sdl.KeyDownEvent:
/* Key presses debug
fmt.Printf("[%d ms] Keyboard\ttype:%d\tsym:%c\tmodifiers:%d\tstate:%d\trepeat:%d\n",
t.Timestamp, t.Type, t.Keysym.Sym, t.Keysym.Mod, t.State, t.Repeat)/**/
switch t.Keysym.Sym {
case 'a': // Turn left
ActiveCamera.Angle -= 0.1
if ActiveCamera.Angle < 0 {
ActiveCamera.Angle = 2 * math.Pi
}
case 'd': // Turn right
ActiveCamera.Angle += 0.1
if ActiveCamera.Angle > 2*math.Pi {
ActiveCamera.Angle = 0
}
case 'w': // Move forwards
ActiveCamera.X += math.Cos(ActiveCamera.Angle) * 5
ActiveCamera.Y += math.Sin(ActiveCamera.Angle) * 5
case 's': // Move backwards
ActiveCamera.X -= math.Cos(ActiveCamera.Angle) * 5
ActiveCamera.Y -= math.Sin(ActiveCamera.Angle) * 5
case 'p': // Increase field of view
ActiveCamera.Fov += 0.1
case 'o': // Decrease field of view
ActiveCamera.Fov -= 0.1
case 'r': // Toggle rendering type (map/perspective)
if renderType == 1 {
renderType = 2
} else {
renderType = 1
}
case 'm': // Increase aux modifier (debug)
aux += 1.0
case 'n': // Decrease aux modifier (debug)
aux -= 1.0
}
}
}
// render stuff
// start by painting the whole screen black
renderer.SetDrawColor(0, 0, 0, 255)
renderer.Clear()
switch renderType {
case 1:
// render map
rect = sdl.Rect{0, 0, int32(winWidth), int32(winHeight)}
renderer.SetDrawColor(179, 108, 57, 255)
renderer.FillRect(&rect)
for i := 0; i < ActiveLevel.Width; i++ {
for j := 0; j < ActiveLevel.Height; j++ {
if ActiveLevel.Tiles[j][i] != '0' {
rect = sdl.Rect{int32(i * ActiveLevel.TileWidth), int32(j * ActiveLevel.TileHeight), int32(ActiveLevel.TileWidth), int32(ActiveLevel.TileHeight)}
switch ActiveLevel.Tiles[j][i] {
case '1':
renderer.SetDrawColor(179, 57, 57, 255)
case '2':
renderer.SetDrawColor(45, 136, 45, 255)
case '3':
renderer.SetDrawColor(34, 102, 102, 255)
case '4':
renderer.SetDrawColor(179, 57, 179, 255)
}
renderer.FillRect(&rect)
}
}
}
// render rays
for i := -(ActiveCamera.Fov / 2); i < ActiveCamera.Fov/2; i += 0.01 {
targetX, targetY := ActiveLevel.CastRay(ActiveCamera.X, ActiveCamera.Y, ActiveCamera.Angle+i)
switch ActiveLevel.Tiles[targetY/ActiveLevel.TileHeight][targetX/ActiveLevel.TileWidth] {
case '1':
renderer.SetDrawColor(179, 57, 57, 255)
case '2':
renderer.SetDrawColor(45, 136, 45, 255)
case '3':
renderer.SetDrawColor(34, 102, 102, 255)
case '4':
renderer.SetDrawColor(179, 57, 179, 255)
}
if ActiveLevel.GetTileSide(targetX, targetY) == "NS" {
r, g, b, a, err := renderer.GetDrawColor()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create window: %s\n", err)
return 1
}
r -= 20
g -= 20
b -= 20
renderer.SetDrawColor(r, g, b, a)
}
renderer.DrawLine(int(ActiveCamera.X), int(ActiveCamera.Y), targetX, targetY)
}
// render camera object on map
rect = sdl.Rect{int32(ActiveCamera.X - 10), int32(ActiveCamera.Y - 10), 20, 20}
renderer.SetDrawColor(255, 255, 255, 255)
renderer.FillRect(&rect)
case 2:
rect = sdl.Rect{0, int32(winHeight / 2), int32(winWidth), int32(winHeight / 2)}
renderer.SetDrawColor(179, 108, 57, 255)
renderer.FillRect(&rect)
j := 0
for i := -(ActiveCamera.Fov / 2); i < ActiveCamera.Fov/2; i += (ActiveCamera.Fov / float64(winWidth)) * math.Cos(i) {
targetX, targetY := ActiveLevel.CastRay(ActiveCamera.X, ActiveCamera.Y, ActiveCamera.Angle+i)
switch ActiveLevel.Tiles[targetY/ActiveLevel.TileHeight][targetX/ActiveLevel.TileWidth] {
case '1':
renderer.SetDrawColor(179, 57, 57, 255)
case '2':
renderer.SetDrawColor(45, 136, 45, 255)
case '3':
renderer.SetDrawColor(34, 102, 102, 255)
case '4':
renderer.SetDrawColor(179, 57, 179, 255)
}
if ActiveLevel.GetTileSide(targetX, targetY) == "NS" {
r, g, b, a, err := renderer.GetDrawColor()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create window: %s\n", err)
return 1
}
r -= 20
g -= 20
b -= 20
renderer.SetDrawColor(r, g, b, a)
}
distance := math.Sqrt(math.Pow(ActiveCamera.X-float64(targetX), 2) + math.Pow(ActiveCamera.Y-float64(targetY), 2))
z := distance * math.Cos(i)
lineheight := float64(winHeight) / z * 64
renderer.DrawLine(j, winHeight/2-int(lineheight), j, winHeight/2+int(lineheight))
j++
}
}
// draw rendering to the screen
renderer.Present()
}
return 0
}
func main() {
os.Exit(run())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment