소스 검색

Ninja movement, controls & camera update in ScriptTest.
Method pointer cache for ScriptInstance::execute().
Script interface additions & bugfixes.
Changed LineEdit & Text constructors to take the UI element name as the first argument.

Lasse Öörni 15 년 전
부모
커밋
b9250e7c04

+ 7 - 0
Bin/Data/Scripts/Controls.as

@@ -0,0 +1,7 @@
+const int CTRL_UP = 1;
+const int CTRL_DOWN = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+const int CTRL_FIRE = 16;
+const int CTRL_JUMP = 32;
+const int CTRL_ALL = 63;

+ 281 - 7
Bin/Data/Scripts/Ninja.as

@@ -1,15 +1,55 @@
+#include "Scripts/Controls.as"
+
+const int ANIM_MOVE = 1;
+const int ANIM_ATTACK = 2;
+
+const float mass = 80;
+const float friction = 0.5;
+const float moveForce = 500000;
+const float airMoveForce = 25000;
+const float dampingForce = 1000;
+const float jumpForce = 9000000;
+const Vector3 throwVelocity(0, 425, 2000);
+const Vector3 throwPosition(0, 20, 100);
+const float throwDelay = 0.1;
+
 class Ninja
 {
+    Controls controls;
+    Controls prevControls;
+    bool okToJump;
+    bool smoke;
+    bool onGround;
+    bool isSliding;
+    float inAirTime;
+    float onGroundTime;
+    float throwTime;
+    float deathTime;
+    float deathDir;
+    float dirChangeTime;
+    float aimX;
+    float aimY;
+
     void start()
     {
-        subscribeToEvent("Create", "handleCreate");
+        okToJump = false;
+        smoke = false;
+        onGround = false;
+        isSliding = false;
+        inAirTime = 1;
+        onGroundTime = 0;
+        throwTime = 0;
+        deathTime = 0;
+        deathDir = 0;
+        dirChangeTime = 0;
+        aimX = 0;
+        aimY = 0;
+
+        subscribeToEvent("EntityCollision", "handleEntityCollision");
     }
 
-    void handleCreate(StringHash eventType, VariantMap& eventData)
+    void create(const Vector3&in pos, const Quaternion&in rot)
     {
-        Vector3 pos = eventData["Pos"].getVector3();
-        Quaternion rot = eventData["Rot"].getQuaternion();
-
         // Create model
         AnimatedModel@ model = entity.createComponent("AnimatedModel");
         model.setModel(cache.getResource("Model", "Models/Ninja.mdl"));
@@ -26,13 +66,247 @@ class Ninja
         body.setPosition(pos);
         body.setRotation(rot);
         body.setMode(PHYS_DYNAMIC);
-        body.setMass(80);
-        body.setFriction(0.5);
+        body.setMass(mass);
+        body.setFriction(friction);
         body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Ninja.xml"));
         body.setCollisionGroup(1);
         body.setCollisionMask(3);
         body.setAngularMaxVelocity(0);
         body.addChild(model);
         model.setPosition(Vector3(0, -90, 0));
+
+        aimX = rot.getYaw();
+    }
+
+    void setControls(uint buttons, float yaw, float pitch)
+    {
+        controls.buttons = buttons;
+        controls.yaw = yaw;
+        controls.pitch = pitch;
+    }
+
+    Quaternion getAim()
+    {
+        Quaternion q;
+        q = q * Quaternion(aimX, Vector3(0, 1, 0));
+        q = q * Quaternion(aimY, Vector3(1, 0, 0));
+        return q;
+    }
+
+    void updateFixed(float time)
+    {
+        RigidBody@ body = entity.getComponent("RigidBody");
+        AnimationController@ controller = entity.getComponent("AnimationController");
+
+        // Turning / horizontal aiming
+        if (aimX != controls.yaw)
+        {
+            body.setActive(true);
+            aimX = controls.yaw;
+        }
+        // Vertical aiming
+        if (aimY != controls.pitch)
+        {
+            body.setActive(true);
+            aimY = controls.pitch;
+        }
+
+        // Force orientation to horizontal aim
+        Quaternion q;
+        q = q * Quaternion(aimX, Vector3(0, 1, 0));
+        body.setRotation(q);
+
+        // Movement ground/air
+        Vector3 vel = body.getLinearVelocity();
+        if (onGround)
+        {
+            inAirTime = 0;
+            onGroundTime += time;
+        }
+        else
+        {
+            onGroundTime = 0;
+            inAirTime += time;
+        }
+
+        if ((inAirTime < 0.3f) && (!isSliding))
+        {
+            bool sidemove = false;
+
+            // Movement in four directions
+            if (controls.isDown(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT))
+            {
+                float animDir = 1.0f;
+                Vector3 force(0, 0, 0);
+                if (controls.isDown(CTRL_UP))
+                    force += q * Vector3(0, 0, 1);
+                if (controls.isDown(CTRL_DOWN))
+                {
+                    animDir = -1.0f;
+                    force += q * Vector3(0, 0, -1);
+                }
+                if (controls.isDown(CTRL_LEFT))
+                {
+                    sidemove = true;
+                    force += q * Vector3(-1, 0, 0);
+                }
+                if (controls.isDown(CTRL_RIGHT))
+                {
+                    sidemove = true;
+                    force += q * Vector3(1, 0, 0);
+                }
+                // Normalize so that diagonal strafing isn't faster
+                force.normalize();
+                force *= moveForce;
+                body.applyForce(force);
+                
+                // Walk or sidestep animation
+                if (sidemove)
+                    controller.setAnimation("Models/Ninja_Stealth.ani", ANIM_MOVE, true, false, animDir * 2.2, 1.0, 0.2, 0.0, true);
+                else
+                    controller.setAnimation("Models/Ninja_Walk.ani", ANIM_MOVE, true, false, animDir * 1.6, 1.0, 0.2, 0.0, true);
+            }
+            else
+            {
+                // Idle animation
+                controller.setAnimation("Models/Ninja_Idle3.ani", ANIM_MOVE, true, false, 1.0, 1.0, 0.2, 0.0, true);
+            }
+
+            // Overall damping to cap maximum speed
+            body.applyForce(Vector3(-dampingForce * vel.x, 0, -dampingForce * vel.z));
+
+            // Jumping
+            if (controls.isDown(CTRL_JUMP))
+            {
+                if ((okToJump) && (inAirTime < 0.1f))
+                {
+                    // Lift slightly off the ground for better animation
+                    body.setPosition(body.getPhysicsPosition() + Vector3(0, 3, 0));
+                    body.applyForce(Vector3(0, jumpForce, 0));
+                    inAirTime = 1.0f;
+                    controller.setAnimation("Models/Ninja_JumpNoHeight.ani", ANIM_MOVE, false, true, 1.0f, 1.0f, 0.0f, 0.0f, true);
+                    okToJump = false;
+                }
+            }
+            else okToJump = true;
+        }
+        else
+        {
+            // Motion in the air
+            // Note: when sliding a steep slope, control (or damping) isn't allowed!
+            if ((inAirTime > 0.3f) && (!isSliding))
+            {
+                if (controls.isDown(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT))
+                {
+                    Vector3 force(0, 0, 0);
+                    if (controls.isDown(CTRL_UP))
+                        force += q * Vector3(0, 0, 1);
+                    if (controls.isDown(CTRL_DOWN))
+                        force += q * Vector3(0, 0, -1);
+                    if (controls.isDown(CTRL_LEFT))
+                        force += q * Vector3(-1, 0, 0);
+                    if (controls.isDown(CTRL_RIGHT))
+                        force += q * Vector3(1, 0, 0);
+                    // Normalize so that diagonal strafing isn't faster
+                    force.normalize();
+                    force *= airMoveForce;
+                    body.applyForce(force);
+                }
+            }
+            
+            // Falling/jumping/sliding animation
+            if (inAirTime > 0.01f)
+                controller.setAnimation("Models/Ninja_JumpNoHeight.ani", ANIM_MOVE, false, false, 1.0f, 1.0f, 0.2f, 0.0f, true);
+        }
+        
+        // Shooting
+        if (throwTime >= 0)
+            throwTime -= time;
+        
+        if ((controls.isPressed(CTRL_FIRE, prevControls)) && (throwTime <= 0))
+        {
+            Vector3 projectileVel = getAim() * throwVelocity;
+            
+            controller.setAnimation("Models/Ninja_Attack1.ani", ANIM_ATTACK, false, true, 1.0f, 0.75f, 0.0f, 0.0f, false);
+            controller.setFade("Models/Ninja_Attack1.ani", 0.0f, 0.5f);
+            controller.setPriority("Models/Ninja_Attack1.ani", 1);
+            
+            /*
+            // Do not spawn object for clientside prediction, only animate for now
+            if (!isProxy())
+            {
+                SnowBall* obj = spawnObject<SnowBall>("Snowball");
+                obj->create(getBody()->getPhysicsPosition() + time * vel + q * tThrowPosition, getAim());
+                obj->setSide(mSide);
+                obj->setOrigin(getEntity()->getID());
+                obj->getBody()->setLinearVelocity(projectileVel);
+
+                //! \todo the throw sound should be instant, and therefore predicted on client
+                playSound("Sounds/NutThrow.wav");
+            }
+            */
+            
+            throwTime = throwDelay;
+        }
+        
+        prevControls = controls;
+        
+        resetWorldCollision();
+    }
+    
+    void handleEntityCollision(StringHash eventType, VariantMap& eventData)
+    {
+        Entity@ otherEntity = eventData["OtherEntity"].getEntity();
+        // If the other entity does not have a ScriptInstance component, it's static world geometry
+        if (!otherEntity.hasComponent("ScriptInstance"))
+            handleWorldCollision(eventData);
+    }
+    
+    void handleWorldCollision(VariantMap& eventData)
+    {
+        RigidBody@ body = entity.getComponent("RigidBody");
+
+        VectorBuffer contacts = eventData["Contacts"].getBuffer();
+        while (!contacts.isEof())
+        {
+            Vector3 contactPosition = contacts.readVector3();
+            Vector3 contactNormal = contacts.readVector3();
+            float contactDepth = contacts.readFloat();
+            float contactVelocity = contacts.readFloat();
+
+            // If contact is below center and mostly vertical, assume it's ground contact
+            if (contactPosition.y < body.getPhysicsPosition().y)
+            {
+                float level = abs(contactNormal.y);
+                if (level > 0.75)
+                    onGround = true;
+                else
+                {
+                    // If contact is somewhere inbetween vertical/horizontal, is sliding a slope
+                    if (level > 0.1)
+                        isSliding = true;
+                }
+            }
+        }
+
+        // Ground contact has priority over sliding contact
+        if (onGround == true)
+            isSliding = false;
+    }
+    
+    void resetWorldCollision()
+    {
+        RigidBody@ body = entity.getComponent("RigidBody");
+        if (body.isActive())
+        {
+            onGround = false;
+            isSliding = false;
+        }
+        else
+        {
+            // If body is not active, assume it rests on the ground
+            onGround = true;
+            isSliding = false;
+        }
     }
 }

+ 92 - 6
Bin/Data/Scripts/NinjaSnowWar.as

@@ -1,3 +1,5 @@
+#include "Scripts/Controls.as"
+
 Scene@ gameScene;
 Camera@ gameCamera;
 Text@ scoreText;
@@ -5,6 +7,12 @@ Text@ hiscoreText;
 Text@ messageText;
 BorderImage@ healthBar;
 
+Controls playerControls;
+float mouseSensitivity = 0.125;
+float cameraMinDist = 25;
+float cameraMaxDist = 500;
+float cameraSafetyDist = 30;
+
 void init(Scene@ scene)
 {
     @gameScene = @scene;
@@ -18,8 +26,8 @@ void init(Scene@ scene)
     engine.createDebugHud();
     debugHud.setFont(cache.getResource("Font", "cour.ttf"), 12);
 
-    registerLocalOnlyEvent("Create");
     subscribeToEvent("Update", "handleUpdate");
+    subscribeToEvent("PostUpdate", "handlePostUpdate");
 }
 
 void initAudio()
@@ -106,11 +114,12 @@ void spawnPlayer()
     ScriptInstance@ instance = playerEntity.createComponent("ScriptInstance");
     instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/Ninja.as"), "Ninja");
 
-    // To work properly, the script object's methods must be executed in its own context. Therefore we don't (and can't) directly access the script object
-    VariantMap eventData;
-    eventData["Pos"] = Vector3(0, 90, 0);
-    eventData["Rot"] = Quaternion();
-    sendEvent(instance, "Create", eventData);
+    // To work properly, the script object's methods must be executed in its own context. Therefore we can't directly access the script object
+    array<Variant> arguments;
+    arguments.resize(2);
+    arguments[0] = Vector3(0, 90, 0);
+    arguments[1] = Quaternion();
+    instance.execute("void create(const Vector3&in, const Quaternion&in)", arguments);
 }
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
@@ -119,4 +128,81 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
         debugHud.toggleAll();
     if (input.getKeyPress(KEY_F2))
         engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
