Browse Source

Improved sprite drawing routines.

* Sprites now use world space coordinates by default. This behavior can be
      changed using the CoordinateSystem property.
    * Exposed effective size (sprite size with scaling) and drawn size
      properties to the Sprite object.
    * Added a CameraView rectangle to the SdlWindow to control what is shown in
      the world space view.
    * Added some arithmetic functions to the Vector2<T> class.
    * Sample.Sprites: Rewrote the view windowing code. It actually works
      correctly now. :)
    * Sample.Sprites: Changed player movement code so that they can no longer
      fly off the edges of the screen.
    * Updated NuGet package information.
improved_timing
Ian Burgmyer 5 years ago
parent
commit
c02010965f
  1. 8
      DotSDL/DotSDL.csproj
  2. 14
      DotSDL/Graphics/CoordinateSystem.cs
  3. 39
      DotSDL/Graphics/SdlWindow.cs
  4. 38
      DotSDL/Graphics/Sprite.cs
  5. 62
      DotSDL/Math/Vector/Vector2.cs
  6. 4
      README.md
  7. 13
      Samples/Sample.Sprites/Player.cs
  8. 70
      Samples/Sample.Sprites/Window.cs

8
DotSDL/DotSDL.csproj

@ -5,10 +5,13 @@
<AssemblyVersion>0.0.1.0</AssemblyVersion>
<FileVersion>0.0.1.0</FileVersion>
<Description>A framework for .NET designed to interoperate with SDL2.</Description>
<Copyright>(c) 2017 Ian Burgmyer</Copyright>
<Copyright>(c) 2019 Ian Burgmyer</Copyright>
<Authors>Ian Burgmyer</Authors>
<Company>The DotSDL Team</Company>
<LangVersion>7.2</LangVersion>
<Title>DotSDL</Title>
<PackageProjectUrl>https://github.com/Spectere/DotSDL</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/Spectere/DotSDL/blob/master/LICENSE.txt</PackageLicenseUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@ -16,4 +19,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
</ItemGroup>
</Project>

14
DotSDL/Graphics/CoordinateSystem.cs

@ -0,0 +1,14 @@
namespace DotSDL.Graphics {
public enum CoordinateSystem {
/// <summary>
/// Uses screen space coordinates. This directly maps to pixels in the window.
/// </summary>
ScreenSpace,
/// <summary>
/// Uses world space coordinates. This maps to a location based on the position of
/// the camera, or view.
/// </summary>
WorldSpace
}
}

39
DotSDL/Graphics/SdlWindow.cs

