Entity Driving

From Garry's Mod
Jump to: navigation, search

Entity driving allows you to control other entities. This isn't particularly only cars - but any move type you want. It could be a simple ball that rolls around, noclip, a helicopter, a dog. It doesn't have to even move. Anything you can control.

Contents

The Drive Class

A drive class is simply a table of functions. Drive classes exist on both the client and the server - and are fully predicted - which means that driving feels the same in multiplayer as in singleplayer. Nice and responsive.

A simple Drive Class

Here's a simple drive class

-- Derive from drive_base (see lua/drive/drive_base.lua )
DEFINE_BASECLASS( "drive_base" )

drive.Register( "drive_example", 
{
	--
	-- Calculates the view when driving the entity
	--
	CalcView =  function( self, view )

		--
		-- Use the utility method on drive_base.lua to give us a 3rd person view
		--
		self:CalcView_ThirdPerson( view, 100, 2, { self.Entity } )
		view.angles.roll = 0

	end,

	--
	-- Called before each move. You should use your entity and cmd to 
	-- fill mv with information you need for your move. 
	--
	StartMove =  function( self, mv, cmd )
		-- Set observer mode to chase, so the entity will be drawn.
		self.Player:SetObserverMode( OBS_MODE_CHASE )
		--
		-- Update move position and velocity from our entity
		--
		mv:SetOrigin( self.Entity:GetNetworkOrigin() )
		mv:SetVelocity( self.Entity:GetAbsVelocity() )

	end,

	--
	-- Runs the actual move. On the client when there's 
	-- prediction errors this can be run multiple times.
	-- You should try to only change mv.
	--
	Move = function( self, mv )

		--
		-- Set up a speed, go faster if shift is held down
		--
		local speed = 0.0005 * FrameTime()
		if ( mv:KeyDown( IN_SPEED ) ) then speed = 0.005 * FrameTime() end

		--
		-- Get information from the movedata
		--
		local ang = mv:GetMoveAngles()
		local pos = mv:GetOrigin()
		local vel = mv:GetVelocity()

		--
		-- Add velocities. This can seem complicated. On the first line
		-- we're basically saying get the forward vector, then multiply it
		-- by our forward speed (which will be > 0 if we're holding W, < 0 if we're
		-- holding S and 0 if we're holding neither) - and add that to velocity.
		-- We do that for right and up too, which gives us our free movement.
		--
		vel = vel + ang:Forward() * mv:GetForwardSpeed() * speed
		vel = vel + ang:Right() * mv:GetSideSpeed() * speed
		vel = vel + ang:Up() * mv:GetUpSpeed() * speed

		--
		-- We don't want our velocity to get out of hand so we apply
		-- a little bit of air resistance. If no keys are down we apply
		-- more resistance so we slow down more.
		--
 		if ( math.abs(mv:GetForwardSpeed()) + math.abs(mv:GetSideSpeed()) + math.abs(mv:GetUpSpeed()) < 0.1 ) then
			vel = vel * 0.90
		else
			vel = vel * 0.99
		end

		--
		-- Add the velocity to the position (this is the movement)
		--
		pos = pos + vel

		--
		-- We don't set the newly calculated values on the entity itself
		-- we instead store them in the movedata. These get applied in F inishMove.
		--
		mv:SetVelocity( vel )
		mv:SetOrigin( pos )

	end,

	--
	-- The move is finished. Use mv to set the new positions
	-- on your entities/players.
	--
	FinishMove =  function( self, mv )

		--
		-- Update our entity!
		--
		self.Entity:SetNetworkOrigin( mv:GetOrigin() )
		self.Entity:SetAbsVelocity( mv:GetVelocity() )
		self.Entity:SetAngles( mv:GetMoveAngles() )

		--
		-- If we have a physics object update that too. But only on the server.
		--
		if ( SERVER && IsValid( self.Entity:GetPhysicsObject() ) ) then

			self.Entity:GetPhysicsObject():EnableMotion( true )
			self.Entity:GetPhysicsObject():SetPos( mv:GetOrigin() )
			self.Entity:GetPhysicsObject():Wake()
			self.Entity:GetPhysicsObject():EnableMotion( false )

		end

	end,

}, "drive_base" )

This might look complicated but it's actually pretty simple. It gives you a simple velocity based noclip mode.

Some things to note. When you register your drive mode you should register them in order. What this means is that if you're deriving your mode from a class you should register that class first.

Making the player drive

To make the player start driving, on the server call

drive.PlayerStartDriving( player, ent, drivemode )

for example

drive.PlayerStartDriving( player, ent, "drive_mydriveclass" )

To stop driving you can call

drive.PlayerStopDriving( player )

Or if you're inside the actual drive class you can call

self:Stop()

Example

For a more in depth example you can look at lua/drive/drive_sandbox.lua. This is the drive mode used to drive around entities in Sandbox mode.

It's worth checking out lua/drive/drive_base.lua too - as this is the common base shared by all of the default drive modes and displays all the hookable functions.

Internals

The class is created once and cached. On server it's pretty much guaranteed that this object will stick around for the duration of the drive - so it's safe to store variables on it. But on the client if there's lag - or prediction errors - it's possible that the object will be re-created.

The controlling player and entity aren't passed to each function because they're saved on the object itself. They're accessible via:

self.Player
self.Entity
Personal tools
Navigation