Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
Further reduced runtime memory allocation when rendering drawables by replacing Sprite groups with Composite Sprites.
Moved Mirror functionality to the Sprites abstract class.
Added Repeat- methods to Sprites class for Scale and Translate respectively.
Break previous behaviour where you could edit a Sprite Group after it's instantiation.
Break previous behaviour where you could edit a Sprite Group after it's instantiation.
- 2.1.0
Added transform functions to Sprites that replace lost Sprite Group functionality
Fixed readme being instructions for an older version of the mixin.
Fixed a floating point error accumulation bug when rotating sprites
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.0
2.1.0
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,37 @@ An API wrapper that supports composing new sprites from multiple existing ones.
## Usage

You can instantiate regular sprites using the helper methods of the `Sprites` class. These return a builder (except for
`Sprites.Group()`, which returns a group directly) that can be used to further configure the initial values of the
`Sprites.Compose()`, which returns a composite sprite directly) that can be used to further configure the initial values
of the
sprite.

All sprites and sprite groups implement the `Sprite` interface, which you can use to `Translate()`, `Rotate()`, or
`Scale()` a sprite after instantiation, among other things. Individual Sprite types may have additional properties and
methods available, like `TextureSprite`'s `Mirror()` functions.

See the [demo project](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/SpriteCompositor.Demo/Program.cs) for a detailed example.

### Grouping Sprites

You can group sprites by calling `Sprites.Group()` or creating a `new SimpleSpriteGroup()`.

If this implementation does
not fit your needs, you can extend the `SpriteGroup` abstract class. If you do, you will be required to implement the
`Clone()` and `GetChildren()` methods.

**Avoid creating a new list in `GetChildren()`**, as the function is invoked
whenever a transformation is applied to the group. I recommend using the following example as a guide:

```csharp
public class CustomSpriteGroup : SpriteGroup
{
private readonly List<Sprite> backingList;

public Sprite UnspecifiedSprite
{
get { return backingList[0]; }
set { backingList[0] = value; }
}

public TextureSprite SpecificSprite
{
get { return (TextureSprite) backingList[1]; }
set { backingList[1] = value; }
}

public CustomSpriteGroup(Sprite unspecifiedSprite, TextureSprite specificSprite)
{
this.backingList = new List<Sprite> { unspecifiedSprite, specificSprite };
}

public override Sprite Clone() => new CustomSpriteGroup(backingList.Select(sprite => sprite.Clone()).ToList());

protected override List<Sprite> GetChildren() => backingList;
}
```

If, for some reason, the abstract `SpriteGroup` class also does not suit your needs, you can implement the `Sprite`
interface directly, though if you feel the need to do so, there may be a structural problem with your program.
methods available.

See
the [demo project](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/SpriteCompositor.Demo/Program.cs)
for a detailed example.

### Composing Sprites

You can compose a new sprite by calling `Sprites.Compose()` or creating a `new CompositeSprite()`. This Composite sprite
will act just like a normal sprite, even though it is made up of multiple child sprites.

Once you have created a composite sprite, you cannot add or remove child sprites. If you need to perform operations on
multiple sprites without creating a composite sprite, use the dedicated `Sprites` methods which are clarified below.

If these options do not
not fit your needs, you can extend the `CompositeSprite` class, or implement the `Sprite` interface yourself.

You should avoid composing a sprite that has both `TextSprite` objects and `TextureSprite` objects as they behave
differently and can cause unexpected behavior when grouped together.

### Performing Transformations On Groups

Sometimes you don't want to compose a sprite, but you do want to apply the same transformation to multiple sprites.
In this case the `Sprites` abstract class has `Translate()`, `Scale()` and `Rotate()` functions available that can
transform multiple sprites at once.

### Anchors

Expand All @@ -69,22 +50,38 @@ an `Anchor` will also scale the distance to the anchor point.

### Displaying Sprites

In order to draw your composed sprites to an LCD screen, you'll need to call the `Sprite.AsRenderable()` method, which will
In order to draw your composed sprites to an LCD screen, you'll need to call the `Sprite.AsRenderable()` method, which
will
return an array of `MySprite` objects that your sprite consists of. You can draw these to the screen in one go
using the `MySpriteDrawFrame.AddRange()` method. `AsRenderable()` takes an optional `RectangleF viewport` as parameter.
Supplying this will move the sprites so that (0,0) is the center of the viewport.

## Legal

`Copyright (c) 2026 Lelebees`

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
You should have received a copy of the GNU Lesser General Public License along with this program. If not,
see <https://www.gnu.org/licenses/>.

### License
You can find a copy of the [GNU General Public License](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/Lelebees.MdkScriptMixin.SpriteCompositor/COPYING) and [GNU Lesser General Public License](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/Lelebees.MdkScriptMixin.SpriteCompositor/COPYING.LESSER) next to this source code in COPYING and COPYING.LESSER respectively.

