// CreateGoalEntity creates the game's goal entity and places // it at a good spot in the world. func (s *Server) createGoalEntity() { // calculate a center location centerX := math.Floor(float64((float32(s.levelWidth)/2.0)*float32(landscape.ChunkSize))) + 0.5 centerZ := math.Floor(float64((float32(s.levelHeight)/2.0)*float32(landscape.ChunkSize))) + 0.5 tallest, err := s.landscapeMan.GetHeightAt(int(centerX), int(centerZ)) if err != nil { groggy.Logsf("ERROR", "S: CreateGoalLocation() couldn't figure out the height at X,Z: %f,%f. %v", centerX, centerZ, err) return } // create the entity groggy.Logsf("DEBUG", "S: CreateGoalLocation() will create goal at %f,%f,%f for height of %d.", centerX, float32(tallest)+1.5, centerZ, tallest) playerGoal := s.entityManager.RegisterNewEntity() playerGoal.Name = "PlayerGoal" playerGoal.AssetPath = "goal" playerGoal.Location = mgl.Vec3{float32(centerX), float32(tallest) + 1.5, float32(centerZ)} playerGoal.Collider = physics.NewCollisionCube(nil, physmath.Vector3{0.5, 0.5, 0.5}) goalBody := playerGoal.Collider.GetBody() goalBody.SetInfiniteMass() goalBody.Acceleration = physmath.Vector3{0.0, 0.0, 0.0} playerGoal.SyncToColliders() goalBody.CalculateDerivedData() playerGoal.Collider.CalculateDerivedData() // set the goal for the player s.player.Goal = playerGoal }
// CreateLocalPlayerForServer makes an object to pass to the server // in a local connection so that the server can directly trigger // the callbacks. func (gm *LocalGameManager) CreateLocalPlayerForServer() *server.LocalPlayer { // Make the object itself for the closure. This tactic subverts the problem // of circular references between gamemanager <--> server. localPlayer := server.NewLocalPlayer() localPlayer.OnEntityForcedReceived = func(e *entity.Entity) { groggy.Logsf("DEBUG", "LP: OnEntityForcedReceived: Entity name: %s ; Id: %d", e.Name, e.Id) gm.entityManager.ForceUpdate(e) } // OnPlayerConnected should be called when the player finishes connecting to the server. localPlayer.OnPlayerConnected = func(e *entity.Entity) { groggy.Logsf("DEBUG", "LP: OnPlayerConnected: Player name: %s ; Id: %d", e.Name, e.Id) if e.Collider != nil { groggy.Logsf("DEBUG", "LP: OnPlayerConnected: Player collider position: %v", e.Collider.GetBody().Position) } // set the playerEntity in the game manager for future use gm.playerEntityId = e.Id // store the player entity gm.entityManager.ForceUpdate(e) // now that we've connected, request the game landscape gm.RequestLandscapeReload() } localPlayer.OnLandscapeChunkUpdate = func(c *landscape.Chunk) { groggy.Logsf("DEBUG", "LP: OnLandscapeChunkUpdate: Location: %d, %d, %d", c.X, c.Y, c.Z) gm.landscapeMan.RegisterChunk(c) // update the physics collisions client-side c.UpdateColliders() // pull a render node just to build it in memory _ = c.GetTheRenderable() } localPlayer.OnStartGameplay = func() { groggy.Logsf("DEBUG", "LP: OnStartGameplay") // the game has started so enable phyics gm.physicsEnabled = true gm.physicsLastTime = time.Now() } return localPlayer }
// Destroy tells the chunk to release any special data. func (c *Chunk) Destroy() { groggy.Logsf("DEBUG", "Chunk.Destroy() @ %d,%d,%d", c.X, c.Y, c.Z) // destroy the renderable if there's one made for the landscape node if c.Renderable != nil { c.Renderable.Destroy() } }
// RegisterEntity attempts to register the entity passed in. If an entity // is already found with that Id, the function does nothing and returns false. // Otherwise it updates the entity map and returns true. func (m *Manager) RegisterEntity(e *Entity) bool { _, okay := m.entities[e.Id] if okay == true { groggy.Logsf("DEBUG", "EntityManager.RegisterEntity() attempted to register a duplicate entity (id=%d)", e.Id) return false } m.entities[e.Id] = e return true }
// setupTestAssets will load up the test prototypes in the assets manager func setupTestAssets() { // setup a test cube for the goal goalCube := fizzle.CreateCube("diffuse", -0.5, -0.5, -0.5, 0.5, 0.5, 0.5) goalCube.Core.Shader = gameRenderer.Shaders["diffuse"] goalCube.Core.DiffuseColor = mgl.Vec4{0.95, 0.1, 0.1, 1.0} goalCube.Core.SpecularColor = mgl.Vec4{1.0, 0.0, 0.0, 1.0} goalCube.Core.Shininess = 4.8 loadedAssets["goal"] = goalCube groggy.Logsf("INFO", "Asset loaded: goal (shader %d)", goalCube.Core.Shader.Prog) // setup a test cube for the player playerCube := fizzle.CreateCube("diffuse", -0.45, 0.0, -0.45, 0.45, 1.5, 0.45) playerCube.Core.Shader = gameRenderer.Shaders["diffuse"] playerCube.Core.DiffuseColor = mgl.Vec4{0.1, 0.1, 0.95, 1.0} playerCube.Core.SpecularColor = mgl.Vec4{0.0, 0.0, 1.0, 1.0} playerCube.Core.Shininess = 0.8 loadedAssets["player"] = playerCube groggy.Log("INFO", "Asset loaded: player") }
// fireDebugPrint prints out some information that might be useful to the terminal func fireDebugPrint(delta float32) { playerEnt := gameManager.GetLocalPlayer() body := playerEnt.Collider.GetBody() groggy.Logsf("DEBUG", "Player location: %v (collider position: %v)", playerEnt.Location, body.Position) }
// UpdatePhysics makes sure the physics objects are updated. func (gm *LocalGameManager) UpdatePhysics(frameDelta float32) { if gm.physicsEnabled == false { return } physicsNow := time.Now() physicsDelta := float32(physicsNow.Sub(gm.physicsLastTime).Seconds()) // sync physics to no more than 60 fps if physicsDelta < 1.0/60.0 { return } // conversely, if it's been too long, we just act like nothing happened ... // what's up with that? if physicsDelta > 0.50 { // try again next time to see if we can process the world gm.physicsLastTime = physicsNow groggy.Logsf("INFO", "LGM:UpdatePhycis() had to skip a physics frame due to excessive lag in delta time (%f)", physicsDelta) return } // update the collider in each entity gm.entityManager.Map(func(e *entity.Entity) { if e.Collider != nil { e.RunCollider(physicsDelta) } }) // generate any contacts //groggy.Logsf("DEBUG", "LGM: UpdatePhysics: checking start") var contacts []*physics.Contact gm.entityManager.Map(func(e *entity.Entity) { if e.Collider != nil { // test collisions against other entities // TODO: do better coarse collision detection gm.entityManager.Map(func(otherEnt *entity.Entity) { if e != otherEnt && otherEnt.Collider != nil { //groggy.Logsf("DEBUG", "\tLGM: UpdatePhysics: checking entitys:") //groggy.Logsf("DEBUG", "\t\tentity %d(%s) @ (%v)", e.Id, e.Name, e.Collider.GetTransform()) //groggy.Logsf("DEBUG", "\t\t%v", e.Collider.GetBody()) //groggy.Logsf("DEBUG", "\t\tentity %d(%s) @ (%v)", otherEnt.Id, otherEnt.Name, otherEnt.Collider.GetTransform()) //groggy.Logsf("DEBUG", "\t\t%v", otherEnt.Collider.GetBody()) _, contacts = physics.CheckForCollisions(e.Collider, otherEnt.Collider, contacts) } }) // stop here with the infinite mass, non-movable entities if e.Collider.GetBody().HasFiniteMass() == false { return } // Now check the entity against the landscape chunks // TODO: super lame selections of chunks and very wasteful intX, intZ := int(e.Location[0]), int(e.Location[2]) chunks := gm.landscapeMan.GetChunksFor(intX, intZ) chunks = append(chunks, gm.landscapeMan.GetChunksFor(intX, intZ+1)...) chunks = append(chunks, gm.landscapeMan.GetChunksFor(intX, intZ-1)...) chunks = append(chunks, gm.landscapeMan.GetChunksFor(intX+1, intZ-1)...) chunks = append(chunks, gm.landscapeMan.GetChunksFor(intX-1, intZ-1)...) for _, chunk := range chunks { // check collision against the relevant landscape collisions for _, chunkCollider := range chunk.Colliders { _, contacts = physics.CheckForCollisions(e.Collider, chunkCollider, contacts) } } } }) // resolve the contacts if contacts != nil && len(contacts) > 0 { physics.ResolveContacts(len(contacts)*2, contacts, physmath.Real(physicsDelta)) } gm.physicsLastTime = physicsNow }
// main is the main entry point for the client. func main() { // Setup the log handlers groggy.Register("DEBUG", nil) groggy.Register("INFO", nil) groggy.Register("ERROR", nil) // for now, create a default options structure loadedOptions = NewOptions() // create a window for the game var err error mainWindow, err = createWindow(loadedOptions) if err != nil { groggy.Logsf("ERROR", "Failed to create a window for the game:\n%v", err) os.Exit(1) } // setup the input callbacks on the window setupCallbacks(mainWindow) setupInputModels(mainWindow) // setup the rendering engine gameRenderer, err = createRenderer(mainWindow) if err != nil { groggy.Logsf("ERROR", "Failed to create the game renderer:\n%v", err) os.Exit(1) } // load the assets loadedAssets = make(map[string]*fizzle.Renderable) setupTestAssets() // setup a new game manager // NOTE: for now we hard code it to the local game manager gameManager = gamemanager.NewLocalGameManager() // start the local game on the server gameManager.StartLocalGame(loadedOptions.PlayerName) // main game loop lastFrame := time.Now() for !mainWindow.ShouldClose() { // calculate the difference in time thisFrame := time.Now() frameDelta := float32(thisFrame.Sub(lastFrame).Seconds()) // do time-sensitive inputs inputUpdate(mainWindow, frameDelta) // update physics gameManager.UpdatePhysics(frameDelta) // HACK: update the camera if there's enough change player := gameManager.GetLocalPlayer() camTarget := gameRenderer.camera.GetTarget() deltaCam := camTarget.Sub(player.Location) if deltaCam.Dot(deltaCam) > 0.001 { gameRenderer.camera.SetTarget(player.Location) } // draw the frame gameRenderer.DoRender(frameDelta) // update our last frame time lastFrame = thisFrame } }
// DoRender does the graphical rendering of a single frame. func (gr *GameRenderer) DoRender(delta float32) { // detect any changes to size of the window tempW, tempH := gr.MainWindow.GetFramebufferSize() currentWidth, currentHeight := int32(tempW), int32(tempH) width, height := gr.Renderer.GetResolution() if width == 0 || height == 0 { groggy.Logsf("ERROR", "GameRenderer.DoRender() window size w/h: %d / %d", width, height) } if width != currentWidth || height != currentHeight { width, height = currentWidth, currentHeight gr.Renderer.ChangeResolution(width, height) } // clear the screen gl.Viewport(0, 0, int32(width), int32(height)) gl.ClearColor(0.05, 0.05, 0.05, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // make the projection and view matrixes perspective := mgl.Perspective(mgl.DegToRad(60.0), float32(width)/float32(height), 1.0, 400.0) view := gr.camera.GetViewMatrix() // Get the landscape chunks to render -- which for now is everything landMan := gameManager.GetLandscapeManager() allChunks := landMan.GetAllChunks() // draw everything you crazy devil for _, c := range allChunks { node := c.GetTheRenderable() if node.Core.Shader == nil { node.Core.Shader = gr.Shaders["landscape"] } gr.Renderer.DrawRenderable(node, customLandscapeBinder, perspective, view) } // get the entity manager and all the entities entMan := gameManager.GetEntityManager() allEnts := entMan.GetAllEntities() // draw everything, because we're crazy for _, e := range allEnts { // make sure we have something to draw if e.Renderable == nil { // try to clone a new renderable from the asset prototypes proto, okay := loadedAssets[e.AssetPath] if okay { e.Renderable = proto.Clone() } } // sync the loc/rot to the renderable e.SyncToRenderable() // did we sort things out and make a renderable? if e.Renderable != nil { gr.Renderer.DrawRenderable(e.Renderable, nil, perspective, view) } } // draw the screen gr.Renderer.EndRenderFrame() }