Browse Source

Added scene load/save to CharacterDemo, both C++ & script versions.

Lasse Öörni 12 years ago
parent
commit
ef43398d7f

+ 2 - 2
Bin/Data/Scripts/11_Physics.as

@@ -116,7 +116,7 @@ void CreateScene()
     }
     }
     
     
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
-    // the scene, because we want it to be unaffected by scene load/save
+    // the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node();
     cameraNode = Node();
     Camera@ camera = cameraNode.CreateComponent("Camera");
     Camera@ camera = cameraNode.CreateComponent("Camera");
     camera.farClip = 500.0f;
     camera.farClip = 500.0f;
@@ -195,7 +195,7 @@ void MoveCamera(float timeStep)
     if (input.mouseButtonPress[MOUSEB_LEFT])
     if (input.mouseButtonPress[MOUSEB_LEFT])
         SpawnObject();
         SpawnObject();
 
 
-    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
+    // Check for loading / saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
     // directory
     // directory
     if (input.keyPress[KEY_F5])
     if (input.keyPress[KEY_F5])
     {
     {

+ 2 - 3
Bin/Data/Scripts/12_PhysicsStressTest.as

@@ -123,7 +123,7 @@ void CreateScene()
     }
     }
     
     
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
-    // the scene, because we want it to be unaffected by scene load/save
+    // the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node();
     cameraNode = Node();
     Camera@ camera = cameraNode.CreateComponent("Camera");
     Camera@ camera = cameraNode.CreateComponent("Camera");
     camera.farClip = 300.0f;
     camera.farClip = 300.0f;
@@ -202,8 +202,7 @@ void MoveCamera(float timeStep)
     if (input.mouseButtonPress[MOUSEB_LEFT])
     if (input.mouseButtonPress[MOUSEB_LEFT])
         SpawnObject();
         SpawnObject();
 
 
-    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
-    // directory
+    // Check for loading / saving the scene
     if (input.keyPress[KEY_F5])
     if (input.keyPress[KEY_F5])
     {
     {
         File saveFile(fileSystem.programDir + "Data/Scenes/PhysicsStressTest.xml", FILE_WRITE);
         File saveFile(fileSystem.programDir + "Data/Scenes/PhysicsStressTest.xml", FILE_WRITE);

+ 2 - 3
Bin/Data/Scripts/13_Ragdolls.as

@@ -111,7 +111,7 @@ void CreateScene()
     }
     }
     
     
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
-    // the scene, because we want it to be unaffected by scene load/save
+    // the scene, because we want it to be unaffected by scene load / save
     cameraNode = Node();
     cameraNode = Node();
     Camera@ camera = cameraNode.CreateComponent("Camera");
     Camera@ camera = cameraNode.CreateComponent("Camera");
     camera.farClip = 300.0f;
     camera.farClip = 300.0f;
@@ -190,8 +190,7 @@ void MoveCamera(float timeStep)
     if (input.mouseButtonPress[MOUSEB_LEFT])
     if (input.mouseButtonPress[MOUSEB_LEFT])
         SpawnObject();
         SpawnObject();
 
 
-    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
-    // directory
+    // Check for loading / saving the scene
     if (input.keyPress[KEY_F5])
     if (input.keyPress[KEY_F5])
     {
     {
         File saveFile(fileSystem.programDir + "Data/Scenes/Ragdolls.xml", FILE_WRITE);
         File saveFile(fileSystem.programDir + "Data/Scenes/Ragdolls.xml", FILE_WRITE);

+ 63 - 24
Bin/Data/Scripts/18_CharacterDemo.as

@@ -3,7 +3,8 @@
 //     - Controlling a humanoid character through physics;
 //     - Controlling a humanoid character through physics;
 //     - Driving animations using the AnimationController component;
 //     - Driving animations using the AnimationController component;
 //     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
 //     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
-//       clipping into scenery
+//       clipping into scenery;
+//     - Saving and loading the variables of a script object;
 
 
 #include "Scripts/Utilities/Sample.as"
 #include "Scripts/Utilities/Sample.as"
 
 
@@ -35,13 +36,13 @@ void Start()
 
 
     // Create static scene content
     // Create static scene content
     CreateScene();
     CreateScene();
-    
+
     // Create the controllable character
     // Create the controllable character
     CreateCharacter();
     CreateCharacter();
-    
+
     // Create the UI content
     // Create the UI content
     CreateInstructions();
     CreateInstructions();
-    
+
     // Subscribe to necessary events
     // Subscribe to necessary events
     SubscribeToEvents();
     SubscribeToEvents();
 }
 }