You can find a copy of
the [GNU General Public License](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/Lelebees.MdkScriptMixin.SpriteCompositor/COPYING)
and [GNU Lesser General Public License](https://github.com/malforge/mdk2-packages/blob/main/libraries/Lelebees.MdkScriptMixin.SpriteCompositor/Lelebees.MdkScriptMixin.SpriteCompositor/COPYING.LESSER)
next to this source code in COPYING and COPYING.LESSER respectively.

### Reaching out
You can reach me as @lelebees on Discord, or through the project's [Github Repository](https://github.com/Lelebees/mdk2-packages-sprite-compositor). Please note while reaching out on Discord that I generally do not accept random friend requests. @Mention me in the [programmable block channel](https://discord.com/channels/125011928711036928/216219467959500800) of the Keen Software House Discord Server to get a hold of me.

You can reach me as @lelebees on Discord, or through the
project's [GitHub Repository](https://github.com/Lelebees/mdk2-packages-sprite-compositor). Please note while reaching
out on Discord that I generally do not accept random friend requests. @Mention me in
the [programmable block channel](https://discord.com/channels/125011928711036928/216219467959500800) of the Keen
Software House Discord Server to get a hold of me.
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,54 @@ public static Sprite MirroredHorizontal(Sprite sprite, Anchor anchor = null)
clone.Scale(new Vector2(-1, 1), anchor);
return clone;
}

/// <summary>
/// Translate a group of sprites with the given vector
/// </summary>
/// <param name="vector">the offset to translate the sprites by</param>
/// <param name="sprites">the sprites to translate</param>
public static void Translate(Vector2 vector, params Sprite[] sprites)
{
foreach (var sprite in sprites) sprite.Translate(vector);
}

/// <summary>
/// Translate a group of sprites with the x and y offset
/// </summary>
/// <param name="x">the x-offset to translate the sprites by</param>
/// <param name="y">the y-offset to translate the sprites by</param>
/// <param name="sprites">the sprites to translate</param>
public static void Translate(float x, float y, params Sprite[] sprites) => Translate(new Vector2(x, y), sprites);

/// <summary>
/// Rotate a group of sprites with the given angle. Rotates in place unless an anchor is given.
/// </summary>
/// <param name="angle">The angle to rotate the sprites by</param>
/// <param name="anchor">Optional anchor to rotate around</param>
/// <param name="sprites">the sprites to rotate</param>
public static void Rotate(Angle angle, Anchor anchor = null, params Sprite[] sprites)
{
foreach (var sprite in sprites) sprite.Rotate(angle, anchor);
}

/// <summary>
/// Scale a group of sprites with the given scalars. X and Y can scale separately. Scales in place unless an anchor is given.
/// </summary>
/// <param name="scalar">The amount to scale x and y by.</param>
/// <param name="anchor">If passed, scales a sprite's distance to this point in addition to the sprite itself.</param>
/// <param name="sprites">The sprites to scale</param>
public static void Scale(Vector2 scalar, Anchor anchor = null, params Sprite[] sprites)
{
foreach (var sprite in sprites) sprite.Scale(scalar, anchor);
}

/// <summary>
/// Scale a group of sprites with the given scalar. Scales in place unless an anchor is given.
/// </summary>
/// <param name="scalar">the amount to scale the sprites by</param>
/// <param name="anchor">If passed, scales a sprite's distance to this point in addition to the sprite itself.</param>
/// <param name="sprites">The sprites to scale</param>
public static void Scale(float scalar, Anchor anchor = null, params Sprite[] sprites) =>
Scale(new Vector2(scalar, scalar), anchor, sprites);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
You should have received a copy of the GNU Lesser General Public License along with Sprite Compositor.
If not, see <https://www.gnu.org/licenses/>. */

using System;
using VRage.Game.GUI.TextPanel;
using VRageMath;

Expand Down Expand Up @@ -51,12 +52,12 @@ public override void Scale(Vector2 scalar, Anchor anchor = null)

public override void Rotate(Angle angle, Anchor positionAnchor = null)
{
Sprite.RotationOrScale += (float)angle.Radians;
Sprite.RotationOrScale = (float)((Sprite.RotationOrScale + angle.Radians) % (2 * Math.PI));
base.Rotate(angle, positionAnchor);
}

public override Sprite Clone() => new TextureSprite(Sprite);


public class TextureSpriteBuilder
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public virtual void Rotate(Angle angle, Anchor positionAnchor = null)
{
if (Children[index].Type != SpriteType.TEXT)
{
Children[index].RotationOrScale += (float)angle.Radians;
Children[index].RotationOrScale =(float)((Children[index].RotationOrScale + angle.Radians) % (2 * Math.PI));
}

if (anchor == Children[index].Position) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ This will therefore not create slanted text! */
// You can also set an initial scale for text using the builder.
// However, here we want to scale the distance the text has to the sun, so we supply the sun as the anchor for the scale operation.
textLayer.Scale(1.5f, sunSprite);
// Any new operations will be applied to the newly grouped sprites as well, but previous operations will not be applied.
sunSprite.Scale(2f);
sunSprite.Scale(2);
// It's also possible to scale X and Y separately.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mal.Mdk2.PbPackager" Version="2.2.3">
<PackageReference Include="Mal.Mdk2.PbPackager" Version="2.2.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ public void ArbitraryRotationsSetCorrectRotation(params double[] radians)
{
sprite.Rotate(angle);
}

// Note here (and this is not obvious) that unlike the angle struct, a sprite's rotation value can grow indefinitely
// This means that values above and below 2 * Math.PI are possible. The test keeps this in mind.
var totalAngle = angles.Sum(angle => angle.Radians);

var totalAngle = angles.Sum(angle => angle.Radians) % (2 * Math.PI);
Assert.That(sprite.Rotation, Is.EqualTo(totalAngle).Within(Precision));
}

Expand Down
Loading