Browse Source

Added CharacterDemo Lua sample.
Added variable initialization to Lua samples where they were missing.
Show sender IP:port on server in Lua Chat sample.

Lasse Öörni 12 years ago
parent
commit
056e53f46d

+ 1 - 1
Bin/Data/LuaScripts/02_HelloGUI.lua

@@ -14,7 +14,7 @@ local cache = GetCache()
 local engine = GetEngine()
 local engine = GetEngine()
 local input = GetInput()
 local input = GetInput()
 local ui = GetUI()
 local ui = GetUI()
-    
+
 function Start()
 function Start()
     -- Execute the common startup for samples
     -- Execute the common startup for samples
     SampleStart()
     SampleStart()

+ 2 - 2
Bin/Data/LuaScripts/08_Decals.lua

@@ -7,8 +7,8 @@
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
-local scene_
-local cameraNode
+local scene_ = nil
+local cameraNode = nil
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 0.0
 local pitch = 0.0
 local drawDebug = false
 local drawDebug = false

+ 3 - 3
Bin/Data/LuaScripts/09_MultipleViewports.lua

@@ -5,9 +5,9 @@
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
-local scene_
-local cameraNode
-local rearCameraNode
+local scene_ = nil
+local cameraNode = nil
+local rearCameraNode = nil
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 0.0
 local pitch = 0.0
 local drawDebug = false
 local drawDebug = false

+ 1 - 1
Bin/Data/LuaScripts/11_Physics.lua

@@ -8,7 +8,7 @@
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
 local scene_ = nil
 local scene_ = nil
-local cameraNode
+local cameraNode = nil
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 0.0
 local pitch = 0.0
 local drawDebug = false
 local drawDebug = false

+ 1 - 1
Bin/Data/LuaScripts/12_PhysicsStressTest.lua

@@ -7,7 +7,7 @@
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
 local scene_ = nil
 local scene_ = nil
-local cameraNode
+local cameraNode = nil
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 0.0
 local pitch = 0.0
 local drawDebug = false
 local drawDebug = false

+ 2 - 2
Bin/Data/LuaScripts/13_Ragdolls.lua

@@ -6,8 +6,8 @@
 
 
 require "LuaScripts/Utilities/Sample"
 require "LuaScripts/Utilities/Sample"
 
 
-local scene_
-local cameraNode
+local scene_ = nil
+local cameraNode = nil
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 0.0
 local pitch = 0.0
 local drawDebug = false
 local drawDebug = false

+ 1 - 1
Bin/Data/LuaScripts/16_Chat.lua

@@ -202,7 +202,7 @@ function HandleNetworkMessage(eventType, eventData)
         -- If we are a client, just display the message
         -- If we are a client, just display the message
         if network.serverRunning then
         if network.serverRunning then
             local sender = eventData:GetPtr("Connection", "Connection")
             local sender = eventData:GetPtr("Connection", "Connection")
-            local text = sender:ToString() .. " " .. text
+            text = sender:ToString() .. " " .. text
             local sendMsg = VectorBuffer()
             local sendMsg = VectorBuffer()
             sendMsg:WriteString(text)
             sendMsg:WriteString(text)
             -- Broadcast as in-order and reliable
             -- Broadcast as in-order and reliable

+ 8 - 8
Bin/Data/LuaScripts/17_SceneReplication.lua

@@ -16,14 +16,14 @@ local CTRL_BACK = 2
 local CTRL_LEFT = 4
 local CTRL_LEFT = 4
 local CTRL_RIGHT = 8
 local CTRL_RIGHT = 8
 
 
-local scene_
-local cameraNode
-local instructionsText
-local buttonContainer
-local textEdit
-local connectButton
-local disconnectButton
-local startServerButton
+local scene_ = nil
+local cameraNode = nil
+local instructionsText = nil
+local buttonContainer = nil
+local textEdit = nil
+local connectButton = nil
+local disconnectButton = nil
+local startServerButton = nil
 local clients = {}
 local clients = {}
 local yaw = 0.0
 local yaw = 0.0
 local pitch = 1.0
 local pitch = 1.0

+ 416 - 0
Bin/Data/LuaScripts/18_CharacterDemo.lua

