-
-
Save dgryski/61763e2ee58d3446b25bbe00b44f974e to your computer and use it in GitHub Desktop.
Port of the FastGame Java impl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a port of the FAST Java version of | |
// https://jackmott.github.io/programming/2016/09/01/performance-in-the-large.html | |
// Original Go translation of naive version from https://gist.github.com/magiconair/68a524fc847ba2860893a799f637f532 | |
// | |
// Code is not pretty!!! ;) | |
// | |
// | |
package main | |
import ( | |
"fmt" | |
"math" | |
"strconv" | |
"time" | |
) | |
type Vector struct { | |
x, y, z float64 | |
} | |
func Mult(a, b Vector) Vector { | |
return Vector{a.x * b.x, a.y * b.y, a.z * b.z} | |
} | |
func Add(a, b Vector) Vector { | |
return Vector{a.x + b.x, a.y + b.y, a.z + b.z} | |
} | |
func Sub(a, b Vector) Vector { | |
return Vector{a.x - b.x, a.y - b.y, a.z - b.z} | |
} | |
func Dist(a, b Vector) float64 { | |
v := Sub(a, b) | |
return math.Sqrt(v.x*v.x + v.y*v.y + v.z*v.z) | |
} | |
type Block struct { | |
location Vector // x,y,z within the chunk | |
name string | |
durability int | |
textureid int | |
breakable bool | |
visible bool | |
typ int | |
} | |
type Type int | |
const ( | |
Zombie Type = iota | |
Chicken | |
Exploder | |
TallCreepyThing | |
) | |
type Entity struct { | |
location Vector | |
name string | |
health int | |
speed Vector | |
typ Type | |
} | |
func NewEntity(location Vector, typ Type) *Entity { | |
e := &Entity{ | |
location: location, | |
typ: typ, | |
} | |
switch typ { | |
case Zombie: | |
e.name = "Zombie" | |
e.health = 50 | |
e.speed = Vector{0.5, 0.0, 0.5} //slow, can't fly | |
case Chicken: | |
e.name = "Chicken" | |
e.health = 25 | |
e.speed = Vector{0.75, 0.25, 0.75} //can fly a bit | |
case Exploder: | |
e.name = "Exploder" | |
e.health = 75 | |
e.speed = Vector{0.75, 0.0, 0.75} | |
case TallCreepyThing: | |
e.name = "Tall Creepy Thing" | |
e.health = 500 | |
e.speed = Vector{1.0, 1.0, 1.0} //does what he wants | |
} | |
return e | |
} | |
func (e *Entity) updatePosition() { | |
// Complex movement AI | |
rndUnitIshVector := Vector{1, 1, 1} | |
movementVector := Mult(rndUnitIshVector, e.speed) | |
e.location = Add(movementVector, e.location) | |
} | |
const NUM_BLOCKS = 65536 //just like minecraft! | |
const NUM_ENTITIES = 1000 | |
type blockID byte | |
type Chunk struct { | |
blocks []blockID | |
entities []*Entity | |
location Vector // x,y,z within world | |
} | |
func NewChunk(location Vector) *Chunk { | |
c := &Chunk{location: location} | |
// Preallocate the growable List because we are clever! | |
c.blocks = make([]blockID, NUM_BLOCKS) | |
for i := range c.blocks { | |
c.blocks[i] = blockID(i) | |
} | |
c.entities = make([]*Entity, 0, NUM_ENTITIES) | |
for i := 0; i < NUM_ENTITIES/4; i++ { | |
// Fancy proc gen initial position equation | |
f := float64(i) | |
c.entities = append(c.entities, NewEntity(Vector{f, f, f}, Chicken)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 2, f, f}, Zombie)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 3, f, f}, Exploder)) | |
c.entities = append(c.entities, NewEntity(Vector{f + 4, f, f}, TallCreepyThing)) | |
} | |
return c | |
} | |
func (c *Chunk) processEntities() { | |
for _, e := range c.entities { | |
e.updatePosition() | |
} | |
} | |
const CHUNK_COUNT = 100 | |
type Game struct { | |
chunks []*Chunk | |
playerLocation Vector | |
chunkCounter int | |
blocks []Block | |
} | |
func (g *Game) loadWorld() { | |
g.chunks = make([]*Chunk, CHUNK_COUNT) | |
for i := range g.chunks { | |
g.chunks[i] = NewChunk(Vector{float64(g.chunkCounter), 0, 0}) | |
g.chunkCounter++ | |
} | |
g.blocks = make([]Block, NUM_BLOCKS) | |
for i := range g.blocks { | |
g.blocks[i] = Block{ | |
location: Vector{float64(i), float64(i), float64(i)}, | |
name: "Block:" + strconv.Itoa(i), | |
durability: 100, | |
textureid: 1, | |
breakable: true, | |
visible: true, | |
typ: 1, | |
} | |
} | |
} | |
func (g *Game) updateChunks() { | |
var toRemove []int | |
for i, c := range g.chunks { | |
c.processEntities() | |
chunkDistance := Dist(c.location, g.playerLocation) | |
if chunkDistance > CHUNK_COUNT { | |
toRemove = append(toRemove, i) | |
} | |
} | |
for _, v := range toRemove { | |
g.chunks[v] = NewChunk(Vector{float64(g.chunkCounter), 0, 0}) | |
g.chunkCounter++ | |
} | |
} | |
func main() { | |
game := &Game{} | |
fmt.Println("Loading World...") | |
start := time.Now() | |
game.loadWorld() | |
loadWorldTime := time.Since(start) | |
fmt.Println("FINISHED!") | |
fmt.Printf("Load Time: %s\n", loadWorldTime) | |
// Game Loop, you can never leave | |
var frames int | |
for { | |
// check for dead entities | |
start = time.Now() | |
// mocking polling of the VR controller | |
playerMovement := Vector{0.1, 0, 0} | |
game.playerLocation = Add(game.playerLocation, playerMovement) | |
game.updateChunks() | |
t := time.Since(start) | |
fmt.Println(frames, t) | |
// Lock it at 60FPS | |
if t < 16*time.Millisecond { | |
time.Sleep(16*time.Millisecond - t) | |
} | |
frames++ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment