浏览代码

Initial implementation of LogicComponent base class, which should make writing C++ logic/updater components more similar to scripting. Refactored C++ examples to use LogicComponent where possible.

Lasse Öörni 11 年之前
父节点
当前提交
d0595084d4

+ 167 - 0
Source/Engine/Scene/LogicComponent.cpp

@@ -0,0 +1,167 @@
+//
+// Copyright (c) 2008-2014 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 "Precompiled.h"
+#include "Log.h"
+#include "LogicComponent.h"
+#include "PhysicsEvents.h"
+#include "PhysicsWorld.h"
+#include "Scene.h"
+#include "SceneEvents.h"
+
+namespace Urho3D
+{
+
+LogicComponent::LogicComponent(Context* context) :
+    Component(context),
+    updateEventMask_(USE_UPDATE | USE_POSTUPDATE | USE_FIXEDUPDATE | USE_FIXEDPOSTUPDATE),
+    delayedStartCalled_(false)
+{
+}
+
+LogicComponent::~LogicComponent()
+{
+}
+
+void LogicComponent::OnSetEnabled()
+{
+    UpdateEventSubscription();
+}
+
+void LogicComponent::SetUpdateEventMask(unsigned mask)
+{
+    if (updateEventMask_ != mask)
+    {
+        updateEventMask_ = mask;
+        UpdateEventSubscription();
+    }
+}
+
+void LogicComponent::OnNodeSet(Node* node)
+{
+    if (node)
+    {
+        // We have been attached to a node. Set initial update event subscription state
+        UpdateEventSubscription();
+        // Then execute the start function
+        Start();
+    }
+    else
+    {
+        // We are being detached from a node: execute stop function and prepare for destruction
+        Stop();
+    }
+}
+
+void LogicComponent::UpdateEventSubscription()
+{
+    // If scene node is not assigned yet, no need to update subscription
+    if (!node_)
+        return;
+    
+    Scene* scene = GetScene();
+    if (!scene)
+    {
+        LOGWARNING("Node is detached from scene, can not subscribe to update events");
+        return;
+    }
+    
+    bool enabled = IsEnabledEffective();
+    
+    if (enabled)
+    {
+        if (updateEventMask_ & USE_UPDATE)
+            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(LogicComponent, HandleSceneUpdate));
+        if (updateEventMask_ & USE_POSTUPDATE)
+            SubscribeToEvent(scene, E_SCENEPOSTUPDATE, HANDLER(LogicComponent, HandleScenePostUpdate));
+        
+        if (updateEventMask_ & (USE_FIXEDUPDATE | USE_FIXEDPOSTUPDATE))
+        {
+            PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
+            if (world)
+            {
+                if (updateEventMask_ & USE_FIXEDUPDATE)
+                    SubscribeToEvent(world, E_PHYSICSPRESTEP, HANDLER(LogicComponent, HandlePhysicsPreStep));
+                if (updateEventMask_ & USE_FIXEDPOSTUPDATE)
+                    SubscribeToEvent(world, E_PHYSICSPOSTSTEP, HANDLER(LogicComponent, HandlePhysicsPostStep));
+            }
+            else
+                LOGERROR("No physics world, can not subscribe to fixed update events");
+        }
+    }
+    else
+    {
+        UnsubscribeFromEvent(scene, E_SCENEUPDATE);
+        UnsubscribeFromEvent(scene, E_SCENEPOSTUPDATE);
+        
+        PhysicsWorld* world = scene->GetComponent<PhysicsWorld>();
+        if (world)
+        {
+            UnsubscribeFromEvent(world, E_PHYSICSPRESTEP);
+            UnsubscribeFromEvent(world, E_PHYSICSPOSTSTEP);
+        }
+    }
+}
+
+void LogicComponent::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace SceneUpdate;
+    
+    // Execute delayed start before first update
+    if (!delayedStartCalled_)
+    {
+        DelayedStart();
+        delayedStartCalled_ = true;
+    }
+    
+    Update(eventData[P_TIMESTEP].GetFloat());
+}
+
+void LogicComponent::HandleScenePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace ScenePostUpdate;
+    
+    PostUpdate(eventData[P_TIMESTEP].GetFloat());
+}
+
+void LogicComponent::HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PhysicsPreStep;
+    
+    // Execute delayed start before first update
+    if (!delayedStartCalled_)
+    {
+        DelayedStart();
+        delayedStartCalled_ = true;
+    }
+    
+    FixedUpdate(eventData[P_TIMESTEP].GetFloat());
+}
+
+void LogicComponent::HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData)
+{
+    using namespace PhysicsPostStep;
+    
+    FixedUpdate(eventData[P_TIMESTEP].GetFloat());
+}
+
+}

+ 94 - 0
Source/Engine/Scene/LogicComponent.h

@@ -0,0 +1,94 @@
+//
+// Copyright (c) 2008-2014 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 "Component.h"
+
+namespace Urho3D
+{
+
+/// Bitmask for using the scene update event.
+static const unsigned USE_UPDATE = 0x1;
+/// Bitmask for using the scene post-update event.
+static const unsigned USE_POSTUPDATE = 0x2;
+/// Bitmask for using the physics update event.
+static const unsigned USE_FIXEDUPDATE = 0x4;
+/// Bitmask for using the phyics post-update event.
+static const unsigned USE_FIXEDPOSTUPDATE = 0x8;
+
+/// Helper base class for user-defined game logic components that hooks up to update events and forwards them to virtual functions similar to ScriptInstance class.
+class URHO3D_API LogicComponent : public Component
+{
+    OBJECT(LogicComponent);
+    
+    /// Construct.
+    LogicComponent(Context* context);
+    /// Destruct.
+    virtual ~LogicComponent();
+    
+    /// Handle enabled/disabled state change. Changes update event subscription.
+    virtual void OnSetEnabled();
+    /// Called when the component is added to a scene node. Other components may not yet exist.
+    virtual void Start() {}
+    /// Called before the first update or fixed update. At this point all other components of the node should exist.
+    virtual void DelayedStart() {}
+    /// Called when the component is detached from a scene node, usually on destruction.
+    virtual void Stop() {}
+    /// Called on scene update, variable timestep.
+    virtual void Update(float timeStep) {}
+    /// Called on scene post-update, variable timestep.
+    virtual void PostUpdate(float timeStep) {}
+    /// Called on physics update, fixed timestep.
+    virtual void FixedUpdate(float timeStep) {}
+    /// Called on physics post-update, fixed timestep.
+    virtual void FixedPostUpdate(float timeStep) {}
+    
+    /// Set what update events should be subscribed to. Use this for optimization: by default all are in use. Note that this is not an attribute and is not saved or network-serialized, therefore it should be always called eg. in the subclass constructor.
+    void SetUpdateEventMask(unsigned mask);
+    
+    /// Return what update events are subscribed to.
+    unsigned GetUpdateEventMask() const { return updateEventMask_; }
+    /// Return whether the DelayedStart() function has been called.
+    bool IsDelayedStartCalled() const { return delayedStartCalled_; }
+    
+protected:
+    /// Handle scene node being assigned at creation.
+    virtual void OnNodeSet(Node* node);
+    
+private:
+    /// Subscribe/unsubscribe to update events based on current enabled state and update event mask.
+    void UpdateEventSubscription();
+    /// Handle scene update event.
+    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle scene post-update event.
+    void HandleScenePostUpdate(StringHash eventType, VariantMap& eventData);
+    /// Handle physics pre-step event.
+    void HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData);
+    /// Handle physics post-step event.
+    void HandlePhysicsPostStep(StringHash eventType, VariantMap& eventData);
+    
+    /// Event subscription mask.
+    unsigned updateEventMask_;
+    /// Flag for delayed start.
+    bool delayedStartCalled_;
+};
+
+}

+ 4 - 1
Source/Samples/05_AnimatingScene/AnimatingScene.cpp

@@ -106,7 +106,10 @@ void AnimatingScene::CreateScene()
         boxObject->SetMaterial(cache->GetResource<Material>("Materials/Stone.xml"));
         
         // Add our custom Rotator component which will rotate the scene node each frame, when the scene sends its update event.