+
+    updateControls();
+}
+
+void handlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    updateCamera();
+}
+
+void updateControls()
+{
+    playerControls.set(CTRL_ALL, false);
+    
+    if (input.getKeyDown('W'))
+        playerControls.set(CTRL_UP, true);
+    if (input.getKeyDown('S'))
+        playerControls.set(CTRL_DOWN, true);
+    if (input.getKeyDown('A'))
+        playerControls.set(CTRL_LEFT, true);
+    if (input.getKeyDown('D'))
+        playerControls.set(CTRL_RIGHT, true);
+    if (input.getKeyDown(KEY_CONTROL))
+        playerControls.set(CTRL_FIRE, true);
+    if (input.getKeyDown(' '))
+        playerControls.set(CTRL_JUMP, true);
+    
+    if (input.getMouseButtonDown(MOUSEB_LEFT))
+        playerControls.set(CTRL_FIRE, true);
+    if (input.getMouseButtonDown(MOUSEB_RIGHT))
+        playerControls.set(CTRL_JUMP, true);
+        
+    playerControls.yaw += mouseSensitivity * input.getMouseMoveX();
+    playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
+    playerControls.pitch = clamp(playerControls.pitch, -60, 60);
+    
+    Entity@ playerEntity = gameScene.getEntity("ObjPlayer");
+    if (@playerEntity == null)
+        return;
+
+    ScriptInstance@ instance = playerEntity.getComponent("ScriptInstance");
+    array<Variant> arguments;
+    arguments.resize(3);
+    arguments[0] = playerControls.buttons;
+    arguments[1] = playerControls.yaw;
+    arguments[2] = playerControls.pitch;
+    instance.execute("void setControls(uint, float, float)", arguments);
+}
+
+void updateCamera()
+{
+    Entity@ playerEntity = gameScene.getEntity("ObjPlayer");
+    if (@playerEntity == null)
+        return;
+
+    RigidBody@ body = playerEntity.getComponent("RigidBody");
+    Vector3 pos = body.getWorldPosition();
+    // Use yaw & pitch from own controls for immediate response
+    Quaternion dir;
+    dir = dir * Quaternion(playerControls.yaw, Vector3(0, 1, 0));
+    dir = dir * Quaternion(playerControls.pitch, Vector3(1, 0, 0));
+
+    Vector3 aimPoint = pos + Vector3(0, 100, 0);
+    Vector3 minDist = aimPoint + dir * Vector3(0, 0, -cameraMinDist);
+    Vector3 maxDist = aimPoint + dir * Vector3(0, 0, -cameraMaxDist);
+    
+    // Collide camera ray with static objects
+    Vector3 rayDir = (maxDist - minDist).getNormalized();
+    float rayDistance = cameraMaxDist - cameraMinDist + cameraSafetyDist;
+    array<PhysicsRaycastResult> result = gameScene.getPhysicsWorld().raycast(Ray(minDist, rayDir), rayDistance, 2);
+    if (result.length() > 0)
+        rayDistance = min(rayDistance, result[0].distance - cameraSafetyDist);
+    
+    gameCamera.setPosition(minDist + rayDir * rayDistance);
+    gameCamera.setRotation(dir);
+
+    audio.setListenerPosition(pos);
+    audio.setListenerRotation(dir);
 }

