Merge branch 'develop' into rename-wasbutton

This commit is contained in:
Max Kopjev
2024-04-28 20:58:25 +03:00
committed by GitHub
115 changed files with 2687 additions and 1003 deletions
+35 -101
View File
@@ -1,15 +1,8 @@
name: "Build test deploy"
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CORE_SDK_VERSION: 6.0.302
MYGET_ACCESS_TOKEN: ${{ secrets.MYGET_ACCESS_TOKEN }}
MYGET_SOURCE_URL: 'https://www.myget.org/F/lithiumtoast/api/v3/index.json'
on:
push:
branches: [develop]
branches: [develop, test/feature]
tags: [v*]
pull_request:
branches: [develop]
@@ -17,99 +10,40 @@ on:
branches: [develop]
jobs:
version-job:
name: "Version"
runs-on: ubuntu-latest
steps:
- name: "Checkout Git repository"
uses: actions/checkout@v2
- name: "Fetch all history for all tags and branches"
run: git fetch --prune --unshallow
- name: "Install GitVersion"
uses: gittools/actions/gitversion/setup@v0.9.6
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true # workaround for https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ until the execute action is updated
with:
versionSpec: '5.x'
- name: "Use GitVersion"
uses: gittools/actions/gitversion/execute@v0.9.6
- run: echo "$GitVersion_NuGetVersionV2" >> version.txt
- name: "Upload NuGetVersion version artifact"
uses: actions/upload-artifact@v2
with:
name: version
path: version.txt
build-test-pack-job:
name: "Build"
needs: [version-job]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
name: "Build-Test-Pack"
runs-on: ubuntu-latest
steps:
- name: "Clone Repository"
uses: actions/checkout@v4
- name: "CAKE (Build -> Test -> Package)"
run: dotnet run --project ./build/Build.csproj -- --target=Default
- name: "Upload Artifacts For Deploy"
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@main
with:
name: MonoGame.Extended
path: artifacts/*
- name: "Download version artifact"
uses: actions/download-artifact@v2
with:
name: version
- name: "Read Version"
id: version
shell: bash
run: |
echo "VERSION=$(cat version.txt)" >> $GITHUB_ENV
- name: "Print Version"
shell: bash
run: |
echo $VERSION
- name: "Checkout repository"
uses: actions/checkout@master
with:
submodules: true
lfs: true
- name: "Setup .NET Core CLI"
uses: actions/setup-dotnet@v1
with:
dotnet-version: '${{ env.DOTNET_CORE_SDK_VERSION }}'
- name: "Install fonts (Ubuntu)"
if: matrix.os == 'ubuntu-latest'
run: |
echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | sudo debconf-set-selections
sudo apt-get install -y ttf-mscorefonts-installer
sudo apt-get install -y fontconfig
sudo fc-cache -f -v
sudo fc-match Arial
- name: "Download NuGet packages"
run: dotnet restore --verbosity quiet
- name: "Build solution"
run: dotnet build --nologo --verbosity minimal --configuration Release --no-restore /p:Version='${{ env.VERSION }}'
- name: "Test solution"
run: dotnet test --nologo --verbosity normal --configuration Release --no-build
- name: "Pack solution"
if: matrix.os == 'ubuntu-latest' && github.event_name == 'push'
run: dotnet pack --nologo --output "./nuget-packages" --verbosity minimal --configuration Release --no-build -p:PackageVersion='${{ env.VERSION }}'
- name: "Add Packages Source: MyGet"
if: matrix.os == 'ubuntu-latest' && github.event_name == 'push'
run: dotnet nuget add source $MYGET_SOURCE_URL --name "MyGet"
- name: "Upload Packages: MyGet"
if: matrix.os == 'ubuntu-latest' && github.event_name == 'push'
run: dotnet nuget push "./**/*.nupkg" --source "MyGet" --skip-duplicate --api-key $MYGET_ACCESS_TOKEN
deploy-job:
name: "Deploy Nugets"
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
needs: [build-test-pack-job]
if: ${{ github.event_name == 'push' }}
steps:
- name: "Clone Repository"
uses: actions/checkout@v4
- name: "Download Artifacts For Deploy"
uses: actions/download-artifact@main
with:
name: MonoGame.Extended
path: artifacts
- name: "Push Package"
run: dotnet run --project ./build/Build.csproj -- --target=Deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MYGET_ACCESS_TOKEN: ${{ secrets.MYGET_ACCESS_TOKEN }}
NUGET_ACCESS_TOKEN: ${{ secrets.NUGET_ACCESS_TOKEN }}
+6
View File
@@ -9,3 +9,9 @@
# macOS
.DS_Store
*.user
# cake build output
artifacts
# Visual Studio
.vs/*
+20
View File
@@ -0,0 +1,20 @@
<Project>
<PropertyGroup>
<Version>3.9.0</Version>
<Authors>craftworkgames</Authors>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<RepositoryBranch>develop</RepositoryBranch>
<NeutralLanguage>en</NeutralLanguage>
<PackageIcon>logo-nuget-128.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<!-- path must be relative to the individual csproj's not this .targets file -->
<None Include="../../../Logos/logo-nuget-128.png" Pack="true" PackagePath="" />
<None Include="../../../README.md" Pack="true" PackagePath="" />
</ItemGroup>
</Project>
@@ -0,0 +1,115 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Collisions;
using MonoGame.Extended.Collisions.Layers;
using MonoGame.Extended.Collisions.QuadTree;
namespace MonoGame.Extended.Benchmarks.Collisions;
[SimpleJob(RunStrategy.ColdStart, launchCount:3)]
public class DifferentPoolSizeCollision
{
private const int COMPONENT_BOUNDARY_SIZE = 1000;
private readonly CollisionComponent _collisionComponent;
private readonly Random _random = new Random();
private readonly GameTime _gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromMilliseconds(16));
public DifferentPoolSizeCollision()
{
var size = new Size2(COMPONENT_BOUNDARY_SIZE, COMPONENT_BOUNDARY_SIZE);
_collisionComponent = new CollisionComponent(new RectangleF(Point2.Zero, size));
}
class Collider: ICollisionActor
{
public Collider(Point2 position)
{
Bounds = new RectangleF(position, new Size2(1, 1));
}
public IShapeF Bounds { get; set; }
public Vector2 Shift { get; set; }
public Point2 Position {
get => Bounds.Position;
set => Bounds.Position = value;
}
public void OnCollision(CollisionEventArgs collisionInfo)
{
}
}
[Params(100, 500, 1000)]
public int N { get; set; }
[Params(1, 2)]
public int LayersCount { get; set; }
public int UpdateCount { get; set; } = 100;
private List<Collider> _colliders = new();
private List<Layer> _layers = new();
[GlobalSetup]
public void GlobalSetup()
{
if (LayersCount > 1)
{
for (int i = 0; i < LayersCount; i++)
{
var size = new Size2(COMPONENT_BOUNDARY_SIZE, COMPONENT_BOUNDARY_SIZE);
var layer = new Layer(new SpatialHash(new Size2(5, 5)));//new QuadTreeSpace(new RectangleF(Point2.Zero, size)))));
_collisionComponent.Add(i.ToString(), layer);
_layers.Add(layer);
}
for (int i = 0; i < LayersCount - 1; i++)
_collisionComponent.AddCollisionBetweenLayer(_layers[i], _layers[i + 1]);
}
for (int i = 0; i < N; i++)
{
var layer = LayersCount == 1
? _collisionComponent.Layers.First().Value
: _layers[i % LayersCount];
var collider = new Collider(new Point2(
_random.Next(COMPONENT_BOUNDARY_SIZE),
_random.Next(COMPONENT_BOUNDARY_SIZE)))
{
Shift = new Vector2(
_random.Next(4) - 2,
_random.Next(4) - 2),
};
_colliders.Add(collider);
layer.Space.Insert(collider);
}
}
[GlobalCleanup]
public void GlobalCleanup()
{
foreach (var collider in _colliders)
_collisionComponent.Remove(collider);
_colliders.Clear();
foreach (var layer in _layers)
_collisionComponent.Remove(layer: layer);
_layers.Clear();
}
[Benchmark]
public void Benchmark()
{
for (int i = 0; i < UpdateCount; i++)
{
foreach (var collider in _colliders)
collider.Position += collider.Shift;
//_collisionComponent.Update(_gameTime);
}
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\cs\MonoGame.Extended.Collisions\MonoGame.Extended.Collisions.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,5 @@
using BenchmarkDotNet.Running;
using MonoGame.Extended.Benchmarks.Collisions;
//var summary = BenchmarkRunner.Run<DifferentPoolSizeCollision>();
var summary = BenchmarkRunner.Run<SpaceAlgorithms>();
@@ -0,0 +1,104 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Benchmarks.Collisions.Utils;
using MonoGame.Extended.Collisions;
using MonoGame.Extended.Collisions.Layers;
using MonoGame.Extended.Collisions.QuadTree;
namespace MonoGame.Extended.Benchmarks.Collisions;
[SimpleJob(RunStrategy.ColdStart, launchCount:10)]
public class SpaceAlgorithms
{
private const int COMPONENT_BOUNDARY_SIZE = 1000;
private readonly Random _random = new ();
private ISpaceAlgorithm _space;
private ICollisionActor _actor;
private RectangleF _bound;
private List<Collider> _colliders = new();
[Params(10, 100, 1000)]
public int N { get; set; }
[Params("SpatialHash", "QuadTree")]
public string Algorithm { get; set; }
[GlobalSetup]
public void GlobalSetup()
{
var size = new Size2(COMPONENT_BOUNDARY_SIZE, COMPONENT_BOUNDARY_SIZE);
_space = Algorithm switch
{
"SpatialHash" => new SpatialHash(new Size2(32, 32)),
"QuadTree" => new QuadTreeSpace(new RectangleF(Point2.Zero, size)),
_ => _space
};
for (int i = 0; i < N; i++)
{
var rect = GetRandomRectangleF();
var actor = new Collider(rect);
_colliders.Add(actor);
_space.Insert(actor);
}
}
[GlobalCleanup]
public void GlobalCleanup()
{
foreach (var collider in _colliders)
_space.Remove(collider);
_colliders.Clear();
}
[GlobalSetup(Targets = new[] { nameof(Insert), nameof(Remove) })]
public void ActorGlobalSetup()
{
GlobalSetup();
var rect = GetRandomRectangleF();
_actor = new Collider(rect);
}
[Benchmark]
public void Insert()
{
_space.Insert(_actor);
}
[Benchmark]
public void Remove()
{
_space.Remove(_actor);
}
[Benchmark]
public void Reset()
{
_space.Reset();
}
[GlobalSetup(Target = nameof(Query))]
public void QueryGlobalSetup()
{
GlobalSetup();
_bound = GetRandomRectangleF();
}
private RectangleF GetRandomRectangleF()
{
return new RectangleF(
_random.Next(COMPONENT_BOUNDARY_SIZE),
_random.Next(COMPONENT_BOUNDARY_SIZE),
_random.Next(32, 128),
_random.Next(32, 128));
}
[Benchmark]
public List<ICollisionActor> Query()
{
return _space.Query(_bound).ToList();
}
}
@@ -0,0 +1,29 @@
using Microsoft.Xna.Framework;
using MonoGame.Extended.Collisions;
namespace MonoGame.Extended.Benchmarks.Collisions.Utils;
public class Collider: ICollisionActor
{
public Collider(Point2 position)
{
Bounds = new RectangleF(position, new Size2(1, 1));
}
public Collider(IShapeF shape)
{
Bounds = shape;
}
public IShapeF Bounds { get; set; }
public Vector2 Shift { get; set; }
public Point2 Position {
get => Bounds.Position;
set => Bounds.Position = value;
}
public void OnCollision(CollisionEventArgs collisionInfo)
{
}
}
@@ -0,0 +1,8 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Tweening.Tests;
public class ColorHandler
{
public Color Color { get; set; }
}
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\src\cs\MonoGame.Extended.Tweening\MonoGame.Extended.Tweening.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
using Microsoft.Xna.Framework;
using Xunit;
namespace MonoGame.Extended.Tweening.Tests;
public class TweenerTests
{
[Fact]
public void TweenerTweenToSuccessTest()
{
var tweener = new Tweener();
var obj = new ColorHandler();
tweener.TweenTo(obj, x => x.Color, Color.Red, 2f);
}
}
+24
View File
@@ -43,6 +43,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Collision
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended", "src\cs\MonoGame.Extended\MonoGame.Extended.csproj", "{4170BBC0-BF49-4307-8272-768EEA77034A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{AAB81CB9-9C71-4499-A51E-933C384D6DBF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended.Benchmarks.Collisions", "MonoGame.Extended.Benchmarks.Collisions\MonoGame.Extended.Benchmarks.Collisions.csproj", "{6B5939ED-E99D-4842-B4FE-A7624C59A60A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Extended.Tweening.Tests", "MonoGame.Extended.Tweening.Tests\MonoGame.Extended.Tweening.Tests.csproj", "{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -187,6 +193,22 @@ Global
{4170BBC0-BF49-4307-8272-768EEA77034A}.Release|Any CPU.Build.0 = Release|Any CPU
{4170BBC0-BF49-4307-8272-768EEA77034A}.Release|x86.ActiveCfg = Release|Any CPU
{4170BBC0-BF49-4307-8272-768EEA77034A}.Release|x86.Build.0 = Release|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Debug|x86.Build.0 = Debug|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Release|Any CPU.Build.0 = Release|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Release|x86.ActiveCfg = Release|Any CPU
{6B5939ED-E99D-4842-B4FE-A7624C59A60A}.Release|x86.Build.0 = Release|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Debug|x86.ActiveCfg = Debug|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Debug|x86.Build.0 = Debug|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Release|Any CPU.Build.0 = Release|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Release|x86.ActiveCfg = Release|Any CPU
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -199,6 +221,8 @@ Global
{43CBA868-9BFC-4E02-8A7A-142C603B087E} = {E5A148A1-DE7B-4D17-ABE8-831B9673B863}
{34841DEB-EEC0-477C-B19D-CBB6DF49ED39} = {E5A148A1-DE7B-4D17-ABE8-831B9673B863}
{CB439E84-F0F6-4790-8CD1-8A66C3D7B4DA} = {E5A148A1-DE7B-4D17-ABE8-831B9673B863}
{6B5939ED-E99D-4842-B4FE-A7624C59A60A} = {AAB81CB9-9C71-4499-A51E-933C384D6DBF}
{A1BD6FDC-5A76-4D14-8FDA-ACD153C8EBCC} = {E5A148A1-DE7B-4D17-ABE8-831B9673B863}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8ED5A62D-25EC-4331-9F99-BDD1E10C02A0}
+11
View File
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake.Frosting" Version="3.1.0" />
</ItemGroup>
</Project>
+41
View File
@@ -0,0 +1,41 @@
using System.Linq;
using Cake.Common;
using Cake.Common.Build;
using Cake.Common.Xml;
using Cake.Core;
using Cake.Frosting;
namespace BuildScripts;
public sealed class BuildContext : FrostingContext
{
public string ArtifactsDirectory { get; }
public string Version { get; }
public string SolutionPath { get; }
public string? RepositoryOwner { get; }
public string? RepositoryUrl { get; }
public bool IsTag { get; }
public bool IsRunningOnGitHubActions { get; }
public string? GitHubToken { get; }
public string? MyGetAccessToken { get; }
public string? NuGetAccessToken { get; }
public BuildContext(ICakeContext context) : base(context)
{
ArtifactsDirectory = context.Argument(nameof(ArtifactsDirectory), "artifacts");
Version = context.XmlPeek("Directory.Build.targets", "/Project/PropertyGroup/Version");
SolutionPath = "./MonoGame.Extended.sln";
IsRunningOnGitHubActions = context.BuildSystem().IsRunningOnGitHubActions;
if (IsRunningOnGitHubActions)
{
Version += "." + context.EnvironmentVariable("GITHUB_RUN_NUMBER");
RepositoryOwner = context.EnvironmentVariable("GITHUB_REPOSITORY_OWNER");
RepositoryUrl = $"https://github.com/{context.EnvironmentVariable("GITHUB_REPOSITORY")}";
IsTag = context.EnvironmentVariable("GITHUB_REF_TYPE") == "tag";
GitHubToken = context.EnvironmentVariable("GITHUB_TOKEN");
MyGetAccessToken = context.EnvironmentVariable("MYGET_ACCESS_TOKEN");
NuGetAccessToken = context.EnvironmentVariable("NUGET_ACCESS_TOKEN");
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.Build;
using Cake.Common.Tools.DotNet.MSBuild;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(BuildTask))]
public sealed class BuildTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
DotNetMSBuildSettings msBuildSettings = new DotNetMSBuildSettings();
msBuildSettings.WithProperty("Version", context.Version);
msBuildSettings.WithProperty("NoWarn", "CS1591");
DotNetBuildSettings buildSettings = new DotNetBuildSettings()
{
MSBuildSettings = msBuildSettings,
Configuration = "Release",
Verbosity = DotNetVerbosity.Minimal,
NoRestore = true,
NoLogo = true
};
context.DotNetBuild(context.SolutionPath, buildSettings);
}
}
+25
View File
@@ -0,0 +1,25 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.NuGet.Push;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(DeployToGitHubTask))]
public sealed class DeployToGitHubTask : FrostingTask<BuildContext>
{
public override bool ShouldRun(BuildContext context)
{
return context.IsRunningOnGitHubActions;
}
public override void Run(BuildContext context)
{
DotNetNuGetPushSettings pushSettings = new DotNetNuGetPushSettings()
{
Source = $"https://nuget.pkg.github.com/{context.RepositoryOwner}/index.json",
ApiKey = context.GitHubToken
};
context.DotNetNuGetPush("artifacts/*.nupkg", pushSettings);
}
}
+25
View File
@@ -0,0 +1,25 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.NuGet.Push;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(DeployToMyGetTask))]
public sealed class DeployToMyGetTask : FrostingTask<BuildContext>
{
public override bool ShouldRun(BuildContext context)
{
return context.IsRunningOnGitHubActions;
}
public override void Run(BuildContext context)
{
DotNetNuGetPushSettings pushSettings = new DotNetNuGetPushSettings()
{
Source = $"https://www.myget.org/F/lithiumtoast/api/v3/index.json",
ApiKey = context.MyGetAccessToken
};
context.DotNetNuGetPush("artifacts/*.nupkg", pushSettings);
}
}
+27
View File
@@ -0,0 +1,27 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.NuGet.Push;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(DeployToNuGetTask))]
public sealed class DeployToNuGetTask : FrostingTask<BuildContext>
{
public override bool ShouldRun(BuildContext context)
{
return context.IsRunningOnGitHubActions &&
context.IsTag &&
context.RepositoryOwner == "craftworksgames";
}
public override void Run(BuildContext context)
{
DotNetNuGetPushSettings pushSettings = new DotNetNuGetPushSettings()
{
Source = $"https://api.nuget.org/v3/index.json",
ApiKey = context.NuGetAccessToken
};
context.DotNetNuGetPush("artifacts/*.nupkg", pushSettings);
}
}
+40
View File
@@ -0,0 +1,40 @@
using Cake.Common.Build;
using Cake.Common.IO;
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.MSBuild;
using Cake.Common.Tools.DotNet.Pack;
using Cake.Core.IO;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(PackageTask))]
public sealed class PackageTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
context.CleanDirectories(context.ArtifactsDirectory);
context.CreateDirectory(context.ArtifactsDirectory);
DotNetMSBuildSettings msBuildSettings = new DotNetMSBuildSettings();
msBuildSettings.WithProperty("Version", context.Version);
// Ignore warnings about adding duplicate items added to package
msBuildSettings.WithProperty("NoWarn", "NU5118");
DotNetPackSettings packSettings = new DotNetPackSettings()
{
MSBuildSettings = msBuildSettings,
Configuration = "Release",
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
OutputDirectory = context.ArtifactsDirectory,
};
FilePathCollection files = context.GetFiles("./src/cs/MonoGame.Extended*/**/*.csproj");
foreach(FilePath file in files)
{
context.DotNetPack(file.FullPath, packSettings);
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Cake.Common.Build;
using Cake.Common.IO;
using Cake.Common.Tools.ReportUnit;
using Cake.Core;
using Cake.Core.IO;
using Cake.Frosting;
namespace BuildScripts;
public static class Program
{
public static int Main(string[] args)
{
return new CakeHost()
.UseContext<BuildContext>()
.UseWorkingDirectory("../")
.Run(args);
}
}
[TaskName("Default")]
[IsDependentOn(typeof(RestoreTask))]
[IsDependentOn(typeof(BuildTask))]
[IsDependentOn(typeof(TestTask))]
[IsDependentOn(typeof(PackageTask))]
public sealed class DefaultTask : FrostingTask {}
[TaskName("Deploy")]
[IsDependentOn(typeof(DeployToGitHubTask))]
[IsDependentOn(typeof(DeployToMyGetTask))]
[IsDependentOn(typeof(DeployToNuGetTask))]
public sealed class DeployTask : FrostingTask {}
+18
View File
@@ -0,0 +1,18 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.Restore;
using Cake.Frosting;
namespace BuildScripts;
public sealed class RestoreTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
DotNetRestoreSettings restoreSettings = new DotNetRestoreSettings()
{
Verbosity = DotNetVerbosity.Quiet
};
context.DotNetRestore(context.SolutionPath, restoreSettings);
}
}
+22
View File
@@ -0,0 +1,22 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.Test;
using Cake.Frosting;
namespace BuildScripts;
[TaskName(nameof(TestTask))]
public class TestTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
DotNetTestSettings testSettings = new DotNetTestSettings()
{
Configuration = "Release",
Verbosity = DotNetVerbosity.Normal,
NoLogo = true,
NoBuild = true
};
context.DotNetTest(context.SolutionPath, testSettings);
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>Animations to make MonoGame more awesome.</Description>
<PackageTags>monogame animations spritesheet sprite</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
</PropertyGroup>
<PropertyGroup>
@@ -24,4 +21,4 @@
<ItemGroup>
<Folder Include="SpriteSheets\" />
</ItemGroup>
</Project>
</Project>
@@ -1,33 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
public class CollisionActor : IActorTarget
{
private readonly IActorTarget _target;
public CollisionActor(IActorTarget target)
{
_target = target;
}
public Vector2 Velocity
{
get { return _target.Velocity; }
set { _target.Velocity = value; }
}
public Vector2 Position
{
get { return _target.Position; }
set { _target.Position = value; }
}
public RectangleF BoundingBox => _target.BoundingBox;
public void OnCollision(CollisionInfo collisionInfo)
{
_target.OnCollision(collisionInfo);
}
}
}
@@ -0,0 +1,341 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Collisions.Layers;
using MonoGame.Extended.Collisions.QuadTree;
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// Handles basic collision between actors.
/// When two actors collide, their OnCollision method is called.
/// </summary>
public class CollisionComponent : SimpleGameComponent
{
public const string DEFAULT_LAYER_NAME = "default";
private Dictionary<string, Layer> _layers = new();
/// <summary>
/// List of collision's layers
/// </summary>
public IReadOnlyDictionary<string, Layer> Layers => _layers;
private HashSet<(Layer, Layer)> _layerCollision = new();
/// <summary>
/// Creates component with default layer, which is a collision tree covering the specified area (using <see cref="QuadTree"/>.
/// </summary>
/// <param name="boundary">Boundary of the collision tree.</param>
public CollisionComponent(RectangleF boundary)
{
SetDefaultLayer(new Layer(new QuadTreeSpace(boundary)));
}
/// <summary>
/// Creates component with specifies default layer.
/// If layer is null, method creates component without default layer.
/// </summary>
/// <param name="layer">Default layer</param>
public CollisionComponent(Layer layer = null)
{
if (layer is not null)
SetDefaultLayer(layer);
}
/// <summary>
/// The main layer has the name from <see cref="DEFAULT_LAYER_NAME"/>.
/// The main layer collision with itself and all other layers.
/// </summary>
/// <param name="layer">Layer to set default</param>
public void SetDefaultLayer(Layer layer)
{
if (_layers.ContainsKey(DEFAULT_LAYER_NAME))
Remove(DEFAULT_LAYER_NAME);
Add(DEFAULT_LAYER_NAME, layer);
foreach (var otherLayer in _layers.Values)
AddCollisionBetweenLayer(layer, otherLayer);
}
/// <summary>
/// Update the collision tree and process collisions.
/// </summary>
/// <remarks>
/// Boundary shapes are updated if they were changed since the last
/// update.
/// </remarks>
/// <param name="gameTime"></param>
public override void Update(GameTime gameTime)
{
foreach (var layer in _layers.Values)
layer.Reset();
foreach (var (firstLayer, secondLayer) in _layerCollision)
foreach (var actor in firstLayer.Space)
{
var collisions = secondLayer.Space.Query(actor.Bounds.BoundingRectangle);
foreach (var other in collisions)
if (actor != other && actor.Bounds.Intersects(other.Bounds))
{
var penetrationVector = CalculatePenetrationVector(actor.Bounds, other.Bounds);
actor.OnCollision(new CollisionEventArgs
{
Other = other,
PenetrationVector = penetrationVector
});
other.OnCollision(new CollisionEventArgs
{
Other = actor,
PenetrationVector = -penetrationVector
});
}
}
}
/// <summary>
/// Inserts the target into the collision tree.
/// The target will have its OnCollision called when collisions occur.
/// </summary>
/// <param name="target">Target to insert.</param>
public void Insert(ICollisionActor target)
{
var layerName = target.LayerName ?? DEFAULT_LAYER_NAME;
if (!_layers.TryGetValue(layerName, out var layer))
{
throw new UndefinedLayerException(layerName);
}
layer.Space.Insert(target);
}
/// <summary>
/// Removes the target from the collision tree.
/// </summary>
/// <param name="target">Target to remove.</param>
public void Remove(ICollisionActor target)
{
if (target.LayerName is not null)
_layers[target.LayerName].Space.Remove(target);
else
foreach (var layer in _layers.Values)
if (layer.Space.Remove(target))
return;
}
#region Layers
/// <summary>
/// Add the new layer. The name of layer must be unique.
/// </summary>
/// <param name="name">Name of layer</param>
/// <param name="layer">The new layer</param>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null</exception>
public void Add(string name, Layer layer)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
if (!_layers.TryAdd(name, layer))
throw new DuplicateNameException(name);
if (name != DEFAULT_LAYER_NAME)
AddCollisionBetweenLayer(_layers[DEFAULT_LAYER_NAME], layer);
}
/// <summary>
/// Remove the layer and all layer's collisions.
/// </summary>
/// <param name="name">The name of the layer to delete</param>
/// <param name="layer">The layer to delete</param>
public void Remove(string name = null, Layer layer = null)
{
name ??= _layers.First(x => x.Value == layer).Key;
_layers.Remove(name, out layer);
_layerCollision.RemoveWhere(tuple => tuple.Item1 == layer || tuple.Item2 == layer);
}
public void AddCollisionBetweenLayer(Layer a, Layer b)
{
_layerCollision.Add((a, b));
}
public void AddCollisionBetweenLayer(string nameA, string nameB)
{
_layerCollision.Add((_layers[nameA], _layers[nameB]));
}
#endregion
#region Penetration Vectors
/// <summary>
/// Calculate a's penetration into b
/// </summary>
/// <param name="a">The penetrating shape.</param>
/// <param name="b">The shape being penetrated.</param>
/// <returns>The distance vector from the edge of b to a's Position</returns>
private static Vector2 CalculatePenetrationVector(IShapeF a, IShapeF b)
{
return a switch
{
CircleF circleA when b is CircleF circleB => PenetrationVector(circleA, circleB),
CircleF circleA when b is RectangleF rectangleB => PenetrationVector(circleA, rectangleB),
CircleF circleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(circleA, orientedRectangleB),
RectangleF rectangleA when b is CircleF circleB => PenetrationVector(rectangleA, circleB),
RectangleF rectangleA when b is RectangleF rectangleB => PenetrationVector(rectangleA, rectangleB),
RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(rectangleA, orientedRectangleB),
OrientedRectangle orientedRectangleA when b is CircleF circleB => PenetrationVector(orientedRectangleA, circleB),
OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => PenetrationVector(orientedRectangleA, rectangleB),
OrientedRectangle orientedRectangleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(orientedRectangleA, orientedRectangleB),
_ => throw new ArgumentOutOfRangeException(nameof(a))
};
}
private static Vector2 PenetrationVector(CircleF circ1, CircleF circ2)
{
if (!circ1.Intersects(circ2))
{
return Vector2.Zero;
}
var displacement = Point2.Displacement(circ1.Center, circ2.Center);
Vector2 desiredDisplacement;
if (displacement != Vector2.Zero)
{
desiredDisplacement = displacement.NormalizedCopy() * (circ1.Radius + circ2.Radius);
}
else
{
desiredDisplacement = -Vector2.UnitY * (circ1.Radius + circ2.Radius);
}
var penetration = displacement - desiredDisplacement;
return penetration;
}
private static Vector2 PenetrationVector(CircleF circ, RectangleF rect)
{
var collisionPoint = rect.ClosestPointTo(circ.Center);
var cToCollPoint = collisionPoint - circ.Center;
if (rect.Contains(circ.Center) || cToCollPoint.Equals(Vector2.Zero))
{
var displacement = Point2.Displacement(circ.Center, rect.Center);
Vector2 desiredDisplacement;
if (displacement != Vector2.Zero)
{
// Calculate penetration as only in X or Y direction.
// Whichever is lower.
var dispx = new Vector2(displacement.X, 0);
var dispy = new Vector2(0, displacement.Y);
dispx.Normalize();
dispy.Normalize();
dispx *= (circ.Radius + rect.Width / 2);
dispy *= (circ.Radius + rect.Height / 2);
if (dispx.LengthSquared() < dispy.LengthSquared())
{
desiredDisplacement = dispx;
displacement.Y = 0;
}
else
{
desiredDisplacement = dispy;
displacement.X = 0;
}
}
else
{
desiredDisplacement = -Vector2.UnitY * (circ.Radius + rect.Height / 2);
}
var penetration = displacement - desiredDisplacement;
return penetration;
}
else
{
var penetration = circ.Radius * cToCollPoint.NormalizedCopy() - cToCollPoint;
return penetration;
}
}
private static Vector2 PenetrationVector(CircleF circleA, OrientedRectangle orientedRectangleB)
{
var rotation = Matrix2.CreateRotationZ(orientedRectangleB.Orientation.Rotation);
var circleCenterInRectangleSpace = rotation.Transform(circleA.Center - orientedRectangleB.Center);
var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circleA.Radius);
var boundingRectangle = new BoundingRectangle(new Point2(), orientedRectangleB.Radii);
var penetrationVector = PenetrationVector(circleInRectangleSpace, boundingRectangle);
var inverseRotation = Matrix2.CreateRotationZ(-orientedRectangleB.Orientation.Rotation);
var transformedPenetration = inverseRotation.Transform(penetrationVector);
return transformedPenetration;
}
private static Vector2 PenetrationVector(RectangleF rect, CircleF circ)
{
return -PenetrationVector(circ, rect);
}
private static Vector2 PenetrationVector(RectangleF rect1, RectangleF rect2)
{
var intersectingRectangle = RectangleF.Intersection(rect1, rect2);
Debug.Assert(!intersectingRectangle.IsEmpty,
"Violation of: !intersect.IsEmpty; Rectangles must intersect to calculate a penetration vector.");
Vector2 penetration;
if (intersectingRectangle.Width < intersectingRectangle.Height)
{
var d = rect1.Center.X < rect2.Center.X
? intersectingRectangle.Width
: -intersectingRectangle.Width;
penetration = new Vector2(d, 0);
}
else
{
var d = rect1.Center.Y < rect2.Center.Y
? intersectingRectangle.Height
: -intersectingRectangle.Height;
penetration = new Vector2(0, d);
}
return penetration;
}
private static Vector2 PenetrationVector(RectangleF rectangleA, OrientedRectangle orientedRectangleB)
{
return PenetrationVector((OrientedRectangle)rectangleA, orientedRectangleB);
}
private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, CircleF circleB)
{
return -PenetrationVector(circleB, orientedRectangleA);
}
private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, RectangleF rectangleB)
{
return -PenetrationVector(rectangleB, orientedRectangleA);
}
private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, OrientedRectangle orientedRectangleB)
{
return OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB)
.MinimumTranslationVector;
}
#endregion
}
}
@@ -1,101 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// Represents a collision grid. This is used to break the game world into
/// chunks to detect collisions efficiently.
/// </summary>
public class CollisionGrid
{
private readonly CollisionGridCell[] _data;
/// <summary>
/// Creates a new collision grid of specified size.
/// </summary>
/// <param name="data"></param>
/// <param name="columns">Number of columns in the grid.</param>
/// <param name="rows">Number of rows in the grid.</param>
/// <param name="cellWidth">The width of each individual cell.</param>
/// <param name="cellHeight">The height of each individual cell.</param>
public CollisionGrid(int[] data, int columns, int rows, int cellWidth, int cellHeight)
{
_data = new CollisionGridCell[data.Length];
for (var y = 0; y < rows; y++)
for (var x = 0; x < columns; x++)
{
var index = x + y*columns;
_data[index] = new CollisionGridCell(this, x, y, data[index]);
}
Columns = columns;
Rows = rows;
CellWidth = cellWidth;
CellHeight = cellHeight;
}
/// <summary>
/// Gets the number of columns in this grid.
/// </summary>
public int Columns { get; }
/// <summary>
/// Gets the number of rows in this grid.
/// </summary>
public int Rows { get; private set; }
/// <summary>
/// Gets the width of each cell.
/// </summary>
public int CellWidth { get; }
/// <summary>
/// Gets the height of each cell.
/// </summary>
public int CellHeight { get; }
public CollisionGridCell GetCellAtIndex(int column, int row)
{
var index = column + row*Columns;
if ((index < 0) || (index >= _data.Length))
return new CollisionGridCell(this, column, row, 0);
return _data[index];
}
public CollisionGridCell GetCellAtPosition(Vector3 position)
{
var column = (int) (position.X/CellWidth);
var row = (int) (position.Y/CellHeight);
return GetCellAtIndex(column, row);
}
public IEnumerable<CollisionGridCell> GetCellsOverlappingRectangle(RectangleF rectangle)
{
var sx = (int) (rectangle.Left/CellWidth);
var sy = (int) (rectangle.Top/CellHeight);
var ex = (int) (rectangle.Right/CellWidth + 1);
var ey = (int) (rectangle.Bottom/CellHeight + 1);
for (var y = sy; y < ey; y++)
for (var x = sx; x < ex; x++)
yield return GetCellAtIndex(x, y);
}
public IEnumerable<ICollidable> GetCollidables(RectangleF overlappingRectangle)
{
return GetCellsOverlappingRectangle(overlappingRectangle)
.Where(cell => cell.Flag != CollisionGridCellFlag.Empty);
}
public Rectangle GetCellRectangle(int column, int row)
{
return new Rectangle(column*CellWidth, row*CellHeight, CellWidth, CellHeight);
}
}
}
@@ -1,46 +0,0 @@
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// Represents a single cell in a collision grid.
/// </summary>
public class CollisionGridCell : ICollidable
{
private readonly CollisionGrid _parentGrid;
/// <summary>
/// Creates a collision grid cell at a location in the parent grid.
/// </summary>
/// <param name="parentGrid">The collision grid which this cell is a part of.</param>
/// <param name="column">The column position of this cell.</param>
/// <param name="row">The row position of this cell.</param>
/// <param name="data"></param>
public CollisionGridCell(CollisionGrid parentGrid, int column, int row, int data)
{
_parentGrid = parentGrid;
Column = column;
Row = row;
Data = data;
Flag = data == 0 ? CollisionGridCellFlag.Empty : CollisionGridCellFlag.Solid;
}
/// <summary>
/// Gets the Column in the parent grid that this cell represents.
/// </summary>
public int Column { get; }
/// <summary>
/// Gets the Row in the parent grid that this cell represents.
/// </summary>
public int Row { get; }
public int Data { get; private set; }
public object Tag { get; set; }
public CollisionGridCellFlag Flag { get; set; }
/// <summary>
/// Gets the bounding box of the cell.
/// </summary>
public RectangleF BoundingBox => _parentGrid.GetCellRectangle(Column, Row).ToRectangleF();
}
}
@@ -1,9 +0,0 @@
namespace MonoGame.Extended.Collisions
{
public enum CollisionGridCellFlag
{
Empty,
Solid,
Interesting
}
}
@@ -1,21 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// This class holds data on a collision. It is passed as a parameter to
/// OnCollision methods.
/// </summary>
public class CollisionInfo
{
/// <summary>
/// Gets the object being collided with.
/// </summary>
public ICollidable Other { get; internal set; }
/// <summary>
/// Gets a vector representing the overlap between the two objects.
/// </summary>
public Vector2 PenetrationVector { get; internal set; }
}
}
@@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
public class CollisionWorld : IDisposable, IUpdate
{
private readonly List<CollisionActor> _actors;
private readonly Vector2 _gravity;
private CollisionGrid _grid;
public CollisionWorld(Vector2 gravity)
{
_gravity = gravity;
_actors = new List<CollisionActor>();
}
public void Dispose()
{
}
public void Update(GameTime gameTime)
{
var deltaTime = (float) gameTime.ElapsedGameTime.TotalSeconds;
foreach (var actor in _actors)
{
actor.Velocity += _gravity*deltaTime;
actor.Position += actor.Velocity*deltaTime;
if (_grid != null)
foreach (var collidable in _grid.GetCollidables(actor.BoundingBox))
{
var intersection = RectangleF.Intersection(collidable.BoundingBox, actor.BoundingBox);
if (intersection.IsEmpty)
continue;
var info = GetCollisionInfo(actor, collidable, intersection);
actor.OnCollision(info);
}
}
}
public CollisionActor CreateActor(IActorTarget target)
{
var actor = new CollisionActor(target);
_actors.Add(actor);
return actor;
}
public CollisionGrid CreateGrid(int[] data, int columns, int rows, int cellWidth, int cellHeight)
{
if (_grid != null)
throw new InvalidOperationException("Only one collision grid can be created per world");
_grid = new CollisionGrid(data, columns, rows, cellWidth, cellHeight);
return _grid;
}
private CollisionInfo GetCollisionInfo(ICollidable first, ICollidable second, RectangleF intersectingRectangle)
{
var info = new CollisionInfo
{
Other = second
};
if (intersectingRectangle.Width < intersectingRectangle.Height)
{
var d = first.BoundingBox.Center.X < second.BoundingBox.Center.X
? intersectingRectangle.Width
: -intersectingRectangle.Width;
info.PenetrationVector = new Vector2(d, 0);
}
else
{
var d = first.BoundingBox.Center.Y < second.BoundingBox.Center.Y
? intersectingRectangle.Height
: -intersectingRectangle.Height;
info.PenetrationVector = new Vector2(0, d);
}
return info;
}
}
}
@@ -1,17 +0,0 @@
//using System.Linq;
//using MonoGame.Extended.Tiled;
//namespace MonoGame.Extended.Collisions
//{
// public static class CollisionWorldExtensions
// {
// public static CollisionGrid CreateGrid(this CollisionWorld world, TiledTileLayer tileLayer)
// {
// var data = tileLayer.Tiles
// .Select(t => (int)t.GlobalIdentifier)
// .ToArray();
// return world.CreateGrid(data, tileLayer.Width, tileLayer.Height, tileLayer.TileWidth, tileLayer.TileHeight);
// }
// }
//}
@@ -1,15 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
public interface ICollidable
{
RectangleF BoundingBox { get; }
}
public interface IActorTarget : IMovable, ICollidable
{
Vector2 Velocity { get; set; }
void OnCollision(CollisionInfo collisionInfo);
}
}
@@ -0,0 +1,27 @@
using System;
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// An actor that can be collided with.
/// </summary>
public interface ICollisionActor
{
/// <summary>
/// A name of layer, which will contains this actor.
/// If it equals null, an actor will insert into a default layer
/// </summary>
string LayerName { get => null; }
/// <summary>
/// A bounds of an actor. It is using for collision calculating
/// </summary>
IShapeF Bounds { get; }
/// <summary>
/// It will called, when collision with an another actor fires
/// </summary>
/// <param name="collisionInfo">Data about collision</param>
void OnCollision(CollisionEventArgs collisionInfo);
}
}
@@ -0,0 +1,40 @@
using System.Collections.Generic;
namespace MonoGame.Extended.Collisions;
/// <summary>
/// Interface, which split space for optimization of collisions.
/// </summary>
public interface ISpaceAlgorithm
{
/// <summary>
/// Inserts the actor into the space.
/// The actor will have its OnCollision called when collisions occur.
/// </summary>
/// <param name="actor">Actor to insert.</param>
void Insert(ICollisionActor actor);
/// <summary>
/// Removes the actor into the space.
/// </summary>
/// <param name="actor">Actor to remove.</param>
bool Remove(ICollisionActor actor);
/// <summary>
/// Removes the actor into the space.
/// The actor will have its OnCollision called when collisions occur.
/// </summary>
/// <param name="actor">Actor to remove.</param>
IEnumerable<ICollisionActor> Query(RectangleF boundsBoundingRectangle);
/// <summary>
/// for foreach
/// </summary>
/// <returns></returns>
List<ICollisionActor>.Enumerator GetEnumerator();
/// <summary>
/// Restructure the space with new positions.
/// </summary>
void Reset();
}
@@ -0,0 +1,39 @@
using System;
namespace MonoGame.Extended.Collisions.Layers;
/// <summary>
/// Layer is a group of collision's actors.
/// </summary>
public class Layer
{
/// <summary>
/// If this property equals true, layer always will reset collision space.
/// </summary>
public bool IsDynamic { get; set; } = true;
/// <summary>
/// The space, which contain actors.
/// </summary>
public readonly ISpaceAlgorithm Space;
/// <summary>
/// Constructor for layer
/// </summary>
/// <param name="spaceAlgorithm">A space algorithm for actors</param>
/// <exception cref="ArgumentNullException"><paramref name="spaceAlgorithm"/> is null</exception>
public Layer(ISpaceAlgorithm spaceAlgorithm)
{
Space = spaceAlgorithm ?? throw new ArgumentNullException(nameof(spaceAlgorithm));
}
/// <summary>
/// Restructure a inner collection, if layer is dynamic, because actors can change own position
/// </summary>
public virtual void Reset()
{
if (IsDynamic)
Space.Reset();
}
}
@@ -0,0 +1,18 @@
namespace MonoGame.Extended.Collisions.Layers;
using System;
/// <summary>
/// Thrown when the collision system has no layer defined with the specified name
/// </summary>
public class UndefinedLayerException : Exception
{
/// <summary>
/// Thrown when the collision system has no layer defined with the specified name
/// </summary>
/// <param name="layerName">The undefined layer name</param>
public UndefinedLayerException(string layerName)
: base($"Layer with name '{layerName}' is undefined")
{
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>Collisions to make MonoGame more awesome.</Description>
<PackageTags>monogame collisions</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -26,4 +23,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -1,230 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// Handles basic collision between actors.
/// When two actors collide, their OnCollision method is called.
/// </summary>
public class CollisionComponent : SimpleGameComponent
{
private readonly Dictionary<ICollisionActor, QuadtreeData> _targetDataDictionary = new();
private readonly Quadtree _collisionTree;
/// <summary>
/// Creates a collision tree covering the specified area.
/// </summary>
/// <param name="boundary">Boundary of the collision tree.</param>
public CollisionComponent(RectangleF boundary)
{
_collisionTree = new Quadtree(boundary);
}
/// <summary>
/// Update the collision tree and process collisions.
/// </summary>
/// <remarks>
/// Boundary shapes are updated if they were changed since the last
/// update.
/// </remarks>
/// <param name="gameTime"></param>
public override void Update(GameTime gameTime)
{
// Detect collisions
foreach (var value in _targetDataDictionary.Values)
{
value.RemoveFromAllParents();
var target = value.Target;
var collisions =_collisionTree.Query(target.Bounds);
// Generate list of collision Infos
foreach (var other in collisions)
{
var collisionInfo = new CollisionEventArgs
{
Other = other.Target,
PenetrationVector = CalculatePenetrationVector(value.Bounds, other.Bounds)
};
target.OnCollision(collisionInfo);
}
_collisionTree.Insert(value);
}
_collisionTree.Shake();
}
/// <summary>
/// Inserts the target into the collision tree.
/// The target will have its OnCollision called when collisions occur.
/// </summary>
/// <param name="target">Target to insert.</param>
public void Insert(ICollisionActor target)
{
if (!_targetDataDictionary.ContainsKey(target))
{
var data = new QuadtreeData(target);
_targetDataDictionary.Add(target, data);
_collisionTree.Insert(data);
}
}
/// <summary>
/// Removes the target from the collision tree.
/// </summary>
/// <param name="target">Target to remove.</param>
public void Remove(ICollisionActor target)
{
if (_targetDataDictionary.ContainsKey(target))
{
var data = _targetDataDictionary[target];
data.RemoveFromAllParents();
_targetDataDictionary.Remove(target);
_collisionTree.Shake();
}
}
/// <summary>
/// Gets if the target is inserted in the collision tree.
/// </summary>
/// <param name="target">Actor to check if contained</param>
/// <returns>True if the target is contained in the collision tree.</returns>
public bool Contains(ICollisionActor target)
{
return _targetDataDictionary.ContainsKey(target);
}
#region Penetration Vectors
/// <summary>
/// Calculate a's penetration into b
/// </summary>
/// <param name="a">The penetrating shape.</param>
/// <param name="b">The shape being penetrated.</param>
/// <returns>The distance vector from the edge of b to a's Position</returns>
private static Vector2 CalculatePenetrationVector(IShapeF a, IShapeF b)
{
switch (a)
{
case RectangleF rectA when b is RectangleF rectB:
return PenetrationVector(rectA, rectB);
case CircleF circA when b is CircleF circB:
return PenetrationVector(circA, circB);
case CircleF circA when b is RectangleF rectB:
return PenetrationVector(circA, rectB);
case RectangleF rectA when b is CircleF circB:
return PenetrationVector(rectA, circB);
}
throw new NotSupportedException("Shapes must be either a CircleF or RectangleF");
}
private static Vector2 PenetrationVector(RectangleF rect1, RectangleF rect2)
{
var intersectingRectangle = RectangleF.Intersection(rect1, rect2);
Debug.Assert(!intersectingRectangle.IsEmpty,
"Violation of: !intersect.IsEmpty; Rectangles must intersect to calculate a penetration vector.");
Vector2 penetration;
if (intersectingRectangle.Width < intersectingRectangle.Height)
{
var d = rect1.Center.X < rect2.Center.X
? intersectingRectangle.Width
: -intersectingRectangle.Width;
penetration = new Vector2(d, 0);
}
else
{
var d = rect1.Center.Y < rect2.Center.Y
? intersectingRectangle.Height
: -intersectingRectangle.Height;
penetration = new Vector2(0, d);
}
return penetration;
}
private static Vector2 PenetrationVector(CircleF circ1, CircleF circ2)
{
if (!circ1.Intersects(circ2))
{
return Vector2.Zero;
}
var displacement = Point2.Displacement(circ1.Center, circ2.Center);
Vector2 desiredDisplacement;
if (displacement != Vector2.Zero)
{
desiredDisplacement = displacement.NormalizedCopy() * (circ1.Radius + circ2.Radius);
}
else
{
desiredDisplacement = -Vector2.UnitY * (circ1.Radius + circ2.Radius);
}
var penetration = displacement - desiredDisplacement;
return penetration;
}
private static Vector2 PenetrationVector(CircleF circ, RectangleF rect)
{
var collisionPoint = rect.ClosestPointTo(circ.Center);
var cToCollPoint = collisionPoint - circ.Center;
if (rect.Contains(circ.Center) || cToCollPoint.Equals(Vector2.Zero))
{
var displacement = Point2.Displacement(circ.Center, rect.Center);
Vector2 desiredDisplacement;
if (displacement != Vector2.Zero)
{
// Calculate penetration as only in X or Y direction.
// Whichever is lower.
var dispx = new Vector2(displacement.X, 0);
var dispy = new Vector2(0, displacement.Y);
dispx.Normalize();
dispy.Normalize();
dispx *= (circ.Radius + rect.Width / 2);
dispy *= (circ.Radius + rect.Height / 2);
if (dispx.LengthSquared() < dispy.LengthSquared())
{
desiredDisplacement = dispx;
displacement.Y = 0;
}
else
{
desiredDisplacement = dispy;
displacement.X = 0;
}
}
else
{
desiredDisplacement = -Vector2.UnitY * (circ.Radius + rect.Height / 2);
}
var penetration = displacement - desiredDisplacement;
return penetration;
}
else
{
var penetration = circ.Radius * cToCollPoint.NormalizedCopy() - cToCollPoint;
return penetration;
}
}
private static Vector2 PenetrationVector(RectangleF rect, CircleF circ)
{
return -PenetrationVector(circ, rect);
}
#endregion
}
}
@@ -1,12 +0,0 @@
namespace MonoGame.Extended.Collisions
{
/// <summary>
/// An actor that can be collided with.
/// </summary>
public interface ICollisionActor
{
IShapeF Bounds { get; }
void OnCollision(CollisionEventArgs collisionInfo);
}
}
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
namespace MonoGame.Extended.Collisions
namespace MonoGame.Extended.Collisions.QuadTree
{
/// <summary>
/// Class for doing collision handling with a quad tree.
/// </summary>
public class Quadtree
public class QuadTree
{
/// <summary>
/// The default maximum depth.
@@ -21,7 +21,7 @@ namespace MonoGame.Extended.Collisions
/// <summary>
/// Contains the children of this node.
/// </summary>
protected List<Quadtree> Children = new List<Quadtree>();
protected List<QuadTree> Children = new List<QuadTree>();
/// <summary>
/// Contains the data for this node in the quadtree.
@@ -32,7 +32,7 @@ namespace MonoGame.Extended.Collisions
/// Creates a quad tree with the given bounds.
/// </summary>
/// <param name="bounds">The bounds of the new quad tree.</param>
public Quadtree(RectangleF bounds)
public QuadTree(RectangleF bounds)
{
CurrentDepth = 0;
NodeBounds = bounds;
@@ -71,7 +71,7 @@ namespace MonoGame.Extended.Collisions
var objectCount = 0;
// Do BFS on nodes to count children.
var process = new Queue<Quadtree>();
var process = new Queue<QuadTree>();
process.Enqueue(this);
while (process.Count > 0)
{
@@ -148,7 +148,7 @@ namespace MonoGame.Extended.Collisions
}
else
{
throw new InvalidOperationException($"Cannot remove from a non leaf {nameof(Quadtree)}");
throw new InvalidOperationException($"Cannot remove from a non leaf {nameof(QuadTree)}");
}
}
@@ -171,7 +171,7 @@ namespace MonoGame.Extended.Collisions
}
else if (numObjects < MaxObjectsPerNode)
{
var process = new Queue<Quadtree>();
var process = new Queue<QuadTree>();
process.Enqueue(this);
while (process.Count > 0)
{
@@ -232,14 +232,14 @@ namespace MonoGame.Extended.Collisions
for (var i = 0; i < childAreas.Length; ++i)
{
var node = new Quadtree(childAreas[i]);
var node = new QuadTree(childAreas[i]);
Children.Add(node);
Children[i].CurrentDepth = CurrentDepth + 1;
}
foreach (QuadtreeData contentQuadtree in Contents)
{
foreach (Quadtree childQuadtree in Children)
foreach (QuadTree childQuadtree in Children)
{
childQuadtree.Insert(contentQuadtree);
}
@@ -247,6 +247,16 @@ namespace MonoGame.Extended.Collisions
Clear();
}
/// <summary>
/// Clear current node and all children
/// </summary>
public void ClearAll()
{
foreach (QuadTree childQuadtree in Children)
childQuadtree.ClearAll();
Clear();
}
private void Clear()
{
foreach (QuadtreeData quadtreeData in Contents)
@@ -261,10 +271,10 @@ namespace MonoGame.Extended.Collisions
/// </summary>
/// <param name="area">The area to query for overlapping targets</param>
/// <returns>A unique list of targets intersected by area.</returns>
public List<QuadtreeData> Query(IShapeF area)
public List<QuadtreeData> Query(ref RectangleF area)
{
var recursiveResult = new List<QuadtreeData>();
QueryWithoutReset(area, recursiveResult);
QueryWithoutReset(ref area, recursiveResult);
foreach (var quadtreeData in recursiveResult)
{
quadtreeData.MarkClean();
@@ -272,7 +282,7 @@ namespace MonoGame.Extended.Collisions
return recursiveResult;
}
private void QueryWithoutReset(IShapeF area, List<QuadtreeData> recursiveResult)
private void QueryWithoutReset(ref RectangleF area, List<QuadtreeData> recursiveResult)
{
if (!NodeBounds.Intersects(area))
return;
@@ -292,7 +302,7 @@ namespace MonoGame.Extended.Collisions
{
for (int i = 0, size = Children.Count; i < size; i++)
{
Children[i].QueryWithoutReset(area, recursiveResult);
Children[i].QueryWithoutReset(ref area, recursiveResult);
}
}
}
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
namespace MonoGame.Extended.Collisions;
namespace MonoGame.Extended.Collisions.QuadTree;
/// <summary>
/// Data structure for the quad tree.
@@ -10,7 +10,7 @@ namespace MonoGame.Extended.Collisions;
public class QuadtreeData
{
private readonly ICollisionActor _target;
private readonly HashSet<Quadtree> _parents = new();
private readonly HashSet<QuadTree> _parents = new();
/// <summary>
/// Initialize a new instance of QuadTreeData.
@@ -19,13 +19,14 @@ public class QuadtreeData
public QuadtreeData(ICollisionActor target)
{
_target = target;
Bounds = _target.Bounds.BoundingRectangle;
}
/// <summary>
/// Remove a parent node.
/// </summary>
/// <param name="parent"></param>
public void RemoveParent(Quadtree parent)
public void RemoveParent(QuadTree parent)
{
_parents.Remove(parent);
}
@@ -34,9 +35,10 @@ public class QuadtreeData
/// Add a parent node.
/// </summary>
/// <param name="parent"></param>
public void AddParent(Quadtree parent)
public void AddParent(QuadTree parent)
{
_parents.Add(parent);
Bounds = _target.Bounds.BoundingRectangle;
}
/// <summary>
@@ -55,7 +57,7 @@ public class QuadtreeData
/// <summary>
/// Gets the bounding box for collision detection.
/// </summary>
public IShapeF Bounds => _target.Bounds;
public RectangleF Bounds { get; set; }
/// <summary>
/// Gets the collision actor target.
@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
namespace MonoGame.Extended.Collisions.QuadTree;
public class QuadTreeSpace: ISpaceAlgorithm
{
private readonly QuadTree _collisionTree;
private readonly List<ICollisionActor> _actors = new();
private readonly Dictionary<ICollisionActor, QuadtreeData> _targetDataDictionary = new();
public QuadTreeSpace(RectangleF boundary)
{
_collisionTree = new QuadTree(boundary);
}
/// <summary>
/// Inserts the target into the collision tree.
/// The target will have its OnCollision called when collisions occur.
/// </summary>
/// <param name="target">Target to insert.</param>
public void Insert(ICollisionActor target)
{
if (!_targetDataDictionary.ContainsKey(target))
{
var data = new QuadtreeData(target);
_targetDataDictionary.Add(target, data);
_collisionTree.Insert(data);
_actors.Add(target);
}
}
/// <summary>
/// Removes the target from the collision tree.
/// </summary>
/// <param name="target">Target to remove.</param>
public bool Remove(ICollisionActor target)
{
if (_targetDataDictionary.ContainsKey(target))
{
var data = _targetDataDictionary[target];
data.RemoveFromAllParents();
_targetDataDictionary.Remove(target);
_collisionTree.Shake();
_actors.Remove(target);
return true;
}
return false;
}
/// <summary>
/// Restructure a inner collection, if layer is dynamic, because actors can change own position
/// </summary>
public void Reset()
{
_collisionTree.ClearAll();
foreach (var value in _targetDataDictionary.Values)
{
_collisionTree.Insert(value);
}
_collisionTree.Shake();
}
/// <summary>
/// foreach support
/// </summary>
/// <returns></returns>
public List<ICollisionActor>.Enumerator GetEnumerator() => _actors.GetEnumerator();
/// <inheritdoc cref="QuadTree.Query"/>
public IEnumerable<ICollisionActor> Query(RectangleF boundsBoundingRectangle)
{
return _collisionTree.Query(ref boundsBoundingRectangle).Select(x => x.Target);
}
}
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MonoGame.Extended.Collisions;
public class SpatialHash: ISpaceAlgorithm
{
private readonly Dictionary<int, List<ICollisionActor>> _dictionary = new();
private readonly List<ICollisionActor> _actors = new();
private readonly Size2 _size;
public SpatialHash(Size2 size)
{
_size = size;
}
public void Insert(ICollisionActor actor)
{
InsertToHash(actor);
_actors.Add(actor);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InsertToHash(ICollisionActor actor)
{
var rect = actor.Bounds.BoundingRectangle;
for (var x = rect.Left; x < rect.Right; x+=_size.Width)
for (var y = rect.Top; y < rect.Bottom; y+=_size.Height)
AddToCell(x, y, actor);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddToCell(float x, float y, ICollisionActor actor)
{
var index = GetIndex(x, y);
if (_dictionary.TryGetValue(index, out var actors))
actors.Add(actor);
else
_dictionary[index] = new() { actor };
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetIndex(float x, float y)
{
return (int)(x / _size.Width) << 16 + (int)(y / _size.Height);
}
public bool Remove(ICollisionActor actor)
{
foreach (var actors in _dictionary.Values)
actors.Remove(actor);
return _actors.Remove(actor);
}
public IEnumerable<ICollisionActor> Query(RectangleF boundsBoundingRectangle)
{
var results = new HashSet<ICollisionActor>();
var bounds = boundsBoundingRectangle.BoundingRectangle;
for (var x = boundsBoundingRectangle.Left; x < boundsBoundingRectangle.Right; x+=_size.Width)
for (var y = boundsBoundingRectangle.Top; y < boundsBoundingRectangle.Bottom; y+=_size.Height)
if (_dictionary.TryGetValue(GetIndex(x, y), out var actors))
foreach (var actor in actors)
if (bounds.Intersects(actor.Bounds))
results.Add(actor);
return results;
}
public List<ICollisionActor>.Enumerator GetEnumerator() => _actors.GetEnumerator();
public void Reset()
{
_dictionary.Clear();
foreach (var actor in _actors)
InsertToHash(actor);
}
}
@@ -0,0 +1,15 @@
using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline;
namespace MonoGame.Extended.Content.Pipeline;
public static class ContentImporterContextExtensions
{
public static string AddDependencyWithLogging(this ContentImporterContext context, string filePath, string source)
{
source = Path.Combine(Path.GetDirectoryName(filePath), source);
ContentLogger.Log($"Adding dependency '{source}'");
context.AddDependency(source);
return source;
}
}
@@ -28,10 +28,10 @@ namespace MonoGame.Extended.Content.Pipeline
public ExternalReference<TInput> GetExternalReference<TInput>(string source)
{
if (_externalReferences.TryGetValue(source, out var contentItem))
if (source is not null && _externalReferences.TryGetValue(source, out var contentItem))
return contentItem as ExternalReference<TInput>;
return null;
}
}
}
}
@@ -12,9 +12,6 @@
<Authors>craftworkgames</Authors>
<Description>Content Pipeline importers and processors to make MonoGame more awesome.</Description>
<PackageTags>monogame content importer processor reader tiled texturepacker bmfont animations</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
</PropertyGroup>
<PropertyGroup>
@@ -45,4 +42,4 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>
@@ -9,11 +9,17 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
// ReSharper disable once SuggestBaseTypeForParameter
public static void WriteTiledMapProperties(this ContentWriter writer, IReadOnlyCollection<TiledMapPropertyContent> value)
{
if (value == null)
{
writer.Write(0);
return;
}
writer.Write(value.Count);
foreach (var property in value)
{
writer.Write(property.Name);
writer.Write(property.Value);
writer.Write(property.Value ?? string.Empty);
WriteTiledMapProperties(writer, property.Properties);
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using MonoGame.Extended.Tiled.Serialization;
namespace MonoGame.Extended.Content.Pipeline.Tiled;
public class TiledContentItem<T>: ContentItem<T>
{
public TiledContentItem(T data) : base(data)
{
}
public void BuildExternalReference<T>(ContentProcessorContext context, TiledMapImageContent image)
{
var parameters = new OpaqueDataDictionary
{
{ "ColorKeyColor", image.TransparentColor },
{ "ColorKeyEnabled", true }
};
BuildExternalReference<Texture2DContent>(context, image.Source, parameters);
}
}
@@ -2,11 +2,11 @@
namespace MonoGame.Extended.Content.Pipeline.Tiled
{
public class TiledMapContentItem : ContentItem<TiledMapContent>
public class TiledMapContentItem : TiledContentItem<TiledMapContent>
{
public TiledMapContentItem(TiledMapContent data)
public TiledMapContentItem(TiledMapContent data)
: base(data)
{
}
}
}
}
@@ -75,7 +75,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
}
}
}
[ContentProcessor(DisplayName = "Tiled Map Processor - MonoGame.Extended")]
public class TiledMapProcessor : ContentProcessor<TiledMapContentItem, TiledMapContentItem>
@@ -95,14 +95,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
if (string.IsNullOrWhiteSpace(tileset.Source))
{
// Load the Texture2DContent for the tileset as it will be saved into the map content file.
//var externalReference = new ExternalReference<Texture2DContent>(tileset.Image.Source);
var parameters = new OpaqueDataDictionary
{
{ "ColorKeyColor", tileset.Image.TransparentColor },
{ "ColorKeyEnabled", true }
};
//tileset.Image.ContentRef = context.BuildAsset<Texture2DContent, Texture2DContent>(externalReference, "", parameters, "", "");
contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image.Source, parameters);
contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image);
}
else
{
@@ -114,7 +107,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
}
ProcessLayers(contentItem, map, context, map.Layers);
return contentItem;
}
catch (Exception ex)
@@ -132,14 +125,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
{
case TiledMapImageLayerContent imageLayer:
ContentLogger.Log($"Processing image layer '{imageLayer.Name}'");
//var externalReference = new ExternalReference<Texture2DContent>(imageLayer.Image.Source);
var parameters = new OpaqueDataDictionary
{
{ "ColorKeyColor", imageLayer.Image.TransparentColor },
{ "ColorKeyEnabled", true }
};
//imageLayer.Image.ContentRef = context.BuildAsset<Texture2DContent, Texture2DContent>(externalReference, "", parameters, "", "");
contentItem.BuildExternalReference<Texture2DContent>(context, imageLayer.Image.Source, parameters);
contentItem.BuildExternalReference<Texture2DContent>(context, imageLayer.Image);
ContentLogger.Log($"Processed image layer '{imageLayer.Name}'");
break;
@@ -335,4 +321,4 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
};
}
}
}
}
@@ -1,12 +1,14 @@
using MonoGame.Extended.Tiled.Serialization;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using MonoGame.Extended.Tiled.Serialization;
namespace MonoGame.Extended.Content.Pipeline.Tiled
{
public class TiledMapTilesetContentItem : ContentItem<TiledMapTilesetContent>
public class TiledMapTilesetContentItem : TiledContentItem<TiledMapTilesetContent>
{
public TiledMapTilesetContentItem(TiledMapTilesetContent data)
public TiledMapTilesetContentItem(TiledMapTilesetContent data)
: base(data)
{
}
}
}
}
@@ -39,23 +39,18 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
var tilesetSerializer = new XmlSerializer(typeof(TiledMapTilesetContent));
var tileset = (TiledMapTilesetContent)tilesetSerializer.Deserialize(reader);
tileset.Image.Source = Path.Combine(Path.GetDirectoryName(filePath), tileset.Image.Source);
ContentLogger.Log($"Adding dependency '{tileset.Image.Source}'");
context.AddDependency(tileset.Image.Source);
if (tileset.Image is not null)
tileset.Image.Source = context.AddDependencyWithLogging(filePath, tileset.Image.Source);
foreach (var tile in tileset.Tiles)
{
foreach (var obj in tile.Objects)
{
if (!string.IsNullOrWhiteSpace(obj.TemplateSource))
{
obj.TemplateSource = Path.Combine(Path.GetDirectoryName(filePath), obj.TemplateSource);
ContentLogger.Log($"Adding dependency '{obj.TemplateSource}'");
// We depend on the template.
context.AddDependency(obj.TemplateSource);
}
obj.TemplateSource = context.AddDependencyWithLogging(filePath, obj.TemplateSource);
}
if (tile.Image is not null)
tile.Image.Source = context.AddDependencyWithLogging(filePath, tile.Image.Source);
}
return tileset;
@@ -15,16 +15,10 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
ContentLogger.Logger = context.Logger;
ContentLogger.Log($"Processing tileset '{tileset.Name}'");
// Build the Texture2D asset and load it as it will be saved as part of this tileset file.
//var externalReference = new ExternalReference<Texture2DContent>(tileset.Image.Source);
var parameters = new OpaqueDataDictionary
{
{ "ColorKeyColor", tileset.Image.TransparentColor },
{ "ColorKeyEnabled", true }
};
//tileset.Image.ContentRef = context.BuildAsset<Texture2DContent, Texture2DContent>(externalReference, "", parameters, "", "");
contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image.Source, parameters);
if (tileset.Image is not null)
contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image);
foreach (var tile in tileset.Tiles)
{
@@ -32,6 +26,8 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
{
TiledMapContentHelper.Process(obj, context);
}
if (tile.Image is not null)
contentItem.BuildExternalReference<Texture2DContent>(context, tile.Image);
}
ContentLogger.Log($"Processed tileset '{tileset.Name}'");
@@ -30,9 +30,9 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
public static void WriteTileset(ContentWriter writer, TiledMapTilesetContent tileset, IExternalReferenceRepository externalReferenceRepository)
{
var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tileset.Image.Source);
var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tileset.Image?.Source);
writer.WriteExternalReference(externalReference);
writer.Write(tileset.Class ?? tileset.Type ?? string.Empty);
writer.Write(tileset.TileWidth);
writer.Write(tileset.TileHeight);
writer.Write(tileset.TileCount);
@@ -42,13 +42,17 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
writer.Write(tileset.Tiles.Count);
foreach (var tilesetTile in tileset.Tiles)
WriteTilesetTile(writer, tilesetTile);
WriteTilesetTile(writer, tilesetTile, externalReferenceRepository);
writer.WriteTiledMapProperties(tileset.Properties);
}
private static void WriteTilesetTile(ContentWriter writer, TiledMapTilesetTileContent tilesetTile)
private static void WriteTilesetTile(ContentWriter writer, TiledMapTilesetTileContent tilesetTile,
IExternalReferenceRepository externalReferenceRepository)
{
var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tilesetTile.Image?.Source);
writer.WriteExternalReference(externalReference);
writer.Write(tilesetTile.LocalIdentifier);
writer.Write(tilesetTile.Type);
writer.Write(tilesetTile.Frames.Count);
@@ -36,6 +36,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
private static void WriteMetaData(ContentWriter writer, TiledMapContent map)
{
writer.Write(map.Class ?? map.Type ?? string.Empty);
writer.Write(map.Width);
writer.Write(map.Height);
writer.Write(map.TileWidth);
@@ -80,9 +81,10 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
private void WriteLayer(ContentWriter writer, TiledMapLayerContent layer)
{
writer.Write((byte)layer.Type);
writer.Write((byte)layer.LayerType);
writer.Write(layer.Name ?? string.Empty);
writer.Write(layer.Class ?? layer.Type ?? string.Empty);
writer.Write(layer.Visible);
writer.Write(layer.Opacity);
writer.Write(layer.OffsetX);
@@ -92,7 +94,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
writer.WriteTiledMapProperties(layer.Properties);
switch (layer.Type)
switch (layer.LayerType)
{
case TiledMapLayerType.ImageLayer:
WriteImageLayer(writer, (TiledMapImageLayerContent)layer);
@@ -107,7 +109,7 @@ namespace MonoGame.Extended.Content.Pipeline.Tiled
WriteLayers(writer, ((TiledMapGroupLayerContent)layer).Layers);
break;
default:
throw new ArgumentOutOfRangeException(nameof(layer.Type));
throw new ArgumentOutOfRangeException(nameof(layer.LayerType));
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>An Entity Component System to make MonoGame more awesome.</Description>
<PackageTags>monogame ecs entity component system</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
</PropertyGroup>
<PropertyGroup>
@@ -25,4 +22,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>Graphics makes MonoGame more awesome.</Description>
<PackageTags>monogame graphics batcher effects</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -46,4 +43,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>A GUI system for MonoGame written from the ground up to make MonoGame more awesome.</Description>
<PackageTags>monogame gui</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -27,4 +24,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -23,7 +23,8 @@ namespace MonoGame.Extended.Input
public bool IsKeyDown(Keys key) => _currentKeyboardState.IsKeyDown(key);
public bool IsKeyUp(Keys key) => _currentKeyboardState.IsKeyUp(key);
public Keys[] GetPressedKeys() => _currentKeyboardState.GetPressedKeys();
public void GetPressedKeys(Keys[] keys) => _currentKeyboardState.GetPressedKeys(keys);
/// <summary>
/// Gets whether the given key was down on the previous state, but is now up.
/// </summary>
@@ -54,6 +55,6 @@ namespace MonoGame.Extended.Input
/// <returns>true if the key was pressed this state-change, otherwise false.</returns>
public readonly bool IsKeyPressed(Keys key) => _previousKeyboardState.IsKeyUp(key) && _currentKeyboardState.IsKeyDown(key);
public bool WasAnyKeyJustDown() => _previousKeyboardState.GetPressedKeys().Any();
public bool WasAnyKeyJustDown() => _previousKeyboardState.GetPressedKeyCount() > 0;
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>An event based input system to MonoGame more awesome.</Description>
<PackageTags>monogame input event based listeners</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -26,4 +23,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -0,0 +1,12 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Particles.Modifiers.Interpolators
{
public class VelocityInterpolator : Interpolator<Vector2>
{
public override unsafe void Update(float amount, Particle* particle)
{
particle->Velocity = (EndValue - StartValue) * amount + StartValue;
}
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>A high performance particle system to make MonoGame more awesome.</Description>
<PackageTags>monogame particle system</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -30,4 +27,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -0,0 +1,20 @@
using Microsoft.Xna.Framework.Content;
namespace MonoGame.Extended.Tiled
{
public static class ContentReaderExtensions
{
public static void ReadTiledMapProperties(this ContentReader reader, TiledMapProperties properties)
{
var count = reader.ReadInt32();
for (var i = 0; i < count; i++)
{
var key = reader.ReadString();
var value = new TiledMapPropertyValue(reader.ReadString());
ReadTiledMapProperties(reader, value.Properties);
properties[key] = value;
}
}
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>Support for Tiled maps to make MonoGame more awesome. See http://www.mapeditor.org</Description>
<PackageTags>monogame tiled maps orthographic isometric</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
</PropertyGroup>
<PropertyGroup>
@@ -30,4 +27,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -52,12 +52,16 @@ namespace MonoGame.Extended.Tiled.Renderers
var tileGid = tile.GlobalIdentifier;
var localTileIdentifier = tileGid - firstGlobalIdentifier;
var position = GetTilePosition(map, tile);
var tilesetColumns = tileset.Columns == 0 ? 1 : tileset.Columns; // fixes a problem (what problem exactly?)
var sourceRectangle = TiledMapHelper.GetTileSourceRectangle(localTileIdentifier, tileset.TileWidth, tileset.TileHeight, tilesetColumns, tileset.Margin, tileset.Spacing);
var sourceRectangle = tileset.GetTileRegion(localTileIdentifier);
var flipFlags = tile.Flags;
// animated tiles
var tilesetTile = tileset.Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier);
if (tilesetTile?.Texture is not null)
{
position.Y += map.TileHeight - sourceRectangle.Height;
texture = tilesetTile.Texture;
}
if (tilesetTile is TiledMapTilesetAnimatedTile animatedTilesetTile)
{
@@ -117,4 +121,4 @@ namespace MonoGame.Extended.Tiled.Renderers
}
}
}
}
}
@@ -19,6 +19,13 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlIgnore]
public string FilePath { get; set; }
// Deprecated as of Tiled 1.9.0 (replaced by "class" attribute)
[XmlAttribute(DataType = "string", AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(DataType = "string", AttributeName = "class")]
public string Class { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
@@ -65,4 +72,4 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlArrayItem("property")]
public List<TiledMapPropertyContent> Properties { get; set; }
}
}
}
@@ -8,9 +8,9 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlInclude(typeof(TiledMapObjectLayerContent))]
public abstract class TiledMapLayerContent
{
protected TiledMapLayerContent(TiledMapLayerType type)
protected TiledMapLayerContent(TiledMapLayerType layerType)
{
Type = type;
LayerType = layerType;
Opacity = 1.0f;
ParallaxX = 1.0f;
ParallaxY = 1.0f;
@@ -21,6 +21,13 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
// Deprecated as of Tiled 1.9.0 (replaced by "class" attribute)
[XmlAttribute(DataType = "string", AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(DataType = "string", AttributeName = "class")]
public string Class { get; set; }
[XmlAttribute(AttributeName = "opacity")]
public float Opacity { get; set; }
@@ -44,7 +51,7 @@ namespace MonoGame.Extended.Tiled.Serialization
public List<TiledMapPropertyContent> Properties { get; set; }
[XmlIgnore]
public TiledMapLayerType Type { get; }
public TiledMapLayerType LayerType { get; }
public override string ToString()
{
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Xml.Serialization;
namespace MonoGame.Extended.Tiled.Serialization
@@ -13,6 +14,10 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlText]
public string ValueBody { get; set; }
[XmlArray("properties")]
[XmlArrayItem("property")]
public List<TiledMapPropertyContent> Properties { get; set; }
public string Value => ValueAttribute ?? ValueBody;
public override string ToString()
@@ -20,4 +25,4 @@ namespace MonoGame.Extended.Tiled.Serialization
return $"{Name}: {Value}";
}
}
}
}
@@ -22,6 +22,13 @@ namespace MonoGame.Extended.Tiled.Serialization
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
// Deprecated as of Tiled 1.9.0 (replaced by "class" attribute)
[XmlAttribute(DataType = "string", AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(DataType = "string", AttributeName = "class")]
public string Class { get; set; }
[XmlAttribute(AttributeName = "tilewidth")]
public int TileWidth { get; set; }
@@ -66,4 +73,4 @@ namespace MonoGame.Extended.Tiled.Serialization
return $"{Name}: {Image}";
}
}
}
}
+3 -1
View File
@@ -17,6 +17,7 @@ namespace MonoGame.Extended.Tiled
private readonly List<Tuple<TiledMapTileset, int>> _firstGlobalIdentifiers = new List<Tuple<TiledMapTileset, int>>();
public string Name { get; }
public string Type { get; }
public int Width { get; }
public int Height { get; }
public int TileWidth { get; }
@@ -44,10 +45,11 @@ namespace MonoGame.Extended.Tiled
Properties = new TiledMapProperties();
}
public TiledMap(string name, int width, int height, int tileWidth, int tileHeight, TiledMapTileDrawOrder renderOrder, TiledMapOrientation orientation, Color? backgroundColor = null)
public TiledMap(string name, string type, int width, int height, int tileWidth, int tileHeight, TiledMapTileDrawOrder renderOrder, TiledMapOrientation orientation, Color? backgroundColor = null)
: this()
{
Name = name;
Type = type;
Width = width;
Height = height;
TileWidth = tileWidth;
@@ -8,8 +8,8 @@ namespace MonoGame.Extended.Tiled
public class TiledMapGroupLayer : TiledMapLayer
{
public List<TiledMapLayer> Layers { get; }
public TiledMapGroupLayer(string name, List<TiledMapLayer> layers, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1, bool isVisible = true)
: base(name, offset, parallaxFactor, opacity, isVisible)
public TiledMapGroupLayer(string name, string type, List<TiledMapLayer> layers, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1, bool isVisible = true)
: base(name, type, offset, parallaxFactor, opacity, isVisible)
{
Layers = layers;
}
@@ -5,8 +5,8 @@ namespace MonoGame.Extended.Tiled
{
public class TiledMapImageLayer : TiledMapLayer, IMovable
{
public TiledMapImageLayer(string name, Texture2D image, Vector2? position = null, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true)
: base(name, offset, parallaxFactor, opacity, isVisible)
public TiledMapImageLayer(string name, string type, Texture2D image, Vector2? position = null, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true)
: base(name, type, offset, parallaxFactor, opacity, isVisible)
{
Image = image;
Position = position ?? Vector2.Zero;
@@ -5,15 +5,17 @@ namespace MonoGame.Extended.Tiled
public abstract class TiledMapLayer
{
public string Name { get; }
public string Type { get; }
public bool IsVisible { get; set; }
public float Opacity { get; set; }
public Vector2 Offset { get; set; }
public Vector2 ParallaxFactor { get; set; }
public TiledMapProperties Properties { get; }
protected TiledMapLayer(string name, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true)
protected TiledMapLayer(string name, string type, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true)
{
Name = name;
Type = type;
Offset = offset ?? Vector2.Zero;
ParallaxFactor = parallaxFactor ?? Vector2.One;
Opacity = opacity;
@@ -4,9 +4,9 @@ namespace MonoGame.Extended.Tiled
{
public class TiledMapObjectLayer : TiledMapLayer
{
public TiledMapObjectLayer(string name, TiledMapObject[] objects, Color? color = null, TiledMapObjectDrawOrder drawOrder = TiledMapObjectDrawOrder.TopDown,
public TiledMapObjectLayer(string name, string type, TiledMapObject[] objects, Color? color = null, TiledMapObjectDrawOrder drawOrder = TiledMapObjectDrawOrder.TopDown,
Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true)
: base(name, offset, parallaxFactor, opacity, isVisible)
: base(name, type, offset, parallaxFactor, opacity, isVisible)
{
Color = color;
DrawOrder = drawOrder;
@@ -2,7 +2,13 @@
namespace MonoGame.Extended.Tiled
{
public class TiledMapProperties : Dictionary<string, string>
public class TiledMapProperties : Dictionary<string, TiledMapPropertyValue>
{
public bool TryGetValue(string key, out string value)
{
bool result = TryGetValue(key, out TiledMapPropertyValue tmpVal);
value = result ? null : tmpVal.Value;
return result;
}
}
}
}
@@ -0,0 +1,32 @@
namespace MonoGame.Extended.Tiled;
public class TiledMapPropertyValue
{
public string Value { get; }
public TiledMapProperties Properties;
public TiledMapPropertyValue()
{
Value = string.Empty;
Properties = new();
}
public TiledMapPropertyValue(string value)
{
Value = value;
Properties = new();
}
public TiledMapPropertyValue(TiledMapProperties properties)
{
Value = string.Empty;
Properties = properties;
}
public override string ToString() => Value;
//public static implicit operator TiledMapPropertyValue(string value) => new(value);
public static implicit operator string(TiledMapPropertyValue value) => value.Value;
public static implicit operator TiledMapProperties(TiledMapPropertyValue value) => value.Properties;
}
@@ -16,27 +16,16 @@ namespace MonoGame.Extended.Tiled
return existingInstance;
var map = ReadTiledMap(reader);
ReadProperties(reader, map.Properties);
reader.ReadTiledMapProperties(map.Properties);
ReadTilesets(reader, map);
ReadLayers(reader, map);
return map;
}
private static void ReadProperties(ContentReader reader, TiledMapProperties properties)
{
var count = reader.ReadInt32();
for (var i = 0; i < count; i++)
{
var key = reader.ReadString();
var value = reader.ReadString();
properties[key] = value;
}
}
private static TiledMap ReadTiledMap(ContentReader reader)
{
var name = reader.AssetName;
var type = reader.ReadString();
var width = reader.ReadInt32();
var height = reader.ReadInt32();
var tileWidth = reader.ReadInt32();
@@ -45,7 +34,7 @@ namespace MonoGame.Extended.Tiled
var renderOrder = (TiledMapTileDrawOrder)reader.ReadByte();
var orientation = (TiledMapOrientation)reader.ReadByte();
return new TiledMap(name, width, height, tileWidth, tileHeight, renderOrder, orientation, backgroundColor);
return new TiledMap(name, type, width, height, tileWidth, tileHeight, renderOrder, orientation, backgroundColor);
}
private static void ReadTilesets(ContentReader reader, TiledMap map)
@@ -88,6 +77,7 @@ namespace MonoGame.Extended.Tiled
{
var layerType = (TiledMapLayerType)reader.ReadByte();
var name = reader.ReadString();
var type = reader.ReadString();
var isVisible = reader.ReadBoolean();
var opacity = reader.ReadSingle();
var offsetX = reader.ReadSingle();
@@ -98,23 +88,23 @@ namespace MonoGame.Extended.Tiled
var parallaxFactor = new Vector2(parallaxX, parallaxY);
var properties = new TiledMapProperties();
ReadProperties(reader, properties);
reader.ReadTiledMapProperties(properties);
TiledMapLayer layer;
switch (layerType)
{
case TiledMapLayerType.ImageLayer:
layer = ReadImageLayer(reader, name, offset, parallaxFactor, opacity, isVisible);
layer = ReadImageLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible);
break;
case TiledMapLayerType.TileLayer:
layer = ReadTileLayer(reader, name, offset, parallaxFactor, opacity, isVisible, map);
layer = ReadTileLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible, map);
break;
case TiledMapLayerType.ObjectLayer:
layer = ReadObjectLayer(reader, name, offset, parallaxFactor, opacity, isVisible, map);
layer = ReadObjectLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible, map);
break;
case TiledMapLayerType.GroupLayer:
layer = new TiledMapGroupLayer(name, ReadGroup(reader, map), offset, parallaxFactor, opacity, isVisible);
layer = new TiledMapGroupLayer(name, type, ReadGroup(reader, map), offset, parallaxFactor, opacity, isVisible);
break;
default:
throw new ArgumentOutOfRangeException();
@@ -126,7 +116,7 @@ namespace MonoGame.Extended.Tiled
return layer;
}
private static TiledMapLayer ReadObjectLayer(ContentReader reader, string name, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map)
private static TiledMapLayer ReadObjectLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map)
{
var color = reader.ReadColor();
var drawOrder = (TiledMapObjectDrawOrder)reader.ReadByte();
@@ -136,7 +126,7 @@ namespace MonoGame.Extended.Tiled
for (var i = 0; i < objectCount; i++)
objects[i] = ReadTiledMapObject(reader, map);
return new TiledMapObjectLayer(name, objects, color, drawOrder, offset, parallaxFactor, opacity, isVisible);
return new TiledMapObjectLayer(name, type, objects, color, drawOrder, offset, parallaxFactor, opacity, isVisible);
}
private static TiledMapObject ReadTiledMapObject(ContentReader reader, TiledMap map)
@@ -154,7 +144,7 @@ namespace MonoGame.Extended.Tiled
var properties = new TiledMapProperties();
const float opacity = 1.0f;
ReadProperties(reader, properties);
reader.ReadTiledMapProperties(properties);
TiledMapObject mapObject;
@@ -205,16 +195,16 @@ namespace MonoGame.Extended.Tiled
return points;
}
private static TiledMapImageLayer ReadImageLayer(ContentReader reader, string name, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible)
private static TiledMapImageLayer ReadImageLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible)
{
var texture = reader.ReadExternalReference<Texture2D>();
var x = reader.ReadSingle();
var y = reader.ReadSingle();
var position = new Vector2(x, y);
return new TiledMapImageLayer(name, texture, position, offset, parallaxFactor, opacity, isVisible);
return new TiledMapImageLayer(name, type, texture, position, offset, parallaxFactor, opacity, isVisible);
}
private static TiledMapTileLayer ReadTileLayer(ContentReader reader, string name, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map)
private static TiledMapTileLayer ReadTileLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map)
{
var width = reader.ReadInt32();
var height = reader.ReadInt32();
@@ -222,7 +212,7 @@ namespace MonoGame.Extended.Tiled
var tileHeight = map.TileHeight;
var tileCount = reader.ReadInt32();
var layer = new TiledMapTileLayer(name, width, height, tileWidth, tileHeight, offset, parallaxFactor, opacity, isVisible);
var layer = new TiledMapTileLayer(name, type, width, height, tileWidth, tileHeight, offset, parallaxFactor, opacity, isVisible);
for (var i = 0; i < tileCount; i++)
{
@@ -5,9 +5,9 @@ namespace MonoGame.Extended.Tiled
{
public class TiledMapTileLayer : TiledMapLayer
{
public TiledMapTileLayer(string name, int width, int height, int tileWidth, int tileHeight, Vector2? offset = null,
public TiledMapTileLayer(string name, string type, int width, int height, int tileWidth, int tileHeight, Vector2? offset = null,
Vector2? parallaxFactor = null, float opacity = 1, bool isVisible = true)
: base(name, offset, parallaxFactor, opacity, isVisible)
: base(name, type, offset, parallaxFactor, opacity, isVisible)
{
Width = width;
Height = height;
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.TextureAtlases;
@@ -20,9 +21,10 @@ namespace MonoGame.Extended.Tiled
public class TiledMapTileset : ITileset
{
public TiledMapTileset(Texture2D texture, int tileWidth, int tileHeight, int tileCount, int spacing, int margin, int columns)
public TiledMapTileset(Texture2D texture, string type, int tileWidth, int tileHeight, int tileCount, int spacing, int margin, int columns)
{
Texture = texture;
Type = type;
TileWidth = tileWidth;
TileHeight = tileHeight;
TileCount = tileCount;
@@ -43,6 +45,7 @@ namespace MonoGame.Extended.Tiled
return new TextureRegion2D(Texture, x, y, TileWidth, TileHeight);
}
public string Type { get; }
public int TileWidth { get; }
public int TileHeight { get; }
public int Spacing { get; }
@@ -58,7 +61,10 @@ namespace MonoGame.Extended.Tiled
public Rectangle GetTileRegion(int localTileIdentifier)
{
return TiledMapHelper.GetTileSourceRectangle(localTileIdentifier, TileWidth, TileHeight, Columns, Margin, Spacing);
return Texture is not null
? TiledMapHelper.GetTileSourceRectangle(localTileIdentifier, TileWidth, TileHeight, Columns, Margin,
Spacing)
: Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier).Texture.Bounds;
}
}
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Tiled
{
@@ -12,8 +13,9 @@ namespace MonoGame.Extended.Tiled
public ReadOnlyCollection<TiledMapTilesetTileAnimationFrame> AnimationFrames { get; }
public TiledMapTilesetTileAnimationFrame CurrentAnimationFrame { get; private set; }
public TiledMapTilesetAnimatedTile(int localTileIdentifier, TiledMapTilesetTileAnimationFrame[] frames, string type = null, TiledMapObject[] objects = null)
: base(localTileIdentifier, type, objects)
public TiledMapTilesetAnimatedTile(int localTileIdentifier,
TiledMapTilesetTileAnimationFrame[] frames, string type = null, TiledMapObject[] objects = null, Texture2D texture = null)
: base(localTileIdentifier, type, objects, texture)
{
if (frames.Length == 0) throw new InvalidOperationException("There must be at least one tileset animation frame");
@@ -33,4 +35,4 @@ namespace MonoGame.Extended.Tiled
CurrentAnimationFrame = AnimationFrames[_frameIndex];
}
}
}
}
@@ -18,6 +18,7 @@ namespace MonoGame.Extended.Tiled
public static TiledMapTileset ReadTileset(ContentReader reader)
{
var texture = reader.ReadExternalReference<Texture2D>();
var @class = reader.ReadString();
var tileWidth = reader.ReadInt32();
var tileHeight = reader.ReadInt32();
var tileCount = reader.ReadInt32();
@@ -26,28 +27,38 @@ namespace MonoGame.Extended.Tiled
var columns = reader.ReadInt32();
var explicitTileCount = reader.ReadInt32();
var tileset = new TiledMapTileset(texture, tileWidth, tileHeight, tileCount, spacing, margin, columns);
var tileset = new TiledMapTileset(texture, @class, tileWidth, tileHeight, tileCount, spacing, margin, columns);
for (var tileIndex = 0; tileIndex < explicitTileCount; tileIndex++)
{
var localTileIdentifier = reader.ReadInt32();
var type = reader.ReadString();
var animationFramesCount = reader.ReadInt32();
var tilesetTile = animationFramesCount <= 0
? ReadTiledMapTilesetTile(reader, tileset, objects =>
new TiledMapTilesetTile(localTileIdentifier, type, objects))
: ReadTiledMapTilesetTile(reader, tileset, objects =>
new TiledMapTilesetAnimatedTile(localTileIdentifier, ReadTiledMapTilesetAnimationFrames(reader, tileset, animationFramesCount), type, objects));
ReadTile(reader, tileset);
ReadProperties(reader, tilesetTile.Properties);
tileset.Tiles.Add(tilesetTile);
}
ReadProperties(reader, tileset.Properties);
reader.ReadTiledMapProperties(tileset.Properties);
return tileset;
}
private static TiledMapTilesetTileAnimationFrame[] ReadTiledMapTilesetAnimationFrames(ContentReader reader, TiledMapTileset tileset, int animationFramesCount)
private static void ReadTile(ContentReader reader, TiledMapTileset tileset)
{
var texture = reader.ReadExternalReference<Texture2D>();
var localTileIdentifier = reader.ReadInt32();
var type = reader.ReadString();
var animationFramesCount = reader.ReadInt32();
var objectCount = reader.ReadInt32();
var objects = new TiledMapObject[objectCount];
for (var i = 0; i < objectCount; i++)
objects[i] = ReadTiledMapObject(reader, tileset);
var tilesetTile = animationFramesCount <= 0
? new TiledMapTilesetTile(localTileIdentifier, type, objects, texture)
: new TiledMapTilesetAnimatedTile(localTileIdentifier,
ReadTiledMapTilesetAnimationFrames(reader, tileset, animationFramesCount), type, objects, texture);
reader.ReadTiledMapProperties(tilesetTile.Properties);
tileset.Tiles.Add(tilesetTile);
}
private static TiledMapTilesetTileAnimationFrame[] ReadTiledMapTilesetAnimationFrames(ContentReader reader, TiledMapTileset tileset, int animationFramesCount)
{
var animationFrames = new TiledMapTilesetTileAnimationFrame[animationFramesCount];
@@ -64,6 +75,7 @@ namespace MonoGame.Extended.Tiled
private static TiledMapTilesetTile ReadTiledMapTilesetTile(ContentReader reader, TiledMapTileset tileset, Func<TiledMapObject[], TiledMapTilesetTile> createTile)
{
var texture = reader.ReadExternalReference<Texture2D>();
var objectCount = reader.ReadInt32();
var objects = new TiledMapObject[objectCount];
@@ -88,7 +100,7 @@ namespace MonoGame.Extended.Tiled
var properties = new TiledMapProperties();
const float opacity = 1.0f;
ReadProperties(reader, properties);
reader.ReadTiledMapProperties(properties);
TiledMapObject mapObject;
@@ -134,17 +146,5 @@ namespace MonoGame.Extended.Tiled
return points;
}
private static void ReadProperties(ContentReader reader, TiledMapProperties properties)
{
var count = reader.ReadInt32();
for (var i = 0; i < count; i++)
{
var key = reader.ReadString();
var value = reader.ReadString();
properties[key] = value;
}
}
}
}
@@ -1,12 +1,15 @@
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Tiled
{
[DebuggerDisplay("{LocalTileIdentifier}: Type: {Type}, Properties: {Properties.Count}, Objects: {Objects.Count}")]
public class TiledMapTilesetTile
{
public TiledMapTilesetTile(int localTileIdentifier, string type = null, TiledMapObject[] objects = null)
// For remove libraries
public TiledMapTilesetTile(int localTileIdentifier, string type = null,
TiledMapObject[] objects = null)
{
LocalTileIdentifier = localTileIdentifier;
Type = type;
@@ -14,9 +17,20 @@ namespace MonoGame.Extended.Tiled
Properties = new TiledMapProperties();
}
public TiledMapTilesetTile(int localTileIdentifier, string type = null,
TiledMapObject[] objects = null, Texture2D texture = null)
{
Texture = texture;
LocalTileIdentifier = localTileIdentifier;
Type = type;
Objects = objects != null ? new List<TiledMapObject>(objects) : new List<TiledMapObject>();
Properties = new TiledMapProperties();
}
public int LocalTileIdentifier { get; }
public string Type { get; }
public TiledMapProperties Properties { get; }
public List<TiledMapObject> Objects { get; }
public Texture2D Texture { get; }
}
}
}
@@ -0,0 +1,15 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Tweening;
public class ColorTween: Tween<Color>
{
internal ColorTween(object target, float duration, float delay, TweenMember<Color> member, Color endValue) : base(target, duration, delay, member, endValue)
{
}
protected override void Interpolate(float n)
{
Member.Value = Color.Lerp(_startValue, _endValue, n);
}
}
@@ -0,0 +1,21 @@
using System;
using System.Linq.Expressions;
namespace MonoGame.Extended.Tweening;
public class LinearOperations<T>
{
static LinearOperations()
{
var a = Expression.Parameter(typeof(T));
var b = Expression.Parameter(typeof(T));
var c = Expression.Parameter(typeof(float));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(a, b), a, b).Compile();
Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(a, b), a, b).Compile();
Multiply = Expression.Lambda<Func<T, float, T>>(Expression.Multiply(a, c), a, c).Compile();
}
public static Func<T, T, T> Add { get; }
public static Func<T, T, T> Subtract { get; }
public static Func<T, float, T> Multiply { get; }
}
@@ -0,0 +1,23 @@
namespace MonoGame.Extended.Tweening;
public class LinearTween<T>: Tween<T>
where T: struct
{
private T _range;
internal LinearTween(object target, float duration, float delay, TweenMember<T> member, T endValue) : base(target, duration, delay, member, endValue)
{
}
protected override void Initialize()
{
base.Initialize();
_range = LinearOperations<T>.Subtract(_endValue, _startValue);
}
protected override void Interpolate(float n)
{
var value = LinearOperations<T>.Add(_startValue, LinearOperations<T>.Multiply(_range, n));
Member.Value = value;
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>A tweening system to make MonoGame more awesome.</Description>
<PackageTags>monogame animations tweening</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
</PropertyGroup>
<PropertyGroup>
@@ -25,4 +22,4 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>
+6 -14
View File
@@ -3,10 +3,10 @@ using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Tweening
{
public class Tween<T> : Tween
where T : struct
public abstract class Tween<T> : Tween
where T : struct
{
internal Tween(object target, float duration, float delay, TweenMember<T> member, T endValue)
internal Tween(object target, float duration, float delay, TweenMember<T> member, T endValue)
: base(target, duration, delay)
{
Member = member;
@@ -16,20 +16,12 @@ namespace MonoGame.Extended.Tweening
public TweenMember<T> Member { get; }
public override string MemberName => Member.Name;
private T _startValue;
private T _endValue;
private T _range;
protected T _startValue;
protected T _endValue;
protected override void Initialize()
{
_startValue = Member.Value;
_range = TweenMember<T>.Subtract(_endValue, _startValue);
}
protected override void Interpolate(float n)
{
var value = TweenMember<T>.Add(_startValue, TweenMember<T>.Multiply(_range, n));
Member.Value = value;
}
protected override void Swap()
@@ -189,4 +181,4 @@ namespace MonoGame.Extended.Tweening
}
}
}
}
@@ -16,29 +16,15 @@ namespace MonoGame.Extended.Tweening
}
public abstract class TweenMember<T> : TweenMember
where T : struct
where T : struct
{
static TweenMember()
{
var a = Expression.Parameter(typeof(T));
var b = Expression.Parameter(typeof(T));
var c = Expression.Parameter(typeof(float));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(a, b), a, b).Compile();
Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(a, b), a, b).Compile();
Multiply = Expression.Lambda<Func<T, float, T>>(Expression.Multiply(a, c), a, c).Compile();
}
public static Func<T, T, T> Add { get; }
public static Func<T, T, T> Subtract { get; }
public static Func<T, float, T> Multiply { get; }
protected TweenMember(object target, Func<object, object> getMethod, Action<object, object> setMethod)
: base(target)
{
_getMethod = getMethod;
_setMethod = setMethod;
_setMethod = setMethod;
}
private readonly Func<object, object> _getMethod;
private readonly Action<object, object> _setMethod;
@@ -48,4 +34,4 @@ namespace MonoGame.Extended.Tweening
set { _setMethod(Target, value); }
}
}
}
}
+20 -2
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.Tweening
{
@@ -26,6 +27,21 @@ namespace MonoGame.Extended.Tweening
public Tween<TMember> TweenTo<TTarget, TMember>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
where TTarget : class
where TMember : struct
{
switch (toValue)
{
case Color toValueColor:
return (Tween<TMember>)(object)TweenTo<TTarget, Color, ColorTween>(target, expression as Expression<Func<TTarget, Color>>, toValueColor, duration, delay);
default:
return TweenTo<TTarget, TMember, LinearTween<TMember>>(target, expression, toValue, duration, delay);
}
}
public Tween<TMember> TweenTo<TTarget, TMember, TTween>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
where TTarget : class
where TMember : struct
where TTween : Tween<TMember>
{
var memberExpression = (MemberExpression)expression.Body;
var memberInfo = memberExpression.Member;
@@ -35,7 +51,9 @@ namespace MonoGame.Extended.Tweening
activeTween?.Cancel();
AllocationCount++;
var tween = new Tween<TMember>(target, duration, delay, member, toValue);
var tween = (TTween)Activator.CreateInstance(typeof(TTween),
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null,
new object?[]{target, duration, delay, member, toValue}, null);
_activeTweens.Add(tween);
return tween;
}
@@ -112,4 +130,4 @@ namespace MonoGame.Extended.Tweening
throw new InvalidOperationException($"'{memberName}' is not a property or field of the target");
}
}
}
}
+2 -2
View File
@@ -220,7 +220,7 @@ namespace MonoGame.Extended
return new HslColor(
c1.H + t*(h2 - c1.H),
c1.S + t*(c2.S - c1.S),
c1.L + t*(c2.L - c2.L));
c1.L + t*(c2.L - c1.L));
}
public static HslColor FromRgb(Color color)
@@ -267,4 +267,4 @@ namespace MonoGame.Extended
return new HslColor(h, s, l);
}
}
}
}
+20 -10
View File
@@ -41,6 +41,16 @@ namespace MonoGame.Extended
set => Center = value;
}
public RectangleF BoundingRectangle
{
get
{
var minX = Center.X - Radius;
var minY = Center.Y - Radius;
return new RectangleF(minX, minY, Diameter, Diameter);
}
}
/// <summary>
/// Gets the distance from a point to the opposite point, both on the boundary of this <see cref="CircleF" />.
/// </summary>
@@ -415,13 +425,13 @@ namespace MonoGame.Extended
}
/// <summary>
/// Performs an implicit conversion from a <see cref="CircleF" /> to a <see cref="Rectangle" />.
/// Performs an explicit conversion from a <see cref="CircleF" /> to a <see cref="Rectangle" />.
/// </summary>
/// <param name="circle">The circle.</param>
/// <returns>
/// The resulting <see cref="Rectangle" />.
/// </returns>
public static implicit operator Rectangle(CircleF circle)
public static explicit operator Rectangle(CircleF circle)
{
var diameter = (int) circle.Diameter;
return new Rectangle((int) (circle.Center.X - circle.Radius), (int) (circle.Center.Y - circle.Radius),
@@ -436,17 +446,17 @@ namespace MonoGame.Extended
/// </returns>
public Rectangle ToRectangle()
{
return this;
return (Rectangle)this;
}
/// <summary>
/// Performs an implicit conversion from a <see cref="Rectangle" /> to a <see cref="CircleF" />.
/// Performs an explicit conversion from a <see cref="Rectangle" /> to a <see cref="CircleF" />.
/// </summary>
/// <param name="rectangle">The rectangle.</param>
/// <returns>
/// The resulting <see cref="CircleF" />.
/// </returns>
public static implicit operator CircleF(Rectangle rectangle)
public static explicit operator CircleF(Rectangle rectangle)
{
var halfWidth = rectangle.Width / 2;
var halfHeight = rectangle.Height / 2;
@@ -455,13 +465,13 @@ namespace MonoGame.Extended
}
/// <summary>
/// Performs an implicit conversion from a <see cref="CircleF" /> to a <see cref="RectangleF" />.
/// Performs an explicit conversion from a <see cref="CircleF" /> to a <see cref="RectangleF" />.
/// </summary>
/// <param name="circle">The circle.</param>
/// <returns>
/// The resulting <see cref="RectangleF" />.
/// </returns>
public static implicit operator RectangleF(CircleF circle)
public static explicit operator RectangleF(CircleF circle)
{
var diameter = circle.Diameter;
return new RectangleF(circle.Center.X - circle.Radius, circle.Center.Y - circle.Radius, diameter, diameter);
@@ -475,17 +485,17 @@ namespace MonoGame.Extended
/// </returns>
public RectangleF ToRectangleF()
{
return this;
return (RectangleF)this;
}
/// <summary>
/// Performs an implicit conversion from a <see cref="RectangleF" /> to a <see cref="CircleF" />.
/// Performs an explicit conversion from a <see cref="RectangleF" /> to a <see cref="CircleF" />.
/// </summary>
/// <param name="rectangle">The rectangle.</param>
/// <returns>
/// The resulting <see cref="CircleF" />.
/// </returns>
public static implicit operator CircleF(RectangleF rectangle)
public static explicit operator CircleF(RectangleF rectangle)
{
var halfWidth = rectangle.Width * 0.5f;
var halfHeight = rectangle.Height * 0.5f;
@@ -0,0 +1,302 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using SharpDX.Direct3D;
namespace MonoGame.Extended
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.4; Bounding Volumes - Oriented Bounding Boxes (OBBs), pg 101.
/// <summary>
/// An oriented bounding rectangle is a rectangular block, much like a bounding rectangle
/// <see cref="BoundingRectangle" /> but with an arbitrary orientation <see cref="Vector2" />.
/// </summary>
/// <seealso cref="IEquatable{T}" />
[DebuggerDisplay($"{{{nameof(DebugDisplayString)},nq}}")]
public struct OrientedRectangle : IEquatable<OrientedRectangle>, IShapeF
{
/// <summary>
/// The centre position of this <see cref="OrientedRectangle" />.
/// </summary>
public Point2 Center;
/// <summary>
/// The distance from the <see cref="Center" /> point along both axes to any point on the boundary of this
/// <see cref="OrientedRectangle" />.
/// </summary>
///
public Vector2 Radii;
/// <summary>
/// The rotation matrix <see cref="Matrix2" /> of the bounding rectangle <see cref="OrientedRectangle" />.
/// </summary>
public Matrix2 Orientation;
/// <summary>
/// Initializes a new instance of the <see cref="BoundingRectangle" /> structure from the specified centre
/// <see cref="Point2" /> and the radii <see cref="Size2" />.
/// </summary>
/// <param name="center">The centre <see cref="Point2" />.</param>
/// <param name="radii">The radii <see cref="Vector2" />.</param>
/// <param name="orientation">The orientation <see cref="Matrix2" />.</param>
public OrientedRectangle(Point2 center, Size2 radii, Matrix2 orientation)
{
Center = center;
Radii = radii;
Orientation = orientation;
}
/// <summary>
/// Gets a list of points defining the corner points of the oriented rectangle.
/// </summary>
public IReadOnlyList<Vector2> Points
{
get
{
var topLeft = -Radii;
var bottomLeft = -new Vector2(Radii.X, -Radii.Y);
var topRight = (Vector2)new Point2(Radii.X, -Radii.Y);
var bottomRight = Radii;
return new List<Vector2>
{
Vector2.Transform(topRight, Orientation) + Center,
Vector2.Transform(topLeft, Orientation) + Center,
Vector2.Transform(bottomLeft, Orientation) + Center,
Vector2.Transform(bottomRight, Orientation) + Center
};
}
}
public Point2 Position
{
get => Vector2.Transform(-Radii, Orientation) + Center;
set => throw new NotImplementedException();
}
public RectangleF BoundingRectangle => (RectangleF)this;
/// <summary>
/// Computes the <see cref="OrientedRectangle"/> from the specified <paramref name="rectangle"/>
/// transformed by <paramref name="transformMatrix"/>.
/// </summary>
/// <param name="rectangle">The <see cref="OrientedRectangle"/> to transform.</param>
/// <param name="transformMatrix">The <see cref="Matrix2"/> transformation.</param>
/// <returns>A new <see cref="OrientedRectangle"/>.</returns>
public static OrientedRectangle Transform(OrientedRectangle rectangle, ref Matrix2 transformMatrix)
{
Transform(ref rectangle, ref transformMatrix, out var result);
return result;
}
private static void Transform(ref OrientedRectangle rectangle, ref Matrix2 transformMatrix, out OrientedRectangle result)
{
PrimitivesHelper.TransformOrientedRectangle(
ref rectangle.Center,
ref rectangle.Orientation,
ref transformMatrix);
result = new OrientedRectangle();
result.Center = rectangle.Center;
result.Radii = rectangle.Radii;
result.Orientation = rectangle.Orientation;
}
/// <summary>
/// Compares to two <see cref="OrientedRectangle"/> structures. The result specifies whether the
/// the values of the <see cref="Center"/>, <see cref="Radii"/> and <see cref="Orientation"/> are
/// equal.
/// </summary>
/// <param name="left">The left <see cref="OrientedRectangle" />.</param>
/// <param name="right">The right <see cref="OrientedRectangle" />.</param>
/// <returns><c>true</c> if left and right argument are equal; otherwise, <c>false</c>.</returns>
public static bool operator ==(OrientedRectangle left, OrientedRectangle right)
{
return left.Equals(right);
}
/// <summary>
/// Compares to two <see cref="OrientedRectangle"/> structures. The result specifies whether the
/// the values of the <see cref="Center"/>, <see cref="Radii"/> or <see cref="Orientation"/> are
/// unequal.
/// </summary>
/// <param name="left">The left <see cref="OrientedRectangle" />.</param>
/// <param name="right">The right <see cref="OrientedRectangle" />.</param>
/// <returns><c>true</c> if left and right argument are unequal; otherwise, <c>false</c>.</returns>
public static bool operator !=(OrientedRectangle left, OrientedRectangle right)
{
return !left.Equals(right);
}
/// <summary>
/// Determines whether two instances of <see cref="OrientedRectangle"/> are equal.
/// </summary>
/// <param name="other">The other <see cref="OrientedRectangle"/>.</param>
/// <returns><c>true</c> if the specified <see cref="OrientedRectangle"/> is equal
/// to the current <see cref="OrientedRectangle"/>; otherwise, <c>false</c>.</returns>
public bool Equals(OrientedRectangle other)
{
return Center.Equals(other.Center) && Radii.Equals(other.Radii) && Orientation.Equals(other.Orientation);
}
/// <summary>
/// Determines whether two instances of <see cref="OrientedRectangle"/> are equal.
/// </summary>
/// <param name="obj">The <see cref="OrientedRectangle"/> to compare to.</param>
/// <returns><c>true</c> if the specified <see cref="OrientedRectangle"/> is equal
/// to the current <see cref="OrientedRectangle"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
return obj is OrientedRectangle other && Equals(other);
}
/// <summary>
/// Returns a hash code for this object instance.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return HashCode.Combine(Center, Radii, Orientation);
}
/// <summary>
/// Performs an implicit conversion from a <see cref="RectangleF" /> to <see cref="OrientedRectangle" />.
/// </summary>
/// <param name="rectangle">The rectangle to convert.</param>
/// <returns>The resulting <see cref="OrientedRectangle" />.</returns>
public static explicit operator OrientedRectangle(RectangleF rectangle)
{
var radii = new Size2(rectangle.Width * 0.5f, rectangle.Height * 0.5f);
var centre = new Point2(rectangle.X + radii.Width, rectangle.Y + radii.Height);
return new OrientedRectangle(centre, radii, Matrix2.Identity);
}
public static explicit operator RectangleF(OrientedRectangle orientedRectangle)
{
var topLeft = -orientedRectangle.Radii;
var rectangle = new RectangleF(topLeft, orientedRectangle.Radii * 2);
var orientation = orientedRectangle.Orientation * Matrix2.CreateTranslation(orientedRectangle.Center);
return RectangleF.Transform(rectangle, ref orientation);
}
/// <summary>
/// See:
/// https://www.flipcode.com/archives/2D_OBB_Intersection.shtml
/// https://dyn4j.org/2010/01/sat
/// </summary>
/// <param name="rectangle"></param>
/// <param name="other"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static (bool Intersects, Vector2 MinimumTranslationVector) Intersects(
OrientedRectangle rectangle, OrientedRectangle other)
{
var corners = rectangle.Points;
var otherCorners = other.Points;
var allAxis = new[]
{
corners[1] - corners[0],
corners[3] - corners[0],
otherCorners[1] - otherCorners[0],
otherCorners[3] - otherCorners[0],
};
var normalizedAxis = new[]
{
allAxis[0],
allAxis[1],
allAxis[2],
allAxis[3]
};
var overlap = 0f;
var minimumTranslationVector = Vector2.Zero;
// Make the length of each axis 1/edge length, so we know any
// dot product must be less than 1 to fall within the edge.
for (var a = 0; a < normalizedAxis.Length; a++)
{
normalizedAxis[a] /= normalizedAxis[a].LengthSquared();
}
for (var a = 0; a < normalizedAxis.Length; a++)
{
var axisProjectedOnto = normalizedAxis[a];
var originalAxis = allAxis[a];
var p1 = Project(corners, axisProjectedOnto);
var p2 = Project(otherCorners, axisProjectedOnto);
if (!IsOverlapping(p1, p2))
{
// There was no intersection along this dimension;
// the boxes cannot possibly overlap.
return (false, Vector2.Zero);
}
var o = GetOverlap(p1, p2);
if (o < overlap || overlap == 0f)
{
overlap = o;
minimumTranslationVector = originalAxis * overlap;
if (p1.Min > p2.Min)
{
minimumTranslationVector = -minimumTranslationVector;
}
}
}
// There was no dimension along which there is no intersection.
// Therefore, the boxes overlap.
return (true, minimumTranslationVector);
(float Min, float Max) Project(IReadOnlyList<Vector2> vertices, Vector2 axis)
{
var t = vertices[0].Dot(axis);
// Find the extent of box 2 on axis a
var min = t;
var max = t;
for (var c = 1; c < 4; c++)
{
t = vertices[c].Dot(axis);
if (t < min)
{
min = t;
}
else if (t > max)
{
max = t;
}
}
return (min, max);
}
bool IsOverlapping((float Min, float Max) p1, (float Min, float Max) p2)
{
return p1.Min <= p2.Max && p1.Max >= p2.Min;
}
float GetOverlap((float Min, float Max) p1, (float Min, float Max) p2)
{
return Math.Min(p1.Max, p2.Max) - Math.Max(p1.Min, p2.Min);
}
}
/// <summary>
/// Returns a <see cref="string" /> that represents this <see cref="OrientedRectangle" />.
/// </summary>
/// <returns>
/// A <see cref="string" /> that represents this <see cref="OrientedRectangle" />.
/// </returns>
public override string ToString()
{
return $"Centre: {Center}, Radii: {Radii}, Orientation: {Orientation}";
}
internal string DebugDisplayString => ToString();
}
}
@@ -71,6 +71,21 @@ namespace MonoGame.Extended
halfExtents.Y = xRadius * Math.Abs(transformMatrix.M21) + yRadius * Math.Abs(transformMatrix.M22);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void TransformOrientedRectangle(
ref Point2 center,
ref Matrix2 orientation,
ref Matrix2 transformMatrix)
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.4; Oriented Bounding Boxes (OBBs), pg 101-105.
center = transformMatrix.Transform(center);
orientation *= transformMatrix;
// Reset the translation since orientation is only about rotation
orientation.M31 = 0;
orientation.M32 = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static float SquaredDistanceToPointFromRectangle(Point2 minimum, Point2 maximum, Point2 point)
{
+11 -9
View File
@@ -6,7 +6,7 @@ using Microsoft.Xna.Framework;
namespace MonoGame.Extended
{
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 77
// Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.2; Bounding Volumes - Axis-aligned Bounding Boxes (AABBs). pg 77
/// <summary>
/// An axis-aligned, four sided, two dimensional box defined by a top-left position (<see cref="X" /> and
@@ -97,6 +97,8 @@ namespace MonoGame.Extended
}
}
public RectangleF BoundingRectangle => this;
/// <summary>
/// Gets the <see cref="Size2" /> representing the extents of this <see cref="RectangleF" />.
/// </summary>
@@ -227,13 +229,13 @@ namespace MonoGame.Extended
/// <param name="transformMatrix">The transform matrix.</param>
/// <param name="result">The resulting transformed rectangle.</param>
/// <returns>
/// The <see cref="BoundingRectangle" /> from the <paramref name="rectangle" /> transformed by the
/// The <see cref="Extended.BoundingRectangle" /> from the <paramref name="rectangle" /> transformed by the
/// <paramref name="transformMatrix" />.
/// </returns>
/// <remarks>
/// <para>
/// If a transformed <see cref="BoundingRectangle" /> is used for <paramref name="rectangle" /> then the
/// resulting <see cref="BoundingRectangle" /> will have the compounded transformation, which most likely is
/// If a transformed <see cref="Extended.BoundingRectangle" /> is used for <paramref name="rectangle" /> then the
/// resulting <see cref="Extended.BoundingRectangle" /> will have the compounded transformation, which most likely is
/// not desired.
/// </para>
/// </remarks>
@@ -252,20 +254,20 @@ namespace MonoGame.Extended
}
/// <summary>
/// Computes the <see cref="RectangleF" /> from the specified <see cref="BoundingRectangle" /> transformed by
/// Computes the <see cref="RectangleF" /> from the specified <see cref="Extended.BoundingRectangle" /> transformed by
/// the
/// specified <see cref="Matrix2" />.
/// </summary>
/// <param name="rectangle">The bounding rectangle.</param>
/// <param name="transformMatrix">The transform matrix.</param>
/// <returns>
/// The <see cref="BoundingRectangle" /> from the <paramref name="rectangle" /> transformed by the
/// The <see cref="Extended.BoundingRectangle" /> from the <paramref name="rectangle" /> transformed by the
/// <paramref name="transformMatrix" />.
/// </returns>
/// <remarks>
/// <para>
/// If a transformed <see cref="BoundingRectangle" /> is used for <paramref name="rectangle" /> then the
/// resulting <see cref="BoundingRectangle" /> will have the compounded transformation, which most likely is
/// If a transformed <see cref="Extended.BoundingRectangle" /> is used for <paramref name="rectangle" /> then the
/// resulting <see cref="Extended.BoundingRectangle" /> will have the compounded transformation, which most likely is
/// not desired.
/// </para>
/// </remarks>
@@ -662,7 +664,7 @@ namespace MonoGame.Extended
}
/// <summary>
/// Performs an implicit conversion from a <see cref="Rectangle" /> to a <see cref="RectangleF" />.
/// Performs an explicit conversion from a <see cref="Rectangle" /> to a <see cref="RectangleF" />.
/// </summary>
/// <param name="rectangle">The rectangle.</param>
/// <returns>
+54 -23
View File
@@ -1,4 +1,7 @@
namespace MonoGame.Extended
using System;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended
{
/// <summary>
/// Base class for shapes.
@@ -12,6 +15,11 @@
/// Gets or sets the position of the shape.
/// </summary>
Point2 Position { get; set; }
/// <summary>
/// Gets escribed rectangle, which lying outside the shape
/// </summary>
RectangleF BoundingRectangle { get; }
}
/// <summary>
@@ -22,31 +30,28 @@
/// <summary>
/// Check if two shapes intersect.
/// </summary>
/// <param name="shapeA">The first shape.</param>
/// <param name="shapeB">The second shape.</param>
/// <param name="a">The first shape.</param>
/// <param name="b">The second shape.</param>
/// <returns>True if the two shapes intersect.</returns>
public static bool Intersects(this IShapeF shapeA, IShapeF shapeB)
public static bool Intersects(this IShapeF a, IShapeF b)
{
var intersects = false;
return a switch
{
CircleF circleA when b is CircleF circleB => circleA.Intersects(circleB),
CircleF circleA when b is RectangleF rectangleB => circleA.Intersects(rectangleB),
CircleF circleA when b is OrientedRectangle orientedRectangleB => Intersects(circleA, orientedRectangleB),
if (shapeA is RectangleF rectangleA && shapeB is RectangleF rectangleB)
{
intersects = rectangleA.Intersects(rectangleB);
}
else if (shapeA is CircleF circleA && shapeB is CircleF circleB)
{
intersects = circleA.Intersects(circleB);
}
else if (shapeA is RectangleF rect1 && shapeB is CircleF circ1)
{
return Intersects(circ1, rect1);
}
else if (shapeA is CircleF circ2 && shapeB is RectangleF rect2)
{
return Intersects(circ2, rect2);
}
RectangleF rectangleA when b is CircleF circleB => Intersects(circleB, rectangleA),
RectangleF rectangleA when b is RectangleF rectangleB => Intersects(rectangleA, rectangleB),
RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => Intersects(rectangleA, orientedRectangleB).Intersects,
return intersects;
OrientedRectangle orientedRectangleA when b is CircleF circleB => Intersects(circleB, orientedRectangleA),
OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => Intersects(rectangleB, orientedRectangleA).Intersects,
OrientedRectangle orientedRectangleA when b is OrientedRectangle orientedRectangleB
=> OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB).Intersects,
_ => throw new ArgumentOutOfRangeException(nameof(a))
};
}
/// <summary>
@@ -60,5 +65,31 @@
var closestPoint = rectangle.ClosestPointTo(circle.Center);
return circle.Contains(closestPoint);
}
/// <summary>
/// Checks whether a <see cref="CircleF"/> and <see cref="OrientedRectangle"/> intersects.
/// </summary>
/// <param name="circle"><see cref="CircleF"/>to use in intersection test.</param>
/// <param name="orientedRectangle"><see cref="OrientedRectangle"/>to use in intersection test.</param>
/// <returns>True if the circle and oriented bounded rectangle intersects, otherwise false.</returns>
public static bool Intersects(CircleF circle, OrientedRectangle orientedRectangle)
{
var rotation = Matrix2.CreateRotationZ(orientedRectangle.Orientation.Rotation);
var circleCenterInRectangleSpace = rotation.Transform(circle.Center - orientedRectangle.Center);
var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circle.Radius);
var boundingRectangle = new BoundingRectangle(new Point2(), orientedRectangle.Radii);
return circleInRectangleSpace.Intersects(boundingRectangle);
}
/// <summary>
/// Checks if a <see cref="RectangleF"/> and <see cref="OrientedRectangle"/> intersects.
/// </summary>
/// <param name="rectangleF"></param>
/// <param name="orientedRectangle"></param>
/// <returns>True if objects are intersecting, otherwise false.</returns>
public static (bool Intersects, Vector2 MinimumTranslationVector) Intersects(RectangleF rectangleF, OrientedRectangle orientedRectangle)
{
return OrientedRectangle.Intersects(orientedRectangle, (OrientedRectangle)rectangleF);
}
}
}
}
@@ -7,9 +7,6 @@
<Authors>craftworkgames</Authors>
<Description>It makes MonoGame more awesome.</Description>
<PackageTags>monogame extended pipeline bmfont tiled texture atlas input viewport fps shapes sprite</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -26,5 +23,5 @@
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
</Project>
@@ -14,6 +14,7 @@ namespace MonoGame.Extended.Serialization
Converters.Add(new RangeJsonConverter<float>());
Converters.Add(new RangeJsonConverter<HslColor>());
Converters.Add(new ThicknessJsonConverter());
Converters.Add(new RectangleFJsonConverter());
Converters.Add(new TextureAtlasJsonConverter(contentManager, contentPath));
Converters.Add(new Size2JsonConverter());
@@ -22,4 +23,4 @@ namespace MonoGame.Extended.Serialization
Formatting = Formatting.Indented;
}
}
}
}
@@ -0,0 +1,24 @@
using System;
using Newtonsoft.Json;
namespace MonoGame.Extended.Serialization;
public class RectangleFJsonConverter: JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var rect = (RectangleF)value;
writer.WriteValue($"{rect.Left} {rect.Top} {rect.Width} {rect.Height}");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var values = reader.ReadAsMultiDimensional<float>();
return new RectangleF(values[0], values[1], values[2], values[3]);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(RectangleF);
}
}
@@ -122,6 +122,31 @@ namespace MonoGame.Extended.VectorDraw
DrawCircle(center, radius, color);
}
public void DrawSolidCircle(Vector2 center, float radius, Color color, Color fillcolor)
{
if (!_primitiveBatch.IsReady())
throw new InvalidOperationException("BeginCustomDraw must be called before drawing anything.");
const double increment = Math.PI * 2.0 / CircleSegments;
double theta = 0.0;
Vector2 v0 = center + radius * new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta));
theta += increment;
for (int i = 1; i < CircleSegments - 1; i++)
{
Vector2 v1 = center + radius * new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta));
Vector2 v2 = center + radius * new Vector2((float)Math.Cos(theta + increment), (float)Math.Sin(theta + increment));
_primitiveBatch.AddVertex(v0, fillcolor, PrimitiveType.TriangleList);
_primitiveBatch.AddVertex(v1, fillcolor, PrimitiveType.TriangleList);
_primitiveBatch.AddVertex(v2, fillcolor, PrimitiveType.TriangleList);
theta += increment;
}
DrawCircle(center, radius, color);
}
public void DrawSegment(Vector2 start, Vector2 end, Color color)
{

Some files were not shown because too many files have changed in this diff Show More