-        // Simply set same rotation speed for all objects
+        // The Rotator component derives from the base class LogicComponent, which has convenience functionality to subscribe
+        // to the various update events, and forward them to virtual functions that can be implemented by subclasses. This way
+        // writing logic/update components in C++ becomes similar to scripting.
+        // Now we simply set same rotation speed for all objects
         Rotator* rotator = boxNode->CreateComponent<Rotator>();
         rotator->SetRotationSpeed(Vector3(10.0f, 20.0f, 30.0f));
     }

+ 4 - 20
Source/Samples/05_AnimatingScene/Rotator.cpp

@@ -27,9 +27,11 @@
 #include "DebugNew.h"
 
 Rotator::Rotator(Context* context) :
-    Component(context),
+    LogicComponent(context),
     rotationSpeed_(Vector3::ZERO)
 {
+    // Only the scene update event is needed: unsubscribe from the rest for optimization
+    SetUpdateEventMask(USE_UPDATE);
 }
 
 void Rotator::SetRotationSpeed(const Vector3& speed)
@@ -37,26 +39,8 @@ void Rotator::SetRotationSpeed(const Vector3& speed)
     rotationSpeed_ = speed;
 }
 
-void Rotator::OnNodeSet(Node* node)
+void Rotator::Update(float timeStep)
 {
-    // If the node pointer is non-null, this component has been created into a scene node. Subscribe to the variable timestep
-    // scene update event now. If the node pointer is null, the component is being removed from a scene node at destruction
-    // time. In that case we do nothing
-    if (node)
-    {
-        Scene* scene = node->GetScene();
-        // The scene pointer can be null if this scene node has been created free-standing and not part of a scene
-        if (scene)
-            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(Rotator, HandleSceneUpdate));
-    }
-}
-
-void Rotator::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
-{
-    // Get the timestep from the update event
-    using namespace SceneUpdate;
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-    
     // Components have their scene node as a member variable for convenient access. Rotate the scene node now: construct a
     // rotation quaternion from Euler angles, scale rotation speed with the scene update time step
     node_->Rotate(Quaternion(rotationSpeed_.x_ * timeStep, rotationSpeed_.y_ * timeStep, rotationSpeed_.z_ * timeStep));

+ 5 - 10
Source/Samples/05_AnimatingScene/Rotator.h

@@ -22,13 +22,13 @@
 
 #pragma once
 
-#include "Component.h"
+#include "LogicComponent.h"
 
 // All Urho3D classes reside in namespace Urho3D
 using namespace Urho3D;
 
-/// Custom component for rotating a scene node.
-class Rotator : public Component
+/// Custom logic component for rotating a scene node.
+class Rotator : public LogicComponent
 {
     OBJECT(Rotator);
     
@@ -38,18 +38,13 @@ public:
     
     /// Set rotation speed about the Euler axes. Will be scaled with scene update time step.
     void SetRotationSpeed(const Vector3& speed);
+    /// Handle scene update. Called by LogicComponent base class.
+    virtual void Update(float timeStep);
     
     /// Return rotation speed.
     const Vector3& GetRotationSpeed() const { return rotationSpeed_; }
     
-protected:
-    /// Handle node being assigned.
-    virtual void OnNodeSet(Node* node);
-    
 private:
-    /// Handle scene update event.
-    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
-    
     /// Rotation speed.
     Vector3 rotationSpeed_;
 };

+ 4 - 18
Source/Samples/06_SkeletalAnimation/Mover.cpp

@@ -29,10 +29,12 @@
 #include "DebugNew.h"
 
 Mover::Mover(Context* context) :
-    Component(context),
+    LogicComponent(context),
     moveSpeed_(0.0f),
     rotationSpeed_(0.0f)
 {
+    // Only the scene update event is needed: unsubscribe from the rest for optimization
+    SetUpdateEventMask(USE_UPDATE);
 }
 
 void Mover::SetParameters(float moveSpeed, float rotationSpeed, const BoundingBox& bounds)
@@ -42,24 +44,8 @@ void Mover::SetParameters(float moveSpeed, float rotationSpeed, const BoundingBo
     bounds_ = bounds;
 }
 
-void Mover::OnNodeSet(Node* node)
+void Mover::Update(float timeStep)
 {
-    // If the node pointer is non-null, this component has been created into a scene node. Subscribe to the variable timestep
-    // scene update event now
-    if (node)
-    {
-        Scene* scene = node->GetScene();
-        if (scene)
-            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(Mover, HandleSceneUpdate));
-    }
-}
-
-void Mover::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
-{
-    // Get the timestep from the update event
-    using namespace SceneUpdate;
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-    
     node_->TranslateRelative(Vector3::FORWARD * moveSpeed_ * timeStep);
     
     // If in risk of going outside the plane, rotate the model right

+ 5 - 10
Source/Samples/06_SkeletalAnimation/Mover.h

@@ -22,12 +22,12 @@
 
 #pragma once
 
-#include "Component.h"
+#include "LogicComponent.h"
 
 using namespace Urho3D;
 
-/// Custom component for moving the animated model and rotating at area edges.
-class Mover : public Component
+/// Custom logic component for moving the animated model and rotating at area edges.
+class Mover : public LogicComponent
 {
     OBJECT(Mover);
     
@@ -37,6 +37,8 @@ public:
     
     /// Set motion parameters: forward movement speed, rotation speed, and movement boundaries.
     void SetParameters(float moveSpeed, float rotateSpeed, const BoundingBox& bounds);
+    /// Handle scene update. Called by LogicComponent base class.
+    virtual void Update(float timeStep);
     
     /// Return forward movement speed.
     float GetMoveSpeed() const { return moveSpeed_; }
@@ -45,14 +47,7 @@ public:
     /// Return movement boundaries.
     const BoundingBox& GetBounds() const { return bounds_; }
     
-protected:
-    /// Handle node being assigned.
-    virtual void OnNodeSet(Node* node);
-    
 private:
-    /// Handle scene update event.
-    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
-    
     /// Forward movement speed.
     float moveSpeed_;
     /// Rotation speed.

+ 4 - 18
Source/Samples/10_RenderToTexture/Rotator.cpp

@@ -27,9 +27,11 @@
 #include "DebugNew.h"
 
 Rotator::Rotator(Context* context) :
-    Component(context),
+    LogicComponent(context),
     rotationSpeed_(Vector3::ZERO)
 {
+    // Only the scene update event is needed: unsubscribe from the rest for optimization
+    SetUpdateEventMask(USE_UPDATE);
 }
 
 void Rotator::SetRotationSpeed(const Vector3& speed)
@@ -37,24 +39,8 @@ void Rotator::SetRotationSpeed(const Vector3& speed)
     rotationSpeed_ = speed;
 }
 
-void Rotator::OnNodeSet(Node* node)
+void Rotator::Update(float timeStep)
 {
-    // If the node pointer is non-null, this component has been created into a scene node. Subscribe to the variable timestep
-    // scene update event now
-    if (node)
-    {
-        Scene* scene = node->GetScene();
-        if (scene)
-            SubscribeToEvent(scene, E_SCENEUPDATE, HANDLER(Rotator, HandleSceneUpdate));
-    }
-}
-
-void Rotator::HandleSceneUpdate(StringHash eventType, VariantMap& eventData)
-{
-    // Get the timestep from the update event
-    using namespace SceneUpdate;
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-    
     // Components have their scene node as a member variable for convenient access. Rotate the scene node now: construct a
     // rotation quaternion from Euler angles, scale rotation speed with the scene update time step
     node_->Rotate(Quaternion(rotationSpeed_.x_ * timeStep, rotationSpeed_.y_ * timeStep, rotationSpeed_.z_ * timeStep));

+ 5 - 10
Source/Samples/10_RenderToTexture/Rotator.h

@@ -22,13 +22,13 @@
 
 #pragma once
 
-#include "Component.h"
+#include "LogicComponent.h"
 
 // All Urho3D classes reside in namespace Urho3D
 using namespace Urho3D;
 
-/// Custom component for rotating a scene node.
-class Rotator : public Component
+/// Custom logic component for rotating a scene node.
+class Rotator : public LogicComponent
 {
     OBJECT(Rotator);
     
@@ -38,18 +38,13 @@ public:
     
     /// Set rotation speed about the Euler axes. Will be scaled with scene update time step.
     void SetRotationSpeed(const Vector3& speed);
+    /// Handle scene update. Called by LogicComponent base class.
+    virtual void Update(float timeStep);
     
     /// Return rotation speed.
     const Vector3& GetRotationSpeed() const { return rotationSpeed_; }
     
-protected:
-    /// Handle node being assigned.
-    virtual void OnNodeSet(Node* node);
-    
 private:
-    /// Handle scene update event.
-    void HandleSceneUpdate(StringHash eventType, VariantMap& eventData);
-    
     /// Rotation speed.
     Vector3 rotationSpeed_;
 };

+ 31 - 37
Source/Samples/18_CharacterDemo/Character.cpp

@@ -31,11 +31,13 @@
 #include "SceneEvents.h"
 
 Character::Character(Context* context) :
-    Component(context),
+    LogicComponent(context),
     onGround_(false),
     okToJump_(true),
     inAirTimer_(0.0f)
 {
+    // Only the physics update event is needed: unsubscribe from the rest for optimization
+    SetUpdateEventMask(USE_FIXEDUPDATE);
 }
 
 void Character::RegisterObject(Context* context)
@@ -51,46 +53,14 @@ void Character::RegisterObject(Context* context)
     ATTRIBUTE(Character, VAR_FLOAT, "In Air Timer", inAirTimer_, 0.0f, AM_DEFAULT);
 }
 
-void Character::OnNodeSet(Node* node)
+void Character::Start()
 {
-    if (node)
-    {
-        // Component has been inserted into its scene node. Subscribe to events now
-        SubscribeToEvent(node, E_NODECOLLISION, HANDLER(Character, HandleNodeCollision));
-        SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Character, HandleFixedUpdate));
-    }
-}
-
-void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
-{
-    // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
-    using namespace NodeCollision;
-    
-    MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
-    
-    while (!contacts.IsEof())
-    {
-        Vector3 contactPosition = contacts.ReadVector3();
-        Vector3 contactNormal = contacts.ReadVector3();
-        float contactDistance = contacts.ReadFloat();
-        float contactImpulse = contacts.ReadFloat();
-        
-        // If contact is below node center and mostly vertical, assume it's a ground contact
-        if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
-        {
-            float level = Abs(contactNormal.y_);
-            if (level > 0.75)
-                onGround_ = true;
-        }
-    }
+    // Component has been inserted into its scene node. Subscribe to events now
+    SubscribeToEvent(GetNode(), E_NODECOLLISION, HANDLER(Character, HandleNodeCollision));
 }
 
