• Replicating ScreenShake
    6 replies, posted
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.