diff --git a/src/actor.h b/src/actor.h
index c72a5586f0..331c7167a6 100644
--- a/src/actor.h
+++ b/src/actor.h
@@ -1089,7 +1089,7 @@ public:
 
 	WORD			sprite;				// used to find patch_t and flip value
 	BYTE			frame;				// sprite frame to draw
-	fixed_t			scaleX, scaleY;		// Scaling values; FRACUNIT is normal size
+	DVector2		Scale;				// Scaling values; 1 is normal size
 	FRenderStyle	RenderStyle;		// Style to draw this actor with
 	ActorRenderFlags	renderflags;		// Different rendering flags
 	FTextureID		picnum;				// Draw this instead of sprite if valid
diff --git a/src/am_map.cpp b/src/am_map.cpp
index 18a711fc99..204a844f05 100644
--- a/src/am_map.cpp
+++ b/src/am_map.cpp
@@ -2844,8 +2844,8 @@ void AM_drawThings ()
 
 					if (texture == NULL) goto drawTriangle;	// fall back to standard display if no sprite can be found.
 
-					const fixed_t spriteXScale = FixedMul(t->scaleX, 10 * scale_mtof);
-					const fixed_t spriteYScale = FixedMul(t->scaleY, 10 * scale_mtof);
+					const fixed_t spriteXScale = fixed_t(t->Scale.X * 10 * scale_mtof);
+					const fixed_t spriteYScale = fixed_t(t->Scale.Y * 10 * scale_mtof);
 
 					DrawMarker (texture, p.x, p.y, 0, !!(frame->Flip & (1 << rotation)),
 						spriteXScale, spriteYScale, t->Translation, FRACUNIT, 0, LegacyRenderStyles[STYLE_Normal]);
@@ -3042,7 +3042,7 @@ void AM_drawAuthorMarkers ()
 				 marked->Sector->MoreFlags & SECF_DRAWN)))
 			{
 				DrawMarker (tex, marked->_f_X() >> FRACTOMAPBITS, marked->_f_Y() >> FRACTOMAPBITS, 0,
-					flip, mark->scaleX, mark->scaleY, mark->Translation,
+					flip, FLOAT2FIXED(mark->Scale.X), FLOAT2FIXED(mark->Scale.Y), mark->Translation,
 					mark->alpha, mark->fillcolor, mark->RenderStyle);
 			}
 			marked = mark->args[0] != 0 ? it.Next() : NULL;
diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp
index 40abf19615..5aa2ce1aaa 100644
--- a/src/d_dehacked.cpp
+++ b/src/d_dehacked.cpp
@@ -928,7 +928,7 @@ static int PatchThing (int thingy)
 			}
 			else if (stricmp (Line1, "Scale") == 0)
 			{
-				info->scaleY = info->scaleX = clamp<fixed_t> (FLOAT2FIXED(atof (Line2)), 1, 256*FRACUNIT);
+				info->Scale.Y = info->Scale.X = clamp(atof (Line2), 1./65536, 256.);
 			}
 			else if (stricmp (Line1, "Decal") == 0)
 			{
diff --git a/src/d_player.h b/src/d_player.h
index 66af66e969..8e7a470775 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -533,7 +533,7 @@ extern player_t players[MAXPLAYERS];
 
 FArchive &operator<< (FArchive &arc, player_t *&p);
 
-void P_CheckPlayerSprite(AActor *mo, int &spritenum, fixed_t &scalex, fixed_t &scaley);
+void P_CheckPlayerSprite(AActor *mo, int &spritenum, DVector2 &scale);
 
 inline void AActor::SetFriendPlayer(player_t *player)
 {
diff --git a/src/doomdata.h b/src/doomdata.h
index 4d82547574..4f372a58e3 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -26,6 +26,7 @@
 
 // The most basic types we use, portability.
 #include "doomtype.h"
+#include "vectors.h"
 
 // Some global defines, that configure the game.
 #include "doomdef.h"
@@ -360,8 +361,7 @@ struct FMapThing
 	fixed_t		gravity;
 	fixed_t		alpha;
 	DWORD		fillcolor;
-	fixed_t		scaleX;
-	fixed_t		scaleY;
+	DVector2	Scale;
 	int			health;
 	int			score;
 	short		pitch;
diff --git a/src/g_doom/a_scriptedmarine.cpp b/src/g_doom/a_scriptedmarine.cpp
index 8c4546219a..a3d33f5553 100644
--- a/src/g_doom/a_scriptedmarine.cpp
+++ b/src/g_doom/a_scriptedmarine.cpp
@@ -649,13 +649,11 @@ void AScriptedMarine::SetSprite (PClassActor *source)
 	{ // A valid actor class wasn't passed, so use the standard sprite
 		SpriteOverride = sprite = GetClass()->OwnedStates[0].sprite;
 		// Copy the standard scaling
-		scaleX = GetDefault()->scaleX;
-		scaleY = GetDefault()->scaleY;
+		Scale = GetDefault()->Scale;
 	}
 	else
 	{ // Use the same sprite and scaling the passed class spawns with
 		SpriteOverride = sprite = GetDefaultByType (source)->SpawnState->sprite;
-		scaleX = GetDefaultByType(source)->scaleX;
-		scaleY = GetDefaultByType(source)->scaleY;
+		Scale = GetDefaultByType(source)->Scale;
 	}
 }
diff --git a/src/g_game.cpp b/src/g_game.cpp
index cc5afc1b06..869a521f5b 100644
--- a/src/g_game.cpp
+++ b/src/g_game.cpp
@@ -1645,8 +1645,8 @@ static void G_QueueBody (AActor *body)
 		const AActor *const defaultActor = body->GetDefault();
 		const FPlayerSkin &skin = skins[skinidx];
 
-		body->scaleX = Scale(body->scaleX, skin.ScaleX, defaultActor->scaleX);
-		body->scaleY = Scale(body->scaleY, skin.ScaleY, defaultActor->scaleY);
+		body->Scale.X *= skin.Scale.X / defaultActor->Scale.X;
+		body->Scale.Y *= skin.Scale.Y / defaultActor->Scale.Y;
 	}
 
 	bodyqueslot++;
diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp
index 30ada14cc2..12e841d7e0 100644
--- a/src/g_shared/a_armor.cpp
+++ b/src/g_shared/a_armor.cpp
@@ -520,9 +520,9 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
 						// -O1 optimizer bug work around. Only needed for
 						// GCC 4.2.1 on OS X for 10.4/10.5 tools compatibility.
 						volatile fixed_t tmp = 300;
-						Slots[i] -= Scale (damage, SlotsIncrement[i], tmp);
+						Slots[i] -= ::Scale (damage, SlotsIncrement[i], tmp);
 #else
-						Slots[i] -= Scale (damage, SlotsIncrement[i], 300);
+						Slots[i] -= ::Scale (damage, SlotsIncrement[i], 300);
 #endif
 						if (Slots[i] < 2*FRACUNIT)
 						{
@@ -535,7 +535,7 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
 					}
 				}
 			}
-			int saved = Scale (damage, savedPercent, 100*FRACUNIT);
+			int saved = ::Scale (damage, savedPercent, 100*FRACUNIT);
 			if (saved > savedPercent >> (FRACBITS-1))
 			{	
 				saved = savedPercent >> (FRACBITS-1);
diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp
index 28b4082d35..d310812fab 100644
--- a/src/g_shared/a_artifacts.cpp
+++ b/src/g_shared/a_artifacts.cpp
@@ -1275,8 +1275,7 @@ void APowerSpeed::DoEffect ()
 		speedMo->floorclip = Owner->floorclip;
 
 		// [BC] Also get the scale from the owner.
-		speedMo->scaleX = Owner->scaleX;
-		speedMo->scaleY = Owner->scaleY;
+		speedMo->Scale = Owner->Scale;
 
 		if (Owner == players[consoleplayer].camera &&
 			!(Owner->player->cheats & CF_CHASECAM))
diff --git a/src/g_shared/a_decals.cpp b/src/g_shared/a_decals.cpp
index 24056b36ef..bcab12b4ab 100644
--- a/src/g_shared/a_decals.cpp
+++ b/src/g_shared/a_decals.cpp
@@ -92,7 +92,7 @@ DBaseDecal::DBaseDecal (int statnum, fixed_t z)
 
 DBaseDecal::DBaseDecal (const AActor *basis)
 : DThinker(STAT_DECAL),
-  WallNext(0), WallPrev(0), LeftDistance(0), Z(basis->_f_Z()), ScaleX(basis->scaleX), ScaleY(basis->scaleY),
+  WallNext(0), WallPrev(0), LeftDistance(0), Z(basis->_f_Z()), ScaleX(FLOAT2FIXED(basis->Scale.X)), ScaleY(FLOAT2FIXED(basis->Scale.Y)),
   Alpha(basis->alpha), AlphaColor(basis->fillcolor), Translation(basis->Translation), PicNum(basis->picnum),
   RenderFlags(basis->renderflags), RenderStyle(basis->RenderStyle)
 {
diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp
index e08fb1a20f..78b7bc859a 100644
--- a/src/g_shared/sbarinfo_commands.cpp
+++ b/src/g_shared/sbarinfo_commands.cpp
@@ -310,8 +310,8 @@ class CommandDrawImage : public SBarInfoCommandFlowControl
 			
 			if (applyscale)
 			{
-				spawnScaleX = FIXED2DBL(item->scaleX);
-				spawnScaleY = FIXED2DBL(item->scaleY);
+				spawnScaleX = item->Scale.X;
+				spawnScaleY = item->Scale.Y;
 			}
 			
 			texture = TexMan[icon];
diff --git a/src/intermission/intermission.cpp b/src/intermission/intermission.cpp
index cceb2cb8c1..3fe677da6c 100644
--- a/src/intermission/intermission.cpp
+++ b/src/intermission/intermission.cpp
@@ -591,8 +591,7 @@ void DIntermissionScreenCast::Drawer ()
 	// draw the current frame in the middle of the screen
 	if (caststate != NULL)
 	{
-		double castscalex = FIXED2DBL(mDefaults->scaleX);
-		double castscaley = FIXED2DBL(mDefaults->scaleY);
+		DVector2 castscale = mDefaults->Scale;
 
 		int castsprite = caststate->sprite;
 
@@ -612,8 +611,7 @@ void DIntermissionScreenCast::Drawer ()
 
 					if (!(mDefaults->flags4 & MF4_NOSKIN))
 					{
-						castscaley = FIXED2DBL(skin->ScaleY);
-						castscalex = FIXED2DBL(skin->ScaleX);
+						castscale = skin->Scale;
 					}
 
 				}
@@ -626,8 +624,8 @@ void DIntermissionScreenCast::Drawer ()
 		screen->DrawTexture (pic, 160, 170,
 			DTA_320x200, true,
 			DTA_FlipX, sprframe->Flip & 1,
-			DTA_DestHeightF, pic->GetScaledHeightDouble() * castscaley,
-			DTA_DestWidthF, pic->GetScaledWidthDouble() * castscalex,
+			DTA_DestHeightF, pic->GetScaledHeightDouble() * castscale.Y,
+			DTA_DestWidthF, pic->GetScaledWidthDouble() * castscale.X,
 			DTA_RenderStyle, mDefaults->RenderStyle,
 			DTA_Alpha, mDefaults->alpha,
 			DTA_Translation, casttranslation,
diff --git a/src/menu/playerdisplay.cpp b/src/menu/playerdisplay.cpp
index ccf3f56330..5caf3ce42a 100644
--- a/src/menu/playerdisplay.cpp
+++ b/src/menu/playerdisplay.cpp
@@ -563,21 +563,19 @@ void FListMenuItemPlayerDisplay::Drawer(bool selected)
 	V_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1);
 
 	spriteframe_t *sprframe = NULL;
-	fixed_t scaleX, scaleY;
+	DVector2 Scale;
 
 	if (mPlayerState != NULL)
 	{
 		if (mSkin == 0)
 		{
 			sprframe = &SpriteFrames[sprites[mPlayerState->sprite].spriteframes + mPlayerState->GetFrame()];
-			scaleX = GetDefaultByType(mPlayerClass->Type)->scaleX;
-			scaleY = GetDefaultByType(mPlayerClass->Type)->scaleY;
+			Scale = GetDefaultByType(mPlayerClass->Type)->Scale;
 		}
 		else
 		{
 			sprframe = &SpriteFrames[sprites[skins[mSkin].sprite].spriteframes + mPlayerState->GetFrame()];
-			scaleX = skins[mSkin].ScaleX;
-			scaleY = skins[mSkin].ScaleY;
+			Scale = skins[mSkin].Scale;
 		}
 	}
 
@@ -590,8 +588,8 @@ void FListMenuItemPlayerDisplay::Drawer(bool selected)
 			if (mTranslate) trans = translationtables[TRANSLATION_Players](MAXPLAYERS);
 			screen->DrawTexture (tex,
 				x + 36*CleanXfac, y + 71*CleanYfac,
-				DTA_DestWidth, MulScale16 (tex->GetScaledWidth() * CleanXfac, scaleX),
-				DTA_DestHeight, MulScale16 (tex->GetScaledHeight() * CleanYfac, scaleY),
+				DTA_DestWidthF, tex->GetScaledWidthDouble() * CleanXfac * Scale.X,
+				DTA_DestHeightF, tex->GetScaledHeightDouble() * CleanYfac * Scale.Y,
 				DTA_Translation, trans,
 				DTA_FlipX, sprframe->Flip & (1 << mRotation),
 				TAG_DONE);
diff --git a/src/p_acs.cpp b/src/p_acs.cpp
index 49adf744ff..5229ecad24 100644
--- a/src/p_acs.cpp
+++ b/src/p_acs.cpp
@@ -3938,11 +3938,11 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value)
 		break;
 
 	case APROP_ScaleX:
-		actor->scaleX = value;
+		actor->Scale.X = FIXED2DBL(value);
 		break;
 
 	case APROP_ScaleY:
-		actor->scaleY = value;
+		actor->Scale.Y = FIXED2DBL(value);
 		break;
 
 	case APROP_Mass:
@@ -4052,8 +4052,8 @@ int DLevelScript::GetActorProperty (int tid, int property)
 	case APROP_TargetTID:	return (actor->target != NULL)? actor->target->tid : 0;
 	case APROP_TracerTID:	return (actor->tracer != NULL)? actor->tracer->tid : 0;
 	case APROP_WaterLevel:	return actor->waterlevel;
-	case APROP_ScaleX: 		return actor->scaleX;
-	case APROP_ScaleY: 		return actor->scaleY;
+	case APROP_ScaleX: 		return FLOAT2FIXED(actor->Scale.X);
+	case APROP_ScaleY: 		return FLOAT2FIXED(actor->Scale.Y);
 	case APROP_Mass: 		return actor->Mass;
 	case APROP_Accuracy:    return actor->accuracy;
 	case APROP_Stamina:     return actor->stamina;
diff --git a/src/p_buildmap.cpp b/src/p_buildmap.cpp
index a866f852f0..d3ee74bbab 100644
--- a/src/p_buildmap.cpp
+++ b/src/p_buildmap.cpp
@@ -883,8 +883,8 @@ void ACustomSprite::BeginPlay ()
 	mysnprintf (name, countof(name), "BTIL%04d", args[0] & 0xffff);
 	picnum = TexMan.GetTexture (name, FTexture::TEX_Build);
 
-	scaleX = args[2] * (FRACUNIT/64);
-	scaleY = args[3] * (FRACUNIT/64);
+	Scale.X = args[2] / 64.;
+	Scale.Y = args[3] / 64.;
 
 	int cstat = args[4];
 	if (cstat & 2)
diff --git a/src/p_effect.cpp b/src/p_effect.cpp
index 1619f7503b..ab749cccd2 100644
--- a/src/p_effect.cpp
+++ b/src/p_effect.cpp
@@ -294,8 +294,8 @@ void P_ThinkParticles ()
 			AActor *skybox = particle->subsector->sector->SkyBoxes[sector_t::ceiling];
 			if (particle->z > skybox->threshold)
 			{
-				particle->x += skybox->scaleX;
-				particle->y += skybox->scaleY;
+				particle->x += FLOAT2FIXED(skybox->Scale.X);
+				particle->y += FLOAT2FIXED(skybox->Scale.Y);
 				particle->subsector = NULL;
 			}
 		}