-void Character::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
+void Character::FixedUpdate(float timeStep)
 {
-    using namespace PhysicsPreStep;
-    
-    float timeStep = eventData[P_TIMESTEP].GetFloat();
-    
     /// \todo Could cache the components for faster access instead of finding them each frame
     RigidBody* body = GetComponent<RigidBody>();
     AnimationController* animCtrl = GetComponent<AnimationController>();
@@ -156,3 +126,27 @@ void Character::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
     // Reset grounded flag for next frame
     onGround_ = false;
 }
+
+void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
+{
+    // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
+    using namespace NodeCollision;
+    
+    MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
+    
+    while (!contacts.IsEof())
+    {
+        Vector3 contactPosition = contacts.ReadVector3();
+        Vector3 contactNormal = contacts.ReadVector3();
+        float contactDistance = contacts.ReadFloat();
+        float contactImpulse = contacts.ReadFloat();
+        
+        // If contact is below node center and mostly vertical, assume it's a ground contact
+        if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
+        {
+            float level = Abs(contactNormal.y_);
+            if (level > 0.75)
+                onGround_ = true;
+        }
+    }
+}

+ 6 - 6
Source/Samples/18_CharacterDemo/Character.h

@@ -22,8 +22,8 @@
 
 #pragma once
 
-#include "Component.h"
 #include "Controls.h"
