diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts
index 78b66c10..3dfe8368 100644
--- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts
+++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts
@@ -62,7 +62,7 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
     const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name;
     const playerResource = PLAYER_RESOURCES[textureName];
     if (playerResource !== undefined) return playerResource;
-    
+
     for (let i=0; i<LAYERS.length;i++) {
         const playerResource = LAYERS[i][textureName];
         if (playerResource !== undefined) return playerResource;
@@ -81,4 +81,4 @@ const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor
         });
         loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
     });
-}
\ No newline at end of file
+}
diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts
new file mode 100644
index 00000000..4b91255f
--- /dev/null
+++ b/front/src/Phaser/Game/DirtyScene.ts
@@ -0,0 +1,52 @@
+import {ResizableScene} from "../Login/ResizableScene";
+import GameObject = Phaser.GameObjects.GameObject;
+import Events = Phaser.Scenes.Events;
+import AnimationEvents = Phaser.Animations.Events;
+
+/**
+ * A scene that can track its dirty/pristine state.
+ */
+export abstract class DirtyScene extends ResizableScene {
+    private isAlreadyTracking: boolean = false;
+    protected dirty:boolean = true;
+    private objectListChanged:boolean = true;
+
+    /**
+     * Track all objects added to the scene and adds a callback each time an animation is added.
+     * Whenever an object is added, removed, or when an animation is played, the dirty state is set to true.
+     *
+     * Note: this does not work with animations from sprites inside containers.
+     */
+    protected trackDirtyAnims(): void {
+        if (this.isAlreadyTracking) {
+            return;
+        }
+        this.isAlreadyTracking = true;
+        const trackAnimationFunction = this.trackAnimation.bind(this);
+        this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => {
+            this.objectListChanged = true;
+            gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
+        });
+
+        this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => {
+            this.objectListChanged = true;
+            gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
+        });
+
+        this.events.on(Events.RENDER, () => {
+            this.objectListChanged = false;
+        });
+    }
+
+    private trackAnimation(): void {
+        this.objectListChanged = true;
+    }
+
+    public isDirty(): boolean {
+        return this.dirty || this.objectListChanged;
+    }
+
+    public onResize(): void {
+        this.objectListChanged = true;
+    }
+}
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 672de5e6..6bedf469 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -81,6 +81,9 @@ import DOMElement = Phaser.GameObjects.DOMElement;
 import {Subscription} from "rxjs";
 import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
 import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
+import RenderTexture = Phaser.GameObjects.RenderTexture;
+import Tilemap = Phaser.Tilemaps.Tilemap;
+import {DirtyScene} from "./DirtyScene";
 
 export interface GameSceneInitInterface {
     initPosition: PointInterface|null,
@@ -119,7 +122,7 @@ interface DeleteGroupEventInterface {
 
 const defaultStartLayerName = 'start';
 
-export class GameScene extends ResizableScene implements CenterListener {
+export class GameScene extends DirtyScene implements CenterListener {
     Terrains : Array<Phaser.Tilemaps.Tileset>;
     CurrentPlayer!: CurrentGamerInterface;
     MapPlayers!: Phaser.Physics.Arcade.Group;
@@ -195,6 +198,7 @@ export class GameScene extends ResizableScene implements CenterListener {
         this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
             this.connectionAnswerPromiseResolve = resolve;
         })
+
     }
 
     //hook preload scene
@@ -354,6 +358,8 @@ export class GameScene extends ResizableScene implements CenterListener {
 
     //hook create scene
     create(): void {
+        this.trackDirtyAnims();
+
         gameManager.gameSceneIsCreated(this);
         urlManager.pushRoomIdToUrl(this.room);
         this.startLayerName = urlManager.getStartLayerNameFromUrl();
@@ -1187,8 +1193,6 @@ ${escapedMessage}
         });
     }
 
-    private dirty:boolean = true;
-
     /**
      * @param time
      * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
@@ -1202,11 +1206,11 @@ ${escapedMessage}
         this.dirty = false;
         mediaManager.setLastUpdateScene();
         this.currentTick = time;
-        if (this.CurrentPlayer.isMoving() === true) {
+        if (this.CurrentPlayer.isMoving()) {
             this.dirty = true;
         }
         this.CurrentPlayer.moveUser(delta);
-        if (this.CurrentPlayer.isMoving() === true) {
+        if (this.CurrentPlayer.isMoving()) {
             this.dirty = true;
             this.physics.enableUpdate();
         } else {
@@ -1413,6 +1417,7 @@ ${escapedMessage}
     }
 
     public onResize(): void {
+        super.onResize();
         this.reposition();
 
         // Send new viewport to server
@@ -1512,8 +1517,4 @@ ${escapedMessage}
             message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
         });
     }
-
-    public isDirty(): boolean {
-        return this.dirty;
-    }
 }