@@ -304,8 +304,8 @@ void P_ThinkParticles ()
 			AActor *skybox = particle->subsector->sector->SkyBoxes[sector_t::floor];
 			if (particle->z < skybox->threshold)
 			{
-				particle->x += skybox->scaleX;
-				particle->y += skybox->scaleY;
+				particle->x += FLOAT2FIXED(skybox->Scale.X);
+				particle->y += FLOAT2FIXED(skybox->Scale.Y);
 				particle->subsector = NULL;
 			}
 		}
diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp
index f9d71b49e5..4a2bcce933 100644
--- a/src/p_enemy.cpp
+++ b/src/p_enemy.cpp
@@ -162,16 +162,16 @@ void P_RecursiveSound (sector_t *sec, AActor *soundtarget, bool splash, int soun
 		if (checkabove)
 		{
 			sector_t *upper =
-				P_PointInSector(check->v1->x + check->dx / 2 + sec->SkyBoxes[sector_t::ceiling]->scaleX,
-					check->v1->y + check->dy / 2 + sec->SkyBoxes[sector_t::ceiling]->scaleY);
+				P_PointInSector(check->v1->x + check->dx / 2 + FLOAT2FIXED(sec->SkyBoxes[sector_t::ceiling]->Scale.X),
+					check->v1->y + check->dy / 2 + FLOAT2FIXED(sec->SkyBoxes[sector_t::ceiling]->Scale.Y));
 
 			P_RecursiveSound(upper, soundtarget, splash, soundblocks, emitter, maxdist);
 		}
 		if (checkbelow)
 		{
 			sector_t *lower =
-				P_PointInSector(check->v1->x + check->dx / 2 + sec->SkyBoxes[sector_t::floor]->scaleX,
-					check->v1->y + check->dy / 2 + sec->SkyBoxes[sector_t::floor]->scaleY);
+				P_PointInSector(check->v1->x + check->dx / 2 + FLOAT2FIXED(sec->SkyBoxes[sector_t::ceiling]->Scale.X),
+					check->v1->y + check->dy / 2 + FLOAT2FIXED(sec->SkyBoxes[sector_t::ceiling]->Scale.Y));
 
 			P_RecursiveSound(lower, soundtarget, splash, soundblocks, emitter, maxdist);
 		}
diff --git a/src/p_map.cpp b/src/p_map.cpp
index 3a0c99adb8..d16679efca 100644
--- a/src/p_map.cpp
+++ b/src/p_map.cpp
@@ -3667,7 +3667,7 @@ struct aim_t
 		newtrace.toppitch = newtoppitch;
 		newtrace.bottompitch = newbottompitch;
 		newtrace.aimdir = position == sector_t::ceiling? aim_t::aim_up : aim_t::aim_down;
-		newtrace.startpos = { startpos.x + portal->scaleX, startpos.y + portal->scaleY, startpos.z };
+		newtrace.startpos = { startpos.x + FLOAT2FIXED(portal->Scale.X), startpos.y + FLOAT2FIXED(portal->Scale.Y), startpos.z };
 		newtrace.startfrac = frac + FixedDiv(FRACUNIT, attackrange);	// this is to skip the transition line to the portal which would produce a bogus opening
 		newtrace.lastsector = P_PointInSector(newtrace.startpos.x + FixedMul(aimtrace.x, newtrace.startfrac) , newtrace.startpos.y + FixedMul(aimtrace.y, newtrace.startfrac));
 		newtrace.limitz = portal->threshold;
diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp
index bf1d08f93f..8db6431485 100644
--- a/src/p_mobj.cpp
+++ b/src/p_mobj.cpp
@@ -239,8 +239,7 @@ void AActor::Serialize(FArchive &arc)
 		<< __pos.z
 		<< Angles.Yaw
 		<< frame
-		<< scaleX
-		<< scaleY
+		<< Scale
 		<< RenderStyle
 		<< renderflags
 		<< picnum
@@ -5172,10 +5171,10 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position)
 		mobj->alpha = mthing->alpha;
 	if (mthing->RenderStyle != STYLE_Count)
 		mobj->RenderStyle = (ERenderStyle)mthing->RenderStyle;