+#include "LogicComponent.h"
 
 using namespace Urho3D;
 
@@ -41,7 +41,7 @@ const float YAW_SENSITIVITY = 0.1f;
 const float INAIR_THRESHOLD_TIME = 0.1f;
 
 /// Character component, responsible for physical movement according to controls, as well as animation.
-class Character : public Component
+class Character : public LogicComponent
 {
     OBJECT(Character)
 
@@ -52,8 +52,10 @@ public:
     /// Register object factory and attributes.
     static void RegisterObject(Context* context);
     
-    /// Handle node being assigned.
-    virtual void OnNodeSet(Node* node);
+    /// Handle startup. Called by LogicComponent base class.
+    virtual void Start();
+    /// Handle physics world update. Called by LogicComponent base class.
+    virtual void FixedUpdate(float timeStep);
     
     /// Movement controls. Assigned by the main program each frame.
     Controls controls_;
@@ -61,8 +63,6 @@ public:
 private:
     /// Handle physics collision event.
     void HandleNodeCollision(StringHash eventType, VariantMap& eventData);
-    /// Handle physics world update event.
-    void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
     
     /// Grounded flag for movement.
     bool onGround_;

+ 50 - 54
Source/Samples/19_VehicleDemo/Vehicle.cpp

@@ -34,9 +34,11 @@
 #include "Vehicle.h"
 
 Vehicle::Vehicle(Context* context) :
-    Component(context),
+    LogicComponent(context),
     steering_(0.0f)
 {
+    // Only the physics update event is needed: unsubscribe from the rest for optimization
+    SetUpdateEventMask(USE_FIXEDUPDATE);
 }
 
 void Vehicle::RegisterObject(Context* context)
@@ -54,12 +56,6 @@ void Vehicle::RegisterObject(Context* context)
     ATTRIBUTE(Vehicle, VAR_INT, "Rear Right Node", rearRightID_, 0, AM_DEFAULT | AM_NODEID);
 }
 
