Compare commits

..

2 Commits

Author SHA1 Message Date
Ian Burgmyer 046bbf4320 Added some basic collision properties to sprites. 5 years ago
Ian Burgmyer b8edb976f7 Unified the drawing routines. 5 years ago
  1. 12
      DotSDL/DotSDL.csproj
  2. 105
      DotSDL/Graphics/SdlWindow.cs
  3. 53
      DotSDL/Graphics/Sprite.cs
  4. 13
      DotSDL/Platform/BasePlatform.cs
  5. 14
      DotSDL/Platform/IPlatform.cs
  6. 29
      DotSDL/Platform/Interop/Fallback/Timing.cs
  7. 36
      DotSDL/Platform/Interop/Posix/Timing.cs
  8. 18
      DotSDL/Platform/PlatformFactory.cs
  9. 10
      DotSDL/Platform/PosixPlatform.cs
  10. 6
      README.md
  11. 2
      Samples/Sample.Audio/Program.cs
  12. 5
      Samples/Sample.Audio/Sample.Audio.csproj
  13. 4
      Samples/Sample.Audio/Window.cs
  14. 2
      Samples/Sample.BasicPixels/Program.cs
  15. 5
      Samples/Sample.BasicPixels/Sample.BasicPixels.csproj
  16. 2
      Samples/Sample.Layers/Program.cs
  17. 5
      Samples/Sample.Layers/Sample.Layers.csproj
  18. 2
      Samples/Sample.Layers/Window.cs
  19. 2
      Samples/Sample.Power/Program.cs
  20. 5
      Samples/Sample.Power/Sample.Power.csproj
  21. 2
      Samples/Sample.Sprites/Program.cs
  22. 5
      Samples/Sample.Sprites/Sample.Sprites.csproj
  23. 2
      Samples/Sample.Sprites/Window.cs

12
DotSDL/DotSDL.csproj

@ -1,19 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.0.2.0</Version>
<AssemblyVersion>0.0.2.0</AssemblyVersion>
<FileVersion>0.0.2.0</FileVersion>
<Version>0.0.1.0</Version>
<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) 2019 Ian Burgmyer</Copyright>
<Authors>Ian Burgmyer</Authors>
<Company>The DotSDL Team</Company>
<LangVersion>7.3</LangVersion>
<LangVersion>7.2</LangVersion>
<Title>DotSDL</Title>
<PackageProjectUrl>https://github.com/Spectere/DotSDL</PackageProjectUrl>
<PackageLicenseExpression>Zlib</PackageLicenseExpression>
<PackageTags>SDL; graphics; audio; input</PackageTags>
<PackageVersion>0.0.2-alpha</PackageVersion>
<PackageLicenseUrl>https://github.com/Spectere/DotSDL/blob/master/LICENSE.txt</PackageLicenseUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

105
DotSDL/Graphics/SdlWindow.cs