-	if (mthing->scaleX)
-		mobj->scaleX = FixedMul(mthing->scaleX, mobj->scaleX);
-	if (mthing->scaleY)
-		mobj->scaleY = FixedMul(mthing->scaleY, mobj->scaleY);
+	if (mthing->Scale.X != 0)
+		mobj->Scale.X = mthing->Scale.X * mobj->Scale.X;
+	if (mthing->Scale.Y != 0)
+		mobj->Scale.X = mthing->Scale.Y * mobj->Scale.Y;
 	if (mthing->pitch)
 		mobj->Angles.Pitch = (double)mthing->pitch;
 	if (mthing->roll)
diff --git a/src/p_spec.cpp b/src/p_spec.cpp
index f0ef2561ec..cb338ac13b 100644
--- a/src/p_spec.cpp
+++ b/src/p_spec.cpp
@@ -1049,8 +1049,8 @@ void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha, int linked
 			reference->special1 = linked ? SKYBOX_LINKEDPORTAL : SKYBOX_PORTAL;
 			anchor->special1 = SKYBOX_ANCHOR;
 			// store the portal displacement in the unused scaleX/Y members of the portal reference actor.
-			anchor->scaleX = -(reference->scaleX = x2 - x1);
-			anchor->scaleY = -(reference->scaleY = y2 - y1);
+			anchor->Scale.X = -(reference->Scale.X = FIXED2DBL(x2 - x1));
+			anchor->Scale.Y = -(reference->Scale.Y = FIXED2DBL(y2 - y1));
 			anchor->threshold = reference->threshold = z;
 
 			reference->Mate = anchor;