-void Vehicle::OnNodeSet(Node* node)
-{
-    if (node)
-        SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Vehicle, HandleFixedUpdate));
-}
-
 void Vehicle::ApplyAttributes()
 {
     // This function is called on each Serializable after the whole scene has been loaded. Reacquire wheel nodes from ID's
@@ -75,6 +71,53 @@ void Vehicle::ApplyAttributes()
     GetWheelComponents();
 }
 
+void Vehicle::FixedUpdate(float timeStep)
+{
+    float newSteering = 0.0f;
+    float accelerator = 0.0f;
+
+    // Read controls
+    if (controls_.buttons_ & CTRL_LEFT)
+        newSteering = -1.0f;
+    if (controls_.buttons_ & CTRL_RIGHT)
+        newSteering = 1.0f;
+    if (controls_.buttons_ & CTRL_FORWARD)
+        accelerator = 1.0f;
+    if (controls_.buttons_ & CTRL_BACK)
+        accelerator = -0.5f;
+
+    // When steering, wake up the wheel rigidbodies so that their orientation is updated
+    if (newSteering != 0.0f)
+    {
+        frontLeftBody_->Activate();
+        frontRightBody_->Activate();
+        steering_ = steering_ * 0.95f + newSteering * 0.05f;
+    }
+    else
+        steering_ = steering_ * 0.8f + newSteering * 0.2f;
+
+    // Set front wheel angles
+    Quaternion steeringRot(0, steering_ * MAX_WHEEL_ANGLE, 0);
+    frontLeftAxis_->SetOtherAxis(steeringRot * Vector3::LEFT);
+    frontRightAxis_->SetOtherAxis(steeringRot * Vector3::RIGHT);
+
+    Quaternion hullRot = hullBody_->GetRotation();
+    if (accelerator != 0.0f)
+    {
+        // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
+        Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
+        
+        frontLeftBody_->ApplyTorque(hullRot * steeringRot * torqueVec);
+        frontRightBody_->ApplyTorque(hullRot * steeringRot * torqueVec);
+        rearLeftBody_->ApplyTorque(hullRot * torqueVec);
+        rearRightBody_->ApplyTorque(hullRot * torqueVec);
+    }
+
+    // Apply downforce proportional to velocity
+    Vector3 localVelocity = hullRot.Inverse() * hullBody_->GetLinearVelocity();
+    hullBody_->ApplyForce(hullRot * Vector3::DOWN * Abs(localVelocity.z_) * DOWN_FORCE);
+}
+
 void Vehicle::Init()
 {
     // This function is called only from the main program when initially creating the vehicle, not on scene load
@@ -149,50 +192,3 @@ void Vehicle::GetWheelComponents()
     rearLeftBody_ = rearLeft_->GetComponent<RigidBody>();
     rearRightBody_ = rearRight_->GetComponent<RigidBody>();
 }