+ 1 - 0
Engine/Engine/RegisterCommon.cpp

@@ -541,6 +541,7 @@ static void registerSerialization(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("VectorBuffer", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructVectorBuffer), asCALL_CDECL_OBJLAST);
     registerSerializer<VectorBuffer>(engine, "VectorBuffer");
     registerDeserializer<VectorBuffer>(engine, "VectorBuffer");
+    engine->RegisterObjectMethod("VectorBuffer", "VectorBuffer &opAssign(const VectorBuffer& in)", asMETHOD(VectorBuffer, operator =), asCALL_THISCALL);
     engine->RegisterObjectMethod("VectorBuffer", "void setData(Deserializer@+, uint)", asFUNCTION(VectorBufferSetData), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("VectorBuffer", "void clear()", asMETHOD(VectorBuffer, clear), asCALL_THISCALL);
     engine->RegisterObjectMethod("VectorBuffer", "void resize(uint)", asMETHOD(VectorBuffer, resize), asCALL_THISCALL);

+ 4 - 0
Engine/Engine/RegisterInput.cpp

@@ -173,6 +173,10 @@ static void registerControls(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Controls", "void read(Deserializer&)", asMETHOD(Controls, read), asCALL_THISCALL);
     engine->RegisterObjectMethod("Controls", "void writeXML(XMLElement&)", asMETHOD(Controls, writeXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Controls", "void readXML(const XMLElement&)", asMETHOD(Controls, readXML), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Controls", "void reset()", asMETHOD(Controls, reset), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Controls", "void set(uint, bool)", asMETHOD(Controls, set), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Controls", "bool isDown(uint)", asMETHOD(Controls, isDown), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Controls", "bool isPressed(uint, const Controls& in)", asMETHOD(Controls, isPressed), asCALL_THISCALL);
     engine->RegisterObjectProperty("Controls", "uint buttons", offsetof(Controls, mButtons));
     engine->RegisterObjectProperty("Controls", "float yaw", offsetof(Controls, mYaw));
     engine->RegisterObjectProperty("Controls", "float pitch", offsetof(Controls, mPitch));

+ 1 - 0
Engine/Engine/RegisterMath.cpp

@@ -71,6 +71,7 @@ static void registerMathFunctions(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("float acos(float)", asFUNCTION(ACos), asCALL_CDECL);
     engine->RegisterGlobalFunction("float atan(float)", asFUNCTION(ATan), asCALL_CDECL);
     engine->RegisterGlobalFunction("float atan2(float, float)", asFUNCTION(ATan2), asCALL_CDECL);
+    engine->RegisterGlobalFunction("float abs(float)", asFUNCTION(fabsf), asCALL_CDECL);
     engine->RegisterGlobalFunction("float sqrt(float)", asFUNCTION(sqrtf), asCALL_CDECL);
     engine->RegisterGlobalFunction("float pow(float)", asFUNCTION(powf), asCALL_CDECL);
     engine->RegisterGlobalFunction("float random()", asFUNCTIONPR(random, (), float), asCALL_CDECL);

+ 10 - 2
Engine/Script/ScriptInstance.cpp

@@ -312,8 +312,15 @@ bool ScriptInstance::execute(const std::string& declaration, const std::vector<V
 {
     if ((!mScriptFile) || (!mScriptObject))
         return false;
-    
-    return mScriptFile->execute(mScriptObject, declaration, mScriptContext, parameters);
+    std::map<std::string, asIScriptFunction*>::const_iterator i = mExecuteCache.find(declaration);
+    if (i == mExecuteCache.end())
+    {
+        asIScriptFunction* method = mScriptFile->getMethod(mScriptObject, declaration);
+        mExecuteCache[declaration] = method;
+        return mScriptFile->execute(mScriptObject, method, mScriptContext, parameters);
+    }
+    else
+        return mScriptFile->execute(mScriptObject, i->second, mScriptContext, parameters);
 }
 
 bool ScriptInstance::execute(asIScriptFunction* method, const std::vector<Variant>& parameters)
@@ -366,6 +373,7 @@ void ScriptInstance::clearMethods()
 {
     for (unsigned i = 0; i < MAX_SCRIPT_METHODS; ++i)
         mMethods[i] = 0;
+    mExecuteCache.clear();
 }
 
 void ScriptInstance::getSupportedMethods()

+ 3 - 1
Engine/Script/ScriptInstance.h

@@ -150,7 +150,9 @@ private:
     std::string mClassName;
     //! Pointers to supported inbuilt methods
     asIScriptFunction* mMethods[MAX_SCRIPT_METHODS];
-
+    //! Cache of executed functions
+    std::map<std::string, asIScriptFunction*> mExecuteCache;
+    
     //! Enabled flag
     bool mEnabled;
 };

+ 2 - 2
Engine/UI/BaseUIElementFactory.cpp

@@ -44,7 +44,7 @@ UIElement* BaseUIElementFactory::createElement(ShortStringHash type, const std::
     if (type == Cursor::getTypeStatic())
         return new Cursor(name);
     if (type == LineEdit::getTypeStatic())
-        return new LineEdit(std::string(), name);
+        return new LineEdit(name);
     if (type == MenuItem::getTypeStatic())
         return new MenuItem(name);
     if (type == ScrollView::getTypeStatic())
@@ -52,7 +52,7 @@ UIElement* BaseUIElementFactory::createElement(ShortStringHash type, const std::
     if (type == Slider::getTypeStatic())
         return new Slider(name);
     if (type == Text::getTypeStatic())
-        return new Text(std::string(), name);
+        return new Text(name);
     if (type == UIElement::getTypeStatic())
         return new UIElement(name);
     if (type == Window::getTypeStatic())

+ 2 - 2
Engine/UI/LineEdit.cpp

@@ -28,7 +28,7 @@
 
 #include "DebugNew.h"
 
-LineEdit::LineEdit(const std::string& text, const std::string& name) :
+LineEdit::LineEdit(const std::string& name, const std::string& text) :
     BorderImage(name),
     mEchoCharacter(0),
     mCursorBlinkRate(1.0f),
@@ -44,7 +44,7 @@ LineEdit::LineEdit(const std::string& text, const std::string& name) :
     addChild(mText);
     addChild(mCursor);
     
-    // Show cursor on top of text, and set initial text
+    // Show cursor on top of text
     mCursor->setPriority(1);
     setText(text);
 }

+ 2 - 2
Engine/UI/LineEdit.h

@@ -33,8 +33,8 @@ class LineEdit : public BorderImage
     DEFINE_TYPE(LineEdit);
     
 public:
-    //! Construct with initial text and name
-    LineEdit(const std::string& text = std::string(), const std::string& name = std::string());
+    //! Construct with name and initial text
+    LineEdit(const std::string& name = std::string(), const std::string& text = std::string());
     //! Destruct
     virtual ~LineEdit();
     

+ 1 - 1
Engine/UI/Text.cpp

@@ -32,7 +32,7 @@
 
 #include "DebugNew.h"
 
-Text::Text(const std::string& text, const std::string& name) :
+Text::Text(const std::string& name, const std::string& text) :
     UIElement(name),
     mFontSize(DEFAULT_FONT_SIZE),
     mMaxWidth(0),

+ 2 - 2
Engine/UI/Text.h

@@ -36,8 +36,8 @@ class Text : public UIElement
     DEFINE_TYPE(Text);
     
 public:
-    //! Construct with initial text and name
-    Text(const std::string& text = std::string(), const std::string& name = std::string());
+    //! Construct with name and initial text
+    Text(const std::string& name = std::string(), const std::string& text = std::string());
     //! Destruct
     virtual ~Text();