diff --git a/src/p_trace.cpp b/src/p_trace.cpp
index a28eb46e8c..20a257173b 100644
--- a/src/p_trace.cpp
+++ b/src/p_trace.cpp
@@ -203,8 +203,8 @@ void FTraceInfo::EnterSectorPortal(int position, fixed_t frac, sector_t *enterse
 
 	memset(&results, 0, sizeof(results));
 
-	newtrace.StartX = StartX + portal->scaleX;
-	newtrace.StartY = StartY + portal->scaleY;
+	newtrace.StartX = StartX + FLOAT2FIXED(portal->Scale.X);
+	newtrace.StartY = StartY + FLOAT2FIXED(portal->Scale.Y);
 	newtrace.StartZ = StartZ;
 
 	frac += FixedDiv(FRACUNIT, MaxDist);
diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp
index bde7c256e3..e332ff77ba 100644
--- a/src/p_udmf.cpp
+++ b/src/p_udmf.cpp
@@ -718,15 +718,15 @@ public:
 				break;
 
 			case NAME_ScaleX:
-				th->scaleX = CheckFixed(key);
+				th->Scale.X = CheckFloat(key);
 				break;
 
 			case NAME_ScaleY:
-				th->scaleY = CheckFixed(key);
+				th->Scale.Y = CheckFloat(key);
 				break;
 
 			case NAME_Scale:
-				th->scaleX = th->scaleY = CheckFixed(key);
+				th->Scale.X = th->Scale.Y = CheckFloat(key);
 				break;
 
 			default:
diff --git a/src/p_user.cpp b/src/p_user.cpp
index f8b3fa828f..11fd54d9e5 100644
--- a/src/p_user.cpp
+++ b/src/p_user.cpp
@@ -1703,7 +1703,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CheckPlayerDone)
 //
 //===========================================================================
 
-void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t &scaley)
+void P_CheckPlayerSprite(AActor *actor, int &spritenum, DVector2 &scale)
 {
 	player_t *player = actor->player;
 	int crouchspriteno;
@@ -1711,10 +1711,9 @@ void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t
 	if (player->userinfo.GetSkin() != 0 && !(actor->flags4 & MF4_NOSKIN))
 	{
 		// Convert from default scale to skin scale.
-		fixed_t defscaleY = actor->GetDefault()->scaleY;
-		fixed_t defscaleX = actor->GetDefault()->scaleX;
-		scaley = Scale(scaley, skins[player->userinfo.GetSkin()].ScaleY, defscaleY);
-		scalex = Scale(scalex, skins[player->userinfo.GetSkin()].ScaleX, defscaleX);
+		DVector2 defscale = actor->GetDefault()->Scale;
+		scale.X *= skins[player->userinfo.GetSkin()].Scale.X / defscale.X;
+		scale.Y *= skins[player->userinfo.GetSkin()].Scale.Y / defscale.Y;
 	}
 
 	// Set the crouch sprite?
@@ -1741,7 +1740,7 @@ void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t
 		}
 		else if (player->playerstate != PST_DEAD && player->crouchfactor < 0.75)
 		{
-			scaley /= 2;
+			scale.Y *= 0.5;
 		}
 	}
 }
diff --git a/src/portal.cpp b/src/portal.cpp
index d7f3d217d8..7e81f46dd9 100644
--- a/src/portal.cpp
+++ b/src/portal.cpp
@@ -813,13 +813,13 @@ static void AddDisplacementForPortal(AStackPoint *portal)
 	FDisplacement & disp = Displacements(thisgroup, othergroup);
 	if (!disp.isSet)
 	{
-		disp.pos.x = portal->scaleX;
-		disp.pos.y = portal->scaleY;
+		disp.pos.x = FLOAT2FIXED(portal->Scale.X);
+		disp.pos.y = FLOAT2FIXED(portal->Scale.Y);
 		disp.isSet = true;
 	}
 	else
 	{
-		if (disp.pos.x != portal->scaleX || disp.pos.y != portal->scaleY)
+		if (disp.pos.x != FLOAT2FIXED(portal->Scale.X) || disp.pos.y != FLOAT2FIXED(portal->Scale.Y))
 		{
 			Printf("Portal between sectors %d and %d has displacement mismatch and will be disabled\n", portal->Sector->sectornum, portal->Mate->Sector->sectornum);
 			portal->special1 = portal->Mate->special1 = SKYBOX_PORTAL;
diff --git a/src/r_data/sprites.cpp b/src/r_data/sprites.cpp
index 903bdd404b..62bb4ba1c4 100644
--- a/src/r_data/sprites.cpp
+++ b/src/r_data/sprites.cpp
@@ -594,8 +594,8 @@ void R_InitSkins (void)
 			}
 			else if (0 == stricmp (key, "scale"))
 			{
-				skins[i].ScaleX = clamp<fixed_t> (FLOAT2FIXED(atof (sc.String)), 1, 256*FRACUNIT);
-				skins[i].ScaleY = skins[i].ScaleX;
+				skins[i].Scale.X = clamp(atof (sc.String), 1./65536, 256.);
+				skins[i].Scale.Y = skins[i].Scale.X;
 			}
 			else if (0 == stricmp (key, "game"))
 			{
@@ -935,8 +935,7 @@ void R_InitSprites ()
 		PClassPlayerPawn *type = PlayerClasses[0].Type;
 		skins[i].range0start = type->ColorRangeStart;
 		skins[i].range0end = type->ColorRangeEnd;
-		skins[i].ScaleX = GetDefaultByType (type)->scaleX;
-		skins[i].ScaleY = GetDefaultByType (type)->scaleY;
+		skins[i].Scale = GetDefaultByType (type)->Scale;
 	}
 
 	R_InitSpriteDefs ();
@@ -965,8 +964,7 @@ void R_InitSprites ()
 		}
 		skins[i].range0start = basetype->ColorRangeStart;
 		skins[i].range0end = basetype->ColorRangeEnd;