@@ -0,0 +1,416 @@
+-- Moving character example.
+-- This sample demonstrates:
+--     - Controlling a humanoid character through physics
+--     - Driving animations using the AnimationController component
+--     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera clipping into scenery
+--     - Saving and loading the variables of a script object
+
+require "LuaScripts/Utilities/Sample"
+
+local CTRL_FORWARD = 1
+local CTRL_BACK = 2
+local CTRL_LEFT = 4
+local CTRL_RIGHT = 8
+local CTRL_JUMP = 16
+
+local MOVE_FORCE = 0.8
+local INAIR_MOVE_FORCE = 0.02
+local BRAKE_FORCE = 0.2
+local JUMP_FORCE = 7.0
+local YAW_SENSITIVITY = 0.1
+local INAIR_THRESHOLD_TIME = 0.1
+
+local CAMERA_MIN_DIST = 1.0
+local CAMERA_MAX_DIST = 5.0
+
+local scene_ = nil
+local cameraNode = nil
+local characterNode = nil
+local firstPerson = false
+
+local context = GetContext()
+
+local cache = GetCache()
+local fileSystem = GetFileSystem()
+local input = GetInput()
+local graphics = GetGraphics()
+local renderer = GetRenderer()
+local ui = GetUI()
+
+function Start()
+    -- Execute the common startup for samples
+    SampleStart()
+
+    -- Create static scene content
+    CreateScene()
+
+    -- Create the controllable character
+    CreateCharacter()
+
+    -- Create the UI content
+    CreateInstructions()
+
+    -- Subscribe to necessary events
+    SubscribeToEvents()
+end
+
+function CreateScene()
+
+    scene_ = Scene(context)
+
+    -- Create scene subsystem components
+    scene_:CreateComponent("Octree")
+    scene_:CreateComponent("PhysicsWorld")
+
+    -- Create camera and define viewport. Camera does not necessarily have to belong to the scene
+    cameraNode = Node(context)
+    local camera = cameraNode:CreateComponent("Camera")
+    camera.farClip = 300.0
+    renderer:SetViewport(0, Viewport:new(context, scene_, camera))
+
+    -- Create a Zone component for ambient lighting & fog control
+    local zoneNode = scene_:CreateChild("Zone")
+    local zone = zoneNode:CreateComponent("Zone")
+    zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+    zone.ambientColor = Color(0.15, 0.15, 0.15)
+    zone.fogColor = Color(0.5, 0.5, 0.7)
+    zone.fogStart = 100.0
+    zone.fogEnd = 300.0
+
+    -- Create a directional light to the world. Enable cascaded shadows on it
+    local lightNode = scene_:CreateChild("DirectionalLight")
+    lightNode.direction = Vector3(0.6, -1.0, 0.8)
+    local light = lightNode:CreateComponent("Light")
+    light.lightType = LIGHT_DIRECTIONAL
+    light.castShadows = true
+    light.shadowBias = BiasParameters(0.0001, 0.5)
+    -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+    light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+    -- Create the floor object
+    local floorNode = scene_:CreateChild("Floor")
+    floorNode.position = Vector3(0.0, -0.5, 0.0)
+    floorNode.scale = Vector3(200.0, 1.0, 200.0)
+    local object = floorNode:CreateComponent("StaticModel")
+    object.model = cache:GetResource("Model", "Models/Box.mdl")
+    object.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+    local body = floorNode:CreateComponent("RigidBody")
+    -- Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
+    -- inside geometry
+    body.collisionLayer = 2
+    local shape = floorNode:CreateComponent("CollisionShape")
+    shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+    -- Create mushrooms of varying sizes
+    local NUM_MUSHROOMS = 60
+    for i = 1, NUM_MUSHROOMS do
+        local objectNode = scene_:CreateChild("Mushroom")
+        objectNode.position = Vector3(Random(180.0) - 90.0, 0.0, Random(180.0) - 90.0)
+        objectNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+        objectNode:SetScale(2.0 + Random(5.0))
+        local object = objectNode:CreateComponent("StaticModel")
+        object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+        object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+        object.castShadows = true
+
+        local body = objectNode:CreateComponent("RigidBody")
+        body.collisionLayer = 2
+        local shape = objectNode:CreateComponent("CollisionShape")
+        shape:SetTriangleMesh(object.model, 0)
+    end
+
+    -- Create movable boxes. Let them fall from the sky at first
+    local NUM_BOXES = 100
+    for i = 1, NUM_BOXES do
+        local scale = Random(2.0) + 0.5
+
+        local objectNode = scene_:CreateChild("Box")
+        objectNode.position = Vector3(Random(180.0) - 90.0, Random(10.0) + 10.0, Random(180.0) - 90.0)
+        objectNode.rotation = Quaternion(Random(360.0), Random(360.0), Random(360.0))
+        objectNode:SetScale(scale)
+        local object = objectNode:CreateComponent("StaticModel")
+        object.model = cache:GetResource("Model", "Models/Box.mdl")
+        object.material = cache:GetResource("Material", "Materials/Stone.xml")
+        object.castShadows = true
+
+        local body = objectNode:CreateComponent("RigidBody")
+        body.collisionLayer = 2
+        -- Bigger boxes will be heavier and harder to move
+        body.mass = scale * 2.0
+        local shape = objectNode:CreateComponent("CollisionShape")
+        shape:SetBox(Vector3(1.0, 1.0, 1.0))
+    end
+end
+
+function CreateCharacter()
+    characterNode = scene_:CreateChild("Jack")
+    characterNode.position = Vector3(0.0, 1.0, 0.0)
+
+    -- Create the rendering component + animation controller
+    local object = characterNode:CreateComponent("AnimatedModel")
+    object.model = cache:GetResource("Model", "Models/Jack.mdl")
+    object.material = cache:GetResource("Material", "Materials/Jack.xml")
+    object.castShadows = true
+    characterNode:CreateComponent("AnimationController")
+
+    -- Create rigidbody, and set non-zero mass so that the body becomes dynamic
+    local body = characterNode:CreateComponent("RigidBody")
+    body.collisionLayer = 1
+    body.mass = 1.0
+
+    -- Set zero angular factor so that physics doesn't turn the character on its own.
+    -- Instead we will control the character yaw manually
+    body.angularFactor = Vector3(0.0, 0.0, 0.0)
+
+    -- Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
+    body.collisionEventMode = COLLISION_ALWAYS
+
+    -- Set a capsule shape for collision
+    local shape = characterNode:CreateComponent("CollisionShape")
+    shape:SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0))
+
+    -- Create the character logic object, which takes care of steering the rigidbody
+    characterNode:CreateScriptObject("Character")
+end
+
+function CreateInstructions()
+    -- Construct new Text object, set string to display and font to use
+    local instructionText = ui.root:CreateChild("Text")
+    instructionText:SetText(
+        "Use WASD keys and mouse to move\n"..
+        "Space to jump, F to toggle 1st/3rd person\n"..
+        "F5 to save scene, F7 to load")
+    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+    -- The text has multiple rows. Center them in relation to each other
+    instructionText.textAlignment = HA_CENTER
+
+    -- Position the text relative to the screen center
+    instructionText.horizontalAlignment = HA_CENTER
+    instructionText.verticalAlignment = VA_CENTER
+    instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SubscribeToEvents()
+    -- Subscribe to Update event for setting the character controls before physics simulation
+    SubscribeToEvent("Update", "HandleUpdate")
+
+    -- Subscribe to PostUpdate event for updating the camera position after physics simulation
+    SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+    if characterNode == nil then
+        return
+    end
+
+    local character = characterNode:GetScriptObject()
+    if character == nil then
+        return
+    end
+
+    -- Get movement controls and assign them to the character logic component. If UI has a focused element, clear controls
+    if ui.focusElement == nil then
+        character.controls:Set(CTRL_FORWARD, input:GetKeyDown(KEY_W))
+        character.controls:Set(CTRL_BACK, input:GetKeyDown(KEY_S))
+        character.controls:Set(CTRL_LEFT, input:GetKeyDown(KEY_A))
+        character.controls:Set(CTRL_RIGHT, input:GetKeyDown(KEY_D))
+        character.controls:Set(CTRL_JUMP, input:GetKeyDown(KEY_SPACE))
+
+        -- Add character yaw & pitch from the mouse motion
+        character.controls.yaw = character.controls.yaw + input.mouseMoveX * YAW_SENSITIVITY
+        character.controls.pitch = character.controls.pitch + input.mouseMoveY * YAW_SENSITIVITY
+        -- Limit pitch
+        character.controls.pitch = Clamp(character.controls.pitch, -80.0, 80.0)
+
+        -- Switch between 1st and 3rd person
+        if input:GetKeyPress(KEY_F) then
+            firstPerson = not firstPerson
+        end
+
+        -- Check for loading / saving the scene
+        if input:GetKeyPress(KEY_F5) then
+            scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/PhysicsStressTest.xml")
+        end
+        if input:GetKeyPress(KEY_F7) then
+            scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/PhysicsStressTest.xml")
+            -- After loading we have to reacquire the character scene node, as it has been recreated
+            -- Simply find by name as there's only one of them
+            characterNode = scene_:GetChild("Jack", true)
+            if characterNode == nil then
+                return
+            end
+        end
+    else
+        character.controls:Set(CTRL_FORWARD + CTRL_BACK + CTRL_LEFT + CTRL_RIGHT + CTRL_JUMP, false)
+    end
+
+    -- Set rotation already here so that it's updated every rendering frame instead of every physics frame
+    characterNode.rotation = Quaternion(character.controls.yaw, Vector3(0.0, 1.0, 0.0))
+end
+
+function HandlePostUpdate(eventType, eventData)
+
+    if characterNode == nil then
+        return
+    end
+
+    local character = characterNode:GetScriptObject()
+    if character == nil then
+        return
+    end
+
+    -- Get camera lookat dir from character yaw + pitch
+    local rot = characterNode.rotation
+    local dir = rot * Quaternion(character.controls.pitch, Vector3(1.0, 0.0, 0.0))
+
+    if firstPerson then
+        -- First person camera: position to the head bone + offset slightly forward & up
+        local headNode = characterNode:GetChild("Bip01_Head", true)
+        if headNode ~= nil then
+            cameraNode.position = headNode.worldPosition + rot * Vector3(0.0, 0.15, 0.2)
+            cameraNode.rotation = dir
+        end
+    else
+        -- Third person camera: position behind the character
+        local aimPoint = characterNode.position + rot * Vector3(0.0, 1.7, 0.0)
+
+        -- Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
+        local rayDir = dir * Vector3(0.0, 0.0, -1.0)
+        local rayDistance = CAMERA_MAX_DIST
+        local result = scene_:GetComponent("PhysicsWorld"):RaycastSingle(Ray(aimPoint, rayDir), rayDistance, 2)
+        if result.body ~= nil then
+            rayDistance = Min(rayDistance, result.distance)
+        end
+        rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
+
+        cameraNode.position = aimPoint + rayDir * rayDistance
+        cameraNode.rotation = dir
+    end
+end
+
+-- Character script object class
+Character = ScriptObject()
+
+function Character:Start()
+    -- Character controls.
+    self.controls = Controls()
+    -- Grounded flag for movement.
+    self.onGround = false
+    -- Jump flag.
+    self.okToJump = true
+    -- In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
+    self.inAirTimer = 0.0
+
+    self:SubscribeToEvent(self.node, "NodeCollision", "Character:HandleNodeCollision")
+end
+
+function Character:Load(deserializer)
+    self.onGround = deserializer:ReadBool()
+    self.okToJump = deserializer:ReadBool()
+    self.inAirTimer = deserializer:ReadFloat()
+    self.controls.yaw = deserializer:ReadFloat()
+    self.controls.pitch = deserializer:ReadFloat()
+end
+
+function Character:Save(serializer)
+    serializer:WriteBool(self.onGround)
+    serializer:WriteBool(self.okToJump)
+    serializer:WriteFloat(self.inAirTimer)
+    serializer:WriteFloat(self.controls.yaw)
+    serializer:WriteFloat(self.controls.pitch)
+end
+
+function Character:HandleNodeCollision(eventType, eventData)
+    local contacts = eventData:GetBuffer("Contacts")
+
+    while not contacts.eof do
+        local contactPosition = contacts:ReadVector3()
+        local contactNormal = contacts:ReadVector3()
+        local contactDistance = contacts:ReadFloat()
+        local contactImpulse = contacts:ReadFloat()
+
+        -- If contact is below node center and mostly vertical, assume it's a ground contact
+        if contactPosition.y < self.node.position.y + 1.0 then
+            local level = Abs(contactNormal.y)
+            if level > 0.75 then
+                self.onGround = true
+            end
+        end
+    end
+end
+
+function Character:FixedUpdate(timeStep)
+    -- Could cache the components for faster access instead of finding them each frame
+    local body = self.node:GetComponent("RigidBody")
+    local animCtrl = self.node:GetComponent("AnimationController")
+
+    -- Update the in air timer. Reset if grounded
+    if not self.onGround then
+        self.inAirTimer = self.inAirTimer + timeStep
+    else
+        self.inAirTimer = 0.0
+    end
+    -- When character has been in air less than 1/10 second, it's still interpreted as being on ground
+    local softGrounded = self.inAirTimer < INAIR_THRESHOLD_TIME
+
+    -- Update movement & animation
+    local rot = self.node.rotation
+    local moveDir = Vector3(0.0, 0.0, 0.0)
+    local velocity = body.linearVelocity
+    -- Velocity on the XZ plane
+    local planeVelocity = Vector3(velocity.x, 0.0, velocity.z)
+
+    if self.controls:IsDown(CTRL_FORWARD) then
+        moveDir = moveDir + Vector3(0.0, 0.0, 1.0)
+    end
+    if self.controls:IsDown(CTRL_BACK) then
+        moveDir = moveDir + Vector3(0.0, 0.0, -1.0)
+    end
+    if self.controls:IsDown(CTRL_LEFT) then
+        moveDir = moveDir + Vector3(-1.0, 0.0, 0.0)
+    end
+    if self.controls:IsDown(CTRL_RIGHT) then
+        moveDir = moveDir + Vector3(1.0, 0.0, 0.0)
+    end
+
+    -- Normalize move vector so that diagonal strafing is not faster
+    if moveDir:LengthSquared() > 0.0 then
+        moveDir:Normalize()
+    end
+
+    -- If in air, allow control, but slower than when on ground
+    if softGrounded then
+        body:ApplyImpulse(rot * moveDir * MOVE_FORCE)
+    else
+        body:ApplyImpulse(rot * moveDir * INAIR_MOVE_FORCE)
+    end
+
+    if softGrounded then
+        -- When on ground, apply a braking force to limit maximum ground velocity
+        local brakeForce = planeVelocity * -BRAKE_FORCE
+        body:ApplyImpulse(brakeForce)
+
+        -- Jump. Must release jump control inbetween jumps
+        if self.controls:IsDown(CTRL_JUMP) then
+            if self.okToJump then
+                body:ApplyImpulse(Vector3(0.0, 1.0, 0.0) * JUMP_FORCE)
+                self.okToJump = false
+            end
+        else
+            self.okToJump = true
+        end
+    end
+
+    -- Play walk animation if moving on ground, otherwise fade it out
+    if softGrounded and not moveDir:Equals(Vector3(0.0, 0.0, 0.0)) then
+        animCtrl:PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2)
+    else
+        animCtrl:Stop("Models/Jack_Walk.ani", 0.2)
+    end
+    -- Set walk animation speed proportional to velocity
+    animCtrl:SetSpeed("Models/Jack_Walk.ani", planeVelocity:Length() * 0.3)
+
+    -- Reset grounded flag for next frame
+    self.onGround = false
+end

+ 1 - 1
Bin/Data/Scripts/18_CharacterDemo.as

@@ -26,7 +26,7 @@ const float CAMERA_MAX_DIST = 5.0f;
 Scene@ scene_;
 Scene@ scene_;
 Node@ cameraNode;
 Node@ cameraNode;
 Node@ characterNode;
 Node@ characterNode;
-bool firstPerson;
+bool firstPerson = false;
 
 
 void Start()
 void Start()
 {
 {