@ -1,10 +1,8 @@
using DotSDL.Events;
using DotSDL.Input;
using DotSDL.Interop.Core;
using DotSDL.Platform;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace DotSDL.Graphics {
@ -21,26 +19,15 @@ namespace DotSDL.Graphics {
private IntPtr _texture;
private bool _hasTexture;
private float _videoUpdateRate, _gameUpdateRate;
private float _videoUpdateMs, _gameUpdateMs;
private bool _videoUpdateUncapped, _gameUpdateUncapped;
private bool _running;
private float _nextVideoUpdate;
private float _nextGameUpdate;
private float _updateDelta;
private uint _nextVideoUpdate;
private uint _nextGameUpdate;
private List<Canvas> _drawList = new List<Canvas>();
private ScalingQuality _scalingQuality = ScalingQuality.Nearest;
/// <summary>
/// An <see cref="IPlatform"/> that contains native functions appropriate to
/// the platform that this application is running on.
/// </summary>
protected IPlatform Platform { get; } = PlatformFactory.GetPlatform();
/// <summary>Gets the background layer of this window. This is equivalent to accessing Layers[0].</summary>
public Canvas Background => Layers[0];
@ -81,35 +68,11 @@ namespace DotSDL.Graphics {
public int RenderHeight { get; }
/// <summary>The amount of time, in milliseconds, from when the application was started.</summary>
public float MillisecondsElapsed { get; private set; } = 0.0f;
/// <summary>Gets or sets the rate, in hertz, between video updates.</summary>
public float VideoUpdateRate {
get => _videoUpdateUncapped ? 0 : _videoUpdateRate;
set {
_videoUpdateRate = value;
if(System.Math.Abs(_videoUpdateRate) < 1.0f) {
_videoUpdateUncapped = true;
} else {
_videoUpdateUncapped = false;
_videoUpdateMs = 1000 / _videoUpdateRate;
}
}
}
/// <summary>Gets or sets the rate, in hertz, between game (logic) updates.</summary>
public float GameUpdateRate {
get => _gameUpdateUncapped ? 0 : _gameUpdateRate;
set {
_gameUpdateRate = value;
if(System.Math.Abs(_gameUpdateRate) < 1.0f) {
_gameUpdateUncapped = true;
} else {
_gameUpdateUncapped = false;
_gameUpdateMs = 1000 / _gameUpdateRate;
}
}
}
public uint TicksElapsed => Timer.GetTicks();
/// <summary>Gets or sets the amount of time, in milliseconds, between video updates.</summary>
public uint VideoUpdateTicks { get; set; }
/// <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; }
@ -319,11 +282,11 @@ namespace DotSDL.Graphics {
/// <summary>
/// Handles updating the application logic for the <see cref="SdlWindow"/>.
/// </summary>
private void BaseUpdate(float delta) {
private void BaseUpdate() {
if(IsDestroyed) return;
Events.EventHandler.ProcessEvents();
OnUpdate(delta); // Call the overridden Update function.
OnUpdate(); // Call the overridden Update function.
}
/// <summary>
@ -455,40 +418,26 @@ namespace DotSDL.Graphics {
/// A game loop that calls the <see cref="SdlWindow"/> update and draw functions.
/// </summary>
private void Loop() {
long MsToNs(float ms) => (long)(ms * 1000000);
var sw = new Stopwatch();
_running = true;
while(_running) {
sw.Restart();
var ticks = TicksElapsed;
if(_nextGameUpdate <= 0 || _gameUpdateUncapped) {
BaseUpdate(_updateDelta);
_updateDelta = 0;
_nextGameUpdate += _gameUpdateMs;
if(ticks > _nextGameUpdate || GameUpdateTicks == 0) {
_nextGameUpdate = ticks + GameUpdateTicks;
BaseUpdate();
}
if(_nextVideoUpdate <= 0 || _videoUpdateUncapped) {
if(ticks > _nextVideoUpdate || VideoUpdateTicks == 0) {
_nextVideoUpdate = ticks + VideoUpdateTicks;
BaseDraw();
_nextVideoUpdate += _videoUpdateMs;
}
var cycleElapsed = (float)sw.Elapsed.TotalMilliseconds;
MillisecondsElapsed += cycleElapsed;
_nextGameUpdate -= cycleElapsed;
_nextVideoUpdate -= cycleElapsed;
_updateDelta += cycleElapsed;
if(VideoUpdateTicks <= 0 && GameUpdateTicks <= 0) continue; // Cook the CPU!
if(!_videoUpdateUncapped && !_gameUpdateUncapped) {
var waitMs = _nextGameUpdate > _nextVideoUpdate ? _nextVideoUpdate : _nextGameUpdate;
if(waitMs > 0)
Platform.Nanosleep(MsToNs(waitMs));
_updateDelta += waitMs;
_nextGameUpdate -= waitMs;
_nextVideoUpdate -= waitMs;
}
var updateTicks = (long)(_nextGameUpdate > _nextVideoUpdate ? _nextVideoUpdate : _nextGameUpdate) - TicksElapsed;
if(updateTicks > 0)
Timer.Delay((uint)updateTicks);
}
}
@ -529,7 +478,7 @@ namespace DotSDL.Graphics {
/// <summary>
/// Called every time the application logic update runs.
/// </summary>
protected virtual void OnUpdate(float delta) { }
protected virtual void OnUpdate() { }
/// <summary>
/// Removes a layer from the layer stack.
@ -551,17 +500,17 @@ namespace DotSDL.Graphics {
/// <summary>
/// Displays the window and begins executing code that's associated with it.
/// </summary>
/// <param name="updateRate">The desired number of video and game logic updates per second. 0 causes the display and game to be updated as quickly as possible.</param>
public void Start(float updateRate) => Start(updateRate, updateRate);
/// <param name="updateRate">The desired number of milliseconds between frames and game logic updates. 0 causes the display and game to be continuously updated.</param>
public void Start(uint updateRate) => Start(updateRate, updateRate);
/// <summary>
/// Displays the window and begins executing code that's associated with it.
/// </summary>
/// <param name="drawRate">The desired number of draw calls per second. 0 causes the display to be updated as quickly as possible.</param>
/// <param name="updateRate">The desired number of game logic updates per second. 0 causes the game to be updated as quickly as possible.</param>
public void Start(float drawRate, float updateRate) {
VideoUpdateRate = drawRate;
GameUpdateRate = updateRate;
/// <param name="drawRate">The desired number of milliseconds between draw calls. 0 causes the display to be continuously updated.</param>
/// <param name="updateRate">The desired number of milliseconds between game logic updates. 0 causes the game to be continuously updated.</param>
public void Start(uint drawRate, uint updateRate) {
VideoUpdateTicks = drawRate;
GameUpdateTicks = updateRate;
BaseLoad();
Video.ShowWindow(_window);

53
DotSDL/Graphics/Sprite.cs

@ -9,6 +9,8 @@ namespace DotSDL.Graphics {
public class Sprite : Canvas {
private readonly Point _effectiveSize = new Point();
private Vector2<float> _scale;
private bool _collisionBoxSet;
private Rectangle _collisionBox;
/// <summary>
/// The position on the screen where the <see cref="Sprite"/> should be drawn.
@ -41,6 +43,9 @@ namespace DotSDL.Graphics {
_scale = value;
_effectiveSize.X = (int)(Width * _scale.X);
_effectiveSize.Y = (int)(Height * _scale.Y);
if(!_collisionBoxSet)
_collisionBox = new Rectangle(new Point(0, 0), _effectiveSize);
}
}
@ -83,6 +88,22 @@ namespace DotSDL.Graphics {
/// </summary>
public CoordinateSystem CoordinateSystem { get; set; } = CoordinateSystem.WorldSpace;
/// <summary>
/// Gets or sets the size and position of the collision box for this <see cref="Sprite"/>.
/// </summary>
public Rectangle CollisionBox {
get => _collisionBox;
set {
_collisionBox = value;
_collisionBoxSet = true;
}
}
/// <summary>
/// Gets or sets whether or not collision calculations are performed on this <see cref="Sprite"/>.
/// </summary>
public bool HasCollision { get; set; }
/// <summary>
/// Initializes a new <see cref="Sprite"/>.
/// </summary>
@ -163,6 +184,35 @@ namespace DotSDL.Graphics {
(int)(Clipping.Size.Y * _scale.Y / 2)
);
/// <summary>
/// Determines whether or not a given point collides with this <see cref="Sprite"/>. By default
/// this will test the point against the sprite's <see cref="CollisionBox"/>, but this method
/// can be overridden.
/// </summary>
/// <param name="point">The <see cref="Point"/> to check.</param>
/// <returns><c>true</c> if this object's collision is enabled and this sprite's collision
/// routine determines that the given pixel collides with the sprite, otherwise <c>false</c>.</returns>
public virtual bool CheckCollision(Point point) {
if(!HasCollision) return false;
return point.X >= CollisionBox.Position.X + Position.X
&& point.X <= CollisionBox.Position.X + CollisionBox.Size.X + Position.X
&& point.Y >= CollisionBox.Position.Y + Position.Y
&& point.Y <= CollisionBox.Position.Y + CollisionBox.Size.Y + Position.Y;
}
/// <summary>
/// Determines whether or not a given point collides with this <see cref="Sprite"/>. By default
/// this will test the point against the sprite's <see cref="CollisionBox"/>. If you wish to
/// override this sprite's collision routine, override the <see cref="CheckCollision(DotSDL.Graphics.Point)"/>
/// method instead.
/// </summary>
/// <param name="x">The X coordinate of the point to check.</param>
/// <param name="y">The Y coordinate of the point to check.</param>
/// <returns><c>true</c> if this object's collision is enabled and this sprite's collision
/// routine determines that the given pixel collides with the sprite, otherwise <c>false</c>.</returns>
public bool CheckCollision(int x, int y) => CheckCollision(new Point(x, y));
/// <inheritdoc/>
internal override void CreateTexture() {
CreateTexture(Render.TextureAccess.Static);
@ -173,7 +223,8 @@ namespace DotSDL.Graphics {
/// <see cref="Canvas.Pixels"/> array is changed after adding this sprite to the sprite list associated
/// with the application's <see cref="SdlWindow"/>.
/// </summary>
/// <returns><c>true</c> if the texture was successfully updated, otherwise <c>false</c>. This will return <c>false</c> if this <see cref="Sprite"/> hasn't been added to the sprite list.</returns>
/// <returns><c>true</c> if the texture was successfully updated, otherwise <c>false</c>. This will return
/// <c>false</c> if this <see cref="Sprite"/> hasn't been added to the sprite list.</returns>
public new bool UpdateTexture() {
return base.UpdateTexture();
}

13
DotSDL/Platform/BasePlatform.cs

@ -1,13 +0,0 @@
using System;
using DotSDL.Platform.Interop.Fallback;
namespace DotSDL.Platform {
/// <summary>
/// Implements the
/// </summary>
public class BasePlatform : IPlatform {
private readonly Timing _fallbackTiming = new Timing();
public virtual Action<long> Nanosleep => _fallbackTiming.Nanosleep;
}
}

14
DotSDL/Platform/IPlatform.cs

@ -1,14 +0,0 @@
using System;
namespace DotSDL.Platform {
/// <summary>
/// Defines a set of function pointers for platform-specific native calls.
/// </summary>
public interface IPlatform {
/// <summary>
/// A high-resolution sleep timer that attempts to rest for a given
/// number of nanoseconds.
/// </summary>
Action<long> Nanosleep { get; }
}
}

29
DotSDL/Platform/Interop/Fallback/Timing.cs

@ -1,29 +0,0 @@
using DotSDL.Interop.Core;
namespace DotSDL.Platform.Interop.Fallback {
public class Timing {
private float _sleepSkew = 0.0f;
/// <summary>
/// Sleeps for a given number of nanoseconds.
/// </summary>
/// <param name="ns">The number of nanoseconds to sleep.</param>
/// <remarks>This implementation uses the SDL_Delay function and should work
/// with all platforms. It attempts to skirt around the resolution issues using
/// the number of fractional milliseconds.</remarks>
public void Nanosleep(long ns) {
var waitTime = ns / 1000000;
_sleepSkew += (float)ns / 1000000 - waitTime;
if(_sleepSkew >= 1) {
// Take the whole part of the skew and add it to the sleep time.
var skewAdd = (long)_sleepSkew;
waitTime += skewAdd;
_sleepSkew -= skewAdd;
}
if(waitTime > 0)
Timer.Delay((uint)waitTime);
}
}
}

36
DotSDL/Platform/Interop/Posix/Timing.cs

@ -1,36 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace DotSDL.Platform.Interop.Posix {
public class Timing {
private struct Timespec {
/// <summary>
/// A number of seconds.
/// </summary>
public int tvSec;
/// <summary>
/// A number of nanoseconds. This field must be in the range of 0 to 999999999.
/// </summary>
public long tvNsec;
}
/// <summary>
/// Suspends a thread until at least the time given in <paramref name="req"/> has
/// elapsed.
/// </summary>
/// <param name="req">The requested amount of time to sleep.</param>
/// <param name="rem">The remaining sleep time, or NULL if this isn't necessary.</param>
[DllImport("c", EntryPoint = "nanosleep", CallingConvention = CallingConvention.Cdecl)]
private static extern void PosixNanosleep(in Timespec req, out Timespec rem);
/// <summary>
/// Sleeps for a given number of nanoseconds.
/// </summary>
/// <param name="ns">The number of nanoseconds to sleep.</param>
/// <remarks>This implementation uses the nanosleep function introduced
/// with POSIX.1.</remarks>
public void Nanosleep(long ns) =>
PosixNanosleep(new Timespec { tvSec = 0, tvNsec = ns}, out _);
}
}

18
DotSDL/Platform/PlatformFactory.cs

@ -1,18 +0,0 @@
using System;
namespace DotSDL.Platform {
/// <summary>
/// Senses the user's platform and returns a new instance of the most
/// appropriate <see cref="IPlatform"/> implementation.
/// </summary>
public static class PlatformFactory {
public static IPlatform GetPlatform() {
switch(Environment.OSVersion.Platform) {
case PlatformID.Unix:
return new PosixPlatform();
default:
return new BasePlatform();
}
}
}
}

10
DotSDL/Platform/PosixPlatform.cs

@ -1,10 +0,0 @@
using System;
using DotSDL.Platform.Interop.Posix;
namespace DotSDL.Platform {
public class PosixPlatform : BasePlatform {
private readonly Timing _posixTiming = new Timing();
public override Action<long> Nanosleep => _posixTiming.Nanosleep;
}
}

6
README.md

@ -19,11 +19,9 @@ At this time, DotSDL supports the following features:
* Keyboard input.
* Window events.
* Graphics
* An arbitrary number of 32-bit ARGB background canvases (useful for pixel
plotting and backgrounds).
* A single 32-bit ARGB background canvas (useful for pixel plotting).
* Sprite drawing, with support for alpha blending, world and screen space
rendering, rotation, flipping, and scaling.
* Configurable Z-order for all sprites and background layers.
rendering, and scaling.
* Power
* Battery state.

2
Samples/Sample.Audio/Program.cs

@ -2,7 +2,7 @@
internal class Program {
internal static void Main(string[] args) {
var window = new Window(720, 180);
window.Start(60);
window.Start(16);
}
}
}

5
Samples/Sample.Audio/Sample.Audio.csproj

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\DotSDL\DotSDL.csproj" />

4
Samples/Sample.Audio/Window.cs

@ -107,7 +107,7 @@ namespace Sample.Audio {
base.OnDraw();
}
protected override void OnUpdate(float _) {
protected override void OnUpdate() {
var delta = Fast ? 10 : 1;
if(UpPressed)
_freq += delta;
@ -117,7 +117,7 @@ namespace Sample.Audio {
if(_freq > _maxFreq) _freq = _maxFreq;
if(_freq < _minFreq) _freq = _minFreq;
base.OnUpdate(delta);
base.OnUpdate();
}
private void Window_KeyPressed(object sender, DotSDL.Events.KeyboardEvent e) {

2
Samples/Sample.BasicPixels/Program.cs

@ -2,7 +2,7 @@
internal class Program {
private static void Main(string[] args) {
var window = new Window(512, 256);
window.Start(10, 60); // 10 fps, 60hz updates
window.Start(100, 16); // 10fps, 62.5ups
}
}
}

5
Samples/Sample.BasicPixels/Sample.BasicPixels.csproj

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\DotSDL\DotSDL.csproj" />

2
Samples/Sample.Layers/Program.cs

@ -2,7 +2,7 @@
internal class Program {
private static void Main(string[] args) {
var window = new Window(4);
window.Start(60);
window.Start(16);
}
}
}

5
Samples/Sample.Layers/Sample.Layers.csproj

@ -2,9 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>

2
Samples/Sample.Layers/Window.cs

@ -82,7 +82,7 @@ namespace Sample.Layers {
Stop();
}
protected override void OnUpdate(float delta) {
protected override void OnUpdate() {
_redPhase += RedSpeed;
_greenPhase += GreenSpeed;
_bluePhase += BlueSpeed;

2
Samples/Sample.Power/Program.cs

@ -4,7 +4,7 @@ namespace Sample.Power {
class Program {
static void Main(string[] args) {
var window = new Window(256, 128);
window.Start(10, 60);
window.Start(100, 16);
}
}
}

5
Samples/Sample.Power/Sample.Power.csproj

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\DotSDL\DotSDL.csproj" />

2
Samples/Sample.Sprites/Program.cs

@ -2,7 +2,7 @@
internal static class Program {
private static void Main(string[] args) {
var window = new Window(4);
window.Start(60);
window.Start(16);
}
}
}

5
Samples/Sample.Sprites/Sample.Sprites.csproj

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\DotSDL\DotSDL.csproj" />

2
Samples/Sample.Sprites/Window.cs

@ -152,7 +152,7 @@ namespace Sample.Sprites {
_player2Rotation = 0;
}
protected override void OnUpdate(float delta) {
protected override void OnUpdate() {
_player1.Move(_player1Delta);
_player1.Rotate(_player1Rotation);

Loading…
Cancel
Save