Replicating ScreenShake

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 .. "	    dur:" .. shake.dur .. "	amp:" .. shake.amp .. "	freq:" .. shake.freq .. "	next:".. 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.

That’s your 7,000th post :open_mouth:

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.