-		skins[i].ScaleX = GetDefaultByType (basetype)->scaleX;
-		skins[i].ScaleY = GetDefaultByType (basetype)->scaleY;
+		skins[i].Scale = GetDefaultByType (basetype)->Scale;
 		skins[i].sprite = GetDefaultByType (basetype)->SpawnState->sprite;
 		skins[i].namespc = ns_global;
 
diff --git a/src/r_data/sprites.h b/src/r_data/sprites.h
index 28feb433e2..d882ee9810 100644
--- a/src/r_data/sprites.h
+++ b/src/r_data/sprites.h
@@ -1,6 +1,8 @@
 #ifndef __RES_SPRITES_H
 #define __RES_SPRITES_H
 
+#include "vectors.h"
+
 #define MAX_SPRITE_FRAMES	29		// [RH] Macro-ized as in BOOM.
 
 //
@@ -51,8 +53,7 @@ public:
 	BYTE		range0start;
 	BYTE		range0end;
 	bool		othergame;	// [GRB]
-	fixed_t		ScaleX;
-	fixed_t		ScaleY;
+	DVector2	Scale;
 	int			sprite;
 	int			crouchsprite;
 	int			namespc;	// namespace for this skin
diff --git a/src/r_things.cpp b/src/r_things.cpp
index ce2652250b..736e15ad5c 100644
--- a/src/r_things.cpp
+++ b/src/r_things.cpp
@@ -776,17 +776,16 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor
 	voxel = NULL;
 
 	int spritenum = thing->sprite;
-	fixed_t spritescaleX = thing->scaleX;
-	fixed_t spritescaleY = thing->scaleY;
+	DVector2 spriteScale = thing->Scale;
 	int renderflags = thing->renderflags;
-	if (spritescaleY < 0)
+	if (spriteScale.Y < 0)
 	{
-		spritescaleY = -spritescaleY;
+		spriteScale.Y = -spriteScale.Y;
 		renderflags ^= RF_YFLIP;
 	}
 	if (thing->player != NULL)
 	{
-		P_CheckPlayerSprite(thing, spritenum, spritescaleX, spritescaleY);
+		P_CheckPlayerSprite(thing, spritenum, spriteScale);
 	}
 
 	if (thing->picnum.isValid())
@@ -864,9 +863,9 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor
 			}
 		}
 	}
-	if (spritescaleX < 0)
+	if (spriteScale.X < 0)
 	{
-		spritescaleX = -spritescaleX;
+		spriteScale.X = -spriteScale.X;
 		renderflags ^= RF_XFLIP;
 	}
 	if (voxel == NULL && (tex == NULL || tex->UseType == FTexture::TEX_Null))
@@ -874,6 +873,8 @@ void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor
 		return;
 	}
 
+	fixed_t spritescaleX = FLOAT2FIXED(spriteScale.X);
+	fixed_t spritescaleY = FLOAT2FIXED(spriteScale.Y);
 	if ((renderflags & RF_SPRITETYPEMASK) == RF_WALLSPRITE)
 	{
 		R_ProjectWallSprite(thing, fx, fy, fz, picnum, spritescaleX, spritescaleY, renderflags);
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index 78b7e8464a..91053703c2 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -715,8 +715,8 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 		AActor *point = viewsector->SkyBoxes[sector_t::ceiling];
 		if (viewz > point->threshold)
 		{
-			viewx += point->scaleX;
-			viewy += point->scaleY;
+			viewx += FLOAT2FIXED(point->Scale.X);
+			viewy += FLOAT2FIXED(point->Scale.Y);
 			viewsector = R_PointInSubsector(viewx, viewy)->sector;
 			moved = true;
 		}
@@ -729,8 +729,8 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
 			AActor *point = viewsector->SkyBoxes[sector_t::floor];
 			if (viewz < point->threshold)
 			{
-				viewx += point->scaleX;
-				viewy += point->scaleY;
+				viewx += FLOAT2FIXED(point->Scale.X);
+				viewy += FLOAT2FIXED(point->Scale.Y);
 				viewsector = R_PointInSubsector(viewx, viewy)->sector;
 				moved = true;
 			}
diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp
index 2b1ff7c8be..63d4026d3f 100644
--- a/src/s_advsound.cpp
+++ b/src/s_advsound.cpp
@@ -2281,7 +2281,7 @@ void AAmbientSound::Activate (AActor *activator)
 				Destroy ();
 				return;
 			}
-			amb->periodmin = Scale(S_GetMSLength(sndnum), TICRATE, 1000);
+			amb->periodmin = ::Scale(S_GetMSLength(sndnum), TICRATE, 1000);
 		}
 
 		NextCheck = level.maptime;
