XNA resize window without increasing resolution - c#

I want to create a low resolution game on a larger window. (96x54 res on 960x540 size window for example).
How would I go about this? Is there a way to resize a window independently of the preferred back buffer width and height? Or should I just keep a low resolution render target I draw on, and just draw it as a full screen quad on my window when I'm done adjusting for nearest texture sampling?
Thanks in advance,
xoorath

I tend to opt for the "render to texture" solution so that I can allow for things like full screen without distortions.
The class I use to achieve this usually looks something like:
class VirtualScreen
{
public readonly int VirtualWidth;
public readonly int VirtualHeight;
public readonly float VirtualAspectRatio;
private GraphicsDevice graphicsDevice;
private RenderTarget2D screen;
public VirtualScreen(int virtualWidth, int virtualHeight, GraphicsDevice graphicsDevice)
{
VirtualWidth = virtualWidth;
VirtualHeight = virtualHeight;
VirtualAspectRatio = (float)(virtualWidth) / (float)(virtualHeight);
this.graphicsDevice = graphicsDevice;
screen = new RenderTarget2D(graphicsDevice, virtualWidth, virtualHeight, false, graphicsDevice.PresentationParameters.BackBufferFormat, graphicsDevice.PresentationParameters.DepthStencilFormat, graphicsDevice.PresentationParameters.MultiSampleCount, RenderTargetUsage.DiscardContents);
}
private bool areaIsDirty = true;
public void PhysicalResolutionChanged()
{
areaIsDirty = true;
}
private Rectangle area;
public void Update()
{
if (!areaIsDirty)
{
return;
}
areaIsDirty = false;
var physicalWidth = graphicsDevice.Viewport.Width;
var physicalHeight = graphicsDevice.Viewport.Height;
var physicalAspectRatio = graphicsDevice.Viewport.AspectRatio;
if ((int)(physicalAspectRatio * 10) == (int)(VirtualAspectRatio * 10))
{
area = new Rectangle(0, 0, physicalWidth, physicalHeight);
return;
}
if (VirtualAspectRatio > physicalAspectRatio)
{
var scaling = (float)physicalWidth / (float)VirtualWidth;
var width = (float)(VirtualWidth) * scaling;
var height = (float)(VirtualHeight) * scaling;
var borderSize = (int)((physicalHeight - height) / 2);
area = new Rectangle(0, borderSize, (int)width, (int)height);
}
else
{
var scaling = (float)physicalHeight / (float)VirtualHeight;
var width = (float)(VirtualWidth) * scaling;
var height = (float)(VirtualHeight) * scaling;
var borderSize = (int)((physicalWidth - width) / 2);
area = new Rectangle(borderSize, 0, (int)width, (int)height);
}
}
public void BeginCapture()
{
graphicsDevice.SetRenderTarget(screen);
}
public void EndCapture()
{
graphicsDevice.SetRenderTarget(null);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(screen, area, Color.White);
}
}
And then in my game, the initialisation tends look something like:
VirtualScreen virtualScreen;
protected override void Initialize()
{
virtualScreen = new VirtualScreen(96, 54, GraphicsDevice);
Window.ClientSizeChanged += new EventHandler<EventArgs>(Window_ClientSizeChanged);
Window.AllowUserResizing = true;
base.Initialize();
}
void Window_ClientSizeChanged(object sender, EventArgs e)
{
virtualScreen.PhysicalResolutionChanged();
}
With the all important call to Update:
protected override void Update(GameTime gameTime)
{
virtualScreen.Update();
base.Update(gameTime);
}
And then the act of drawing itself:
protected override void Draw(GameTime gameTime)
{
virtualScreen.BeginCapture();
GraphicsDevice.Clear(Color.CornflowerBlue);
// game rendering happens here...
virtualScreen.EndCapture();
GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
virtualScreen.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
With this in place, I can basically stop caring about resolution at all and just focus on the game.

Using the RenderToTexture method you're talking about might be a good idea (plus it will be easier for you if you want to do post-process shaders).
Alternatively, you can set the Window's size, but your code will only work on a desktop.
You must add those 2 references in your project:
using System.Drawing;
using System.Windows.Forms;
And then in your Game class (i.e. in the Initialize method)
GraphicsDeviceManager.PreferredBackBufferWidth = 96;
GraphicsDeviceManager.PreferredBackBufferHeight = 54;
IntPtr ptr = this.Window.Handle;
Form form = (Form) Control.FromHandle(ptr);
form.Size = new Size(960, 540);

Related

Replacement for CSS3 function repeating-linear-gradient() in .NET (WinForms)

Is there any replacement (analogue) for CSS3 function repeating-linear-gradient() in .NET (WinForms, not WPF)?
I need to paint repeating "zebra stripes" (e.g. red, blue, green, red, blue, green, ...) at an angle 45 degrees.
UPD:
Following Jimi's advice I managed to solve the problem only partially:
private void DrawRepeatingStripes(int degree, int stripeWidth, Color[] colors, Rectangle rect, Graphics graphics)
{
using (var img = new Bitmap(colors.Length * stripeWidth, rect.Height))
{
using (var g = Graphics.FromImage(img))
{
for (int i = 0; i < colors.Length; i++)
{
// TODO: cache SolidBrush
g.FillRectangle(new SolidBrush(colors[i]), stripeWidth * i, 0, stripeWidth, rect.Height);
}
}
using (var tb = new TextureBrush(img, WrapMode.Tile))
{
using (var myMatrix = new Matrix())
{
myMatrix.Rotate(degree);
graphics.Transform = myMatrix;
graphics.FillRectangle(tb, rect);
graphics.ResetTransform();
}
}
}
}
Usage (in some form's code):
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
DrawRepeatingStripes(45, 10, new Color[] { Color.Red, Color.Yellow, Color.Green }, e.ClipRectangle, e.Graphics);
}
The problem is that rotation is... well, a rotation, so part of rect is filled with stripes and part is empty. Have no idea how to solve it :(
An example about using a TextureBrush to fill the surface of a Control used as canvas.
The LinearRepeatingGradient class exposes a bindable ColorBands Property (of Type BindingList<ColorBand>) that allows to add or remove ColorBand objects, a record that defines the Color and size of each band you want to generate.
The RotationAngle Property specifies the rotation to apply to the rendering.
In the Paint event of the Control used as canvas, call the Fill(Graphics g) method, passing the e.Graphics object provided by the PaintEventArgs argument.
A new Bitmap is generated, based on the content of the ColorBands Property.
When the rotation angle cannot be exactly divided by 90, the canvas' dimensions are inflated by a third of its diagonal (as the maximum distance from the non-rotated rectangle).
The TextureBrush fills this inflated surface, so no blank space is left on the sides of the canvas.
Since this test sample is built with .NET 7, I'm using record to store the color bands' settings. You can replace it with a class object without changes to the rest of the code.
public record ColorBand(Color Color, int Size) {
public override string ToString() => $"Color: {Color.Name} Size: {Size}";
}
Same as above: using declaration instead of using statements
using System.Drawing;
using System.Drawing.Drawing2D;
public class LinearRepeatingGradient
{
public LinearRepeatingGradient(float rotation = .0f)
{
ColorBands = new BindingList<ColorBand>();
RotationAngle = rotation;
}
public float RotationAngle { get; set; }
[Bindable(true), ListBindable(BindableSupport.Default)]
public BindingList<ColorBand> ColorBands { get; }
public void Fill(Graphics g) => Fill(g, g.ClipBounds);
public void Fill(Graphics g, Rectangle fillArea) => Fill(g, new RectangleF(fillArea.Location, fillArea.Size));
protected virtual void Fill(Graphics g, RectangleF display)
{
if (ColorBands is null || ColorBands.Count == 0 || g.Clip.IsInfinite(g)) return;
var canvas = InflateCanvas(display);
var centerPoint = new PointF(canvas.X + canvas.Width / 2, canvas.Y + canvas.Height / 2);
using var texture = GetTexture(canvas.Width);
if (texture is null) return;
using var brush = new TextureBrush(texture, WrapMode.Tile);
using var mx = new Matrix();
mx.RotateAt(RotationAngle, centerPoint);
g.Transform = mx;
g.FillRectangle(brush, canvas);
g.ResetTransform();
}
private RectangleF InflateCanvas(RectangleF rect)
{
if (RotationAngle % 90.0f == 0) return rect;
float maxInflate = (float)Math.Sqrt(Math.Pow(rect.X - rect.Right, 2) +
Math.Pow(rect.Y - rect.Bottom, 2)) / 3.0f;
var canvas = rect;
canvas.Inflate(maxInflate, maxInflate);
return canvas;
}
private Bitmap? GetTexture(float width)
{
int height = ColorBands!.Sum(c => c.Size);
if (height <= 0) return null;
var texture = new Bitmap((int)(width + .5f), height);
int startPosition = 0;
using var g = Graphics.FromImage(texture);
for (int i = 0; i < ColorBands!.Count; i++) {
var rect = new Rectangle(0, startPosition, texture.Width, ColorBands![i].Size);
using var brush = new SolidBrush(ColorBands![i].Color);
g.FillRectangle(brush, rect);
startPosition += ColorBands![i].Size;
}
return texture;
}
}
This is how it works:
Since the ColorBands property is bindable, you can use data bindings to perform actions, when a ColorBand object is added or removed and also bind the ColorBands collection to Controls, as shown in the animation:
public partial class SomeForm : Form {
LinearRepeatingGradient gradient = new();
public SomeForm()
{
InitializeComponent();
[DataGridView].DataSource = gradient.ColorBands;
gradient.ColorBands.ListChanged += (s, e) => someControl.Invalidate();
}
private void someControl_Paint(object sender, PaintEventArgs e) => gradient.Fill(e.Graphics);
As a consequence, when you add a new ColorBand (or remove it), the internal collection changes and the Control used as canvas is invalidated, showing the new fill:
gradient.ColorBands.Add(new ColorBand(Color.Red, 45f));
The RotationAngle property doesn't use data bindings, so you have to invalidate the canvas manually when you change it. You can of course change that and make this property bindable:
gradient.RotationAngle = 215f;
someControl.Invalidate();

How to optimize draw area in pixel art editor

I have pixel art creator program, and I have rectangles on canvas that are one field (pixel?). And this is good solution on not huge amount of it (for example 128x128). if i want to create 1024x1024 rectangles on canvas this process is very long, ram usage is about 1-2 gb and after that program runs very slowly. How to optimize this, or create better solution?
Using a Rectangle to represent each pixel is the wrong way to do this. As a FrameworkElement, every rectangle participates in layout and input hit testing. That approach is too heavy weight to be scalable. Abandon it now.
I would recommend drawing directly to a WriteableBitmap and using a custom surface to render the bitmap as the user draws.
Below is a minimum proof of concept that allows simple drawing in a single color. It requires the WriteableBitmapEx library, which is available from NuGet.
public class PixelEditor : FrameworkElement
{
private readonly Surface _surface;
private readonly Visual _gridLines;
public int PixelWidth { get; } = 128;
public int PixelHeight { get; } = 128;
public int Magnification { get; } = 10;
public PixelEditor()
{
_surface = new Surface(this);
_gridLines = CreateGridLines();
Cursor = Cursors.Pen;
AddVisualChild(_surface);
AddVisualChild(_gridLines);
}
protected override int VisualChildrenCount => 2;
protected override Visual GetVisualChild(int index)
{
return index == 0 ? _surface : _gridLines;
}
private void Draw()
{
var p = Mouse.GetPosition(_surface);
var magnification = Magnification;
var surfaceWidth = PixelWidth * magnification;
var surfaceHeight = PixelHeight * magnification;
if (p.X < 0 || p.X >= surfaceWidth || p.Y < 0 || p.Y >= surfaceHeight)
return;
_surface.SetColor(
(int)(p.X / magnification),
(int)(p.Y / magnification),
Colors.DodgerBlue);
_surface.InvalidateVisual();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed && IsMouseCaptured)
Draw();
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
CaptureMouse();
Draw();
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ReleaseMouseCapture();
}
protected override Size MeasureOverride(Size availableSize)
{
var magnification = Magnification;
var size = new Size(PixelWidth* magnification, PixelHeight * magnification);
_surface.Measure(size);
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
_surface.Arrange(new Rect(finalSize));
return finalSize;
}
private Visual CreateGridLines()
{
var dv = new DrawingVisual();
var dc = dv.RenderOpen();
var w = PixelWidth;
var h = PixelHeight;
var m = Magnification;
var d = -0.5d; // snap gridlines to device pixels
var pen = new Pen(new SolidColorBrush(Color.FromArgb(63, 63, 63, 63)), 1d);
pen.Freeze();
for (var x = 1; x < w; x++)
dc.DrawLine(pen, new Point(x * m + d, 0), new Point(x * m + d, h * m));
for (var y = 1; y < h; y++)
dc.DrawLine(pen, new Point(0, y * m + d), new Point(w * m, y * m + d));
dc.Close();
return dv;
}
private sealed class Surface : FrameworkElement
{
private readonly PixelEditor _owner;
private readonly WriteableBitmap _bitmap;
public Surface(PixelEditor owner)
{
_owner = owner;
_bitmap = BitmapFactory.New(owner.PixelWidth, owner.PixelHeight);
_bitmap.Clear(Colors.White);
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor);
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
var magnification = _owner.Magnification;
var width = _bitmap.PixelWidth * magnification;
var height = _bitmap.PixelHeight * magnification;
dc.DrawImage(_bitmap, new Rect(0, 0, width, height));
}
internal void SetColor(int x, int y, Color color)
{
_bitmap.SetPixel(x, y, color);
}
}
}
Just import it into your Xaml, preferably inside a ScrollViewer:
<Window x:Class="WpfTest.PixelArtEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfTest"
Title="PixelArtEditor"
Width="640"
Height="480">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<l:PixelEditor />
</ScrollViewer>
</Window>
Obviously, this is a far cry from being a fully-featured pixel art editor, but it's functional, and it's enough to get you on the right track. The difference in memory usage between editing a 128x128 image vs. 1024x1024 is about ~30mb. Fire it up and see it in action:
Hey, that was fun! Thanks for the diversion.
Just to improve Mike Strobel solution to snap gridlines to device pixels.
var d = -0.5d; // snap gridlines to device pixels
using (DrawingContext dc = _dv.RenderOpen())
{
GuidelineSet guidelineSet = new GuidelineSet();
guidelineSet.GuidelinesX.Add(0.5);
guidelineSet.GuidelinesY.Add(0.5);
dc.PushGuidelineSet(guidelineSet);
// Draw grid
}

XNA: ArgumentNullException when referencing value from a public class

I am still a novice at programming, and I'm currently trying to make a menu for a test game. However, I am having an issue where, when I reference a value from class, Language.cs in my Main.cs using spriteBatch.DrawString, it returns an ArgumentNullException. Any help would be greatly appreciated.
The debugger highlights the following segment under DrawMainMenu() in my Main.cs:
spriteBatch.DrawString(UIFont, Language.mainMenu[0], new Vector2(mainWidth / 2, mainHeight / 2 - 192), Color.White);
I'm not sure if it's an issue of what order I'm using my methods in, but here's what the whole page of Main.cs looks like. Please pardon some of the mess, as I have been experimenting with a variety of things in this program.
public class Main : Game
{
GraphicsDeviceManager graphics;
public static SpriteBatch spriteBatch;
public static SpriteFont UIFont;
public static Texture2D logo;
public static Texture2D titleBack;
public static Texture2D titleSelect;
public static int menuType;
public static Texture2D cursor1;
public static MouseState mouseState;
public static MouseState mouseStatePrevious;
public static MouseState oldState;
public static MouseState newState;
public static int mouseX = mouseState.X;
public static int mouseY = mouseState.Y;
public static Vector2 cursorPos;
public static int strength = 0;
public static int mainWidth = 1920;
public static int mainHeight = 1080;
public static bool showSplash = true;
public int splashCounter;
public static int fadeCounter;
public static bool gameTimeActive = false;
private static int maxMenuItems = 12;
public static int selectedMenu = -1;
public static int selectedMenuType = -1;
public static bool mainMenu = false;
public static Vector2 screenPosition;
Player player = new Player();
public Main()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = mainWidth; // set this value to the desired width of your window
graphics.PreferredBackBufferHeight = mainHeight; // set this value to the desired height of your window
//graphics.PreferredBackBufferWidth = GraphicsDevice.DisplayMode.Width;
//graphics.PreferredBackBufferHeight = GraphicsDevice.DisplayMode.Height;
graphics.ApplyChanges();
}
// Allows the game to perform any initialization it needs to before starting to run.
// This is where it can query for any required services and load any non-graphic
// related content. Calling base.Initialize will enumerate through any components
// and initialize them as well.
protected override void Initialize()
{
// TODO: Add your initialization logic here
player.Initialize();
base.Initialize();
}
// LoadContent will be called once per game and is the place to load
// all of your content.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
// Load initial content...
spriteBatch = new SpriteBatch(GraphicsDevice);
logo = Content.Load<Texture2D>(#"Textures\StudioLogoW");
UIFont = Content.Load<SpriteFont>(#"Textures\Fonts\Font_FrontEnd");
cursor1 = Content.Load<Texture2D>(#"Textures\CursorWhite");
titleBack = Content.Load<Texture2D>(#"Textures\UI\TitleBackground");
titleSelect = Content.Load<Texture2D>(#"Textures\UI\TitleSelect");
player.LoadContent(Content);
// TODO: use this.Content to load the rest of the game content...
}
// UnloadContent will be called once per game and is the place to unload
// all content.
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
// Allows the game to run logic such as updating the world,
// checking for collisions, gathering input, and playing audio.
// <param n
protected override void Update(GameTime gameTime)
{
// Enable mouse funtion in game.
//this.IsMouseVisible = true;
cursorPos = new Vector2(mouseState.X, mouseState.Y);
mouseState = Mouse.GetState();
base.Update(gameTime);
// Get Mouse State, in this case, we're checking to see if a button was clicked, and which one.
// Depending on which button was pressed, it will either add or subract strength.
if (gameTimeActive)
{
player.Update(gameTime);
if (mouseState.LeftButton == ButtonState.Pressed && mouseStatePrevious.LeftButton == ButtonState.Released)
strength++;
if (mouseState.RightButton == ButtonState.Pressed && mouseStatePrevious.RightButton == ButtonState.Released)
strength--;
if (strength > 255)
strength = 255;
if (strength < 0)
strength = 0;
mouseStatePrevious = mouseState;
}
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
//Add your update logic here
}
protected void DrawSplash(GameTime gameTime) //Section for drawing our splash logo, and fading it in and out.
{
base.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.Black);
base.Draw(gameTime);
Main.spriteBatch.Begin();
this.splashCounter++;
Microsoft.Xna.Framework.Color white = Microsoft.Xna.Framework.Color.White;
byte splashByte = 0;
if (this.splashCounter <= 75)
{
float splashNum = (float)this.splashCounter / 75f * 255f;
splashByte = (byte)splashNum;
}
else if (this.splashCounter <= 225)
{
splashByte = 255;
}
else if (this.splashCounter <= 300)
{
int splashNum2 = 225 - this.splashCounter;
float splashNum3 = (float)splashNum2 / 75f * 255f;
splashByte = (byte)splashNum3;
}
else
{
Main.showSplash = false;
Main.mainMenu = true;
Main.selectedMenu = 0;
Main.fadeCounter = 75;
}
white = new Color((int)splashByte, (int)splashByte, (int)splashByte, (int)splashByte);
Main.spriteBatch.Draw(Main.logo, new Rectangle(0, 0, Main.mainWidth, Main.mainHeight), white);
Main.spriteBatch.End();
}
protected void DrawMainMenu() //Section for drawing our Main Menu and fading it in after the splash logo.
{
Language.lang = 1;
graphics.GraphicsDevice.Clear(Color.Black);
// Display some stuff. In this case, we're displaying the logo and some text.
spriteBatch.Begin();
splashCounter++;
Microsoft.Xna.Framework.Color white = Microsoft.Xna.Framework.Color.White;
spriteBatch.Draw(titleBack, new Rectangle(0, 0, mainWidth, mainHeight), Color.White);
//spriteBatch.DrawString(UIFont, "Strength: " + strength, new Vector2(mainWidth / 2, 50), Color.White);
//player.Draw(spriteBatch);
if (selectedMenu == 0)
{
spriteBatch.DrawString(UIFont, Language.mainMenu[0], new Vector2(mainWidth / 2, mainHeight / 2 - 192), Color.White);
}
spriteBatch.Draw(cursor1, cursorPos, Color.White);
spriteBatch.End();
//base.Draw(gameTime);
}
// This is called when the game should draw itself.
// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
if (Main.showSplash)
{
DrawSplash(gameTime);
return;
}
if (Main.mainMenu)
{
gameTimeActive = false;
DrawMainMenu();
return;
}
}
}
And here is my Language.cs:
public class Language
{
public static int lang = 0;
public static string[] mainMenu = new string[99];
public static string[] debugMenu = new string[99];
public static void MenuStrings()
{
if (lang == 1)
{
//For unavailable functions
mainMenu[99] = "Currently unavailable";
//Main Menu
mainMenu[0] = "Single Player";
mainMenu[1] = "Multiplayer";
mainMenu[2] = "Options";
mainMenu[3] = "Credits";
mainMenu[4] = "Exit";
//Single Player - Character
mainMenu[5] = "New Character";
mainMenu[6] = "Load Character";
//Single Player - World
mainMenu[7] = "New World";
mainMenu[8] = "Load World";
//Multiplayer - Front
mainMenu[9] = "Host";
mainMenu[10] = "Join";
//Multiplayer - Host
mainMenu[11] = "Game Mode";
}
}
}
You never called your Language.MenuStrings() method, so Language.mainMenu just contains 99 nulls.
From the code you've shown, you never actually call Language.MenuStrings. Which means Language.mainMenu[0] is still a null reference when you access it.

Is there a fast way to manipulate and buffer a screen in Windows Forms?

I am working on a game for learning purposes, I want to make it only with the .NET-Framework and a Windows Forms project in C#.
I want to get the 'screen' (Something that can be displayed on the window) as an int[]. Modify the array and reapply the altered array to the 'screen' in a buffered manner (So that it doesn't flicker).
I am currently using a Panel, which I draw a Bitmap on with Graphics. The Bitmap is converted to an int[] which I then can modify and reapply to the Bitmap and redraw. It works, but is very slow, especially because I have to scale up the image every frame because my game is only 300x160 and the screen 900x500.
Build up:
// Renders 1 frame
private void Render()
{
// Buffer setup
_bufferedContext = BufferedGraphicsManager.Current;
_buffer = _bufferedContext.Allocate(panel_canvas.CreateGraphics(), new Rectangle(0, 0, _scaledWidth, _scaledHeight));
_screen.clear();
// Get position of player on map
_xScroll = _player._xMap - _screen._width / 2;
_yScroll = _player._yMap - _screen._height / 2;
// Indirectly modifies the int[] '_pixels'
_level.render(_xScroll, _yScroll, _screen);
_player.render(_screen);
// Converts the int[] into a Bitmap (unsafe method is faster)
unsafe
{
fixed (int* intPtr = &_screen._pixels[0])
{
_screenImage = new Bitmap(_trueWidth, _trueHeight, _trueWidth * 4, PixelFormat.Format32bppRgb, new IntPtr(intPtr));
}
}
// Draw generated image on buffer
Graphics g = _buffer.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506));
// Update panel buffered
_buffer.Render();
}
Is there a faster way without external libraries to make this work?
I'm not to sure about the unsafe code , But I do know about the buffered graphics manager. I think you should create a class for it instead of creating a new one every time.As well as having all of your sprites widths and heights be determined at the load instead of scaling them. That sped up my small game engine a good bit.
class Spritebatch
{
private Graphics Gfx;
private BufferedGraphics bfgfx;
private BufferedGraphicsContext cntxt = BufferedGraphicsManager.Current;
public Spritebatch(Size clientsize, Graphics gfx)
{
cntxt.MaximumBuffer = new Size(clientsize.Width + 1, clientsize.Height + 1);
bfgfx = cntxt.Allocate(gfx, new Rectangle(Point.Empty, clientsize));
Gfx = gfx;
}
public void Begin()
{
bfgfx.Graphics.Clear(Color.Black);
}
public void Draw(Sprite s)
{
bfgfx.Graphics.DrawImageUnscaled(s.Texture, new Rectangle(s.toRec.X - s.rotationOffset.Width,s.toRec.Y - s.rotationOffset.Height,s.toRec.Width,s.toRec.Height));
}
public void drawImage(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaled(b, rec);
}
public void drawImageClipped(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaledAndClipped(b, rec);
}
public void drawRectangle(Pen p, Rectangle rec)
{
bfgfx.Graphics.DrawRectangle(p, rec);
}
public void End()
{
bfgfx.Render(Gfx);
}
}
This is a example of what I used. It's set up to mimic the Spritebatch in Xna. Drawing the images Unscaled will really increase the speed of it.Also creating one instance of the buffered graphics and Context will be faster then creating a new one every time you have to render. So I would advise you to change the line g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506)); to DrawImageUnscaled(_screenImage, new Rectangle(0, 0, 900, 506));
Edited : Example of how to scale code on sprite load
public Sprite(Bitmap texture, float x, float y, int width, int height)
{
//texture is the image you originally start with.
Bitmap b = new Bitmap(width, height);
// Create a bitmap with the desired width and height
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(texture, 0, 0, width, height);
}
// get the graphics from the new image and draw the old image to it
//scaling it to the proper width and height
Texture = b;
//set Texture which is the final picture to the sprite.
//Uppercase Texture is different from lowercase
Scaling of the image is expensive enough, even when is done without any interpolation. To speed up the things, you should minimize memory allocations: when you create brand new Bitmap every frame, it leads to object creation and pixmap buffer allocation. This fact negates all the benefits you get from BufferedGraphics. I advise you to do the following:
Create the Bitmap instance of required size (equal to screen size) only once, outside of Render method.
Use direct access to bitmap data through LockBits method, and try to implement the scaling be hand using nearest pixel.
Of course, using some sort of hardware acceleration for scaling operation is the most preferred option (for example, in opengl all images are usually drawn using textured rectangles, and rendering such rectangles implicitly involves the process of "scaling" when texture sampling is performed).
I'm wondering why do you call this "very slow", because I did some tests and the performance doesn't seem bad. Also have you measured the performance of your rendering code into int[] '_pixels' (unfortunately you haven't provided that code) separately from the bitmap operations, because it might be the slow part.
About your concrete question. As others mentioned, using preallocated buffered graphics and bitmap objects would speed up it a bit.
But do you really need that int[] buffer? BufferedGraphics is already backed internally with a bitmap, so what really happens is:
(1) You fill the int[] buffer
(2) int[] buffer is copied to the new/preallocated Bitmap
(3) Bitmap from step 2 is copied (applying scale) to the BufferedGraphics internal bitmap (via DrawImage)
(4) BufferedGraphics internal bitmap is copied to the screen (via Render)
As you can see, there are a lot of copy operations. The intended usage of BufferedGraphics is:
(1) You fill the BufferedGraphics internal bitmap via drawing methods of the BufferedGraphics.Graphics property. If setup, the Graphics will do the scaling (as well other transformations) for you.
(2) BufferedGraphics internal bitmap is copied to the screen (via Render)
I don't know what your drawing code is doing, but if you can afford it, this definitely should provide the best performance.
Here is my quick and dirty test in case you are interested in:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
namespace Test
{
enum RenderMode { NewBitmap, PreallocatedBitmap, Graphics }
class Screen
{
Control canvas;
public Rectangle area;
int[,] pixels;
BitmapData info;
Bitmap bitmap;
BufferedGraphics buffer;
float scaleX, scaleY;
public RenderMode mode = RenderMode.NewBitmap;
public Screen(Control canvas, Size size)
{
this.canvas = canvas;
var bounds = canvas.DisplayRectangle;
scaleX = (float)bounds.Width / size.Width;
scaleY = (float)bounds.Height / size.Height;
area.Size = size;
info = new BitmapData { Width = size.Width, Height = size.Height, PixelFormat = PixelFormat.Format32bppRgb, Stride = size.Width * 4 };
pixels = new int[size.Height, size.Width];
bitmap = new Bitmap(size.Width, size.Height, info.PixelFormat);
buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds);
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
ApplyMode();
}
public void ApplyMode()
{
buffer.Graphics.ResetTransform();
if (mode == RenderMode.Graphics)
buffer.Graphics.ScaleTransform(scaleX, scaleY);
}
public void FillRectangle(Color color, Rectangle rect)
{
if (mode == RenderMode.Graphics)
{
using (var brush = new SolidBrush(color))
buffer.Graphics.FillRectangle(brush, rect);
}
else
{
rect.Intersect(area);
if (rect.IsEmpty) return;
int colorData = color.ToArgb();
var pixels = this.pixels;
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
pixels[y, x] = colorData;
}
}
public unsafe void Render()
{
if (mode == RenderMode.NewBitmap)
{
var bounds = canvas.DisplayRectangle;
using (var buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds))
{
Bitmap bitmap;
fixed (int* pixels = &this.pixels[0, 0])
bitmap = new Bitmap(info.Width, info.Height, info.Stride, info.PixelFormat, new IntPtr(pixels));
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
buffer.Graphics.DrawImage(bitmap, bounds);
buffer.Render();
}
}
else
{
if (mode == RenderMode.PreallocatedBitmap)
{
fixed (int* pixels = &this.pixels[0, 0])
{
info.Scan0 = new IntPtr(pixels); info.Reserved = 0;
bitmap.LockBits(area, ImageLockMode.WriteOnly | ImageLockMode.UserInputBuffer, info.PixelFormat, info);
bitmap.UnlockBits(info);
}
buffer.Graphics.DrawImage(bitmap, canvas.DisplayRectangle);
}
buffer.Render();
}
}
}
class Game
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var game = new Game();
game.Run();
}
Form form;
Control canvas;
Screen screen;
Level level;
Player player;
private Game()
{
form = new Form();
canvas = new Control { Parent = form, Bounds = new Rectangle(0, 0, 900, 506) };
form.ClientSize = canvas.Size;
screen = new Screen(canvas, new Size(300, 160));
level = new Level { game = this };
player = new Player { game = this };
}
private void Run()
{
bool toggleModeRequest = false;
canvas.MouseClick += (sender, e) => toggleModeRequest = true;
var worker = new Thread(() =>
{
int frameCount = 0;
Stopwatch drawT = new Stopwatch(), applyT = new Stopwatch(), advanceT = Stopwatch.StartNew(), renderT = Stopwatch.StartNew(), infoT = Stopwatch.StartNew();
while (true)
{
if (advanceT.ElapsedMilliseconds >= 3)
{
level.Advance(); player.Advance();
advanceT.Restart();
}
if (renderT.ElapsedMilliseconds >= 8)
{
frameCount++;
drawT.Start(); level.Render(); player.Render(); drawT.Stop();
applyT.Start(); screen.Render(); applyT.Stop();
renderT.Restart();
}
if (infoT.ElapsedMilliseconds >= 1000)
{
double drawS = drawT.ElapsedMilliseconds / 1000.0, applyS = applyT.ElapsedMilliseconds / 1000.0, totalS = drawS + applyS;
var info = string.Format("Render using {0} - Frames:{1:n0} FPS:{2:n0} Draw:{3:p2} Apply:{4:p2}",
screen.mode, frameCount, frameCount / totalS, drawS / totalS, applyS / totalS);
form.BeginInvoke(new Action(() => form.Text = info));
infoT.Restart();
}
if (toggleModeRequest)
{
toggleModeRequest = false;
screen.mode = (RenderMode)(((int)screen.mode + 1) % 3);
screen.ApplyMode();
frameCount = 0; drawT.Reset(); applyT.Reset();
}
}
});
worker.IsBackground = true;
worker.Start();
Application.Run(form);
}
class Level
{
public Game game;
public int pos = 0; bool right = true;
public void Advance() { Game.Advance(ref pos, ref right, 0, game.screen.area.Right - 1); }
public void Render()
{
game.screen.FillRectangle(Color.SaddleBrown, new Rectangle(0, 0, pos, game.screen.area.Height));
game.screen.FillRectangle(Color.DarkGreen, new Rectangle(pos, 0, game.screen.area.Right, game.screen.area.Height));
}
}
class Player
{
public Game game;
public int x = 0, y = 0;
public bool right = true, down = true;
public void Advance()
{
Game.Advance(ref x, ref right, game.level.pos, game.screen.area.Right - 5, 2);
Game.Advance(ref y, ref down, 0, game.screen.area.Bottom - 1, 2);
}
public void Render() { game.screen.FillRectangle(Color.Yellow, new Rectangle(x, y, 4, 4)); }
}
static void Advance(ref int pos, ref bool forward, int minPos, int maxPos, int delta = 1)
{
if (forward) { pos += delta; if (pos < minPos) pos = minPos; else if (pos > maxPos) { pos = maxPos; forward = false; } }
else { pos -= delta; if (pos > maxPos) pos = maxPos; else if (pos < minPos) { pos = minPos; forward = true; } }
}
}
}

