Initial work for serializing Node / Component handles from a script object automatically.
This commit is contained in:
parent
1034f8856f
commit
dda39f2b7d
@ -240,9 +240,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
characterNode = scene_.GetChild("Jack", true);
|
||||
if (characterNode is null)
|
||||
return;
|
||||
character = cast<Character>(characterNode.scriptObject);
|
||||
if (character is null)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -297,8 +294,8 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
//
|
||||
// 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.
|
||||
// (yaw and pitch inside the Controls object) so we write manual binary format load / save methods for them. These functions
|
||||
// will be called by ScriptInstance when the script object is being loaded or saved.
|
||||
class Character : ScriptObject
|
||||
{
|
||||
// Character controls.
|
||||
|
@ -116,7 +116,7 @@ void CreateVehicle()
|
||||
{
|
||||
vehicleNode = scene_.CreateChild("Vehicle");
|
||||
vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
|
||||
|
||||
|
||||
// Create the vehicle logic script object
|
||||
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
|
||||
// Create the rendering and physics components
|
||||
@ -127,8 +127,11 @@ void CreateInstructions()
|
||||
{
|
||||
// Construct new Text object, set string to display and font to use
|
||||
Text@ instructionText = ui.root.CreateChild("Text");
|
||||
instructionText.text = "Use WASD keys to drive, mouse to rotate camera";
|
||||
instructionText.text = "Use WASD keys to drive, mouse to rotate camera\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;
|
||||
@ -140,7 +143,7 @@ void SubscribeToEvents()
|
||||
{
|
||||
// Subscribe to Update event for setting the vehicle controls before physics simulation
|
||||
SubscribeToEvent("Update", "HandleUpdate");
|
||||
|
||||
|
||||
// Subscribe to PostUpdate event for updating the camera position after physics simulation
|
||||
SubscribeToEvent("PostUpdate", "HandlePostUpdate");
|
||||
}
|
||||
@ -149,7 +152,7 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (vehicleNode is null)
|
||||
return;
|
||||
|
||||
|
||||
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
|
||||
if (vehicle is null)
|
||||
return;
|
||||
@ -161,12 +164,27 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
vehicle.controls.Set(CTRL_BACK, input.keyDown['S']);
|
||||
vehicle.controls.Set(CTRL_LEFT, input.keyDown['A']);
|
||||
vehicle.controls.Set(CTRL_RIGHT, input.keyDown['D']);
|
||||
|
||||
|
||||
// Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
|
||||
vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
|
||||
vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
|
||||
// Limit pitch
|
||||
vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
|
||||
|
||||
// Check for loading / saving the scene
|
||||
if (input.keyPress[KEY_F5])
|
||||
{
|
||||
File saveFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_WRITE);
|
||||
scene_.SaveXML(saveFile);
|
||||
}
|
||||
if (input.keyPress[KEY_F7])
|
||||
{
|
||||
File loadFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_READ);
|
||||
scene_.LoadXML(loadFile);
|
||||
// After loading we have to reacquire the vehicle scene node, as it has been recreated
|
||||
// Simply find by name as there's only one of them
|
||||
vehicleNode = scene_.GetChild("Vehicle", true);
|
||||
}
|
||||
}
|
||||
else
|
||||
vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT, false);
|
||||
@ -176,7 +194,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (vehicleNode is null)
|
||||
return;
|
||||
|
||||
|
||||
Vehicle@ vehicle = cast<Vehicle>(vehicleNode.scriptObject);
|
||||
if (vehicle is null)
|
||||
return;
|
||||
@ -185,7 +203,7 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
|
||||
dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
|
||||
dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
|
||||
Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
|
||||
Vector3 cameraStartPos = vehicleNode.position;
|
||||
|
||||
@ -202,6 +220,11 @@ void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
}
|
||||
|
||||
// Vehicle script object class
|
||||
//
|
||||
// When saving, the node and component handles are automatically converted into nodeID or componentID attributes
|
||||
// and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
|
||||
// The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
|
||||
|
||||
class Vehicle : ScriptObject
|
||||
{
|
||||
Node@ frontLeft;
|
||||
@ -221,6 +244,18 @@ class Vehicle : ScriptObject
|
||||
// Vehicle controls.
|
||||
Controls controls;
|
||||
|
||||
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 Init()
|
||||
{
|
||||
StaticModel@ hullObject = node.CreateComponent("StaticModel");
|
||||
@ -236,7 +271,7 @@ class Vehicle : ScriptObject
|
||||
hullBody.linearDamping = 0.2f; // Some air resistance
|
||||
hullBody.angularDamping = 0.5f;
|
||||
hullBody.collisionLayer = 1;
|
||||
|
||||
|
||||
frontLeft = InitWheel("FrontLeft", Vector3(-0.6f, -0.4f, 0.3f));
|
||||
frontRight = InitWheel("FrontRight", Vector3(0.6f, -0.4f, 0.3f));
|
||||
rearLeft = InitWheel("RearLeft", Vector3(-0.6f, -0.4f, -0.3f));
|
||||
@ -282,7 +317,7 @@ class Vehicle : ScriptObject
|
||||
wheelConstraint.lowLimit = Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
|
||||
wheelConstraint.highLimit = Vector2(180.0f, 0.0f);
|
||||
wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
|
||||
|
||||
|
||||
return wheelNode;
|
||||
}
|
||||
|
||||
@ -319,7 +354,7 @@ class Vehicle : ScriptObject
|
||||
{
|
||||
// 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(node.rotation * steeringRot * torqueVec);
|
||||
frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
|
||||
rearLeftBody.ApplyTorque(node.rotation * torqueVec);
|
||||
|
@ -409,7 +409,8 @@ Note that these are not actual Node member functions on the C++ side, as the %Sc
|
||||
|
||||
\section Scripting_ObjectSerialization Script object serialization
|
||||
|
||||
After instantiation, the script object's public member variables that can be converted into Variant, and that don't begin with an underscore are automatically available as attributes of the ScriptInstance, and will be serialized. Note: this means that a ScriptInstance's attribute list changes dynamically depending on the class that has been instantiated.
|
||||
After instantiation, the script object's public member variables that can be converted into Variant, and that don't begin with an underscore are automatically available as attributes of the ScriptInstance, and will be serialized.
|
||||
Node and Component handles are also converted into nodeID and componentID attributes automatically. Note: this automatic attribute mechanism means that a ScriptInstance's attribute list changes dynamically depending on the class that has been instantiated.
|
||||
|
||||
If the script object contains more complex data structures, you can also serialize and deserialize into a binary buffer manually by implementing the Load() and Save() methods.
|
||||
|
||||
|
@ -57,6 +57,8 @@ static const char* methodDeclarations[] = {
|
||||
"void ApplyAttributes()"
|
||||
};
|
||||
|
||||
extern const char* UI_CATEGORY;
|
||||
|
||||
ScriptInstance::ScriptInstance(Context* context) :
|
||||
Component(context),
|
||||
script_(GetSubsystem<Script>()),
|
||||
@ -91,10 +93,68 @@ void ScriptInstance::RegisterObject(Context* context)
|
||||
ACCESSOR_ATTRIBUTE(ScriptInstance, VAR_BUFFER, "Script Network Data", GetScriptNetworkDataAttr, SetScriptNetworkDataAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_NET | AM_NOEDIT);
|
||||
}
|
||||
|
||||
void ScriptInstance::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
|
||||
{
|
||||
if (attr.mode_ & (AM_NODEID | AM_COMPONENTID))
|
||||
{
|
||||
// The component / node to which the ID refers to may not be in scene yet. Delay searching for it to ApplyAttributes
|
||||
idAttributes_[const_cast<AttributeInfo*>(&attr)] = src.GetUInt();
|
||||
}
|
||||
else
|
||||
Serializable::OnSetAttribute(attr, src);
|
||||
}
|
||||
|
||||
void ScriptInstance::OnGetAttribute(const AttributeInfo& attr, Variant& dest) const
|
||||
{
|
||||
// Get ID's of node / component handle attributes
|
||||
if (attr.mode_ & AM_NODEID)
|
||||
{
|
||||
Node* node = *(reinterpret_cast<Node**>(attr.ptr_));
|
||||
unsigned nodeID = node ? node->GetID() : 0;
|
||||
dest = Variant(nodeID);
|
||||
}
|
||||
else if (attr.mode_ & AM_COMPONENTID)
|
||||
{
|
||||
Component* component = *(reinterpret_cast<Component**>(attr.ptr_));
|
||||
unsigned componentID = component ? component->GetID() : 0;
|
||||
dest = Variant(componentID);
|
||||
}
|
||||
else
|
||||
Serializable::OnGetAttribute(attr, dest);
|
||||
}
|
||||
|
||||
void ScriptInstance::ApplyAttributes()
|
||||
{
|
||||
// Apply node / component ID's now
|
||||
for (HashMap<AttributeInfo*, unsigned>::Iterator i = idAttributes_.Begin(); i != idAttributes_.End(); ++i)
|
||||
{
|
||||
AttributeInfo& attr = *i->first_;
|
||||
if (attr.mode_ & AM_NODEID)
|
||||
{
|
||||
Node*& nodePtr = *(reinterpret_cast<Node**>(attr.ptr_));
|
||||
// Decrease reference count of the old object, then increment the new
|
||||
if (nodePtr)
|
||||
nodePtr->ReleaseRef();
|
||||
nodePtr = GetScene()->GetNode(i->second_);
|
||||
if (nodePtr)
|
||||
nodePtr->AddRef();
|
||||
}
|
||||
else if (attr.mode_ & AM_COMPONENTID)
|
||||
{
|
||||
Component*& componentPtr = *(reinterpret_cast<Component**>(attr.ptr_));
|
||||
if (componentPtr)
|
||||
componentPtr->ReleaseRef();
|
||||
componentPtr = GetScene()->GetComponent(i->second_);
|
||||
if (componentPtr)
|
||||
componentPtr->AddRef();
|
||||
}
|
||||
}
|
||||
|
||||
idAttributes_.Clear();
|
||||
|
||||
if (scriptObject_ && methods_[METHOD_APPLYATTRIBUTES])
|
||||
scriptFile_->Execute(scriptObject_, methods_[METHOD_APPLYATTRIBUTES]);
|
||||
|
||||
}
|
||||
|
||||
void ScriptInstance::OnSetEnabled()
|
||||
@ -428,14 +488,15 @@ void ScriptInstance::GetScriptMethods()
|
||||
|
||||
void ScriptInstance::GetScriptAttributes()
|
||||
{
|
||||
asIScriptEngine* engine = GetSubsystem<Script>()->GetScriptEngine();
|
||||
attributeInfos_ = *context_->GetAttributes(GetTypeStatic());
|
||||
|
||||
|
||||
unsigned numProperties = scriptObject_->GetPropertyCount();
|
||||
for (unsigned i = 0; i < numProperties; ++i)
|
||||
{
|
||||
const char* name;
|
||||
int typeId;
|
||||
bool isPrivate;
|
||||
bool isPrivate, isHandle;
|
||||
|
||||
scriptObject_->GetObjectType()->GetProperty(i, &name, &typeId, &isPrivate);
|
||||
|
||||
@ -443,28 +504,80 @@ void ScriptInstance::GetScriptAttributes()
|
||||
if (isPrivate || name[0] == '_')
|
||||
continue;
|
||||
|
||||
String typeName = engine->GetTypeDeclaration(typeId);
|
||||
isHandle = typeName.EndsWith("@");
|
||||
if (isHandle)
|
||||
typeName = typeName.Substring(0, typeName.Length() - 1);
|
||||
|
||||
AttributeInfo info;
|
||||
info.name_ = name;
|
||||
info.ptr_ = scriptObject_->GetAddressOfProperty(i);
|
||||
|
||||
switch (typeId)
|
||||
if (!isHandle)
|
||||
{
|
||||
case asTYPEID_BOOL:
|
||||
info.type_ = VAR_BOOL;
|
||||
break;
|
||||
switch (typeId)
|
||||
{
|
||||
case asTYPEID_BOOL:
|
||||
info.type_ = VAR_BOOL;
|
||||
break;
|
||||
|
||||
case asTYPEID_INT32:
|
||||
case asTYPEID_UINT32:
|
||||
info.type_ = VAR_INT;
|
||||
break;
|
||||
|
||||
case asTYPEID_FLOAT:
|
||||
info.type_ = VAR_FLOAT;
|
||||
break;
|
||||
|
||||
default:
|
||||
info.type_ = Variant::GetTypeFromName(typeName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For a handle type, check if it's an Object subclass with a registered factory
|
||||
ShortStringHash typeHash(typeName);
|
||||
|
||||
case asTYPEID_INT32:
|
||||
case asTYPEID_UINT32:
|
||||
info.type_ = VAR_INT;
|
||||
break;
|
||||
|
||||
case asTYPEID_FLOAT:
|
||||
info.type_ = VAR_FLOAT;
|
||||
break;
|
||||
|
||||
default:
|
||||
info.type_ = Variant::GetTypeFromName(GetSubsystem<Script>()->GetScriptEngine()->GetTypeDeclaration(typeId));
|
||||
break;
|
||||
if (context_->GetObjectFactories().Find(typeHash) != context_->GetObjectFactories().End())
|
||||
{
|
||||
// There are three possibilities: the handle is for a Node, a Component or UIElement subclass
|
||||
// We want to identify nodes and components so that we can store their ID's for serialization
|
||||
const HashMap<String, Vector<ShortStringHash> >& categories = context_->GetObjectCategories();
|
||||
if (categories.Contains(UI_CATEGORY))
|
||||
{
|
||||
const Vector<ShortStringHash>& uiCategory = categories.Find(UI_CATEGORY)->second_;
|
||||
if (uiCategory.Contains(typeHash))
|
||||
continue; // Is handle to a UIElement; can not handle those
|
||||
}
|
||||
|
||||
if (typeHash == Node::GetTypeStatic() || typeHash == Scene::GetTypeStatic())
|
||||
{
|
||||
info.mode_ |= AM_NODEID;
|
||||
info.type_ = VAR_INT;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundComponent = false;
|
||||
// If is found in one of the component categories, must be a component
|
||||
for (HashMap<String, Vector<ShortStringHash> >::ConstIterator i = categories.Begin(); i != categories.End();
|
||||
++i)
|
||||
{
|
||||
if (i->second_.Contains(typeHash))
|
||||
{
|
||||
foundComponent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundComponent)
|
||||
{
|
||||
info.mode_ |= AM_COMPONENTID;
|
||||
info.type_ = VAR_INT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.type_ != VAR_NONE)
|
||||
|
@ -80,6 +80,10 @@ public:
|
||||
/// Register object factory.
|
||||
static void RegisterObject(Context* context);
|
||||
|
||||
/// Handle attribute write access.
|
||||
virtual void OnSetAttribute(const AttributeInfo& attr, const Variant& src);
|
||||
/// Handle attribute read access.
|
||||
virtual void OnGetAttribute(const AttributeInfo& attr, Variant& dest) const;
|
||||
/// Return attribute descriptions, or null if none defined.
|
||||
virtual const Vector<AttributeInfo>* GetAttributes() const { return &attributeInfos_; }
|
||||
/// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
|
||||
@ -190,6 +194,8 @@ private:
|
||||
Vector<DelayedMethodCall> delayedMethodCalls_;
|
||||
/// Attributes, including script object variables.
|
||||
Vector<AttributeInfo> attributeInfos_;
|
||||
/// Storage for unapplied node and component ID attributes
|
||||
HashMap<AttributeInfo*, unsigned> idAttributes_;
|
||||
/// Subscribed to scene update events flag.
|
||||
bool subscribed_;
|
||||
/// Subscribed to scene post and fixed update events flag.
|
||||
|
Loading…
Reference in New Issue
Block a user