diff --git a/src/thingdef/olddecorations.cpp b/src/thingdef/olddecorations.cpp
index 0fa34344d6..7924a2ffe2 100644
--- a/src/thingdef/olddecorations.cpp
+++ b/src/thingdef/olddecorations.cpp
@@ -445,7 +445,7 @@ static void ParseInsideDecoration (Baggage &bag, AActor *defaults,
 		else if (sc.Compare ("Scale"))
 		{
 			sc.MustGetFloat ();
-			defaults->scaleX = defaults->scaleY = FLOAT2FIXED(sc.Float);
+			defaults->Scale.X = defaults->Scale.Y = sc.Float;
 		}
 		else if (sc.Compare ("RenderStyle"))
 		{
diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp
index baf5b3dc5d..ea9cfb4708 100644
--- a/src/thingdef/thingdef_codeptr.cpp
+++ b/src/thingdef/thingdef_codeptr.cpp
@@ -2310,8 +2310,7 @@ static bool InitSpawnedItem(AActor *self, AActor *mo, int flags)
 	}
 	if (flags & SIXF_TRANSFERSCALE)
 	{
-		mo->scaleX = self->scaleX;
-		mo->scaleY = self->scaleY;
+		mo->Scale = self->Scale;
 	}
 	if (flags & SIXF_TRANSFERAMBUSHFLAG)
 	{
@@ -2884,8 +2883,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo)
 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale)
 {
 	PARAM_ACTION_PROLOGUE;
-	PARAM_FIXED		(scalex);
-	PARAM_FIXED_OPT	(scaley)	{ scaley = scalex; }
+	PARAM_FLOAT		(scalex);
+	PARAM_FLOAT_OPT	(scaley)	{ scaley = scalex; }
 	PARAM_INT_OPT	(ptr)		{ ptr = AAPTR_DEFAULT; }
 	PARAM_BOOL_OPT	(usezero)	{ usezero = false; }
 
@@ -2897,8 +2896,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale)
 		{
 			scaley = scalex;
 		}
-		ref->scaleX = scalex;
-		ref->scaleY = scaley;
+		ref->Scale = { scalex, scaley };
 	}
 	return 0;
 }
diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp
index cca433fff4..73ba2ee878 100644
--- a/src/thingdef/thingdef_data.cpp
+++ b/src/thingdef/thingdef_data.cpp
@@ -620,18 +620,18 @@ void InitThingdef()
 	PSymbolTable &symt = RUNTIME_CLASS(AActor)->Symbols;
 	PType *array5 = NewArray(TypeSInt32, 5);
 	symt.AddSymbol(new PField(NAME_Alpha,		TypeFixed,	VARF_Native, myoffsetof(AActor,alpha)));
-	symt.AddSymbol(new PField(NAME_Angle,		TypeFloat64,VARF_Native, myoffsetof(AActor,Angles.Yaw)));
-	symt.AddSymbol(new PField(NAME_Args,		array5,		VARF_Native, myoffsetof(AActor,args)));
+	symt.AddSymbol(new PField(NAME_Angle,		TypeFloat64,	VARF_Native, myoffsetof(AActor,Angles.Yaw)));
+	symt.AddSymbol(new PField(NAME_Args,		array5,			VARF_Native, myoffsetof(AActor,args)));
 	symt.AddSymbol(new PField(NAME_CeilingZ,	TypeFixed,	VARF_Native, myoffsetof(AActor,ceilingz)));
 	symt.AddSymbol(new PField(NAME_FloorZ,		TypeFixed,	VARF_Native, myoffsetof(AActor,floorz)));
-	symt.AddSymbol(new PField(NAME_Health,		TypeSInt32,	VARF_Native, myoffsetof(AActor,health)));
-	symt.AddSymbol(new PField(NAME_Mass,		TypeSInt32,	VARF_Native, myoffsetof(AActor,Mass)));
-	symt.AddSymbol(new PField(NAME_Pitch,		TypeFloat64,VARF_Native, myoffsetof(AActor,Angles.Pitch)));
-	symt.AddSymbol(new PField(NAME_Roll,		TypeFloat64,VARF_Native, myoffsetof(AActor,Angles.Roll)));
-	symt.AddSymbol(new PField(NAME_Special,		TypeSInt32,	VARF_Native, myoffsetof(AActor,special)));
-	symt.AddSymbol(new PField(NAME_TID,			TypeSInt32,	VARF_Native, myoffsetof(AActor,tid)));
-	symt.AddSymbol(new PField(NAME_TIDtoHate,	TypeSInt32,	VARF_Native, myoffsetof(AActor,TIDtoHate)));
-	symt.AddSymbol(new PField(NAME_WaterLevel,	TypeSInt32,	VARF_Native, myoffsetof(AActor,waterlevel)));
+	symt.AddSymbol(new PField(NAME_Health,		TypeSInt32,		VARF_Native, myoffsetof(AActor,health)));
+	symt.AddSymbol(new PField(NAME_Mass,		TypeSInt32,		VARF_Native, myoffsetof(AActor,Mass)));
+	symt.AddSymbol(new PField(NAME_Pitch,		TypeFloat64,	VARF_Native, myoffsetof(AActor,Angles.Pitch)));
+	symt.AddSymbol(new PField(NAME_Roll,		TypeFloat64,	VARF_Native, myoffsetof(AActor,Angles.Roll)));
+	symt.AddSymbol(new PField(NAME_Special,		TypeSInt32,		VARF_Native, myoffsetof(AActor,special)));
+	symt.AddSymbol(new PField(NAME_TID,			TypeSInt32,		VARF_Native, myoffsetof(AActor,tid)));
+	symt.AddSymbol(new PField(NAME_TIDtoHate,	TypeSInt32,		VARF_Native, myoffsetof(AActor,TIDtoHate)));
+	symt.AddSymbol(new PField(NAME_WaterLevel,	TypeSInt32,		VARF_Native, myoffsetof(AActor,waterlevel)));
 	symt.AddSymbol(new PField(NAME_X,			TypeFixed,	VARF_Native, myoffsetof(AActor,__pos.x)));	// must remain read-only!
 	symt.AddSymbol(new PField(NAME_Y,			TypeFixed,	VARF_Native, myoffsetof(AActor,__pos.y)));	// must remain read-only!
 	symt.AddSymbol(new PField(NAME_Z,			TypeFixed,	VARF_Native, myoffsetof(AActor,__pos.z)));	// must remain read-only!