@@ -49,11 +50,11 @@ void Start()
 void CreateScene()
 void CreateScene()
 {
 {
     scene_ = Scene();
     scene_ = Scene();
-    
+
     // Create scene subsystem components
     // Create scene subsystem components
     scene_.CreateComponent("Octree");
     scene_.CreateComponent("Octree");
     scene_.CreateComponent("PhysicsWorld");
     scene_.CreateComponent("PhysicsWorld");
-    
+
     // Create camera and define viewport. Camera does not necessarily have to belong to the scene
     // Create camera and define viewport. Camera does not necessarily have to belong to the scene
     cameraNode = Node();
     cameraNode = Node();
     Camera@ camera = cameraNode.CreateComponent("Camera");
     Camera@ camera = cameraNode.CreateComponent("Camera");
@@ -86,14 +87,14 @@ void CreateScene()
     StaticModel@ object = floorNode.CreateComponent("StaticModel");
     StaticModel@ object = floorNode.CreateComponent("StaticModel");
     object.model = cache.GetResource("Model", "Models/Box.mdl");
     object.model = cache.GetResource("Model", "Models/Box.mdl");
     object.material = cache.GetResource("Material", "Materials/Stone.xml");
     object.material = cache.GetResource("Material", "Materials/Stone.xml");
-    
+
     RigidBody@ body = floorNode.CreateComponent("RigidBody");
     RigidBody@ 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
     // Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
     // inside geometry
     // inside geometry
     body.collisionLayer = 2;
     body.collisionLayer = 2;
     CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
     CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
     shape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
     shape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
-    
+
     // Create mushrooms of varying sizes
     // Create mushrooms of varying sizes
     const uint NUM_MUSHROOMS = 60;
     const uint NUM_MUSHROOMS = 60;
     for (uint i = 0; i < NUM_MUSHROOMS; ++i)
     for (uint i = 0; i < NUM_MUSHROOMS; ++i)
@@ -106,19 +107,19 @@ void CreateScene()
         object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
         object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
         object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
         object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
         object.castShadows = true;
         object.castShadows = true;
-        
+
         RigidBody@ body = objectNode.CreateComponent("RigidBody");
         RigidBody@ body = objectNode.CreateComponent("RigidBody");
         body.collisionLayer = 2;
         body.collisionLayer = 2;
         CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
         CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
         shape.SetTriangleMesh(object.model, 0);
         shape.SetTriangleMesh(object.model, 0);
     }
     }
