garbage-day/game.go

350 lines
8.2 KiB
Go

package main
import (
"fmt"
"math"
"math/rand"
"os"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/solarlune/tetra3d"
)
type Game struct {
Width, Height int
Scene *tetra3d.Scene
Camera *tetra3d.Camera
truck *garbageTruck
wasm bool
debug int
}
func NewGame() *Game {
game := &Game{
Width: 796,
Height: 448,
}
game.Init()
return game
}
func (g *Game) Init() {
g.Scene = tetra3d.NewScene("")
light := tetra3d.NewDirectionalLight("", 1, 1, 1, 1.0)
light.Move(0, 14, 0)
light.Rotate(1, 0, 0, -math.Pi/2)
g.Scene.Root.AddChildren(light)
const (
brightnessEastWest = 0.8
brightnessNorthSouth = 1.0
)
{
// West.
l := tetra3d.NewDirectionalLight("", 1, 1, 1, brightnessEastWest)
l.Move(0, 14, 0)
l.Rotate(1, 0, 0, -math.Pi/2)
l.Rotate(0, 1, 0, -math.Pi/2)
g.Scene.Root.AddChildren(l)
}
{
// East.
l := tetra3d.NewDirectionalLight("", 1, 1, 1, brightnessEastWest)
l.Move(0, 14, 0)
l.Rotate(1, 0, 0, -math.Pi/2)
l.Rotate(0, 1, 0, math.Pi/2)
g.Scene.Root.AddChildren(l)
}
{
// North.
l := tetra3d.NewDirectionalLight("", 1, 1, 1, brightnessNorthSouth)
l.Move(0, 14, 0)
l.Rotate(1, 0, 0, -math.Pi/2)
l.Rotate(1, 0, 0, math.Pi/2)
g.Scene.Root.AddChildren(l)
}
{
// South.
l := tetra3d.NewDirectionalLight("", 1, 1, 1, brightnessNorthSouth)
l.Move(0, 14, 0)
l.Rotate(1, 0, 0, -math.Pi/2)
l.Rotate(1, 0, 0, -math.Pi/2)
g.Scene.Root.AddChildren(l)
}
// Create road.
{
const planeSize = 10000
plane := tetra3d.NewModel(tetra3d.NewPlaneMesh(2, 2), "plane")
plane.Color.Set(40.0/255.0, 40.0/255.0, 40.0/255.0, 1)
plane.Grow(planeSize, 0, planeSize)
g.Scene.Root.AddChildren(plane)
}
// Create garbage truck.
g.truck = &garbageTruck{
x: 4,
y: 8,
}
{
mesh := tetra3d.NewCubeMesh()
model := tetra3d.NewModel(mesh, "")
model.Color = tetra3d.NewColor(0.12, 0.44, 0.24, 1)
model.Grow(-0.75, -0.25, -0.3)
g.truck.Model = model
g.Scene.Root.AddChildren(g.truck.Model)
}
const (
lightX = 0.55
headlightDistance = 0.85
headlightShade = float32(0.75)
)
// Headlights.
{
model := tetra3d.NewModel(tetra3d.NewCubeMesh(), "")
model.Color = tetra3d.NewColor(headlightShade, headlightShade, headlightShade, 1)
model.Grow(-0.75, -0.75, -0.75)
model.Move(lightX, 0, headlightDistance)
g.truck.Model.AddChildren(model)
}
{
model := tetra3d.NewModel(tetra3d.NewCubeMesh(), "")
model.Color = tetra3d.NewColor(headlightShade, headlightShade, headlightShade, 1)
model.Grow(-0.75, -0.75, -0.75)
model.Move(-lightX, 0, headlightDistance)
g.truck.Model.AddChildren(model)
}
// Brake lights.
{
model := tetra3d.NewModel(tetra3d.NewCubeMesh(), "")
model.Color = tetra3d.NewColor(0.6, 0, 0, 1)
model.Grow(-0.75, -0.75, -0.75)
model.Move(lightX, 0, -0.85)
g.truck.Model.AddChildren(model)
}
{
model := tetra3d.NewModel(tetra3d.NewCubeMesh(), "")
model.Color = tetra3d.NewColor(0.6, 0, 0, 1)
model.Grow(-0.75, -0.75, -0.75)
model.Move(-lightX, 0, -0.85)
g.truck.Model.AddChildren(model)
}
// Create camera.
g.Camera = tetra3d.NewCamera(g.Width, g.Height)
g.Camera.Move(0, 12, 0)
g.Camera.Rotate(1, 0, 0, -math.Pi/2)
g.Scene.Root.AddChildren(g.Camera)
// Generate a random maze.
const (
mazeW, mazeH = 13, 11
)
maze := newMaze(mazeW, mazeH)
// TODO Place behind a build constant.
for y := 0; y < mazeH; y++ {
for x := 0; x < mazeW; x++ {
if maze[y][x] {
fmt.Print("..")
} else {
fmt.Print(" ")
}
}
fmt.Print("\n")
}
// Add walls.
wallAt := func(x, y int) bool {
if x < 0 || y < 0 || x > mazeW-1 || y > mazeH-1 {
return true
}
return maze[y][x]
}
buildingScale := 2.5
buildingSize := 7.0
addPlane := func(x float64, y float64, z float64, colorR float32, colorG float32, colorB float32) *tetra3d.Model {
if colorR == -1 {
for {
colorR, colorG, colorB = float32(rand.Intn(256))/255.0, float32(rand.Intn(256))/255.0, float32(rand.Intn(256))/255.0
if colorR >= 0.5 || colorG >= 0.5 || colorB >= 0.5 {
break
}
}
}
p := tetra3d.NewPlaneMesh(4, 4)
plane := tetra3d.NewModel(p, "")
plane.Color.Set(colorR, colorG, colorB, 1)
plane.Move(x, y, z)
g.Scene.Root.AddChildren(plane)
return plane
}
wallColors := make([][][]float32, mazeH)
for y := range wallColors {
wallColors[y] = make([][]float32, mazeW)
}
wallColor := func(x, y int) (colorR, colorG, colorB float32) {
// Check for existing wall color.
colors := wallColors[y][x]
if len(colors) == 3 {
return colors[0], colors[1], colors[2]
}
// Generate new wall color.
r := rand.New(rand.NewSource(int64(y*mazeW + x)))
for {
colorR, colorG, colorB = float32(r.Intn(256))/255.0, float32(r.Intn(256))/255.0, float32(r.Intn(256))/255.0
if colorR >= 0.5 || colorG >= 0.5 || colorB >= 0.5 {
wallColors[y][x] = []float32{colorR, colorG, colorB}
return
}
}
}
addWallWest := func(x, y int) {
colorR, colorG, colorB := wallColor(x, y)
plane := addPlane(float64(x)*buildingSize, 0, float64(y)*buildingSize, colorR, colorG, colorB)
plane.Grow(buildingScale*10, 0, buildingScale*2.4)
plane.Rotate(0, 0, 1, -math.Pi/2)
}
addWallEast := func(x, y int) {
colorR, colorG, colorB := wallColor(x, y)
x-- // Adjust position.
plane := addPlane(float64(x)*buildingSize, 0, float64(y)*buildingSize, colorR, colorG, colorB)
plane.Grow(buildingScale*10, 0, buildingScale*2.4)
plane.Rotate(0, 0, 1, math.Pi/2)
}
addWallNorth := func(x, y int) {
colorR, colorG, colorB := wallColor(x, y)
plane := addPlane(float64(x)*buildingSize, 0, float64(y)*buildingSize, colorR, colorG, colorB)
plane.Grow(buildingScale*2.4, 0, buildingScale*10)
plane.Rotate(1, 0, 0, math.Pi/2)
plane.Move(-buildingSize/2, 0, buildingSize/2)
}
addWallSouth := func(x, y int) {
colorR, colorG, colorB := wallColor(x, y)
y-- // Adjust position.
plane := addPlane(float64(x)*buildingSize, 0, float64(y)*buildingSize, colorR, colorG, colorB)
plane.Grow(buildingScale*2.4, 0, buildingScale*10)
plane.Rotate(1, 0, 0, -math.Pi/2)
plane.Move(-buildingSize/2, 0, buildingSize/2)
}
for y := range maze {
for x := range maze[y] {
if wallAt(x, y) {
continue
}
if wallAt(x-1, y) {
addWallWest(x-1, y)
}
if wallAt(x+1, y) {
addWallEast(x+1, y)
}
if wallAt(x, y-1) {
addWallNorth(x, y-1)
}
if wallAt(x, y+1) {
addWallSouth(x, y+1)
}
}
}
}
func (g *Game) Update() error {
if ebiten.IsWindowBeingClosed() {
os.Exit(0)
return nil
}
if !g.wasm && ebiten.IsKeyPressed(ebiten.KeyEscape) {
os.Exit(0)
return nil
}
if inpututil.IsKeyJustPressed(ebiten.KeyV) && ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyShift) {
g.debug++
if g.debug > 1 {
g.debug = 0
}
}
const moveSize = 1
moveX, moveY := 0, 0
if ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
moveX -= moveSize
}
if ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
moveX += moveSize
}
if ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
moveY -= moveSize
}
if ebiten.IsKeyPressed(ebiten.KeyDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
moveY += moveSize
}
braking := ebiten.IsKeyPressed(ebiten.KeySpace)
// Update truck.
t := g.truck
t.MoveX, t.MoveY, t.Braking = moveX, moveY, braking
t.Update()
// Move truck model and camera.
g.truck.Model.SetLocalPosition(t.x, g.truck.Model.LocalPosition().Y, t.y)
g.truck.Model.SetLocalRotation(tetra3d.NewMatrix4().Rotated(0, 1, 0, -t.angle+math.Pi))
g.Camera.SetLocalPosition(t.x, g.Camera.LocalPosition().Y, t.y)
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
// Clear the Camera.
g.Camera.Clear()
// Render the scene.
g.Camera.RenderScene(g.Scene)
// Draw the resulting color texture.
screen.DrawImage(g.Camera.ColorTexture(), nil)
if g.debug == 0 {
return
}
// Print twice to increase shadow.
for i := 0; i < 2; i++ {
pos := g.Camera.LocalPosition()
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("FPS %.0f\nTPS %.0f\nX %.2f\nY %.2f \nZ %.2f", ebiten.ActualFPS(), ebiten.ActualTPS(), pos.X, pos.Y, pos.Z), 1, 0)
}
}
func (g *Game) Layout(_, _ int) (int, int) {
return g.Width, g.Height
}