-
-void Vehicle::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
-{
-    float newSteering = 0.0f;
-    float accelerator = 0.0f;
-
-    // Read controls
-    if (controls_.buttons_ & CTRL_LEFT)
-        newSteering = -1.0f;
-    if (controls_.buttons_ & CTRL_RIGHT)
-        newSteering = 1.0f;
-    if (controls_.buttons_ & CTRL_FORWARD)
-        accelerator = 1.0f;
-    if (controls_.buttons_ & CTRL_BACK)
-        accelerator = -0.5f;
-
-    // When steering, wake up the wheel rigidbodies so that their orientation is updated
-    if (newSteering != 0.0f)
-    {
-        frontLeftBody_->Activate();
-        frontRightBody_->Activate();
-        steering_ = steering_ * 0.95f + newSteering * 0.05f;
-    }
-    else
-        steering_ = steering_ * 0.8f + newSteering * 0.2f;
-
-    // Set front wheel angles
-    Quaternion steeringRot(0, steering_ * MAX_WHEEL_ANGLE, 0);
-    frontLeftAxis_->SetOtherAxis(steeringRot * Vector3::LEFT);
-    frontRightAxis_->SetOtherAxis(steeringRot * Vector3::RIGHT);
-
-    Quaternion hullRot = hullBody_->GetRotation();
-    if (accelerator != 0.0f)
-    {
-        // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
-        Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
-        
-        frontLeftBody_->ApplyTorque(hullRot * steeringRot * torqueVec);
-        frontRightBody_->ApplyTorque(hullRot * steeringRot * torqueVec);
-        rearLeftBody_->ApplyTorque(hullRot * torqueVec);
-        rearRightBody_->ApplyTorque(hullRot * torqueVec);
-    }
-
-    // Apply downforce proportional to velocity
-    Vector3 localVelocity = hullRot.Inverse() * hullBody_->GetLinearVelocity();
-    hullBody_->ApplyForce(hullRot * Vector3::DOWN * Abs(localVelocity.z_) * DOWN_FORCE);
-}

+ 6 - 7
Source/Samples/19_VehicleDemo/Vehicle.h

@@ -22,8 +22,8 @@
 
 #pragma once
 
-#include "Component.h"
 #include "Controls.h"
+#include "LogicComponent.h"
 
 namespace Urho3D
 {
@@ -47,7 +47,7 @@ const float DOWN_FORCE = 10.0f;
 const float MAX_WHEEL_ANGLE = 22.5f;
 
 /// Vehicle component, responsible for physical movement according to controls.
-class Vehicle : public Component
+class Vehicle : public LogicComponent
 {
     OBJECT(Vehicle)
 
@@ -58,12 +58,12 @@ public:
     /// Register object factory and attributes.
     static void RegisterObject(Context* context);
     
-    /// Handle node being assigned.
-    virtual void OnNodeSet(Node* node);
     /// Perform post-load after deserialization. Acquire the components from the scene nodes.
     virtual void ApplyAttributes();
+    /// Handle physics world update. Called by LogicComponent base class.
+    virtual void FixedUpdate(float timeStep);
     
-    /// Initialize the vehicle. Create rendering and physics components.
+    /// Initialize the vehicle. Create rendering and physics components. Called by the application.
     void Init();
     
     /// Movement controls.
@@ -74,8 +74,7 @@ private:
     void InitWheel(const String& name, const Vector3& offset, WeakPtr<Node>& wheelNode, unsigned& wheelNodeID);
     /// Acquire wheel components from wheel scene nodes.
     void GetWheelComponents();
-    /// Handle physics world update event.
-    void HandleFixedUpdate(StringHash eventType, VariantMap& eventData);
+
     
     // Wheel scene nodes.
     WeakPtr<Node> frontLeft_;