@ -61,6 +61,9 @@ namespace DotSDL.Graphics {
/// <summary>Gets or sets the amount of time, in milliseconds, between game (logic) updates.</summary>
public uint GameUpdateTicks { get; set; }
/// <summary>Gets a <see cref="Rectangle"/> that can be manipulated to modify how much of the scene is displayed.</summary>
public Rectangle CameraView { get; }
/// <summary>The list of active <see cref="Sprite"/> objects.</summary>
public SpriteList Sprites { get; }
@ -177,6 +180,11 @@ namespace DotSDL.Graphics {
};
Background.CreateTexture();
CameraView = new Rectangle(
new Point(0, 0),
new Point(WindowWidth, WindowHeight)
);
Sprites = new SpriteList(_renderer);
IsDestroyed = false;
@ -202,6 +210,8 @@ namespace DotSDL.Graphics {
Render.SetRenderTarget(_renderer, _texture);
// Blit the Canvas to the target texture.
Background.Clipping.Position = CameraView.Position;
Background.Clipping.Size = CameraView.Size;
Background.UpdateTexture();
unsafe {
var canvasClippingRect = Background.Clipping.SdlRect;
@ -271,11 +281,30 @@ namespace DotSDL.Graphics {
Render.SetRenderTarget(_renderer, _texture);
foreach(var sprite in Sprites.Where(e => e.Shown).OrderBy(e => e.ZOrder)) {
var srcRect = sprite.Clipping.SdlRect;
var drawSize = new Point(
(int)(srcRect.W * sprite.Scale.X),
(int)(srcRect.H * sprite.Scale.Y)
);
var destRect = new Rectangle(sprite.Position, drawSize).SdlRect;
var drawSize = sprite.DrawSize;
Rectangle dest;
if(sprite.CoordinateSystem == CoordinateSystem.ScreenSpace) {
dest = new Rectangle(sprite.Position, drawSize);
} else {
// Create a set of world coordinates based on the position of the camera
// and this sprite.
var relPosition = new Point(sprite.Position - CameraView.Position);
var screenPosition = new Point(
(int)((float)relPosition.X / CameraView.Size.X * TextureWidth),
(int)((float)relPosition.Y / CameraView.Size.Y * TextureHeight)
);
var scaleFactorX = (float)TextureWidth / CameraView.Size.X;
var scaleFactorY = (float)TextureHeight / CameraView.Size.Y;
var size = new Point(
(int)(drawSize.X * scaleFactorX),
(int)(drawSize.Y * scaleFactorY)
);
dest = new Rectangle(screenPosition, size);
}
var destRect = dest.SdlRect;
unsafe {
var srcRectPtr = new IntPtr(&srcRect);

38
DotSDL/Graphics/Sprite.cs

@ -7,6 +7,9 @@ namespace DotSDL.Graphics {
/// Represents a graphical, two-dimensional object in a program.
/// </summary>
public class Sprite : Canvas {
private Point _effectiveSize = new Point();
private Vector2<float> _scale;
/// <summary>
/// The position on the screen where the <see cref="Sprite"/> should be drawn.
/// </summary>
@ -32,7 +35,14 @@ namespace DotSDL.Graphics {
/// <summary>
/// The scale of the <see cref="Sprite"/>. 1.0f is 100%.
/// </summary>
public Vector2<float> Scale { get; set; }
public Vector2<float> Scale {
get => _scale;
set {
_scale = value;
_effectiveSize.X = (int)(Width * _scale.X);
_effectiveSize.Y = (int)(Height * _scale.Y);
}
}
/// <inheritdoc/>
public override ScalingQuality ScalingQuality {
@ -47,6 +57,32 @@ namespace DotSDL.Graphics {
}
}
/// <summary>
/// Gets the size of the sprite as it would be drawn to the screen, in pixels, taking
/// both scaling and clipping into account.
/// </summary>
/// <remarks>
/// This value is calculated every time it's called. If you plan to the results of this
/// in a loop, be sure to save the results to a variable and perform operations on that
/// instead of calling this multiple times.
/// </remarks>
public Point DrawSize => new Point(
(int)(Clipping.Size.X * Scale.X),
(int)(Clipping.Size.Y * Scale.Y)
);
/// <summary>
/// Gets the effective size of the sprite. This is the size of the sprite, in pixels,
/// after taking scaling into account.
/// </summary>
public Point EffectiveSize => _effectiveSize;
/// <summary>
/// Defines the coordinate system that this sprite should use. This defaults to
/// <see cref="CoordinateSystem.WorldSpace"/>.
/// </summary>
public CoordinateSystem CoordinateSystem { get; set; } = CoordinateSystem.WorldSpace;
/// <summary>
/// <c>true</c> if the sprite should be drawn to the screen, otherwise <c>false</c>.
/// </summary>

62
DotSDL/Math/Vector/Vector2.cs

@ -73,6 +73,57 @@ namespace DotSDL.Math.Vector {
return !(left == right);
}
/// <summary>
/// Adds two <see cref="Vector2{T}"/> objects together.
/// </summary>
/// <param name="left">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="right">The <see cref="Vector2{T}"/> to use as the operand.</param>
/// <returns>A <see cref="Vector2{T}"/> containing the result.</returns>
public static Vector2<T> operator +(Vector2<T> left, Vector2<T> right) =>
new Vector2<T>(
(dynamic)left.X + (dynamic)right.X,
(dynamic)left.Y + (dynamic)right.Y
);
/// <summary>
/// Adds a <see cref="Vector2{T}"/> object with a scalar value.
/// </summary>
/// <param name="vec">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="scalar">The <see cref="T"/> to use as the operand.</param>
/// <returns>A <see cref="Vector2{T}"/> containing the result.</returns>
public static Vector2<T> operator +(Vector2<T> vec, T scalar) =>
new Vector2<T>((dynamic)vec.X + scalar, (dynamic)vec.Y + scalar);
/// <summary>
/// Subtracts two <see cref="Vector2{T}"/> objects.
/// </summary>
/// <param name="left">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="right">The <see cref="Vector2{T}"/> to use as the operand.</param>
/// <returns>A <see cref="Vector2{T}"/> containing the result.</returns>
public static Vector2<T> operator -(Vector2<T> left, Vector2<T> right) =>
new Vector2<T>(
(dynamic)left.X - (dynamic)right.X,
(dynamic)left.Y - (dynamic)right.Y
);
/// <summary>
/// Multiples a <see cref="Vector2{T}"/> by a scalar value.
/// </summary>
/// <param name="vec">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="scalar">The <see cref="T"/> to use as the operand.</param>
/// <returns>A <see cref="Vector2{T}"/> containing the result.</returns>
public static Vector2<T> operator *(Vector2<T> vec, T scalar) =>
new Vector2<T>((dynamic)vec.X * scalar, (dynamic)vec.Y + scalar);
/// <summary>
/// Subtracts a scalar value from a <see cref="Vector2{T}"/>.
/// </summary>
/// <param name="vec">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="scalar">The <see cref="T"/> to use as the operand.</param>
/// <returns>A <see cref="Vector2{T}"/> containing the result.</returns>
public static Vector2<T> operator -(Vector2<T> vec, T scalar) =>
new Vector2<T>((dynamic)vec.X - scalar, (dynamic)vec.Y - scalar);
/// <summary>Returns a new <see cref="Vector2{T}"/> with both X and Y set to 1.</summary>
public static Vector2<T> One => new Vector2<T>(VectorBase<T>.GetOne());
@ -85,6 +136,15 @@ namespace DotSDL.Math.Vector {
/// <summary>Returns a new <see cref="Vector2{T}"/> with both X and Y set to 0.</summary>
public static Vector2<T> Zero => new Vector2<T>(VectorBase<T>.GetZero());
/// <summary>
/// Calculates the dot product of two <see cref="Vector2{T}"/> objects.
/// </summary>
/// <param name="left">The <see cref="Vector2{T}"/> to use as the operator.</param>
/// <param name="right">The <see cref="Vector2{T}"/> to use as the operand.</param>
/// <returns>A <see cref="T"/> containing the result.</returns>
public T Dot(Vector2<T> left, Vector2<T> right) =>
(dynamic)left.X * (dynamic)right.X + (dynamic)left.Y + (dynamic)right.Y;
/// <inheritdoc/>
public bool Equals(Vector2<T> other) {
if(ReferenceEquals(null, other)) return false;
@ -108,5 +168,7 @@ namespace DotSDL.Math.Vector {
return (EqualityComparer<T>.Default.GetHashCode(X) * 397) ^ EqualityComparer<T>.Default.GetHashCode(Y);
}
}
public override string ToString() => $"{X}, {Y}";
}
}

4
README.md

@ -19,7 +19,9 @@ At this time, DotSDL supports the following features:
* Keyboard input.
* Window events.
* Graphics
* A single 32-bit ARGB canvas (useful for pixel plotting).
* A single 32-bit ARGB background canvas (useful for pixel plotting).
* Sprite drawing, with support for alpha blending, world and screen space
rendering, and scaling.
* Power
* Battery state.

13
Samples/Sample.Sprites/Player.cs

@ -1,17 +1,20 @@
using DotSDL.Graphics;
using System;
using DotSDL.Graphics;
namespace Sample.Sprites {
public class Player : Sprite {
private const int Radius = 15;
private const int Size = 32;
private int _speed;
private readonly int _speed;
private readonly Point _limit;
public Player(Color color, int speed, int playerId) : base(Size, Size, playerId) {
public Player(Color color, int speed, int playerId, Point limit) : base(Size, Size, playerId) {
const byte baseAlpha = 128;
ColorMod = color;
_speed = speed;
_limit = limit;
Shown = true;
@ -51,9 +54,11 @@ namespace Sample.Sprites {
}
public void Move(Point delta) {
// TODO: Support some basic vector arithmetic for points.
Position.X += delta.X * _speed;
Position.Y += delta.Y * _speed;
Position.X = Math.Clamp(Position.X, 0, (int)(_limit.X - (Size * Scale.X)));
Position.Y = Math.Clamp(Position.Y, 0, (int)(_limit.Y - (Size * Scale.Y)));
}
private void PlotMirroredPoints(int x, int y, byte gray, byte alpha) {

70
Samples/Sample.Sprites/Window.cs

@ -5,10 +5,11 @@ using System;
namespace Sample.Sprites {
public class Window : SdlWindow {
//private int _camX = 0, _camY = 0, _deltaX = 2, _deltaY = 1;
private Player _player1, _player2;
private Point _player1Delta, _player2Delta;
private const int ViewMargin = 16;
public Window(int scale) : base("Sprites Test",
new Point(WindowPosUndefined, WindowPosUndefined),
256 * scale, 196 * scale,
@ -76,8 +77,9 @@ namespace Sample.Sprites {
}
private void GeneratePlayers() {
_player1 = new Player(new Color { R = 255, G = 64, B = 64 }, 2, 1);
_player2 = new Player(new Color { R = 64, G = 64, B = 255 }, 3, 2);
var limit = new Point(1024, 1024);
_player1 = new Player(new Color { R = 255, G = 64, B = 64 }, 2, 1, limit);
_player2 = new Player(new Color { R = 64, G = 64, B = 255 }, 3, 2, limit);
_player1.Position.X = 24;
_player1.Position.Y = 24;
@ -157,41 +159,33 @@ namespace Sample.Sprites {
_player1.Move(_player1Delta);
_player2.Move(_player2Delta);
var x1 = _player1.Position.X <= _player2.Position.X
? _player1.Position.X - (_player1.Width * _player1.Scale.X)
: _player2.Position.X - (_player2.Width * _player2.Scale.X);
var x2 = _player1.Position.X >= _player2.Position.X
? _player1.Position.X + (_player1.Width * _player1.Scale.X)
: _player2.Position.X + (_player2.Width * _player2.Scale.X);
var y1 = _player1.Position.Y <= _player2.Position.Y
? _player1.Position.Y - (_player1.Height * _player1.Scale.Y)
: _player2.Position.Y - (_player2.Height * _player2.Scale.Y);
var y2 = _player1.Position.Y >= _player2.Position.Y
? _player1.Position.Y + (_player1.Height * _player1.Scale.Y)
: _player2.Position.Y + (_player2.Height * _player2.Scale.Y);
x1 = x1 < 0 ? 0 : x1;
x2 = x2 >= Background.Width ? Background.Width : x2;
y1 = y1 < 0 ? 0 : y1;
y2 = y2 >= Background.Height ? Background.Height : y2;
Background.Clipping.Position.X = (int)x1;
Background.Clipping.Position.Y = (int)y1;
Background.Clipping.Size.X = (int)(x2 - x1);
Background.Clipping.Size.Y = (int)(y2 - y1);
WindowTitle = $"({(int)x1} {(int)y2}), ({(int)(x2 - x1)}, {(int)(y2 - y1)}) / ({Background.Width}, {Background.Height})";
/*_camX += _deltaX;
_camY += _deltaY;
if(_camX + Background.Clipping.Size.X >= Background.Width || _camX <= 0)
_deltaX = -_deltaX;
if(_camY + Background.Clipping.Size.Y >= Background.Height || _camY <= 0)
_deltaY = -_deltaY;
Background.Clipping.Position.X = _camX;
Background.Clipping.Position.Y = _camY;*/
var p1End = _player1.Position + _player1.DrawSize;
var p2End = _player2.Position + _player2.DrawSize;
var x1 = (_player1.Position.X <= _player2.Position.X
? _player1.Position.X
: _player2.Position.X) - ViewMargin;
var x2 = (p1End.X >= p2End.X ? p1End.X : p2End.X) + ViewMargin;
var y1 = (_player1.Position.Y <= _player2.Position.Y
? _player1.Position.Y
: _player2.Position.Y) - ViewMargin;
var y2 = (p1End.Y >= p2End.Y ? p1End.Y : p2End.Y) + ViewMargin;
x1 = Math.Clamp(x1, 0, Background.Width - ViewMargin);
x2 = Math.Clamp(x2, ViewMargin, Background.Width);
y1 = Math.Clamp(y1, 0, Background.Height - ViewMargin);
y2 = Math.Clamp(y2, ViewMargin, Background.Height);
CameraView.Position.X = x1;
CameraView.Position.Y = y1;
CameraView.Size.X = x2 - x1;
CameraView.Size.Y = y2 - y1;
WindowTitle = $"({x1} {y2}), ({x2 - x1}, {y2 - y1})" +
$" | P1: {_player1.Position} | P2: {_player2.Position}";
}
}
}

Loading…
Cancel
Save