@@ -641,16 +641,16 @@ void InitThingdef()
 	symt.AddSymbol(new PField(NAME_MomX,		TypeFloat64,	VARF_Native, myoffsetof(AActor, Vel.X)));
 	symt.AddSymbol(new PField(NAME_MomY,		TypeFloat64,	VARF_Native, myoffsetof(AActor, Vel.Y)));
 	symt.AddSymbol(new PField(NAME_MomZ,		TypeFloat64,	VARF_Native, myoffsetof(AActor, Vel.Z)));
-	symt.AddSymbol(new PField(NAME_ScaleX,		TypeFixed,	VARF_Native, myoffsetof(AActor,scaleX)));
-	symt.AddSymbol(new PField(NAME_ScaleY,		TypeFixed,	VARF_Native, myoffsetof(AActor,scaleY)));
-	symt.AddSymbol(new PField(NAME_Score,		TypeSInt32,	VARF_Native, myoffsetof(AActor,Score)));
-	symt.AddSymbol(new PField(NAME_Accuracy,	TypeSInt32,	VARF_Native, myoffsetof(AActor,accuracy)));
-	symt.AddSymbol(new PField(NAME_Stamina,		TypeSInt32,	VARF_Native, myoffsetof(AActor,stamina)));
+	symt.AddSymbol(new PField(NAME_ScaleX,		TypeFloat64,	VARF_Native, myoffsetof(AActor, Scale.X)));
+	symt.AddSymbol(new PField(NAME_ScaleY,		TypeFloat64,	VARF_Native, myoffsetof(AActor, Scale.Y)));
+	symt.AddSymbol(new PField(NAME_Score,		TypeSInt32,		VARF_Native, myoffsetof(AActor,Score)));
+	symt.AddSymbol(new PField(NAME_Accuracy,	TypeSInt32,		VARF_Native, myoffsetof(AActor,accuracy)));
+	symt.AddSymbol(new PField(NAME_Stamina,		TypeSInt32,		VARF_Native, myoffsetof(AActor,stamina)));
 	symt.AddSymbol(new PField(NAME_Height,		TypeFixed,	VARF_Native, myoffsetof(AActor,height)));
 	symt.AddSymbol(new PField(NAME_Radius,		TypeFixed,	VARF_Native, myoffsetof(AActor,radius)));
-	symt.AddSymbol(new PField(NAME_ReactionTime,TypeSInt32,	VARF_Native, myoffsetof(AActor,reactiontime)));
+	symt.AddSymbol(new PField(NAME_ReactionTime,TypeSInt32,		VARF_Native, myoffsetof(AActor,reactiontime)));
 	symt.AddSymbol(new PField(NAME_MeleeRange,	TypeFixed,	VARF_Native, myoffsetof(AActor,meleerange)));
 	symt.AddSymbol(new PField(NAME_Speed,		TypeFloat64,	VARF_Native, myoffsetof(AActor,Speed)));
-	symt.AddSymbol(new PField(NAME_Threshold,	TypeSInt32, VARF_Native, myoffsetof(AActor,threshold)));
-	symt.AddSymbol(new PField(NAME_DefThreshold,TypeSInt32, VARF_Native, myoffsetof(AActor,DefThreshold)));
+	symt.AddSymbol(new PField(NAME_Threshold,	TypeSInt32,		VARF_Native, myoffsetof(AActor,threshold)));
+	symt.AddSymbol(new PField(NAME_DefThreshold,TypeSInt32,		VARF_Native, myoffsetof(AActor,DefThreshold)));
 }
diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp
index 51b8dee541..d5996cf5ec 100644
--- a/src/thingdef/thingdef_properties.cpp
+++ b/src/thingdef/thingdef_properties.cpp
@@ -660,8 +660,8 @@ DEFINE_PROPERTY(mass, I, Actor)
 //==========================================================================
 DEFINE_PROPERTY(xscale, F, Actor)
 {
-	PROP_FIXED_PARM(id, 0);
-	defaults->scaleX = id;
+	PROP_DOUBLE_PARM(id, 0);
+	defaults->Scale.X = id;
 }
 
 //==========================================================================
@@ -669,8 +669,8 @@ DEFINE_PROPERTY(xscale, F, Actor)
 //==========================================================================
 DEFINE_PROPERTY(yscale, F, Actor)
 {
-	PROP_FIXED_PARM(id, 0);
-	defaults->scaleY = id;
+	PROP_DOUBLE_PARM(id, 0);
+	defaults->Scale.Y = id;
 }
 
 //==========================================================================
@@ -678,8 +678,8 @@ DEFINE_PROPERTY(yscale, F, Actor)
 //==========================================================================
 DEFINE_PROPERTY(scale, F, Actor)
 {
-	PROP_FIXED_PARM(id, 0);
-	defaults->scaleX = defaults->scaleY = id;
+	PROP_DOUBLE_PARM(id, 0);
+	defaults->Scale.X = defaults->Scale.Y = id;
 }
 
 //==========================================================================