Windows Phone XNA animation

I have VS2012 and try to make a simple xna app for Windows Phone 7/8.
I have something like this:
public partial class GamePage : PhoneApplicationPage
{
ContentManager contentManager;
GameTimer timer;
SpriteBatch spriteBatch;
public Texture2D firstSprite { get; set; }
public Vector2 transition = new Vector2(0, 0);
public GamePage()
{
InitializeComponent();
contentManager = (Application.Current as App).Content;
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
timer.Update += OnUpdate;
timer.Draw += OnDraw;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
firstSprite = this.contentManager.Load<Texture2D>("ikona");
timer.Start();
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
timer.Stop();
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
base.OnNavigatedFrom(e);
}
float d = 0;
private void OnUpdate(object sender, GameTimerEventArgs e)
{
TouchCollection touchCollection = TouchPanel.GetState();
foreach (TouchLocation tl in touchCollection)
{
if (tl.State == TouchLocationState.Pressed && tl.Position.X < 240)
{
d = -10;
}
if (tl.State == TouchLocationState.Pressed && tl.Position.X > 240)
{
d = 10;
}
if (tl.State == TouchLocationState.Released)
d = 0;
}
transition += new Vector2(d, 0);
}
private void OnDraw(object sender, GameTimerEventArgs e)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(firstSprite, transition, null, Color.White, 0, new Vector2(0, 0), 1f, SpriteEffects.None, 0);
spriteBatch.End();
}
}
Is it a good way do animate sprite? Will it be animated same way on every device? When I was testing it on lumia920 it wasn't very smooth, so I think I do it badly.
And second thing. I want to move picture left when I press left half of the screen and move right when I press right side. It partly works but when I press left (it moves left), then at the same time press right (it moves right), and then release right, picture stops. I thought it would move again left. How can I achieve this?
/edit/
And what about touch handling?
I'm not sure how it's animated as I'm not familiar with TouchCollection.
But using XNA to animate works like this for most of the solutions out there:
You have your texture which is a spritesheet I assume containing all the frames for your animated sprite.
You use a Rectangle which will hold Position and size on the screen.
You also use something called a source rectangle, which is a rectangle that represents an area inside your sprite, which will stretch in your position rectangle. Here is an image:
Rectangle A is your position + size rectangle, and rectangle B is your source rectangle.
To create an animation you say the following ( pseudo code ):
int timer = 0;
Rectangle position = new Rectangle(100, 100, 80, 80); // position 100x, 100y, and size is 80 width and height.
Rectangle source = new Rectangle(0, 0, 80, 80); // Position on the spritesheet is 0, 0 which should be first frame, and the frames are all 80*80 pixels in size.
private void OnUpdate(object sender, GameTimerEventArgs e)
{
timer += e.ElapsedTime.TotalMilliseconds;
if(timer > 30) // If it has been 0.03 seconds = 33 frames per second
{
timer = 0; // reset timer
source.X += 80; // Move x value to next frame
if(source.X > widthOfYourSpriteSheet) source.X = 0; // Reset the animation to the beginning when we are at the end
}
}
Your draw function will look pretty much the same, except for the sourcerectangle argument you will put in it.

Categories