Because our gamemode overrides CalcView, I had to attempt to replicate screen shake so we could apply it to our custom camera. I noticed a lot of people are making their own custom views for games so I figured I'd release the work I'd done.
Its not perfect and it lacks prediction and smoothing like Source natively does. Oh well, if you can make it better please let me know I'll update this thread.
put this in a shared file or do whatever with it:
[lua]
if ( CLIENT ) then
function GAMEMODE:CalcView( pl, origin, angles, fov )
// create default view
local view = {
origin = origin,
angles = angles,
fov = fov
};
view.origin, view.angles = util.ApplyShake( view.origin, view.angles, 1 );
return view;
end
local ShakeList = {};
/*------------------------------------
AddShake()
------------------------------------*/
function util.AddShake( pos, amp, freq, dur, rad )
table.insert( ShakeList,
{
pos = pos,
amp = amp,
freq = freq,
dur = dur,
endtime = dur + CurTime(),
rad = rad,
nextshake = 0,
offset = vector_origin,
angle = 0
}
);
end
/*------------------------------------
AddShake()
------------------------------------*/
local function AddShake( msg )
local pos = msg:ReadVector();
local amp = msg:ReadFloat();
local freq = msg:ReadFloat();
local dur = msg:ReadFloat();
local rad = msg:ReadFloat();
util.AddShake( pos, amp, freq, dur, rad );
end
usermessage.Hook( "AddShake", AddShake );
/*------------------------------------
CalcShake()
------------------------------------*/
local function CalcShake()
// count shake list
local count = #ShakeList;
// no shakes, dont continue
if ( count == 0 ) then
return false;
end
// get current time
local ct = CurTime();
// starting values
local totalOffset = vector_origin;
local totalAngle = 0;
// cycle through each shake
for i = count, 1, -1 do
// get current shake
local shake = ShakeList[ i ];
// make sure it has a valid end time
if ( shake.endtime > 0 ) then
// time to retire?
if ( ct > shake.endtime || shake.dur <= 0 || shake.amp <= 0 || shake.freq <= 0 ) then
// remove from list
table.remove( ShakeList, i );
else
// debug
local str = "shake " .. i .. "\t dur:" .. shake.dur .. "\tamp:" .. shake.amp .. "\tfreq:" .. shake.freq .. "\tnext:".. shake.nextshake
print( str );
// time to shake?
if ( ct > shake.nextshake ) then
// higher frequency means we recalc the extents more often
shake.nextshake = ct + ( 1.0 / shake.freq );
// compute random shake extents
for j = 1, 3 do
shake.offset[ j ] = math.random( shake.amp * -100, shake.amp * 100 ) / 100;
end
// shake the angle
shake.angle = math.random( -shake.amp * 25, shake.amp * 25 ) / 100;
end
// ramp down amplitude
local frac = ( shake.endtime - ct ) / shake.dur;
// ramp up frequence over duration
local freq;
if ( frac > 0 ) then
freq = shake.freq / frac;
else
freq = 0;
end
// square fraction to approach zero more quickly
frac = frac * frac;
// sine wave that slowly settles to zero
local ang = ct * freq;
if ( ang > 1e8 ) then
ang = 1e8;
end
frac = frac * math.sin( ang );
// add to view origin
totalOffset = totalOffset + ( shake.offset * frac );
// add to roll
totalAngle = totalAngle + ( shake.angle * frac );
// drop amplitude a bit
shake.amp = shake.amp - ( shake.amp * ( FrameTime() / ( shake.dur * shake.freq ) ) );
end
end
end
return totalOffset, totalAngle;
end
/*------------------------------------
ApplyShake()
------------------------------------*/
function util.ApplyShake( origin, angles, factor )
// calculate the shake
local totalOffset, totalAngle = CalcShake();
// ignore
if ( totalOffset == false ) then
return origin, angles;
end
// add to origin
local o = origin + ( factor * totalOffset );
// add to angles
local a = angles.Roll + ( totalAngle * factor );
return o, a;
end
end
/*------------------------------------
ComputeShakeAmplitude()
------------------------------------*/
local function ComputeShakeAmplitude( center, shakept, amp, rad )
// without a radius just keep original amplitude
if ( rad <= 0 ) then
return amp;
end
local localAmp = -1;
// compute delta and measure distance
local delta = center - shakept;
local dist = delta:Length();
if ( dist < rad ) then
// make the amplitude fall off over distance
local perc = 1.0 - ( dist / rad );
localAmp = amp * perc;
end
return localAmp;
end
/*------------------------------------
ScreenShake()
------------------------------------*/
function util.ScreenShake( pos, amp, freq, dur, rad )
// clamp maximum amplitude
amp = math.min( 16, amp );
// fix clientside problem
if ( CLIENT ) then
// calculate amplitude
local localAmp = ComputeShakeAmplitude( pos, LocalPlayer():GetPos(), amp, rad );
if ( localAmp > 0 ) then
util.AddShake( pos, localAmp, freq, dur, rad );
end
return;
end
// cycle each player
for _, pl in pairs( player.GetAll() ) do
// calculate amplitude
local localAmp = ComputeShakeAmplitude( pos, pl:GetPos(), amp, rad );
if ( localAmp > 0 ) then
// send message
umsg.Start( "AddShake", pl );
umsg.Vector( pos );
umsg.Float( localAmp );
umsg.Float( freq );
umsg.Float( dur );
umsg.Float( rad );
umsg.End();
end
end
end
[/lua]
Delicious.
Thanks for sharing.
Epic. Me likes.
And what the hell is with the boxes? *sigh* Kids will be kids i guess..
This is roughly what I used in my gamemode. Not as advanced, but still does the job.
[lua]local shake = 0
function GM:CalcView( ply, origin, angles, fov )
local view = {}
view.angles = angles
view.fov = fov
view.origin = origin + Shake()
return view
end
function Shake()
return VectorRand() * shake
end
function GM:Think()
shake = shake * 0.9 --Gradually reduce shake amount
end
function AddShake( um )
shake = shake + um:ReadFloat()
end
usermessage.Hook( "AddShake", AddShake )[/lua]
Nice job. Bookmark'd.
Wow, my 7000th post already? Damn, I should probably go outside.
[QUOTE=grea$emonkey;20049423]Nice job. Bookmark'd.[/QUOTE]
That's your 7,000th post :O
Anyhow, great code, I'll probably use it in the future, thanks for posting!
We need an online database of useful snippets. Unless we are allowed post long snippets like that on the Wikis snippet section.
Sorry, you need to Log In to post a reply to this thread.