-    
+
     // Create movable boxes. Let them fall from the sky at first
     // Create movable boxes. Let them fall from the sky at first
     const uint NUM_BOXES = 100;
     const uint NUM_BOXES = 100;
     for (uint i = 0; i < NUM_BOXES; ++i)
     for (uint i = 0; i < NUM_BOXES; ++i)
     {
     {
         float scale = Random(2.0f) + 0.5f;
         float scale = Random(2.0f) + 0.5f;
-        
+
         Node@ objectNode = scene_.CreateChild("Box");
         Node@ objectNode = scene_.CreateChild("Box");
         objectNode.position = Vector3(Random(180.0f) - 90.0f, Random(10.0f) + 10.0f, Random(180.0f) - 90.0f);
         objectNode.position = Vector3(Random(180.0f) - 90.0f, Random(10.0f) + 10.0f, Random(180.0f) - 90.0f);
         objectNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
         objectNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
@@ -127,7 +128,7 @@ void CreateScene()
         object.model = cache.GetResource("Model", "Models/Box.mdl");
         object.model = cache.GetResource("Model", "Models/Box.mdl");
         object.material = cache.GetResource("Material", "Materials/Stone.xml");
         object.material = cache.GetResource("Material", "Materials/Stone.xml");
         object.castShadows = true;
         object.castShadows = true;
-        
+
         RigidBody@ body = objectNode.CreateComponent("RigidBody");
         RigidBody@ body = objectNode.CreateComponent("RigidBody");
         body.collisionLayer = 2;
         body.collisionLayer = 2;
         // Bigger boxes will be heavier and harder to move
         // Bigger boxes will be heavier and harder to move
@@ -141,7 +142,7 @@ void CreateCharacter()
 {
 {
     characterNode = scene_.CreateChild("Jack");
     characterNode = scene_.CreateChild("Jack");
     characterNode.position = Vector3(0.0f, 1.0f, 0.0f);
     characterNode.position = Vector3(0.0f, 1.0f, 0.0f);
-    
+
     // Create the rendering component + animation controller
     // Create the rendering component + animation controller
     AnimatedModel@ object = characterNode.CreateComponent("AnimatedModel");
     AnimatedModel@ object = characterNode.CreateComponent("AnimatedModel");
     object.model = cache.GetResource("Model", "Models/Jack.mdl");
     object.model = cache.GetResource("Model", "Models/Jack.mdl");
@@ -153,18 +154,18 @@ void CreateCharacter()
     RigidBody@ body = characterNode.CreateComponent("RigidBody");
     RigidBody@ body = characterNode.CreateComponent("RigidBody");
     body.collisionLayer = 1;
     body.collisionLayer = 1;
     body.mass = 1.0f;
     body.mass = 1.0f;
-    
+
     // Set zero angular factor so that physics doesn't turn the character on its own.
     // Set zero angular factor so that physics doesn't turn the character on its own.
     // Instead we will control the character yaw manually
     // Instead we will control the character yaw manually
     body.angularFactor = Vector3(0.0f, 0.0f, 0.0f);
     body.angularFactor = Vector3(0.0f, 0.0f, 0.0f);
-    
+
     // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
     // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
     body.collisionEventMode = COLLISION_ALWAYS;
     body.collisionEventMode = COLLISION_ALWAYS;
-    
+
     // Set a capsule shape for collision
     // Set a capsule shape for collision
     CollisionShape@ shape = characterNode.CreateComponent("CollisionShape");
     CollisionShape@ shape = characterNode.CreateComponent("CollisionShape");
     shape.SetCapsule(0.7f, 1.8f, Vector3(0.0f, 0.9f, 0.0f));
     shape.SetCapsule(0.7f, 1.8f, Vector3(0.0f, 0.9f, 0.0f));
-    
+
     // Create the character logic object, which takes care of steering the rigidbody
     // Create the character logic object, which takes care of steering the rigidbody
     characterNode.CreateScriptObject(scriptFile, "Character");
     characterNode.CreateScriptObject(scriptFile, "Character");
 }
 }
@@ -175,7 +176,8 @@ void CreateInstructions()
     Text@ instructionText = ui.root.CreateChild("Text");
     Text@ instructionText = ui.root.CreateChild("Text");
     instructionText.text =
     instructionText.text =
         "Use WASD keys and mouse to move\n"
         "Use WASD keys and mouse to move\n"
-        "Space to jump, F to toggle 1st/3rd person";
+        "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);
     instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     // The text has multiple rows. Center them in relation to each other
     instructionText.textAlignment = HA_CENTER;
     instructionText.textAlignment = HA_CENTER;
@@ -212,7 +214,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         character.controls.Set(CTRL_LEFT, input.keyDown['A']);
         character.controls.Set(CTRL_LEFT, input.keyDown['A']);
         character.controls.Set(CTRL_RIGHT, input.keyDown['D']);
         character.controls.Set(CTRL_RIGHT, input.keyDown['D']);
         character.controls.Set(CTRL_JUMP, input.keyDown[KEY_SPACE]);
         character.controls.Set(CTRL_JUMP, input.keyDown[KEY_SPACE]);
-        
+
         // Add character yaw & pitch from the mouse motion
         // Add character yaw & pitch from the mouse motion
         character.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
         character.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
         character.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
         character.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
@@ -222,10 +224,30 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         // Switch between 1st and 3rd person
         // Switch between 1st and 3rd person
         if (input.keyPress['F'])
         if (input.keyPress['F'])
             firstPerson = !firstPerson;
             firstPerson = !firstPerson;
+
+        // Check for loading / saving the scene
+        if (input.keyPress[KEY_F5])
+        {
+            File saveFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_WRITE);
+            scene_.SaveXML(saveFile);
+        }
+        if (input.keyPress[KEY_F7])
+        {
+            File loadFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_READ);
+            scene_.LoadXML(loadFile);
+            // 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 is null)
+                return;
+            character = cast<Character>(characterNode.scriptObject);
+            if (character is null)
+                return;
+        }
     }
     }
     else
     else
         character.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
         character.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
