diff --git a/DotSDL/DotSDL.csproj b/DotSDL/DotSDL.csproj
index 31f203e..fe9d32b 100644
--- a/DotSDL/DotSDL.csproj
+++ b/DotSDL/DotSDL.csproj
@@ -5,10 +5,13 @@
0.0.1.0
0.0.1.0
A framework for .NET designed to interoperate with SDL2.
- (c) 2017 Ian Burgmyer
+ (c) 2019 Ian Burgmyer
Ian Burgmyer
The DotSDL Team
7.2
+
DotSDL
+ https://github.com/Spectere/DotSDL
+ https://github.com/Spectere/DotSDL/blob/master/LICENSE.txt
True
@@ -16,4 +19,7 @@
True
+
+
+
\ No newline at end of file
diff --git a/DotSDL/Graphics/CoordinateSystem.cs b/DotSDL/Graphics/CoordinateSystem.cs
new file mode 100644
index 0000000..e061a61
--- /dev/null
+++ b/DotSDL/Graphics/CoordinateSystem.cs
@@ -0,0 +1,14 @@
+namespace DotSDL.Graphics {
+ public enum CoordinateSystem {
+ ///
+ /// Uses screen space coordinates. This directly maps to pixels in the window.
+ ///
+ ScreenSpace,
+
+ ///
+ /// Uses world space coordinates. This maps to a location based on the position of
+ /// the camera, or view.
+ ///
+ WorldSpace
+ }
+}
diff --git a/DotSDL/Graphics/SdlWindow.cs b/DotSDL/Graphics/SdlWindow.cs
index cf5784b..5b39347 100644
--- a/DotSDL/Graphics/SdlWindow.cs
+++ b/DotSDL/Graphics/SdlWindow.cs
@@ -61,6 +61,9 @@ namespace DotSDL.Graphics {
/// Gets or sets the amount of time, in milliseconds, between game (logic) updates.
public uint GameUpdateTicks { get; set; }
+ /// Gets a that can be manipulated to modify how much of the scene is displayed.
+ public Rectangle CameraView { get; }
+
/// The list of active objects.
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);
diff --git a/DotSDL/Graphics/Sprite.cs b/DotSDL/Graphics/Sprite.cs
index 93c162f..a787122 100644
--- a/DotSDL/Graphics/Sprite.cs
+++ b/DotSDL/Graphics/Sprite.cs
@@ -7,6 +7,9 @@ namespace DotSDL.Graphics {
/// Represents a graphical, two-dimensional object in a program.
///
public class Sprite : Canvas {
+ private Point _effectiveSize = new Point();
+ private Vector2 _scale;
+
///
/// The position on the screen where the should be drawn.
///
@@ -32,7 +35,14 @@ namespace DotSDL.Graphics {
///
/// The scale of the . 1.0f is 100%.
///
- public Vector2 Scale { get; set; }
+ public Vector2 Scale {
+ get => _scale;
+ set {
+ _scale = value;
+ _effectiveSize.X = (int)(Width * _scale.X);
+ _effectiveSize.Y = (int)(Height * _scale.Y);
+ }
+ }
///
public override ScalingQuality ScalingQuality {
@@ -47,6 +57,32 @@ namespace DotSDL.Graphics {
}
}
+ ///
+ /// Gets the size of the sprite as it would be drawn to the screen, in pixels, taking
+ /// both scaling and clipping into account.
+ ///
+ ///
+ /// 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.
+ ///
+ public Point DrawSize => new Point(
+ (int)(Clipping.Size.X * Scale.X),
+ (int)(Clipping.Size.Y * Scale.Y)
+ );
+
+ ///
+ /// Gets the effective size of the sprite. This is the size of the sprite, in pixels,
+ /// after taking scaling into account.
+ ///
+ public Point EffectiveSize => _effectiveSize;
+
+ ///
+ /// Defines the coordinate system that this sprite should use. This defaults to
+ /// .
+ ///
+ public CoordinateSystem CoordinateSystem { get; set; } = CoordinateSystem.WorldSpace;
+
///
/// true if the sprite should be drawn to the screen, otherwise false.
///
diff --git a/DotSDL/Math/Vector/Vector2.cs b/DotSDL/Math/Vector/Vector2.cs
index d16779b..441e99e 100644
--- a/DotSDL/Math/Vector/Vector2.cs
+++ b/DotSDL/Math/Vector/Vector2.cs
@@ -73,6 +73,57 @@ namespace DotSDL.Math.Vector {
return !(left == right);
}
+ ///
+ /// Adds two objects together.
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public static Vector2 operator +(Vector2 left, Vector2 right) =>
+ new Vector2(
+ (dynamic)left.X + (dynamic)right.X,
+ (dynamic)left.Y + (dynamic)right.Y
+ );
+
+ ///
+ /// Adds a object with a scalar value.
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public static Vector2 operator +(Vector2 vec, T scalar) =>
+ new Vector2((dynamic)vec.X + scalar, (dynamic)vec.Y + scalar);
+
+ ///
+ /// Subtracts two objects.
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public static Vector2 operator -(Vector2 left, Vector2 right) =>
+ new Vector2(
+ (dynamic)left.X - (dynamic)right.X,
+ (dynamic)left.Y - (dynamic)right.Y
+ );
+
+ ///
+ /// Multiples a by a scalar value.
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public static Vector2 operator *(Vector2 vec, T scalar) =>
+ new Vector2((dynamic)vec.X * scalar, (dynamic)vec.Y + scalar);
+
+ ///
+ /// Subtracts a scalar value from a .
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public static Vector2 operator -(Vector2 vec, T scalar) =>
+ new Vector2((dynamic)vec.X - scalar, (dynamic)vec.Y - scalar);
+
/// Returns a new with both X and Y set to 1.
public static Vector2 One => new Vector2(VectorBase.GetOne());
@@ -85,6 +136,15 @@ namespace DotSDL.Math.Vector {
/// Returns a new with both X and Y set to 0.
public static Vector2 Zero => new Vector2(VectorBase.GetZero());
+ ///
+ /// Calculates the dot product of two objects.
+ ///
+ /// The to use as the operator.
+ /// The to use as the operand.
+ /// A containing the result.
+ public T Dot(Vector2 left, Vector2 right) =>
+ (dynamic)left.X * (dynamic)right.X + (dynamic)left.Y + (dynamic)right.Y;
+
///
public bool Equals(Vector2 other) {
if(ReferenceEquals(null, other)) return false;
@@ -108,5 +168,7 @@ namespace DotSDL.Math.Vector {
return (EqualityComparer.Default.GetHashCode(X) * 397) ^ EqualityComparer.Default.GetHashCode(Y);
}
}
+
+ public override string ToString() => $"{X}, {Y}";
}
}
diff --git a/README.md b/README.md
index 4b3ccf3..fe081e4 100644
--- a/README.md
+++ b/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.
diff --git a/Samples/Sample.Sprites/Player.cs b/Samples/Sample.Sprites/Player.cs
index c71f323..74fde37 100644
--- a/Samples/Sample.Sprites/Player.cs
+++ b/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) {
diff --git a/Samples/Sample.Sprites/Window.cs b/Samples/Sample.Sprites/Window.cs
index 53cca15..81529b0 100644
--- a/Samples/Sample.Sprites/Window.cs
+++ b/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}";
}
}
}