Merge branch 'develop' into pipeline-update-class-properties

This commit is contained in:
Max Kopjev
2024-04-07 23:12:01 +03:00
committed by GitHub
82 changed files with 1492 additions and 689 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 }}
+3
View File
@@ -9,3 +9,6 @@
# macOS
.DS_Store
*.user
# cake build output
artifacts
+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.1.0"/>
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<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);
}
}
}
@@ -1,7 +1,11 @@
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
{
@@ -11,17 +15,49 @@ namespace MonoGame.Extended.Collisions
/// </summary>
public class CollisionComponent : SimpleGameComponent
{
private readonly Dictionary<ICollisionActor, QuadtreeData> _targetDataDictionary = new();
public const string DEFAULT_LAYER_NAME = "default";
private readonly Quadtree _collisionTree;
private Dictionary<string, Layer> _layers = new();
/// <summary>
/// Creates a collision tree covering the specified area.
/// 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)
{
_collisionTree = new Quadtree(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>
@@ -34,28 +70,31 @@ namespace MonoGame.Extended.Collisions
/// <param name="gameTime"></param>
public override void Update(GameTime gameTime)
{
// Detect collisions
foreach (var value in _targetDataDictionary.Values)
foreach (var layer in _layers.Values)
layer.Reset();
foreach (var (firstLayer, secondLayer) in _layerCollision)
foreach (var actor in firstLayer.Space)
{
value.RemoveFromAllParents();
var target = value.Target;
var collisions =_collisionTree.Query(target.Bounds);
// Generate list of collision Infos
var collisions = secondLayer.Space.Query(actor.Bounds.BoundingRectangle);
foreach (var other in collisions)
{
var collisionInfo = new CollisionEventArgs
{
Other = other.Target,
PenetrationVector = CalculatePenetrationVector(value.Bounds, other.Bounds)
};
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
});
}
target.OnCollision(collisionInfo);
}
_collisionTree.Insert(value);
}
_collisionTree.Shake();
}
/// <summary>
@@ -65,12 +104,7 @@ namespace MonoGame.Extended.Collisions
/// <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);
}
_layers[target.LayerName ?? DEFAULT_LAYER_NAME].Space.Insert(target);
}
/// <summary>
@@ -79,25 +113,58 @@ namespace MonoGame.Extended.Collisions
/// <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();
}
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>
/// Gets if the target is inserted in the collision tree.
/// Remove the layer and all layer's collisions.
/// </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)
/// <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)
{
return _targetDataDictionary.ContainsKey(target);
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>
@@ -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();
}
}
@@ -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,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>
@@ -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.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);
@@ -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>
@@ -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>
@@ -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>
@@ -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
}
}
}
}
}
@@ -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;
@@ -58,7 +59,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];
}
}
}
}
@@ -29,25 +29,35 @@ namespace MonoGame.Extended.Tiled
var tileset = new TiledMapTileset(texture, 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));
reader.ReadTiledMapProperties(tilesetTile.Properties);
tileset.Tiles.Add(tilesetTile);
}
ReadTile(reader, tileset);
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 +74,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];
@@ -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);
}
}
}
}
+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>
+10 -8
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>
+6 -1
View File
@@ -12,6 +12,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>
@@ -61,4 +66,4 @@
return circle.Contains(closestPoint);
}
}
}
}
@@ -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);
}
}
@@ -138,7 +138,7 @@ namespace MonoGame.Extended.Collisions.Tests
// Actor should have moved up because the distance is shorter.
Assert.True(actor1.Position.Y > actor2.Position.Y);
// The circle centers should be about 4 units away after moving
Assert.True(Math.Abs(actor1.Position.Y - 2.0f) < float.Epsilon);
// Assert.True(Math.Abs(actor1.Position.Y - 2.0f) < float.Epsilon);
}
#endregion
@@ -331,6 +331,34 @@ namespace MonoGame.Extended.Collisions.Tests
Assert.False(dynamicActor.IsColliding);
}
[Fact]
public void Actors_is_colliding_when_dynamic_actor_is_moved_after_update()
{
var staticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
var staticActor = new CollisionIndicatingActor(staticBounds);
_collisionComponent.Insert(staticActor);
for (int i = 0; i < QuadTree.QuadTree.DefaultMaxObjectsPerNode; i++)
{
var fillerBounds = new RectangleF(new Point2(0, 2), new Size2(.1f, .1f));
var fillerActor = new CollisionIndicatingActor(fillerBounds);
_collisionComponent.Insert(fillerActor);
}
var dynamicBounds = new RectangleF(new Point2(2, 2), new Size2(1, 1));
var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
_collisionComponent.Insert(dynamicActor);
_collisionComponent.Update(_gameTime);
Assert.False(staticActor.IsColliding);
Assert.False(dynamicActor.IsColliding);
dynamicActor.MoveTo(new Point2(0, 0));
_collisionComponent.Update(_gameTime);
Assert.True(dynamicActor.IsColliding);
Assert.True(staticActor.IsColliding);
}
[Fact]
public void Actors_is_colliding_when_dynamic_actor_is_moved_into_collision_bounds()
{
@@ -1,26 +1,27 @@
using System.Collections.Generic;
using MonoGame.Extended.Collisions.QuadTree;
using Xunit;
namespace MonoGame.Extended.Collisions.Tests
{
public class QuadTreeTests
{
private Quadtree MakeTree()
private QuadTree.QuadTree MakeTree()
{
// Bounds set to ensure actors will fit inside the tree with default bounds.
var bounds = _quadTreeArea;
var tree = new Quadtree(bounds);
var tree = new QuadTree.QuadTree(bounds);
return tree;
}
private readonly RectangleF _quadTreeArea = new RectangleF(-10f, -15, 20.0f, 30.0f);
private RectangleF _quadTreeArea = new RectangleF(-10f, -15, 20.0f, 30.0f);
[Fact]
public void ConstructorTest()
{
var bounds = new RectangleF(-10f, -15, 20.0f, 30.0f);
var tree = new Quadtree(bounds);
var tree = new QuadTree.QuadTree(bounds);
Assert.Equal(bounds, tree.NodeBounds);
Assert.True(tree.IsLeaf);
@@ -41,7 +42,7 @@ namespace MonoGame.Extended.Collisions.Tests
var actor = new BasicActor();
tree.Insert(new QuadtreeData(actor));
Assert.Equal(1, tree.NumTargets());
}
@@ -330,7 +331,7 @@ namespace MonoGame.Extended.Collisions.Tests
public void ShakeWhenContainingManyTest()
{
var tree = MakeTree();
var numTargets = Quadtree.DefaultMaxObjectsPerNode + 1;
var numTargets = QuadTree.QuadTree.DefaultMaxObjectsPerNode + 1;
for (int i = 0; i < numTargets; i++)
{
@@ -347,8 +348,8 @@ namespace MonoGame.Extended.Collisions.Tests
{
var tree = MakeTree();
var query = tree.Query(_quadTreeArea);
var query = tree.Query(ref _quadTreeArea);
Assert.Empty(query);
Assert.Equal(0, tree.NumTargets());
}
@@ -358,7 +359,8 @@ namespace MonoGame.Extended.Collisions.Tests
{
var tree = MakeTree();
var query = tree.Query(new RectangleF(100f, 100f, 1f, 1f));
var area = new RectangleF(100f, 100f, 1f, 1f);
var query = tree.Query(ref area);
Assert.Empty(query);
Assert.Equal(0, tree.NumTargets());
@@ -371,7 +373,7 @@ namespace MonoGame.Extended.Collisions.Tests
var actor = new BasicActor();
tree.Insert(new QuadtreeData(actor));
var query = tree.Query(_quadTreeArea);
var query = tree.Query(ref _quadTreeArea);
Assert.Single(query);
Assert.Equal(tree.NumTargets(), query.Count);
}
@@ -383,7 +385,8 @@ namespace MonoGame.Extended.Collisions.Tests
var actor = new BasicActor();
tree.Insert(new QuadtreeData(actor));
var query = tree.Query(new RectangleF(100f, 100f, 1f, 1f));
var area = new RectangleF(100f, 100f, 1f, 1f);
var query = tree.Query(ref area);
Assert.Empty(query);
}
@@ -391,7 +394,7 @@ namespace MonoGame.Extended.Collisions.Tests
public void QueryLeafNodeMultipleTest()
{
var tree = MakeTree();
var numTargets = Quadtree.DefaultMaxObjectsPerNode;
var numTargets = QuadTree.QuadTree.DefaultMaxObjectsPerNode;
for (int i = 0; i < numTargets; i++)
{
var data = new QuadtreeData(new BasicActor());
@@ -399,7 +402,7 @@ namespace MonoGame.Extended.Collisions.Tests
}
var query = tree.Query(_quadTreeArea);
var query = tree.Query(ref _quadTreeArea);
Assert.Equal(numTargets, query.Count);
Assert.Equal(tree.NumTargets(), query.Count);
}
@@ -408,7 +411,7 @@ namespace MonoGame.Extended.Collisions.Tests
public void QueryNonLeafManyTest()
{
var tree = MakeTree();
var numTargets = 2*Quadtree.DefaultMaxObjectsPerNode;
var numTargets = 2*QuadTree.QuadTree.DefaultMaxObjectsPerNode;
for (int i = 0; i < numTargets; i++)
{
var data = new QuadtreeData(new BasicActor());
@@ -416,7 +419,7 @@ namespace MonoGame.Extended.Collisions.Tests
}
var query = tree.Query(_quadTreeArea);
var query = tree.Query(ref _quadTreeArea);
Assert.Equal(numTargets, query.Count);
Assert.Equal(tree.NumTargets(), query.Count);
}
@@ -425,7 +428,7 @@ namespace MonoGame.Extended.Collisions.Tests
public void QueryTwiceConsecutiveTest()
{
var tree = MakeTree();
var numTargets = 2 * Quadtree.DefaultMaxObjectsPerNode;
var numTargets = 2 * QuadTree.QuadTree.DefaultMaxObjectsPerNode;
for (int i = 0; i < numTargets; i++)
{
var data = new QuadtreeData(new BasicActor());
@@ -433,11 +436,11 @@ namespace MonoGame.Extended.Collisions.Tests
}
var query1 = tree.Query(_quadTreeArea);
var query2 = tree.Query(_quadTreeArea);
var query1 = tree.Query(ref _quadTreeArea);
var query2 = tree.Query(ref _quadTreeArea);
Assert.Equal(numTargets, query1.Count);
Assert.Equal(tree.NumTargets(), query1.Count);
Assert.Equal(query1.Count, query2.Count);
}
}
}
}
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace MonoGame.Extended.Collisions.Tests;
public class SpatialHashTests
{
private SpatialHash generateSpatialHash() => new SpatialHash(new Size2(64, 64));
private readonly RectangleF RECT = new RectangleF(10, 10, 20, 20);
[Fact]
public void CollisionOneTrueTest()
{
var hash = generateSpatialHash();
hash.Insert(new BasicActor()
{
Bounds = RECT,
});
var collisions = hash.Query(RECT);
Assert.Equal(1, collisions.Count());
}
[Fact]
public void CollisionTwoTest()
{
var hash = generateSpatialHash();
hash.Insert(new BasicActor
{
Bounds = RECT,
});
hash.Insert(new BasicActor
{
Bounds = RECT,
});
var collisions = hash.Query(RECT);
Assert.Equal(2, collisions.Count());
}
}
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;
@@ -35,7 +36,12 @@ namespace MonoGame.Extended.Tests.Collections
Assert.True(false);
}
GC.EndNoGCRegion();
// Wrap in if statement due to exception thrown when running test through
// cake build script or when debugging test script manually.
if(GCSettings.LatencyMode == GCLatencyMode.NoGCRegion)
{
GC.EndNoGCRegion();
}
}
}
@@ -0,0 +1,33 @@
using System.IO;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
using Xunit;
namespace MonoGame.Extended.Tests.Serialization;
public class RectangleFJsonConverterTest
{
public class TestContent
{
public RectangleF Box { get; set; }
}
[Fact]
public void ConstructorTest()
{
var jsonData = @"
{
box: ""1 1 10 10""
}
";
var serializer = new JsonSerializer();
serializer.Converters.Add(new RectangleFJsonConverter());
var content = serializer.Deserialize<TestContent>(new JsonTextReader(new StringReader(jsonData)));
Assert.Equal(1, content.Box.Left);
Assert.Equal(1, content.Box.Top);
Assert.Equal(10, content.Box.Width);
Assert.Equal(10, content.Box.Height);
}
}