Skip to content

Instantly share code, notes, and snippets.

@npocmaka
Created August 27, 2024 07:24
Show Gist options
  • Save npocmaka/e87d5c90f692369ed15c03a299a3e92d to your computer and use it in GitHub Desktop.
Save npocmaka/e87d5c90f692369ed15c03a299a3e92d to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"image/color"
"log"
"math"
)
const (
screenWidth = 640
screenHeight = 480
mapWidth = 24
mapHeight = 24
)
var worldMap = [mapWidth][mapHeight]int{
{1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 0, 0, 0, 0, 5, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 0, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
}
type Game struct {
posX, posY float64
dirX, dirY float64
planeX, planeY float64
}
func NewGame() *Game {
return &Game{
posX: 22,
posY: 12,
dirX: -1,
dirY: 0,
planeX: 0,
planeY: 0.66,
}
}
func (g *Game) Update() error {
frameTime := 1.0 / 60.0 // Assuming 60 FPS
moveSpeed := frameTime * 5.0
rotSpeed := frameTime * 3.0
if ebiten.IsKeyPressed(ebiten.KeyW) {
if worldMap[int(g.posX+g.dirX*moveSpeed)][int(g.posY)] == 0 {
g.posX += g.dirX * moveSpeed
}
if worldMap[int(g.posX)][int(g.posY+g.dirY*moveSpeed)] == 0 {
g.posY += g.dirY * moveSpeed
}
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
if worldMap[int(g.posX-g.dirX*moveSpeed)][int(g.posY)] == 0 {
g.posX -= g.dirX * moveSpeed
}
if worldMap[int(g.posX)][int(g.posY-g.dirY*moveSpeed)] == 0 {
g.posY -= g.dirY * moveSpeed
}
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
oldDirX := g.dirX
g.dirX = g.dirX*math.Cos(-rotSpeed) - g.dirY*math.Sin(-rotSpeed)
g.dirY = oldDirX*math.Sin(-rotSpeed) + g.dirY*math.Cos(-rotSpeed)
oldPlaneX := g.planeX
g.planeX = g.planeX*math.Cos(-rotSpeed) - g.planeY*math.Sin(-rotSpeed)
g.planeY = oldPlaneX*math.Sin(-rotSpeed) + g.planeY*math.Cos(-rotSpeed)
}
if ebiten.IsKeyPressed(ebiten.KeyA) {
oldDirX := g.dirX
g.dirX = g.dirX*math.Cos(rotSpeed) - g.dirY*math.Sin(rotSpeed)
g.dirY = oldDirX*math.Sin(rotSpeed) + g.dirY*math.Cos(rotSpeed)
oldPlaneX := g.planeX
g.planeX = g.planeX*math.Cos(rotSpeed) - g.planeY*math.Sin(rotSpeed)
g.planeY = oldPlaneX*math.Sin(rotSpeed) + g.planeY*math.Cos(rotSpeed)
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.RGBA{0, 0, 0, 255})
for x := 0; x < screenWidth; x++ {
cameraX := 2*float64(x)/float64(screenWidth) - 1
rayDirX := g.dirX + g.planeX*cameraX
rayDirY := g.dirY + g.planeY*cameraX
mapX := int(g.posX)
mapY := int(g.posY)
var sideDistX, sideDistY float64
deltaDistX := math.Abs(1 / rayDirX)
deltaDistY := math.Abs(1 / rayDirY)
var perpWallDist float64
var stepX, stepY int
hit := 0
var side int
if rayDirX < 0 {
stepX = -1
sideDistX = (g.posX - float64(mapX)) * deltaDistX
} else {
stepX = 1
sideDistX = (float64(mapX) + 1.0 - g.posX) * deltaDistX
}
if rayDirY < 0 {
stepY = -1
sideDistY = (g.posY - float64(mapY)) * deltaDistY
} else {
stepY = 1
sideDistY = (float64(mapY) + 1.0 - g.posY) * deltaDistY
}
for hit == 0 {
if sideDistX < sideDistY {
sideDistX += deltaDistX
mapX += stepX
side = 0
} else {
sideDistY += deltaDistY
mapY += stepY
side = 1
}
if worldMap[mapX][mapY] > 0 {
hit = 1
}
}
if side == 0 {
perpWallDist = sideDistX - deltaDistX
} else {
perpWallDist = sideDistY - deltaDistY
}
lineHeight := int(float64(screenHeight) / perpWallDist)
drawStart := -lineHeight/2 + screenHeight/2
if drawStart < 0 {
drawStart = 0
}
drawEnd := lineHeight/2 + screenHeight/2
if drawEnd >= screenHeight {
drawEnd = screenHeight - 1
}
col := getColor(worldMap[mapX][mapY], side)
ebitenutil.DrawLine(screen, float64(x), float64(drawStart), float64(x), float64(drawEnd), col)
}
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
}
func getColor(value int, side int) color.Color {
var col color.RGBA
switch value {
case 1:
col = color.RGBA{255, 0, 0, 255} // Red
case 2:
col = color.RGBA{0, 255, 0, 255} // Green
case 3:
col = color.RGBA{0, 0, 255, 255} // Blue
case 4:
col = color.RGBA{255, 255, 255, 255} // White
default:
col = color.RGBA{255, 255, 0, 255} // Yellow
}
if side == 1 {
col.R /= 2
col.G /= 2
col.B /= 2
}
return col
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Raycaster in Ebiten")
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment