diff --git a/source/games/blood/src/actor.cpp b/source/games/blood/src/actor.cpp
index 2fbf93190..efa8b7a63 100644
--- a/source/games/blood/src/actor.cpp
+++ b/source/games/blood/src/actor.cpp
@@ -6007,7 +6007,7 @@ static void actCheckExplosion()
 				{
 					if (gImpactSpritesList[i] == nullptr) continue;
 
-					auto impactactor = gImpactSpritesList[i];
+					DBloodActor* impactactor = gImpactSpritesList[i];
 					if (!impactactor->hasX() || !impactactor->s().insector() || (impactactor->s().flags & kHitagFree) != 0)	continue;
 
 					if (!CheckSector(sectorMap, &impactactor->s()) || !CheckProximity(impactactor, x, y, z, pSector, radius))
diff --git a/source/games/blood/src/aiunicult.h b/source/games/blood/src/aiunicult.h
index 0b31fe114..fa41dd869 100644
--- a/source/games/blood/src/aiunicult.h
+++ b/source/games/blood/src/aiunicult.h
@@ -168,8 +168,8 @@ struct GENDUDEEXTRA
     uint16_t weaponType;
     uint16_t baseDispersion;
     uint16_t slaveCount;              // how many dudes is summoned
-    DBloodActor* pLifeLeech;        // spritenum of dropped dude's leech
-    DBloodActor* slave[kGenDudeMaxSlaves];  // index of the ones dude is summon
+    TObjPtr<DBloodActor*> pLifeLeech;        // spritenum of dropped dude's leech
+    TObjPtr<DBloodActor*> slave[kGenDudeMaxSlaves];  // index of the ones dude is summon
     signed short dmgControl[kDamageMax];    // depends of current weapon, drop armor item, sprite yrepeat and surface type
     bool updReq[kGenDudePropertyMax]; // update requests
     union
diff --git a/source/games/blood/src/blood.cpp b/source/games/blood/src/blood.cpp
index 2e6039365..b207580ba 100644
--- a/source/games/blood/src/blood.cpp
+++ b/source/games/blood/src/blood.cpp
@@ -78,6 +78,24 @@ size_t DBloodActor::PropagateMark()
 	return 2 + Super::PropagateMark();
 }
 
+static void markgcroots()
+{
+	GC::MarkArray(gProxySpritesList, gProxySpritesCount);
+	GC::MarkArray(gSightSpritesList, gSightSpritesCount);
+	GC::MarkArray(gPhysSpritesList, gPhysSpritesCount);
+	GC::MarkArray(gImpactSpritesList, gImpactSpritesCount);
+	for (auto& pl : gPlayer)
+	{
+		GC::Mark(pl.actor);
+		GC::MarkArray(pl.ctfFlagState, 2);
+		GC::Mark(pl.aimTarget);
+		GC::MarkArray(pl.aimTargets, 16);
+		GC::Mark(pl.fragger);
+		GC::Mark(pl.voodooTarget);
+	}
+}
+
+
 void InitCheats();
 
 bool bNoDemo = false;
@@ -518,6 +536,7 @@ void GameInterface::loadPalette(void)
 void GameInterface::app_init()
 {
 	SetupActors(RUNTIME_CLASS(DBloodActor));
+	GC::AddMarkerFunc(markgcroots);
 	InitCheats();
 	memcpy(&gGameOptions, &gSingleGameOptions, sizeof(GAMEOPTIONS));
 	gGameOptions.nMonsterSettings = !userConfig.nomonsters;
diff --git a/source/games/blood/src/blood.h b/source/games/blood/src/blood.h
index d83ab7f82..3bcd4b555 100644
--- a/source/games/blood/src/blood.h
+++ b/source/games/blood/src/blood.h
@@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 #include "gamestruct.h"
 #include "mapinfo.h"
 #include "d_net.h"
+#include "serialize_obj.h"
 
 #include "common_game.h"
 #include "fx.h"
diff --git a/source/games/blood/src/bloodactor.h b/source/games/blood/src/bloodactor.h
index 11c785b39..decb84c78 100644
--- a/source/games/blood/src/bloodactor.h
+++ b/source/games/blood/src/bloodactor.h
@@ -26,8 +26,8 @@ public:
 	DUDEEXTRA dudeExtra;
 	SPRITEMASS spriteMass;
 	GENDUDEEXTRA genDudeExtra;
-	DBloodActor* prevmarker;	// needed by the nnext marker code. This originally hijacked targetX in XSPRITE
-	DBloodActor* ownerActor;	// was previously stored in the sprite's owner field.
+	TObjPtr<DBloodActor*> prevmarker;	// needed by the nnext marker code. This originally hijacked targetX in XSPRITE
+	TObjPtr<DBloodActor*> ownerActor;	// was previously stored in the sprite's owner field.
 	POINT3D basePoint;
 	EventObject condition[2];
 	bool explosionhackflag; // this originally hijacked the target field which is not safe when working with pointers.
diff --git a/source/games/blood/src/mapstructs.h b/source/games/blood/src/mapstructs.h
index 1d86a6cf0..9bf21c6e5 100644
--- a/source/games/blood/src/mapstructs.h
+++ b/source/games/blood/src/mapstructs.h
@@ -46,8 +46,8 @@ struct XSPRITE {
        };
     };
 
-    DBloodActor* target;           // target sprite
-    DBloodActor* burnSource;
+    TObjPtr<DBloodActor*> target;           // target sprite
+    TObjPtr<DBloodActor*> burnSource;
 
     int32_t targetX;          // target x
     int32_t targetY;          // target y
diff --git a/source/games/blood/src/nnexts.cpp b/source/games/blood/src/nnexts.cpp
index 84434e9d9..5c106f0b8 100644
--- a/source/games/blood/src/nnexts.cpp
+++ b/source/games/blood/src/nnexts.cpp
@@ -40,14 +40,6 @@ inline int mulscale8(int a, int b) { return MulScale(a, b, 8); }
 
 bool gAllowTrueRandom = false;
 bool gEventRedirectsUsed = false;
-DBloodActor* gProxySpritesList[];  // list of additional sprites which can be triggered by Proximity
-int gProxySpritesCount;   // current count
-DBloodActor* gSightSpritesList[];  // list of additional sprites which can be triggered by Sight
-int gSightSpritesCount;   // current count
-DBloodActor* gPhysSpritesList[];   // list of additional sprites which can be affected by physics
-int gPhysSpritesCount;    // current count
-DBloodActor* gImpactSpritesList[];
-int gImpactSpritesCount;
 
 
 
@@ -1176,14 +1168,15 @@ void nnExtProcessSuperSprites()
     {
         for (int i = 0; i < gProxySpritesCount; i++)
         {
-            if (!gProxySpritesList[i] || !gProxySpritesList[i]->hasX()) continue;
+            DBloodActor* pProx = gProxySpritesList[i];
+            if (!pProx || !pProx->hasX()) continue;
 
-            auto const pProxSpr = &gProxySpritesList[i]->s();
-            XSPRITE* pXProxSpr = &gProxySpritesList[i]->x();
+            auto const pProxSpr = &pProx->s();
+            XSPRITE* pXProxSpr = &pProx->x();
             if (!pXProxSpr->Proximity || (!pXProxSpr->Interrutable && pXProxSpr->state != pXProxSpr->restState) || pXProxSpr->locked == 1
                 || pXProxSpr->isTriggered) continue;  // don't process locked or triggered sprites
 
-            int okDist = (gProxySpritesList[i]->IsDudeActor()) ? 96 : ClipLow(pProxSpr->clipdist * 3, 32);
+            int okDist = (pProx->IsDudeActor()) ? 96 : ClipLow(pProxSpr->clipdist * 3, 32);
             int x = pProxSpr->x;
             int y = pProxSpr->y;
             int z = pProxSpr->z;	
@@ -1197,7 +1190,7 @@ void nnExtProcessSuperSprites()
                     if (!affected->hasX() || affected->x().health <= 0) continue;
                     else if (CheckProximity(affected, x, y, z, pSect, okDist))
                     {
-                        trTriggerSprite(gProxySpritesList[i], kCmdSpriteProximity);
+                        trTriggerSprite(pProx, kCmdSpriteProximity);
                         break;
                     }
                 }
@@ -1212,7 +1205,7 @@ void nnExtProcessSuperSprites()
 
                     if (pPlayer->pXSprite->health > 0 && CheckProximity(gPlayer->actor, x, y, z, pSect, okDist))
                     {
-                        trTriggerSprite(gProxySpritesList[i], kCmdSpriteProximity);
+                        trTriggerSprite(pProx, kCmdSpriteProximity);
                         break;
                     }
                 }
@@ -1225,19 +1218,20 @@ void nnExtProcessSuperSprites()
     {
         for (int i = 0; i < gSightSpritesCount; i++)
         {
-            if (!gSightSpritesList[i] || !gSightSpritesList[i]->hasX()) continue;
+            DBloodActor* pSight = gSightSpritesList[i];
+            if (!pSight || !pSight->hasX()) continue;
 
-            auto const pSightSpr = &gSightSpritesList[i]->s();
-            XSPRITE* pXSightSpr = &gSightSpritesList[i]->x();
+            auto const pSightSpr = &pSight->s();
+            XSPRITE* pXSightSpr = &pSight->x();
 
             if ((!pXSightSpr->Interrutable && pXSightSpr->state != pXSightSpr->restState) || pXSightSpr->locked == 1 ||
                 pXSightSpr->isTriggered) continue; // don't process locked or triggered sprites
 
             // sprite is drawn for one of players
-            if ((pXSightSpr->unused3 & kTriggerSpriteScreen) && (gSightSpritesList[i]->s().cstat2 & CSTAT2_SPRITE_MAPPED))
+            if ((pXSightSpr->unused3 & kTriggerSpriteScreen) && (pSight->s().cstat2 & CSTAT2_SPRITE_MAPPED))
             {
-                trTriggerSprite(gSightSpritesList[i], kCmdSpriteSight);
-                gSightSpritesList[i]->s().cstat2 &= ~CSTAT2_SPRITE_MAPPED;
+                trTriggerSprite(pSight, kCmdSpriteSight);
+                pSight->s().cstat2 &= ~CSTAT2_SPRITE_MAPPED;
                 continue;
             }
 
@@ -1259,7 +1253,7 @@ void nnExtProcessSuperSprites()
                 {
                     if (pXSightSpr->Sight)
                     {
-                    trTriggerSprite(gSightSpritesList[i], kCmdSpriteSight);
+                    trTriggerSprite(pSight, kCmdSpriteSight);
                         break;
                     }
 
@@ -1274,7 +1268,7 @@ void nnExtProcessSuperSprites()
                         if (!vector)
                             pSightSpr->cstat &= ~CSTAT_SPRITE_BLOCK_HITSCAN;
 
-                        if (gHitInfo.actor() == gSightSpritesList[i])
+                        if (gHitInfo.actor() == pSight)
                         {
                             trTriggerSprite(gHitInfo.actor(), kCmdSpriteSight);
                             break;
@@ -1290,7 +1284,7 @@ void nnExtProcessSuperSprites()
     {
         for (int i = 0; i < gPhysSpritesCount; i++)
         {
-            auto debrisactor = gPhysSpritesList[i];
+            DBloodActor* debrisactor = gPhysSpritesList[i];
             if (debrisactor == nullptr || !debrisactor->hasX()) continue;
             auto const pDebris = &debrisactor->s();
 
diff --git a/source/games/blood/src/nnexts.h b/source/games/blood/src/nnexts.h
index 25dc2a950..3c89cbe1b 100644
--- a/source/games/blood/src/nnexts.h
+++ b/source/games/blood/src/nnexts.h
@@ -273,14 +273,14 @@ extern const MISSILEINFO_EXTRA gMissileInfoExtra[kMissileMax];
 extern const DUDEINFO_EXTRA gDudeInfoExtra[kDudeMax];
 extern TRPLAYERCTRL gPlayerCtrl[kMaxPlayers];
 extern TRCONDITION gCondition[kMaxTrackingConditions];
-extern DBloodActor* gProxySpritesList[kMaxSuperXSprites];
-extern DBloodActor* gSightSpritesList[kMaxSuperXSprites];
-extern DBloodActor* gPhysSpritesList[kMaxSuperXSprites];
-extern DBloodActor* gImpactSpritesList[kMaxSuperXSprites];
-extern int gProxySpritesCount;
-extern int gSightSpritesCount;
-extern int gPhysSpritesCount;
-extern int gImpactSpritesCount;
+inline TObjPtr<DBloodActor*> gProxySpritesList[kMaxSuperXSprites];
+inline TObjPtr<DBloodActor*> gSightSpritesList[kMaxSuperXSprites];
+inline TObjPtr<DBloodActor*> gPhysSpritesList[kMaxSuperXSprites];
+inline TObjPtr<DBloodActor*> gImpactSpritesList[kMaxSuperXSprites];
+inline int gProxySpritesCount;
+inline int gSightSpritesCount;
+inline int gPhysSpritesCount;
+inline int gImpactSpritesCount;
 extern int gTrackingCondsCount;
 extern AISTATE genPatrolStates[kPatrolStateSize];
 
diff --git a/source/games/blood/src/player.h b/source/games/blood/src/player.h
index d55141218..e8f279274 100644
--- a/source/games/blood/src/player.h
+++ b/source/games/blood/src/player.h
@@ -80,7 +80,7 @@ extern POSTURE gPostureDefaults[kModeMax][kPostureMax];
 
 struct PLAYER
 {
-    DBloodActor*        actor;
+    TObjPtr<DBloodActor*>        actor;
     spritetype*         pSprite;
     XSPRITE*            pXSprite;
     DUDEINFO*           pDudeInfo;
@@ -113,7 +113,7 @@ struct PLAYER
     bool                isUnderwater;
     bool                hasKey[8];
     int8_t              hasFlag;
-    DBloodActor*        ctfFlagState[2];
+    TObjPtr<DBloodActor*>        ctfFlagState[2];
     int                 damageControl[7];
     int8_t              curWeapon;
     int8_t              nextWeapon;
@@ -137,15 +137,15 @@ struct PLAYER
     //int               relAim;
     //int               at1ce;
     //int               at1d2;
-    DBloodActor*        aimTarget;  // aim target sprite
+    TObjPtr<DBloodActor*>        aimTarget;  // aim target sprite
     int                 aimTargetsCount;
-    DBloodActor*        aimTargets[16];
+    TObjPtr<DBloodActor*>        aimTargets[16];
     int                 deathTime;
     int                 pwUpTime[kMaxPowerUps];
     int                 fragCount;
     int                 fragInfo[8];
     int                 teamId;
-    DBloodActor*        fragger;
+    TObjPtr<DBloodActor*>        fragger;
     int                 underwaterTime;
     int                 bubbleTime;
     int                 restTime;
@@ -160,7 +160,7 @@ struct PLAYER
     int                 armor[3];      // armor
     //int               at342;
     //int               at346;
-    DBloodActor*        voodooTarget;
+    TObjPtr<DBloodActor*>        voodooTarget;
     int                 voodooTargets;  // --> useless
     int                 voodooVar1;     // --> useless
     int                 vodooVar2;      // --> useless