Skip to content

Commit c584a03

Browse files
cursoragent4ian
andcommitted
feat: Add spritesheet support for sprites
This commit introduces support for spritesheets, allowing sprites to use frames from a spritesheet texture atlas instead of standalone images. Key changes include: - **Sprite Class:** Added `spritesheetResourceName` and `spritesheetFrameName` properties to the `Sprite` class to store spritesheet information. - **Serialization:** Updated `Direction::UnserializeFrom` and `SaveSpritesDirection` to handle saving and loading spritesheet data. - **Resource Management:** - Introduced `SpritesheetResource` to represent spritesheet files in the project. - Added `PixiSpritesheetManager` in GDJS to load and manage spritesheet JSON data and PIXI.Spritesheet objects. - Integrated spritesheet loading into `ResourceLoader` and `ArbitraryResourceWorker`. - **Runtime:** - Modified `SpriteAnimator` and `SpriteAnimationFrame` to handle spritesheet frames. - Updated `PixiAnimationFrameTextureManager` and `PixiRenderer` to retrieve textures from spritesheets. - Added `getSpritesheetFrameTexture` to `CustomRuntimeObject3DRenderer` (though 3D objects do not support spritesheets). This feature enhances the flexibility of sprite animations by enabling the use of more efficient texture atlases. Co-authored-by: florian <[email protected]>
1 parent ee41a58 commit c584a03

File tree

15 files changed

+627
-5
lines changed

15 files changed

+627
-5
lines changed

