Capricorn 76
First-person-shooter example

---------------------------
---------------------------
-- 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)