public void DrawDebugInfo( ViewPortInfo viewPort, SpriteBatch spriteBatch, Rectangle destRectangle, SpriteFont spriteFont, float globalDispX, float globalDispY) { float actualBoxWidth = BoxWidth * viewPort.ActualZoom; float actualBoxHeight = BoxHeight * viewPort.ActualZoom; int startBoxX = (int)(globalDispX / actualBoxWidth); int startBoxY = (int)(globalDispY / actualBoxHeight); int viewBoxCountX = (int)Math.Ceiling(destRectangle.Width / actualBoxWidth) + 1; int viewBoxCountY = (int)Math.Ceiling(destRectangle.Height / actualBoxHeight) + 1; for (int i = 0; i < viewBoxCountX; i++) { for (int j = 0; j < viewBoxCountY; j++) { int I = i + startBoxX; int J = j + startBoxY; if (I < 0 || J < 0 || I >= _boxCountX || J >= _boxCountY) { continue; } int index = I + J * _boxCountX; int x = (int)(I * BoxWidth * viewPort.ActualZoom - globalDispX); int y = (int)(J * BoxHeight * viewPort.ActualZoom - globalDispY); int width = (int)(BoxWidth * viewPort.ActualZoom) - 1; int height = (int)(BoxHeight * viewPort.ActualZoom) - 1; int entityCount = _hashLists[index].Count; Color drawColor = debugColors[Math.Min(entityCount, debugColors.Length - 1)]; string debugText = string.Format("{0}\n\r{1}", index, entityCount); Vector2 debugTextMeasurements = spriteFont.MeasureString(debugText); SpriteBatchExtensions.DrawRectangle( spriteBatch, new Rectangle(x, y, width, height), drawColor, 0); if (debugTextMeasurements.X < width && debugTextMeasurements.Y < height) { SpriteBatchExtensions.DrawCenteredString( spriteBatch, spriteFont, debugText, new Vector2(x, y) + new Vector2(width, height) / 2, drawColor); } } } }
public void DrawDebugInfo( ViewPortInfo viewPort, SpriteBatch spriteBatch, Rectangle destRectangle, SpriteFont spriteFont, float globalDispX, float globalDispY) { DrawQuadTreeNode(viewPort, spriteBatch, Root, destRectangle, spriteFont, globalDispX, globalDispY); }
internal void DrawQuadTreeNode(ViewPortInfo viewPort, SpriteBatch spriteBatch, QuadTreeNode node, Rectangle destRectangle, SpriteFont spriteFont, float globalDispX, float globalDispY) { if (node == null) { return; } int actualX = (int)Math.Ceiling(node.pxBounds.X * viewPort.ActualZoom - globalDispX); int actualY = (int)Math.Ceiling(node.pxBounds.Y * viewPort.ActualZoom - globalDispY); // We need to calculate the 'Actual' width and height otherwise drawing might be innacurate when zoomed. int actualWidth = (int)Math.Ceiling(node.pxBounds.Width * viewPort.ActualZoom); int actualHeight = (int)Math.Ceiling(node.pxBounds.Height * viewPort.ActualZoom); // Only draw leaf nodes which are within the viewport specified. if (node.ChildNode1 == null && new Rectangle(actualX, actualY, actualWidth, actualHeight).Intersects(destRectangle)) { string nodeText = string.Format("{0}\n\r{1}", node.NodeID, node.Entities.Count); SpriteBatchExtensions.DrawRectangle(spriteBatch, new Rectangle(actualX, actualY, actualWidth, actualHeight), Color.Lime, 0); spriteBatch.DrawString( spriteFont, nodeText, new Vector2(actualX + actualWidth / 2.0f, actualY + actualHeight / 2.0f) - spriteFont.MeasureString(nodeText) / 2, Color.Lime ); } DrawQuadTreeNode(viewPort, spriteBatch, node.ChildNode1, destRectangle, spriteFont, globalDispX, globalDispY); DrawQuadTreeNode(viewPort, spriteBatch, node.ChildNode2, destRectangle, spriteFont, globalDispX, globalDispY); DrawQuadTreeNode(viewPort, spriteBatch, node.ChildNode3, destRectangle, spriteFont, globalDispX, globalDispY); DrawQuadTreeNode(viewPort, spriteBatch, node.ChildNode4, destRectangle, spriteFont, globalDispX, globalDispY); }
//TODO: HIGHLY UNOPTIMIZED, DO NOT INITIALISE VERTICES EACH ROUND, USE VERTEX BUFFERS AND INDICES public override void ApplyShader(SpriteBatch spriteBatch, ViewPortInfo viewPortInfo, GameTime gameTime, RenderTarget2D inputBuffer, RenderTarget2D outputBuffer) { GraphicsDevice.SetRenderTarget(_lightTarget); GraphicsDevice.Clear(AmbientLight); GraphicsDevice.BlendState = BlendState.Additive; foreach (LightSource lightSource in LightSources) { float x = lightSource.Pos.X; float y = lightSource.Pos.Y; if (lightSource.PositionType == LightPositionType.Relative) { x -= viewPortInfo.pxTopLeftX; y -= viewPortInfo.pxTopLeftY; } x *= viewPortInfo.ActualZoom; y *= viewPortInfo.ActualZoom; x /= _lightTarget.Width; y /= _lightTarget.Height; x = -1.0f + x * 2; y = 1.0f - y * 2; float radiusX = lightSource.Width * viewPortInfo.ActualZoom; float radiusY = lightSource.Height * viewPortInfo.ActualZoom; double pulseValue = Math.Sin( MathHelper.Pi * ((gameTime.TotalGameTime.TotalMilliseconds - lightSource.PulseStartTime) / lightSource.PulseDuration) ); radiusX += (float)(lightSource.Pulse * lightSource.Width * pulseValue); radiusY += (float)(lightSource.Pulse * lightSource.Height * pulseValue); radiusX /= _lightTarget.Width; radiusY /= _lightTarget.Height; //TODO: This can be vastly improved by only initializing the vertices once //TODO: Use a VertexBuffer to keep in VRAM //TODO: Use TriangleIndex rather than TriangleList for optimal vertex count VertexPositionColor[] vertexCircle = SetUpCircle( radiusX, radiusY, new Vector3(x, y, 0), lightSource.Color, CirclePointAccurracy, null ); _colorShader.CurrentTechnique = _colorShader.Techniques["Pretransformed"]; foreach (EffectPass pass in _colorShader.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawUserPrimitives( PrimitiveType.TriangleList, vertexCircle, 0, CirclePointAccurracy, VertexPositionColor.VertexDeclaration ); } } GraphicsDevice.SetRenderTarget(outputBuffer); _lightShader.Parameters["LightMap"].SetValue(_lightTarget); spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, null, null, null, _lightShader); { spriteBatch.Draw(inputBuffer, inputBuffer.Bounds, Color.White); } spriteBatch.End(); }
public ViewPortInfo DrawWorldViewPort(SpriteBatch spriteBatch, Vector2 center, float zoom, Rectangle destRectangle, Color color, SamplerState samplerState, SpriteFont spriteFont = null) { ViewPortInfo viewPortInfo = new ViewPortInfo(); { viewPortInfo.pxTileWidth = (int)Math.Ceiling(Map.TileWidth * zoom); viewPortInfo.pxTileHeight = (int)Math.Ceiling(Map.TileHeight * zoom); // Note about ActualZoom Property: // because there is a loss of data between to conversion from Map.pxTileWidth * Zoom -> (int) // we need to determine what was the actual level of zoom that was applied to the tiles and use that // this ensures that entities that will be drawn will be placed correctly on the screen. viewPortInfo.ActualZoom = viewPortInfo.pxTileWidth / Map.TileWidth; viewPortInfo.pxWidth = destRectangle.Width / viewPortInfo.ActualZoom; viewPortInfo.pxHeight = destRectangle.Height / viewPortInfo.ActualZoom; viewPortInfo.pxTopLeftX = center.X - viewPortInfo.pxWidth / 2.0f; viewPortInfo.pxTopLeftY = center.Y - viewPortInfo.pxHeight / 2.0f; viewPortInfo.TileCountX = (int)Math.Ceiling((double)viewPortInfo.pxWidth / Map.TileWidth) + 1; viewPortInfo.TileCountY = (int)Math.Ceiling((double)viewPortInfo.pxHeight / Map.TileHeight) + 1; // Prevent the View from going outisde of the WORLD coordinates. if (viewPortInfo.pxTopLeftX < 0) { viewPortInfo.pxTopLeftX = 0; } if (viewPortInfo.pxTopLeftY < 0) { viewPortInfo.pxTopLeftY = 0; } if (viewPortInfo.pxTopLeftX + viewPortInfo.pxWidth >= Map.pxWidth) { viewPortInfo.pxTopLeftX = Map.pxWidth - viewPortInfo.pxWidth; } if (viewPortInfo.pxTopLeftY + viewPortInfo.pxHeight >= Map.pxHeight) { viewPortInfo.pxTopLeftY = Map.pxHeight - viewPortInfo.pxHeight; } // Calculate any decimal displacement required (For Positions with decimal points). viewPortInfo.pxDispX = viewPortInfo.pxTopLeftX - ((int)viewPortInfo.pxTopLeftX / Map.TileWidth) * Map.TileWidth; viewPortInfo.pxDispY = viewPortInfo.pxTopLeftY - ((int)viewPortInfo.pxTopLeftY / Map.TileHeight) * Map.TileHeight; viewPortInfo.pxViewPortBounds = new Rectangle( (int)Math.Ceiling(viewPortInfo.pxTopLeftX), (int)Math.Ceiling(viewPortInfo.pxTopLeftY), (int)Math.Ceiling(viewPortInfo.pxWidth), (int)Math.Ceiling(viewPortInfo.pxHeight) ); } // RENDER THE GAME WORLD TO THE VIEWPORT RENDER TARGET GraphicsDevice.SetRenderTarget(_inputBuffer); GraphicsDevice.Clear(Map.Background); // DRAW THE WORLD MAP OverallPerformance.StartTiming("TileRenderingTime"); // Deferred Rendering should be fine for rendering tile as long as we draw tile layers one at a time spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, samplerState, null, null); { for (int layerIndex = 0; layerIndex < Map.TileLayers.Count; layerIndex++) { // DRAW EACH LAYER TileLayer tileLayer = Map.TileLayers[layerIndex]; if (tileLayer.Visible) { Color layerColor = new Color() { R = tileLayer.Color.R, G = tileLayer.Color.G, B = tileLayer.Color.B, A = (byte)(tileLayer.Color.A * tileLayer.Opacity) }; float depth = 1 - (layerIndex / 10000.0f); for (int i = 0; i < viewPortInfo.TileCountX; i++) { for (int j = 0; j < viewPortInfo.TileCountY; j++) { int tileX = (int)(i + viewPortInfo.pxTopLeftX / Map.TileWidth); int tileY = (int)(j + viewPortInfo.pxTopLeftY / Map.TileHeight); int tileGid = tileLayer[tileX, tileY]; Rectangle pxTileDestRect = new Rectangle( (int)Math.Ceiling(i * viewPortInfo.pxTileWidth - viewPortInfo.pxDispX * viewPortInfo.ActualZoom), (int)Math.Ceiling(j * viewPortInfo.pxTileHeight - viewPortInfo.pxDispY * viewPortInfo.ActualZoom), (int)viewPortInfo.pxTileWidth, (int)viewPortInfo.pxTileHeight ); if (tileGid != 0 && tileGid != -1) // NULL or INVALID Tile Gid is ignored { Tile tile = Map.Tiles[tileGid]; spriteBatch.Draw( tile.sourceTexture, pxTileDestRect, tile.SourceRectangle, layerColor, 0, Vector2.Zero, SpriteEffects.None, depth ); } // DRAW THE TILE LAYER GRID IF ENABLE if (DrawingOptions.ShowTileGrid && layerIndex == Map.TileLayers.Count - 1) { spriteBatch.DrawRectangle(pxTileDestRect, Color.Black, 0); } } } } } } spriteBatch.End(); OverallPerformance.StopTiming("TileRenderingTime"); // Calculate the entity Displacement caused by pxTopLeft at a global scale to prevent jittering. // Each entity should be displaced by the *same amount* based on the pxTopLeftX/Y values // this is to prevent entities 'jittering on the screen' when moving the camera. float globalDispX = (int)Math.Ceiling(viewPortInfo.pxTopLeftX * viewPortInfo.ActualZoom); float globalDispY = (int)Math.Ceiling(viewPortInfo.pxTopLeftY * viewPortInfo.ActualZoom); // DRAW VISIBLE REGISTERED ENTITIES OverallPerformance.RestartTiming("TotalEntityRenderTime"); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, samplerState, null, null); { EntitiesOnScreen = Collider.GetIntersectingEntites(new FRectangle(viewPortInfo.pxViewPortBounds)); // DRAW EACH ENTITIY THAT IS WITHIN THE SCREENS VIEWPORT foreach (Entity entity in EntitiesOnScreen) { EntityRenderPerformance.RestartTiming(entity.Name); if (!entity.Visible) { continue; } entity.IsOnScreen = true; // Determine the absolute position on the screen for entity position and the bounding box. Vector2 pxAbsEntityPos = new Vector2( entity.Pos.X * viewPortInfo.ActualZoom - globalDispX, entity.Pos.Y * viewPortInfo.ActualZoom - globalDispY ); FRectangle pxAbsBoundingBox = new FRectangle( entity.CurrentBoundingBox.X * viewPortInfo.ActualZoom - globalDispX, entity.CurrentBoundingBox.Y * viewPortInfo.ActualZoom - globalDispY, entity.CurrentBoundingBox.Width * viewPortInfo.ActualZoom, entity.CurrentBoundingBox.Height * viewPortInfo.ActualZoom ); // DRAW ENTITY BOUNDING BOXES IF ENABLED if (DrawingOptions.ShowBoundingBoxes) { spriteBatch.DrawRectangle(pxAbsBoundingBox.ToRectangle(), Color.Red, 0.0001f); SpriteBatchExtensions.DrawCross( spriteBatch, new Vector2( (int)Math.Ceiling(pxAbsEntityPos.X), (int)Math.Ceiling(pxAbsEntityPos.Y) ), 13, Color.Black, 0f); } // DRAW ENTITY DETAILS IF ENABLED (ENTITY DEBUG INFO) if (DrawingOptions.ShowEntityDebugInfo) { SpriteBatchExtensions.DrawMultiLineString( spriteBatch, spriteFont, entity.GetDebugInfo(), (int)entity.CurrentBoundingBox.Width * 2, 4, new Vector2( pxAbsBoundingBox.X + pxAbsBoundingBox.Width / 2, pxAbsBoundingBox.Y + pxAbsBoundingBox.Height / 2 ), Color.Purple); } // DRAW EVERY GAMEDRAWABLE INSTANCE CURRENTLY ACTIVE IN THE ENTITIES DRAWABLE SET. HashSet <GameDrawableInstance> drawableInstances = entity.Drawables.GetByState(entity.CurrentDrawableState); if (drawableInstances != null) { foreach (GameDrawableInstance drawable in drawableInstances) { if (!drawable.Visible) { continue; } // The relative position of the object should always be (X,Y) - (globalDispX, globalDispY). globalDispX and globalDispY // are based on viewPortInfo.TopLeftX and viewPortInfo.TopLeftY. viewPortInfo.TopLeftX and viewPortInfo.TopLeftY have // already been corrected in terms of the bounds of the WORLD map coordinates. This allows for panning at the edges. Rectangle pxCurrentFrame = drawable.GetSourceRectangle(LastUpdateTime); int pxObjectWidth = (int)Math.Ceiling(pxCurrentFrame.Width * entity.ScaleX * viewPortInfo.ActualZoom); int pxObjectHeight = (int)Math.Ceiling(pxCurrentFrame.Height * entity.ScaleY * viewPortInfo.ActualZoom); // Draw the Object based on the current Frame dimensions and the specified Object Width Height values. Rectangle objectDestRect = new Rectangle( (int)Math.Ceiling(pxAbsEntityPos.X) + (int)Math.Ceiling(drawable.Offset.X * viewPortInfo.ActualZoom), (int)Math.Ceiling(pxAbsEntityPos.Y) + (int)Math.Ceiling(drawable.Offset.Y * viewPortInfo.ActualZoom), pxObjectWidth, pxObjectHeight ); Vector2 drawableOrigin = new Vector2( (float)Math.Ceiling(drawable.Drawable.Origin.X * pxCurrentFrame.Width), (float)Math.Ceiling(drawable.Drawable.Origin.Y * pxCurrentFrame.Height) ); Color drawableColor = new Color() { R = drawable.Color.R, G = drawable.Color.G, B = drawable.Color.B, A = (byte)(drawable.Color.A * entity.Opacity) }; // Layer depth should depend how far down the object is on the map (Relative to Y). // Important to also take into account the animation layers for the entity. float layerDepth = Math.Min(0.99f, 1 / (entity.Pos.Y + ((float)drawable.Layer / Map.pxHeight))); // FINALLY ... DRAW spriteBatch.Draw( drawable.GetSourceTexture(LastUpdateTime), objectDestRect, pxCurrentFrame, drawableColor, drawable.Rotation, drawableOrigin, drawable.SpriteEffects, layerDepth); // DRAW BOUNDING BOXES OF EACH INDIVIDUAL DRAWABLE COMPONENT if (DrawingOptions.ShowDrawableComponents) { Rectangle drawableComponentRect = new Rectangle( (int)Math.Floor(objectDestRect.X - objectDestRect.Width * drawable.Drawable.Origin.X), (int)Math.Floor(objectDestRect.Y - objectDestRect.Height * drawable.Drawable.Origin.Y), objectDestRect.Width, objectDestRect.Height); SpriteBatchExtensions.DrawRectangle( spriteBatch, drawableComponentRect, Color.Blue, 0); } } } EntityRenderPerformance.StopTiming(entity.Name); } } spriteBatch.End(); OverallPerformance.StopTiming("TotalEntityRenderTime"); // APPLY GAME SHADERS TO THE RESULTANT IMAGE OverallPerformance.RestartTiming("TotalPostGameShaderRenderTime"); foreach (PostGameShader postGameShader in GameShaders) { if (postGameShader.Enabled) { postGameShader.ApplyShader(spriteBatch, viewPortInfo, LastUpdateTime, _inputBuffer, _outputBuffer); // Swap buffers after each render. _dummyBuffer = _inputBuffer; _inputBuffer = _outputBuffer; _outputBuffer = _dummyBuffer; } } OverallPerformance.StopTiming("TotalPostGameShaderRenderTime"); // DRAW COLLIDER DEBUG INFORMATION IF ENABLED if (DrawingOptions.ShowColliderDebugInfo) { spriteBatch.Begin(); Collider.DrawDebugInfo(viewPortInfo, spriteBatch, destRectangle, spriteFont, globalDispX, globalDispY); spriteBatch.End(); } // DRAW THE VIEWPORT TO THE STANDARD SCREEN GraphicsDevice.SetRenderTarget(null); spriteBatch.Begin(); { spriteBatch.Draw(_inputBuffer, destRectangle, color); } spriteBatch.End(); return(viewPortInfo); }
protected override void Draw(GameTime gameTime) { GraphicsDevice.Viewport = new Viewport { X = 0, Y = 0, Width = WINDOW_WIDTH, Height = WINDOW_HEIGHT, MinDepth = 0, MaxDepth = 1 }; GraphicsDevice.Clear(Color.Black); TextCounter = 0; float textHeight = DefaultSpriteFont.MeasureString("d").Y; Rectangle pxDestRectangle = new Rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); // Draw the World View Port, Centered on the CurrentPlayer Actor. ViewPortInfo viewPort = Engine.DrawWorldViewPort( SpriteBatch, Engine.GetEntity("Player").Pos, Zoom, pxDestRectangle, Color.White, CurrentSampler, DefaultSpriteFont); // DRAW DEBUGGING INFORMATION SpriteBatch.Begin(); { if (showDebugInfo) { // DRAW THE LIGHT MAP OUTPUT TO THE SCREEN FOR DEBUGGING if (LightShader.Enabled) { int lightMapHeight = 100; int lightMapWidth = (int)Math.Ceiling(100 * ((float)LightShader.LightMap.Width / LightShader.LightMap.Height)); SpriteBatch.Draw( LightShader.LightMap, new Rectangle( WINDOW_WIDTH - lightMapWidth, 0, lightMapWidth, lightMapHeight ), Color.White ); } double fps = 1000 / gameTime.ElapsedGameTime.TotalMilliseconds; Color fpsColor = (Math.Ceiling(fps) < 60) ? Color.Red : Color.White; float TX = CurrentPlayer.Pos.X / Engine.Map.TileWidth; float TY = CurrentPlayer.Pos.Y / Engine.Map.TileHeight; this.IsMouseVisible = true; MouseState mouseState = Mouse.GetState(); Vector2 worldCoord = viewPort.GetWorldCoordinates(new Point(mouseState.X, mouseState.Y)); Vector2 tileCoord = worldCoord / (new Vector2(Engine.Map.TileWidth, Engine.Map.TileHeight));; tileCoord.X = (int)Math.Floor(tileCoord.X); tileCoord.Y = (int)Math.Floor(tileCoord.Y); SpriteBatch.DrawString(DefaultSpriteFont, CurrentPlayer.Pos.ToString(), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, TX.ToString("0.0") + "," + TY.ToString("0.0"), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Coins=" + CurrentPlayer.Coins, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "HP=" + CurrentPlayer.HP, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, fps.ToString("0.0 FPS"), GeneratePos(textHeight), fpsColor); SpriteBatch.DrawString(DefaultSpriteFont, "Resolution=" + Engine.PixelWidth + "x" + Engine.PixelHeight, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "MapSize=" + Engine.Map.txWidth + "x" + Engine.Map.txHeight, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Sampler=" + CurrentSampler.ToString(), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Entities On Screen = " + Engine.EntitiesOnScreen.Count, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Total Entities = " + Engine.Entities.Count, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Actual Zoom = " + viewPort.ActualZoom, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Mouse World Coordinates = " + worldCoord, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, "Mouse Tile Coordinates = " + tileCoord, GeneratePos(textHeight), Color.White); } if (showDiagnostics) { StringBuilder builder = new StringBuilder(); builder.AppendLine(Engine.OverallPerformance.Description); builder.AppendLine(Engine.OverallPerformance.ShowAll(false)); builder.AppendLine(Engine.EntityUpdatePerformance.Description); builder.AppendLine(Engine.EntityUpdatePerformance.ShowTop(5)); string textOutput = builder.ToString(); SpriteBatch.DrawString(DefaultSpriteFont, textOutput, new Vector2(0, WINDOW_HEIGHT - DefaultSpriteFont.MeasureString(textOutput).Y), Color.White); } } SpriteBatch.End(); base.Draw(gameTime); }
public virtual void ApplyShader(SpriteBatch spriteBatch, ViewPortInfo viewPortInfo, GameTime gameTime, RenderTarget2D inputBuffer, RenderTarget2D outputBuffer) { throw new NotImplementedException(); }