Core/GDCore/Extensions/Builtin/SpriteExtension/Direction.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ void Direction::UnserializeFrom(const gd::SerializerElement& element) {
9898
Sprite sprite;
9999

100100
sprite.SetImageName(spriteElement.GetStringAttribute("image"));
101+
102+
// Spritesheet support: load spritesheet resource name and frame name if present
103+
if (spriteElement.HasAttribute("spritesheetResourceName") ||
104+
spriteElement.HasChild("spritesheetResourceName")) {
105+
sprite.SetSpritesheetResourceName(
106+
spriteElement.GetStringAttribute("spritesheetResourceName", ""));
107+
sprite.SetSpritesheetFrameName(
108+
spriteElement.GetStringAttribute("spritesheetFrameName", ""));
109+
}
110+
101111
OpenPointsSprites(sprite.GetAllNonDefaultPoints(),
102112
spriteElement.GetChild("points", 0, "Points"));
103113

@@ -164,6 +174,15 @@ void SaveSpritesDirection(const vector<Sprite>& sprites,
164174
gd::SerializerElement& spriteElement = element.AddChild("sprite");
165175

166176
spriteElement.SetAttribute("image", sprites[i].GetImageName());
177+
178+
// Spritesheet support: save spritesheet resource name and frame name if used
179+
if (sprites[i].UsesSpritesheetFrame()) {
180+
spriteElement.SetAttribute("spritesheetResourceName",
181+
sprites[i].GetSpritesheetResourceName());
182+
spriteElement.SetAttribute("spritesheetFrameName",
183+
sprites[i].GetSpritesheetFrameName());
184+
}
185+
167186
SavePointsSprites(sprites[i].GetAllNonDefaultPoints(),
168187
spriteElement.AddChild("points"));
169188

Core/GDCore/Extensions/Builtin/SpriteExtension/Sprite.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ namespace gd {
1818
/**
1919
* \brief Represents a sprite to be displayed on the screen.
2020
*
21+
* A sprite can display either:
22+
* - A standalone image (using `image` field)
23+
* - A frame from a spritesheet (using `spritesheetResourceName` and `spritesheetFrameName`)
24+
*
25+
* When using a spritesheet, the `image` field should be empty, and the texture
26+
* will be obtained from the spritesheet's frame data.
27+
*
2128
* \see Direction
2229
* \see SpriteObject
2330
* \ingroup SpriteObjectExtension
@@ -29,6 +36,7 @@ class GD_CORE_API Sprite {
2936

3037
/**
3138
* \brief Change the name of the sprite image.
39+
* \note When using a spritesheet, this should be empty.
3240
*/
3341
inline void SetImageName(const gd::String& image_) { image = image_; }
3442

@@ -42,6 +50,46 @@ class GD_CORE_API Sprite {
4250
*/
4351
inline gd::String& GetImageName() { return image; }
4452

53+
/**
54+
* \brief Set the spritesheet resource name for this frame.
55+
* \note When set, the frame texture will be obtained from the spritesheet.
56+
*/
57+
inline void SetSpritesheetResourceName(const gd::String& resourceName) {
58+
spritesheetResourceName = resourceName;
59+
}
60+
61+
/**
62+
* \brief Get the spritesheet resource name for this frame.
63+
* \return The spritesheet resource name, or empty string if not using a spritesheet.
64+
*/
65+
inline const gd::String& GetSpritesheetResourceName() const {
66+
return spritesheetResourceName;
67+
}
68+
69+
/**
70+
* \brief Set the frame name within the spritesheet.
71+
* \note This corresponds to a key in the "frames" object of the spritesheet JSON.
72+
*/
73+
inline void SetSpritesheetFrameName(const gd::String& frameName) {
74+
spritesheetFrameName = frameName;
75+
}
76+
77+
/**
78+
* \brief Get the frame name within the spritesheet.
79+
* \return The frame name within the spritesheet, or empty string if not using a spritesheet.
80+
*/
81+
inline const gd::String& GetSpritesheetFrameName() const {
82+
return spritesheetFrameName;
83+
}
84+
85+
/**
86+
* \brief Check if this sprite uses a spritesheet frame.
87+
* \return true if using a spritesheet frame, false if using a standalone image.
88+
*/
89+
inline bool UsesSpritesheetFrame() const {
90+
return !spritesheetResourceName.empty() && !spritesheetFrameName.empty();
91+
}
92+
4593
/**
4694
* \brief Get the collision mask (custom or automatically generated owing to
4795
* IsFullImageCollisionMask())
@@ -162,6 +210,11 @@ class GD_CORE_API Sprite {
162210
private:
163211
gd::String image; ///< Name of the image to be loaded in Image Manager.
164212

213+
// Spritesheet support: if spritesheetResourceName is not empty, the texture
214+
// is obtained from the spritesheet's frame data instead of the image field.
215+
gd::String spritesheetResourceName; ///< Name of the spritesheet resource (if using spritesheet).
216+
gd::String spritesheetFrameName; ///< Frame name within the spritesheet (key in "frames" object).
217+
165218
bool fullImageCollisionMask; ///< True to use a bounding box wrapping the
166219
///< whole image as collision mask. If false,
167220
///< custom collision mask is used.

Core/GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,21 @@ void SpriteAnimationList::ExposeResources(gd::ArbitraryResourceWorker& worker) {
109109
for (std::size_t l = 0;
110110
l < GetAnimation(j).GetDirection(k).GetSpritesCount();
111111
l++) {
112-
worker.ExposeImage(
113-
GetAnimation(j).GetDirection(k).GetSprite(l).GetImageName());
112+
Sprite& sprite = GetAnimation(j).GetDirection(k).GetSprite(l);
113+
114+
// Expose either the image or the spritesheet resource
115+
if (sprite.UsesSpritesheetFrame()) {
116+
// Expose the spritesheet resource (and its embedded image)
117+
gd::String spritesheetResourceName = sprite.GetSpritesheetResourceName();
118+
worker.ExposeSpritesheet(spritesheetResourceName);
119+
120+
// Update the resource name if it was changed by the worker
121+
if (spritesheetResourceName != sprite.GetSpritesheetResourceName()) {
122+
sprite.SetSpritesheetResourceName(spritesheetResourceName);
123+
}
124+
} else {
125+
worker.ExposeImage(sprite.GetImageName());
126+
}
114127
}
115128
}
116129
}

Core/GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ namespace gd {
2222
*
2323
* It's used in the configuration of object that implements image-based animations.
2424
*
25+
* ## Spritesheet Support
26+
*
27+
* Individual sprites (frames) can use textures from spritesheets. When a sprite's
28+
* `spritesheetResourceName` and `spritesheetFrameName` are set, the texture is
29+
* obtained from the spritesheet's frame data instead of a standalone image.
30+
*
31+
* ### Future Improvements (Spritesheet Animation Import)
32+
*
33+
* Spritesheet JSON files can contain animation definitions in their `meta.animations`
34+
* field. These are arrays of frame names that define complete animations. For example:
35+
*
36+
* ```json
37+
* {
38+
* "frames": { "walk1": {...}, "walk2": {...}, "idle1": {...}, "idle2": {...} },
39+
* "meta": {
40+
* "image": "sprites.png",
41+
* "animations": {
42+
* "walk": ["walk1", "walk2"],
43+
* "idle": ["idle1", "idle2"]
44+
* }
45+
* }
46+
* }
47+
* ```
48+
*
49+
* A future improvement could add methods to:
50+
* - Import all frames from a spritesheet as a Direction
51+
* - Import animation definitions from `meta.animations` as complete Animations
52+
* - The Sprite editor could provide UI to import spritesheet animations directly
53+
*
2554
* \see Animation
2655
* \see Direction
2756
* \see Sprite

Core/GDCore/IDE/Project/ArbitraryResourceWorker.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ void ArbitraryResourceWorker::ExposeSpine(gd::String& resourceName){
6262
// do.
6363
};
6464

65+
void ArbitraryResourceWorker::ExposeSpritesheet(gd::String& resourceName){
66+
// Nothing to do by default - each child class can define here the action to
67+
// do.
68+
};
69+
6570
void ArbitraryResourceWorker::ExposeJavaScript(gd::String& resourceName){
6671
// Nothing to do by default - each child class can define here the action to
6772
// do.
@@ -200,6 +205,11 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
200205
ExposeSpine(resourceName);
201206
return;
202207
}
208+
if (resourceType == "spritesheet") {
209+
ExposeSpritesheet(resourceName);
210+
ExposeEmbeddeds(resourceName);
211+
return;
212+
}
203213
if (resourceType == "javascript") {
204214
ExposeJavaScript(resourceName);
205215
return;

Core/GDCore/IDE/Project/ArbitraryResourceWorker.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ class GD_CORE_API ArbitraryResourceWorker {
107107
*/
108108
virtual void ExposeSpine(gd::String &resourceName);
109109

110+
/**
111+
* \brief Expose a spritesheet, which is always a reference to a "spritesheet" resource.
112+
*/
113+
virtual void ExposeSpritesheet(gd::String &resourceName);
114+
110115
/**
111116
* \brief Expose a video, which is always a reference to a "video" resource.
112117
*/

Core/GDCore/Project/ResourcesContainer.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ ResourcesContainer::CreateResource(const gd::String &kind) {
130130
return std::make_shared<AtlasResource>();
131131
else if (kind == "spine")
132132
return std::make_shared<SpineResource>();
133+
else if (kind == "spritesheet")
134+
return std::make_shared<SpritesheetResource>();
133135
else if (kind == "javascript")
134136
return std::make_shared<JavaScriptResource>();
135137
else if (kind == "internal-in-game-editor-only-svg")
@@ -150,6 +152,7 @@ const gd::String Resource::bitmapType = "bitmapFont";
150152
const gd::String Resource::model3DType = "model3D";
151153
const gd::String Resource::atlasType = "atlas";
152154
const gd::String Resource::spineType = "spine";
155+
const gd::String Resource::spritesheetType = "spritesheet";
153156
const gd::String Resource::javaScriptType = "javascript";
154157
const gd::String Resource::internalInGameEditorOnlySvgType = "internal-in-game-editor-only-svg";
155158

@@ -616,6 +619,20 @@ void AtlasResource::SerializeTo(SerializerElement &element) const {
616619
element.SetAttribute("file", GetFile());
617620
}
618621

622+
void SpritesheetResource::SetFile(const gd::String &newFile) {
623+
file = NormalizePathSeparator(newFile);
624+
}
625+
626+
void SpritesheetResource::UnserializeFrom(const SerializerElement &element) {
627+
SetUserAdded(element.GetBoolAttribute("userAdded"));
628+
SetFile(element.GetStringAttribute("file"));
629+
}
630+
631+
void SpritesheetResource::SerializeTo(SerializerElement &element) const {
632+
element.SetAttribute("userAdded", IsUserAdded());
633+
element.SetAttribute("file", GetFile());
634+
}
635+
619636
void JavaScriptResource::SetFile(const gd::String &newFile) {
620637
file = NormalizePathSeparator(newFile);
621638
}

Core/GDCore/Project/ResourcesContainer.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class GD_CORE_API Resource {
3737
static const gd::String model3DType;
3838
static const gd::String atlasType;
3939
static const gd::String spineType;
40+
static const gd::String spritesheetType;
4041
static const gd::String javaScriptType;
4142
static const gd::String internalInGameEditorOnlySvgType;
4243

@@ -566,6 +567,37 @@ class GD_CORE_API AtlasResource : public Resource {
566567
gd::String file;
567568
};
568569

570+
/**
571+
* \brief Describe a spritesheet JSON file used by a project.
572+
*
573+
* A spritesheet resource contains JSON data that describes frames in a texture
574+
* atlas. The JSON format follows the standard PixiJS/TexturePacker format with
575+
* "frames" (mapping frame names to coordinates/dimensions) and "meta" (containing
576+
* the texture image reference).
577+
*
578+
* \see Resource
579+
* \ingroup ResourcesManagement
580+
*/
581+
class GD_CORE_API SpritesheetResource : public Resource {
582+
public:
583+
SpritesheetResource() : Resource() { SetKind("spritesheet"); };
584+
virtual ~SpritesheetResource(){};
585+
virtual SpritesheetResource *Clone() const override {
586+
return new SpritesheetResource(*this);
587+
}
588+
589+
virtual const gd::String &GetFile() const override { return file; };
590+
virtual void SetFile(const gd::String &newFile) override;
591+
592+
virtual bool UseFile() const override { return true; }
593+
void SerializeTo(SerializerElement &element) const override;
594+
595+
void UnserializeFrom(const SerializerElement &element) override;
596+
597+
private:
598+
gd::String file;
599+
};
600+
569601
/**
570602
* \brief Describe a JavaScript file used by a project.
571603
*

Extensions/3D/CustomRuntimeObject3DRenderer.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,22 @@ namespace gdjs {
163163
});
164164
}
165165

166+
getSpritesheetFrameTexture(
167+
spritesheetResourceName: string,
168+
frameName: string
169+
): THREE.Material {
170+
// Spritesheet frames are not supported for 3D objects.
171+
// Return an invalid/empty material.
172+
console.warn(
173+
'Spritesheet frames are not supported for 3D objects. Returning empty material.'
174+
);
175+
return this._imageManager.getThreeMaterial('', {
176+
useTransparentTexture: true,
177+
forceBasicMaterial: true,
178+
vertexColors: false,
179+
});
180+
}
181+
166182
getAnimationFrameWidth(material: THREE.Material) {
167183
const map = (
168184
material as THREE.MeshBasicMaterial | THREE.MeshStandardMaterial

GDJS/Runtime/ResourceCache.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,13 @@ namespace gdjs {
5050
this._nameToContent.clear();
5151
this._fileToContent.clear();
5252
}
53+
54+
/**
55+
* Get all the values stored in the cache.
56+
* @returns An iterable of all cached content values.
57+
*/
58+
getAllValues(): IterableIterator<C> {
59+
return this._nameToContent.values();
60+
}
5361
}
5462
}

0 commit comments

Comments
 (0)