mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-15 23:19:29 +00:00
Merge branch 'develop' into rename-wasbutton
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -9,3 +9,9 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.user
|
||||
|
||||
# cake build output
|
||||
artifacts
|
||||
|
||||
# Visual Studio
|
||||
.vs/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user