-    
+
     // Set rotation already here so that it's updated every rendering frame instead of every physics frame
     // 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.0f, 1.0f, 0.0f));
     characterNode.rotation = Quaternion(character.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
 }
 }
@@ -257,7 +279,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
     {
     {
         // Third person camera: position behind the character
         // Third person camera: position behind the character
         Vector3 aimPoint = characterNode.position + rot * Vector3(0.0f, 1.7f, 0.0f);
         Vector3 aimPoint = characterNode.position + rot * Vector3(0.0f, 1.7f, 0.0f);
-        
+
         // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
         // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
         Vector3 rayDir = dir * Vector3(0.0f, 0.0f, -1.0f);
         Vector3 rayDir = dir * Vector3(0.0f, 0.0f, -1.0f);
         float rayDistance = CAMERA_MAX_DIST;
         float rayDistance = CAMERA_MAX_DIST;
@@ -265,13 +287,18 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
         if (result.body !is null)
         if (result.body !is null)
             rayDistance = Min(rayDistance, result.distance);
             rayDistance = Min(rayDistance, result.distance);
         rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
         rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
-        
+
         cameraNode.position = aimPoint + rayDir * rayDistance;
         cameraNode.position = aimPoint + rayDir * rayDistance;
         cameraNode.rotation = dir;
         cameraNode.rotation = dir;
     }
     }
 }
 }
 
 
 // Character script object class
 // Character script object class
+//
+// Those public member variables that can be expressed with a Variant and do not begin with an underscore are automatically
+// loaded / saved as attributes of the ScriptInstance component. We also have variables which can not be automatically saved
+// (controls yaw and pitch) so we write manual binary format load / save functions for them. These functions will be called by
+// ScriptInstance when the script object is being loaded or saved.
 class Character : ScriptObject
 class Character : ScriptObject
 {
 {
     // Character controls.
     // Character controls.
@@ -282,12 +309,24 @@ class Character : ScriptObject
     bool okToJump = true;
     bool 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.
     // In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
     float inAirTimer = 0.0f;
     float inAirTimer = 0.0f;
-    
+
     void Start()
     void Start()
     {
     {
         SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision");
         SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision");
     }
     }
-    
+
+    void Load(Deserializer& deserializer)
+    {
+        controls.yaw = deserializer.ReadFloat();
+        controls.pitch = deserializer.ReadFloat();
+    }
+
+    void Save(Serializer& serializer)
+    {
+        serializer.WriteFloat(controls.yaw);
+        serializer.WriteFloat(controls.pitch);
+    }
+
     void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
     void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
     {
     {
         VectorBuffer contacts = eventData["Contacts"].GetBuffer();
         VectorBuffer contacts = eventData["Contacts"].GetBuffer();

+ 1 - 1
Source/Samples/11_Physics/Physics.cpp

@@ -164,7 +164,7 @@ void Physics::CreateScene()
     }
     }
     
     
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
     // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside the scene, because
-    // we want it to be unaffected by scene load/save
+    // we want it to be unaffected by scene load / save
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(500.0f);
     camera->SetFarClip(500.0f);

+ 3 - 4
Source/Samples/12_PhysicsStressTest/PhysicsStressTest.cpp

@@ -169,7 +169,7 @@ void PhysicsStressTest::CreateScene()
     }
     }
     
     
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
-    // the scene, because we want it to be unaffected by scene load/save
+    // the scene, because we want it to be unaffected by scene load / save
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(300.0f);
     camera->SetFarClip(300.0f);
