//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; 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 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 Rectangle Draw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 destination, float layerDepth, float entityScaleX, float entityScaleY, float opacity, float maxY, ViewPortInfo viewPortInfo) { int width = GetWidth(gameTime); int height = GetHeight(gameTime); int targetWidth = (int)Math.Ceiling(width * entityScaleX * viewPortInfo.ActualZoom * this.ScaleX); int targetHeight = (int)Math.Ceiling(height * entityScaleY * viewPortInfo.ActualZoom * this.ScaleY); // Draw the Object based on the current Frame dimensions and the specified Object Width Height values. Rectangle objectDestRect = new Rectangle( (int)Math.Ceiling(destination.X) + (int)Math.Ceiling(Offset.X * viewPortInfo.ActualZoom), (int)Math.Ceiling(destination.Y) + (int)Math.Ceiling(Offset.Y * viewPortInfo.ActualZoom), targetWidth, targetHeight ); Vector2 targetOrigin = new Vector2( (float)Math.Ceiling(Drawable.Origin.X * width), (float)Math.Ceiling(Drawable.Origin.Y * height) ); Color targetColor = new Color() { R = Color.R, G = Color.G, B = Color.B, A = (byte)(Color.A * opacity) }; // Further adjust the layer depth based on this drawables layer value layerDepth += Layer / maxY; if (layerDepth > 1) layerDepth = 1; if (layerDepth < 0) layerDepth = 0; Drawable.Draw(spriteBatch, objectDestRect, targetColor, Rotation, targetOrigin, SpriteEffects, layerDepth, GetElapsedMS(gameTime)); return objectDestRect; }
/// <summary> /// This is called when the game should draw itself. /// </summary> /// <param name="gameTime">Provides a snapshot of timing values.</param> 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 Engine.GetEntity("Player") Actor. ViewPortInfo viewPort = Engine.DrawWorldViewPort( SpriteBatch, Engine.GetEntity("Player").Pos, Zoom, pxDestRectangle, Color.White, CurrentSampler, DefaultSpriteFont); // Update static variable ViewPortInfo = viewPort; // Render the UI if (Engine.MapScript != null) { ArenaScript script = (ArenaScript)Engine.MapScript; if (script.Hud != null) script.Hud.RenderUI(SpriteBatch, gameTime, pxDestRectangle); } // 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 = Engine.GetEntity("Player").Pos.X / Engine.Map.TileWidth; float TY = Engine.GetEntity("Player").Pos.Y / Engine.Map.TileHeight; Hero player = (Hero)Engine.GetEntity("Player"); SpriteBatch.DrawString(DefaultSpriteFont, Engine.GetEntity("Player").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=" + player.Gold, GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("Lvl={0}", player.Level), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("HP={0}/{1}", player.HP, player.MaxHP), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("Mana={0}/{1}", player.Mana, player.MaxMana), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("XP={0}/{1}", player.XP, player.XPToNextLevel), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("Stats={0},{1},{2}", player.Strength, player.Dexterity, player.Wisdom), GeneratePos(textHeight), Color.White); SpriteBatch.DrawString(DefaultSpriteFont, String.Format("Intensity = {0}", player.Intensity), 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); } 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 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; }
public virtual void ApplyShader(SpriteBatch spriteBatch, ViewPortInfo viewPortInfo, GameTime gameTime, RenderTarget2D inputBuffer, RenderTarget2D outputBuffer ) { throw new NotImplementedException(); }