Hope this is the right section, it is discussion after all, in a technical way...
Been talking to ChanClan about posting technical reviews, gonna start here.
Optimizing code and reducing processing time, starting with mines.
====
First of all, we have the mine Timer:
There is also a RadiusActors iteration, this checks the entire actor list, besides, the iterator doesn't include a break so the Damage() function can be called multiple times lagging even more the server or making the mine deal over 500 damage on lower levels.Code:simulated event Timer()
{
local Pawn p;
Super.Timer();
if ( SCount > 0 || Role != ROLE_Authority )
return;
foreach RadiusActors(class'Pawn', p, 75)
if ( p.bIsPlayer && p.Health > 0 &&
p.PlayerReplicationInfo != None &&
p.PlayerReplicationInfo.Team != Team && !p.PlayerReplicationInfo.bIsSpectator || ScriptedPawn(p) != None )
if (p != None && !bDisabledByEMP)
Damage();
}
Also, bDisabledByEMP is a global conditional, check it outside of the iterator and to sum up, ForEach iterators ALWAYS return a valid actor, otherwise the code isn't even called, so we don't need to check for (p != none).
Suggested code:
VisibleCollidingActors is faster, it checks the collision hash for colliding actors instead of the entire actor list, is faster when used with small radius (< 300).Code:simulated event Timer()
{
local Pawn p;
Super.Timer();
if ( SCount > 0 || bDisabledByEMP || Role != ROLE_Authority) //EMP check prevents iterations
return;
ForEach VisibleCollidingActors (class'Pawn', p, 75,,true) //This true makes the iterator ignore actors with bHidden=true property
if ( p.bIsPlayer && p.Health > 0 &&
p.PlayerReplicationInfo != None &&
p.PlayerReplicationInfo.Team != Team && !p.PlayerReplicationInfo.bIsSpectator || ScriptedPawn(p) != None )
{
Damage();
return; //Prevent multiple detonation on same frame
}
}
- It checks if the actors have a valid collision (players have, spectators don't so it works)
- It checks if they are in sight so there is no need to call FastTrace on them
- Checks visibility if specified (bHidden).
Next function: Damage
Ok, we have here: unneeded variables, slow iterators inside fast iterators and sets of functions that can be replaced with faster stuff.Code:function Damage()
{
local int i;
local Pawn p;
local Pawn pAward;
local vector dir;
local int x;
local int y;
local int z;
local string sMessage;
local string sName;
Self.PlaySound(Sound'SharpExplosion',, 4.0);
pAward = GetAwardedPlayer();
for ( i = 0; i <= Grade; i++ )
{
dir.x = Rand(80)-40;
dir.y = Rand(80)-40;
dir.z = Rand(80)-40;
spawn(class'MineExplosion',,,Location + dir);
foreach RadiusActors(class'Pawn', p, 75+(i*25))
if (p != None)
if (ShouldAttack(p))
{
dir = Location - p.Location;
dir = normal(dir);
if (p.Health>0)
{
p.TakeDamage((Grade+1) * 15, instigator, 0.5 * (p.CollisionHeight + p.CollisionRadius)*dir, vect(0,0,0), 'mine');
sgPRI(pAward.PlayerReplicationInfo).AddRU((Grade+1) * 4);
}
}
}
sgPRI(pAward.PlayerReplicationInfo).AddRU(50);
Destroy();
}
- Calling a function in unrealscript is VERY EXPENSIVE, unrealscript functions are 40 times slower than their c++ counterparts, so we should focus on reducing the unrealscript function calls even if some functionality is wasted...
- Look how I generate a random dir: (dir = VRand() * 40), 3 functions total: equal, VRand() and *. If i do this without storing the variable, it will only be 2 functions total.
- How it's done in the main code: (dir.x = Rand(80)-40; 3 times) 12 functions total: equal, dot (accessing X in vector struct), Rand(), - (minus), 3 times a row.
- Second, no need to check (p != none) this always returns true here.
PD: Why call SELF.Function if you can simply call function?
Suggested code:
Here we call a collision hash iterator *grade* times, we also removed a lot of unused variables and reduced the amount of function calls.Code:function Damage()
{
local int i;
local Pawn p, pAward;
PlaySound(Sound'SharpExplosion',, 4.0);
pAward = GetAwardedPlayer();
for ( i = 0; i <= Grade; i++ )
{
Spawn (class'MineExplosion',,, Location + VRand() * 40);
ForEach VisibleCollidingActors (class'Pawn', p, 75+(i*25),,true)
{
if ( (p.Health > 0) && ShouldAttack(P) ) //Health check first, faster
{
p.TakeDamage((Grade+1) * 15, instigator, normal( Location - p.Location) * 0.5 * (p.CollisionHeight + p.CollisionRadius), vect(0,0,0), 'mine');
sgPRI(pAward.PlayerReplicationInfo).AddRU((Grade+1) * 4);
}
}
}
sgPRI(pAward.PlayerReplicationInfo).AddRU(50);
Destroy();
}
Next function: ShouldAttack
This one is simple, we remove the most expensive function here: FastTrace.Code:function bool ShouldAttack(Pawn enemy)
{
if ( ScriptedPawn(enemy) != None )
return true;
if ( enemy == None )
return false;
if ( !FastTrace(enemy.Location) )
return false;
if ( sgBuilding(enemy) != None )
{
if ( sgBuilding(enemy).Team == Team || sgBuilding(enemy).Energy < 0 )
return false;
}
else if ( enemy.PlayerReplicationInfo == None ||
enemy.PlayerReplicationInfo.Team == Team ||
!enemy.bProjTarget )
return false;
return true;
}
Why? VisibleCollidingActors already performs this check.
We also remove the (enemy == none) check, seeing the context this function is called, enemy ALWAYS exists so this check will always return FALSE.
Suggested code:
Supermine optimization in next post...Code:function bool ShouldAttack(Pawn enemy)
{
if ( ScriptedPawn(enemy) != None )
return true;
if ( sgBuilding(enemy) != None )
{
if ( sgBuilding(enemy).Team == Team || sgBuilding(enemy).Energy < 0 )
return false;
}
else if ( enemy.PlayerReplicationInfo == None ||
enemy.PlayerReplicationInfo.Team == Team ||
!enemy.bProjTarget )
return false;
return true;
}