@@ -254,10 +254,9 @@ void PhysicsStressTest::MoveCamera(float timeStep)
     
     
     // "Shoot" a physics object with left mousebutton
     // "Shoot" a physics object with left mousebutton
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
-        SpawnObject();  
+        SpawnObject();
 
 
-    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
-    // directory
+    // Check for loading / saving the scene
     if (input->GetKeyPress(KEY_F5))
     if (input->GetKeyPress(KEY_F5))
     {
     {
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/PhysicsStressTest.xml", FILE_WRITE);
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/PhysicsStressTest.xml", FILE_WRITE);

+ 2 - 3
Source/Samples/13_Ragdolls/Ragdolls.cpp

@@ -162,7 +162,7 @@ void Ragdolls::CreateScene()
     }
     }
     
     
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
     // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
-    // the scene, because we want it to be unaffected by scene load/save
+    // the scene, because we want it to be unaffected by scene load / save
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(300.0f);
     camera->SetFarClip(300.0f);
@@ -239,8 +239,7 @@ void Ragdolls::MoveCamera(float timeStep)
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
     if (input->GetMouseButtonPress(MOUSEB_LEFT))
         SpawnObject();
         SpawnObject();
     
     
-    // Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
-    // directory
+    // Check for loading / saving the scene
     if (input->GetKeyPress(KEY_F5))
     if (input->GetKeyPress(KEY_F5))
     {
     {
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/Ragdolls.xml", FILE_WRITE);
         File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/Ragdolls.xml", FILE_WRITE);

+ 34 - 20
Source/Samples/18_CharacterDemo/Character.cpp

@@ -1,27 +1,28 @@
-//
-// Copyright (c) 2008-2013 the Urho3D project.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
 //
 //
 
 
 #include "AnimationController.h"
 #include "AnimationController.h"
 #include "Character.h"
 #include "Character.h"
+#include "Context.h"
 #include "MemoryBuffer.h"
 #include "MemoryBuffer.h"
 #include "PhysicsEvents.h"
 #include "PhysicsEvents.h"
 #include "PhysicsWorld.h"
 #include "PhysicsWorld.h"
@@ -37,6 +38,19 @@ Character::Character(Context* context) :
 {
 {
 }
 }
 
 
+void Character::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Character>();
+    
+    // These macros register the class attributes to the Context for automatic load / save handling.
+    // We specify the Default attribute mode which means it will be used both for saving into file, and network replication
+    ATTRIBUTE(Character, VAR_FLOAT, "Controls Yaw", controls_.yaw_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Character, VAR_FLOAT, "Controls Pitch", controls_.pitch_, 0.0f, AM_DEFAULT);
+    ATTRIBUTE(Character, VAR_BOOL, "On Ground", onGround_, false, AM_DEFAULT);
+    ATTRIBUTE(Character, VAR_BOOL, "OK To Jump", okToJump_, true, AM_DEFAULT);
+    ATTRIBUTE(Character, VAR_FLOAT, "In Air Timer", inAirTimer_, 0.0f, AM_DEFAULT);
+}
+
 void Character::OnNodeSet(Node* node)
 void Character::OnNodeSet(Node* node)
 {
 {
     if (node)
     if (node)

+ 4 - 1
Source/Samples/18_CharacterDemo/Character.h

@@ -49,10 +49,13 @@ public:
     /// Construct.
     /// Construct.
     Character(Context* context);
     Character(Context* context);
     
     
+    /// Register object factory and attributes.
+    static void RegisterObject(Context* context);
+    
     /// Handle node being assigned.
     /// Handle node being assigned.
     virtual void OnNodeSet(Node* node);
     virtual void OnNodeSet(Node* node);
     
     
-    /// Movement controls.
+    /// Movement controls. Assigned by the main program each frame.
     Controls controls_;
     Controls controls_;
     
     
 private:
 private:

+ 25 - 4
Source/Samples/18_CharacterDemo/CharacterDemo.cpp

@@ -28,6 +28,7 @@
 #include "Controls.h"
 #include "Controls.h"
 #include "CoreEvents.h"
 #include "CoreEvents.h"
 #include "Engine.h"
 #include "Engine.h"
+#include "FileSystem.h"
 #include "Font.h"
 #include "Font.h"
 #include "Input.h"
 #include "Input.h"
 #include "Light.h"
 #include "Light.h"
@@ -58,8 +59,8 @@ CharacterDemo::CharacterDemo(Context* context) :
     Sample(context),
     Sample(context),
     firstPerson_(false)
     firstPerson_(false)
 {
 {
-    // Register factory for the Character component so it can be created via CreateComponent
-    context_->RegisterFactory<Character>();
+    // Register factory and attributes for the Character component so it can be created via CreateComponent, and loaded / saved
+    Character::RegisterObject(context);
 }
 }
 
 
 void CharacterDemo::Start()
 void CharacterDemo::Start()
@@ -90,7 +91,8 @@ void CharacterDemo::CreateScene()
     scene_->CreateComponent<Octree>();
     scene_->CreateComponent<Octree>();
     scene_->CreateComponent<PhysicsWorld>();
     scene_->CreateComponent<PhysicsWorld>();
     
     
-    // Create camera and define viewport. Camera does not necessarily have to belong to the scene
+    // Create camera and define viewport. We will be doing load / save, in which case it's convenient to create the camera
+    // outside the scene, so that it isn't being destroyed and recreated, and we don't have to redefine the viewport on load
     cameraNode_ = new Node(context_);
     cameraNode_ = new Node(context_);
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     Camera* camera = cameraNode_->CreateComponent<Camera>();
     camera->SetFarClip(300.0f);
     camera->SetFarClip(300.0f);
@@ -218,7 +220,8 @@ void CharacterDemo::CreateInstructions()
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     instructionText->SetText(
     instructionText->SetText(
         "Use WASD keys and mouse to move\n"
         "Use WASD keys and mouse to move\n"
-        "Space to jump, F to toggle 1st/3rd person"
+        "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);
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     // The text has multiple rows. Center them in relation to each other
@@ -268,6 +271,24 @@ void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
             // Switch between 1st and 3rd person
             // Switch between 1st and 3rd person
             if (input->GetKeyPress('F'))
             if (input->GetKeyPress('F'))
                 firstPerson_ = !firstPerson_;
                 firstPerson_ = !firstPerson_;
+
+            // Check for loading / saving the scene
+            if (input->GetKeyPress(KEY_F5))
+            {
+                File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml",
+                    FILE_WRITE);
+                scene_->SaveXML(saveFile);
+            }
+            if (input->GetKeyPress(KEY_F7))
+            {
+                File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/CharacterDemo.xml", FILE_READ);
+                scene_->LoadXML(loadFile);
+                // After loading we have to reacquire the weak pointer to the Character component, as it has been recreated
+                // Simply find the character's scene node by name as there's only one of them
+                Node* characterNode = scene_->GetChild("Jack", true);
+                if (characterNode)
+                    character_ = characterNode->GetComponent<Character>();
+            }
         }
         }
         else
         else
             character_->controls_.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
             character_->controls_.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);

+ 2 - 1
Source/Samples/18_CharacterDemo/CharacterDemo.h

@@ -39,7 +39,8 @@ class Character;
 ///     - Controlling a humanoid character through physics;
 ///     - Controlling a humanoid character through physics;
 ///     - Driving animations using the AnimationController component;
 ///     - Driving animations using the AnimationController component;
 ///     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
 ///     - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera
-///       clipping into scenery
+///       clipping into scenery;
+///     - Defining attributes of a custom component so that it can be saved and loaded;
 class CharacterDemo : public Sample
 class CharacterDemo : public Sample
 {
 {
     OBJECT(CharacterDemo);
     OBJECT(CharacterDemo);