---------------------------
---------------------------
-- First-person-shooter example
-- Copyright (c) 2007-09 Capricorn 76 Pty. Ltd.
--
-- NOTE:
-- The ./Demos/runDemo.e76script is a utility script used to run all the examples.
-- It loads the world, calls the OnWorldLoad(), OnWorldUnload() functions, and runs the graphics engine loop, waiting for the user to press escape.
---------------------------
---------------------------
-- handle for the mouse button subscriber
local subMouseButton;
local subTick;
---------------------------
-- runDemo.e76script calls this function when the world is loaded
---------------------------
function OnWorldLoad(worldName)
--print('OnWorldLoad');
---------------------------
-- The SDK world editor creates a worldIds.e76script when the world is saved. It contains all the entity ids.
-- We load the IDs so that we can refer to the world entities using friendly names.
---------------------------
IApp:loadScript(IWorld:getNameLong() .. '/worldIds');
---------------------------
-- Load constants file we need for this demo
---------------------------
IApp:loadScript(IApp:getExeFilePath() .. './Constants/constantsKeyCodes');
-- Load this example's GUI
OnWorldLoadGui();
-- Cache the world'c static geometry to disk to speed up future load times
CheckGeometryCache();
subMouseButton = IEvents:subscribeMouseButton('OnMouseButton');
subTick = IEvents:subscribeTickPeriodic('OnTick', 25);
IWorld:setActiveCameraId(idCamera_cam);
---------------------------
-- Add the camera to the physics engine, by assigning it a mass.
-- Make it collidable, otherwise it will fall thru the floor.
-- ScriptENGINE will only manage the camera's look at direction via the mouse.
-- We will manage the camera's position by applying physical forces to it.
-- Alternatively, we could ask ScriptENGINE to stop managing the camera motion altogether
-- IWorld:setActiveCameraAutoProcessed(false)
---------------------------
-- Add the camera to the physical world by assigning it a mass.
IPhysics:setEntityMass(idCamera_cam, 100);
IPhysics:setEntityCollisionType(idCamera_cam, 1); -- Make the physical bounding entity an ellipsoid instead of the default bounding box
IPhysics:setEntityCollidable(idCamera_cam, true); -- by default, the cameras aren't collidable.
---------------------------
-- We need to readd the camera, so that it will now be added to the physics engine
-- Also, we want to enable the auto-management of the camera using mouse/key input.
-- In this example, the active camera type if 'pivot' meaning that it only responds to mouse by rotating the view.
-- The lateral camera motion is supplied by this script by applying forces.
---------------------------
IWorld:addCamera(idCamera_cam);
IWorld:setActiveCameraAutoProcessed(true);
---------------------------
-- Make sure the camera physics box always stays up.
-- Otherwise it can topple over as we move and collide.
---------------------------
IPhysics:createJointUpVector('camera', idCamera_cam, '0 1 0');
---------------------------
-- Prepare the sound engine
---------------------------
ISound:load('sfx', 'sfx.wav', false);
end
---------------------------
-- Load this example's specific GUI
---------------------------
function OnWorldLoadGui()
IGraphics:loadGui('help');
end
---------------------------
-- runDemo.e76script calls this function when the world is unloaded
---------------------------
function OnWorldUnload(worldName)
--print('OnWorldUnload');
IEvents:unsubscribe(subMouseButton);
IEvents:unsubscribe(subTick);
ISound:unload('sfx');
end
-- Table of new ball identifiers.
-- Once we have reached a pre-set limit, we start to remove the older ones
local ballIdTable = {};
local maxBalls = 20;
---------------------------
-- Mouse button handler
---------------------------
function OnMouseButton(eventType, mouseX, mouseY, mouseWheel, userData)
---------------------------
-- Left button clicked?
-- Fire a ball into the world
---------------------------
if (IMouse:leftButtonClicked(eventType)) then
--print('Mouse clicked');
-- If we have already reached the max number of balls, we remove the oldest
if (table.getn(ballIdTable) >= maxBalls) then
oldestId = ballIdTable[1];
if (oldestId) then
IWorld:deleteUnit(oldestId);
table.remove(ballIdTable, 1);
--print('Removed oldest ball:', oldestId);
end
end
-- Copy the original ball to the scene and fire it in.
newId = IWorld:copyEntity(idUnit_ball);
-- Place the new ball where the camera is
cameraPos = IWorld:getEntityPosition(IWorld:getActiveCameraId());
-- The camera has been offset (to be closer to eye level)
cameraPos = IMisc:vectorAdd(cameraPos, IWorld:getCameraOffset(IWorld:getActiveCameraId()));
-- Calculate a direction based upon where the mouse is on the screen
mouseDir = IPhysics:rayCastMousePosition();
if (mouseDir) then
mouseDir = mouseDir['intersection'];
else
mouseDir = IWorld:getCameraLookAt(IWorld:getActiveCameraId());
end
-- Fire the ball in the direction we're looking
forceVector = IMisc:vectorSubtract(mouseDir, cameraPos);
forceVector = IMisc:vectorNormaliseFast(forceVector);
-- Place the ball just in front of the camera, otherwise the camera wll collide with it.
newBallPos = IMisc:vectorAdd(cameraPos, IMisc:vectorMultiply(forceVector, 2));
IWorld:setEntityPosition(newId, newBallPos);
-- Apply the impulse force to the ball
forceVector = IMisc:vectorMultiply(forceVector, 1000);
IPhysics:addEntityImpulseForce(newId, forceVector);
-- All the new ball id to the table
table.insert(ballIdTable, newId);
-- play a sound fx
if (ISound:soundExists('sfx')) then
ISound:play('sfx');
end
return true; -- we handled the event
--[[
---------------------------
-- Right-button clicked?
-- Jet-pack style (keep clicking to stay in the air)
---------------------------
elseif (IMouse:rightButtonClicked(eventType)) then
local jumpForce = IMisc:vectorMultiply('0 500 0', IPhysics:getEntityMass(idCamera_cam));
IPhysics:addEntityImpulseForce(idCamera_cam, jumpForce);
return true; -- we handled the event
]]
---------------------------
-- Right-button clicked?
-- Jump style (force only applied when on the ground)
-- This style allows a double-click for a larger jump because the second click may also be within the raycast limit, if it's fast enough.
---------------------------
elseif (IMouse:rightButtonClicked(eventType)) then
-- Check if we have something under us by performing a raycast
local rayCastStart = IWorld:getEntityPosition(idCamera_cam);
local rayCastEnd = IMisc:vectorAdd(rayCastStart, '0 -4 0');
if (IPhysics:rayCast(rayCastStart, rayCastEnd)) then
local jumpForce = IMisc:vectorMultiply('0 500 0', IPhysics:getEntityMass(idCamera_cam));
IPhysics:addEntityImpulseForce(idCamera_cam, jumpForce);
end
return true; -- we handled the event
end
end
---------------------------
-- Physical static entities can have their geometry cached to disk to speed up future loading.
---------------------------
function CheckGeometryCache()
---------------------------
-- Cache the main map's physical representation to disk for faster loading next time.
-- The graphics engine internally manages caching things like textures
---------------------------
local geometryCacheFile = IPhysics:geometryCacheExists(idUnit_chapel);
if (not(geometryCacheFile)) then
print('Caching physics geometry...');
IPhysics:saveGeometryCache(idUnit_chapel, IWorld:getNameLong());
else
-- The engine will automatically use the cached file, if found.
print('Using cache:', geometryCacheFile);
end
end
---------------------------
-- Graphics tick handler
---------------------------
function OnTick(currentTime, lastTime, isWindowActive, userData)
---------------------------
-- Check the camera key codes, and apply forces to the active camera.
-- These key codes are usually used to auto-manage the active camera.
-- But we can also listen out for the codes instead of creating new ones.
-- Here's a bit of physics using Newton's laws of motion.
-- We use the formulas 1) s = u*t + 1/2*a*t*t (s = distance, u = initial velocity, a = constant acceleration, t = total time)
-- 2) v = u + a*t (v = end velocity)
-- 3) F = m*a (F = force, m = mass, a = acceleration)
--
-- to find the required force:
-- F = m * (v - u) / t
--
---------------------------
local camId = IWorld:getActiveCameraId();
local lookAt = IMisc:vectorNormaliseFast(IWorld:getCameraLookAt(camId));
local lookAtOrtho = IMisc:vectorNormaliseFast(IMisc:vectorCrossProduct(lookAt, IWorld:getCameraUpVector(camId)));
local mass = IPhysics:getEntityMass(camId);
local initVel = IPhysics:getEntityVelocity(camId);
local timeDiffMs = (currentTime - lastTime);
local timeDiff = timeDiffMs / 1000; -- This time is how long we want it to take to change velocity vector
--local timeDiff = 0.5; -- This time is how long we want it to take to change velocity vector
local endVel = '0 0 0';
local velDiff;
local forceVector;
local camVel = 10; -- Default velocity
-------------------------
-- Zero out the y-component of the initial/lookAt velocity, because we only want to cancel the x/z
-- The y is usually due to gravity or jumping
-------------------------
initVel = IMisc:vectorCreate(IMisc:vectorGetX(initVel), 0, IMisc:vectorGetZ(initVel));
lookAt = IMisc:vectorCreate(IMisc:vectorGetX(lookAt), 0, IMisc:vectorGetZ(lookAt));
lookAtOrtho = IMisc:vectorCreate(IMisc:vectorGetX(lookAtOrtho), 0, IMisc:vectorGetZ(lookAtOrtho));
if (IKey:isActionOn(KEY_CAMERA_MOVE_FORWARD)) then
endVel = IMisc:vectorMultiply(lookAt, camVel);
elseif (IKey:isActionOn(KEY_CAMERA_MOVE_BACKWARD)) then
endVel = IMisc:vectorMultiply(lookAt, -camVel);
end
if (IKey:isActionOn(KEY_CAMERA_STRAFE_LEFT)) then
endVel = IMisc:vectorAdd(endVel, IMisc:vectorMultiply(lookAtOrtho, camVel));
elseif (IKey:isActionOn(KEY_CAMERA_STRAFE_RIGHT)) then
endVel = IMisc:vectorAdd(endVel, IMisc:vectorMultiply(lookAtOrtho, -camVel));
end
velDiff = IMisc:vectorSubtract(endVel, initVel);
forceVector = IMisc:vectorMultiply(velDiff, mass/timeDiff);
IPhysics:addEntityImpulseForce(camId, forceVector);
end
Copyright © 2006-23 Sep 2009 Capricorn 76 Pty. Ltd. (created on Wed Sep 23 16:49:12 2009)