In this tutorial we will make a newcomponent, which will be moving an entity to way points and movement start will be activated by trigger zone
Let's start with making WayPoint component:
In the Ultra Editor click plus button in Project tab:
Now open Visual Studio. Refresh Soution Editor if it was already open to see new component files in Source/Component/Logic folder.
Include into project WayPoint.h and WayPoint.cpp.
Add WayPoint include to Source/ComponentSystem.h
#include "Components/Logic/WayPoint.h"
In RegisterComponents() add:
RegisterComponent<WayPoint>();
You might do RegisterComponent first and add include with VS help
Open WayPoint.json
- "properties" is a list of component's fields avaible for edit in the Editor
- "name" - actual name of property that will be used in code later
- "label" - just a name to display in the Editor
- "value" - initial value that property will have after map load by default. Can be changed in the Editor for specific entity.
- "options" - allows to choose int value in Combo Box in the Editor. First option - 0 value
- Default value here also defines type of this property.
- New component made via editor have all possible types.
Replace WayPoint.json content with:
{ "component": { "properties": [ { "name": "nextPoint", "label": "Next point", "value": null }, { "name": "doStayOnPoint", "label": "Do stay on point", "value": false } ] } }
nextPoint - another WayPoint, where platform will move to once reach this one
doStayOnPoint - wait for command before moving to next WayPoint
Take a note that the Editor sees only json which could be same for LUA and C++ projects which allows to work on same map even if people have different engine versions (Pro/Standard) or make a level before programming components.
Replace WayPoint.h content with:
#pragma once #include "UltraEngine.h" using namespace UltraEngine; class WayPoint : public Component { protected: //another WayPoint's entity, where platform will move to once reaching this one //it should be weak pointer for avoiding circular dependency when entities can't be removed from scene because they keep shared pointers to each other std::weak_ptr<Entity> nextPoint; public: //wait for command before moving to next WayPoint bool doStayOnPoint; WayPoint(); //override specifier ensures that the method is virtual and is overriding a virtual function from a base class //it means that this class method will be called even if class instance was casted to base class //it allows to change class behaviour from base one //Start is called when Load() of all components was called already void Start() override; //will be called on map load bool Load(table& t, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) override; //Can be used to save current component state on map save bool Save(table& t, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) override; //get next WayPoint's entity shared_ptr<Entity> getNextPoint() const; //can be used to specify what should and what should not be copied to new class instance shared_ptr<Component> Copy() override; };
Replace WayPoint.cpp content with:
#pragma once #include "UltraEngine.h" #include "WayPoint.h" using namespace UltraEngine; WayPoint::WayPoint() { //name should always match class name for correct component work name = "WayPoint"; doStayOnPoint = false; } void WayPoint::Start() { //empty for this case } bool WayPoint::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) { //internally entity saves in the Editor as String unique id //can be empty if this way point is final if (properties["nextPoint"].is_string()) { std::string id = properties["nextPoint"]; nextPoint = scene->GetEntity(id); if (properties["doStayOnPoint"].is_boolean()) { doStayOnPoint = properties["doStayOnPoint"]; } } return true; } bool WayPoint::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) { if (nextPoint.lock()) { properties["nextPoint"] = nextPoint.lock()->GetUuid(); properties["doStayOnPoint"] = doStayOnPoint; } return true; } shared_ptr<Entity> WayPoint::getNextPoint() const { return nextPoint.lock(); } shared_ptr<Component> WayPoint::Copy() { return std::make_shared<WayPoint>(*this); }
Let's create our elevator/floating platfom component and call it WayMover
WayMover.json:
{ "component": { "properties": [ { "name": "moveSpeed", "label": "Move Speed", "value": 4.0 }, { "name": "nextPoint", "label": "Next point", "value": null }, { "name": "doDeleteAfterMovement", "label": "Del after move", "value": false } ], "inputs": [ { "name": "DoMove" } ], "outputs": [ { "name": "EndMove" } ] } }
- moveSpeed - how fast entity will move to way point
- doDeleteAfterMovement - auto remove entity when it's reach final waypoint. Can be used for door, that goes into walls or floor
- inputs - it's commands for components, that usually triggered by another components via flowgrough
- outputs - commands that component sends to other components inputs via FireOutputs("EndMove"); in the component code
WayMover.h:
#pragma once #include "UltraEngine.h" using namespace UltraEngine; class WayMover : public Component { protected: float moveSpeed; bool isMoving; std::weak_ptr<Entity> nextPoint; bool doDeleteAfterMovement; //need scene pointer to remove entity if doDeleteAfterMovement true //should be weak_ptr to avoid circular dependency std::weak_ptr<Map> sceneWeak; public: WayMover(); std::shared_ptr<Component> Copy() override; bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) override; bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) override; //CallMethod procceds input signals std::any CallMethod(shared_ptr<Component> caller, const WString& name, const std::vector<std::any>& args) override; void Update() override; //called when reaching next WayPoint void moveEnd(); };
WayMover.cpp:
#pragma once #include "UltraEngine.h" #include "WayMover.h" #include "../Logic/WayPoint.h" using namespace UltraEngine; WayMover::WayMover() { name = "WayMover"; moveSpeed = 4.0f; isMoving = false; doDeleteAfterMovement = false; } shared_ptr<Component> WayMover::Copy() { return std::make_shared<WayMover>(*this); } bool WayMover::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) { if (properties["moveSpeed"].is_number()) { moveSpeed = properties["moveSpeed"]; } if (properties["moveSpeed"].is_boolean()) { isMoving = properties["isMoving"]; } if (properties["doDeleteAfterMovement"].is_boolean()) { doDeleteAfterMovement = properties["doDeleteAfterMovement"]; } if (properties["nextPoint"].is_string()) { std::string id = properties["nextPoint"]; nextPoint = scene->GetEntity(id); } sceneWeak = scene; return Component::Load(properties, binstream, scene, flags, extra); } bool WayMover::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) { properties["moveSpeed"] = moveSpeed; properties["isMoving"] = isMoving; properties["doDeleteAfterMovement"] = doDeleteAfterMovement; if (nextPoint.lock()) { properties["nextPoint"] = nextPoint.lock()->GetUuid(); } return Component::Save(properties, binstream, scene, flags, extra); } std::any WayMover::CallMethod(shared_ptr<Component> caller, const WString& name, const std::vector<std::any>& args) { //start moving by triggering by another component if (name == "DoMove") { isMoving = true; } return false; } void WayMover::Update() { if (!isMoving) { return; } auto entity = GetEntity(); auto wayPoint = nextPoint.lock(); if (!entity || !wayPoint) { return; } //60 HZ game loop, change to own value if different to keep same final speed float speed = moveSpeed / 60.0f; auto targetPosition = wayPoint->GetPosition(true); //moving to point with same speed directly to point no matter which axis auto pos = entity->GetPosition(true); float distanceX = abs(targetPosition.x - pos.x); float distanceY = abs(targetPosition.y - pos.y); float distanceZ = abs(targetPosition.z - pos.z); float biggestDelta = distanceZ; if (distanceX > distanceY && distanceX > distanceZ) { biggestDelta = distanceX; } else if (distanceY > distanceX && distanceY > distanceZ) { biggestDelta = distanceY; } float moveX = MoveTowards(pos.x, targetPosition.x, speed * (distanceX / biggestDelta)); float moveY = MoveTowards(pos.y, targetPosition.y, speed * (distanceY / biggestDelta)); float moveZ = MoveTowards(pos.z, targetPosition.z, speed * (distanceZ / biggestDelta)); entity->SetPosition(moveX, moveY, moveZ); if (entity->GetPosition(true) == targetPosition) { moveEnd(); } } void WayMover::moveEnd() { auto wayPoint = nextPoint.lock(); bool doStay = false; if (wayPoint) { doStay = wayPoint->GetComponent<WayPoint>()->doStayOnPoint; wayPoint = wayPoint->GetComponent<WayPoint>()->getNextPoint(); nextPoint = wayPoint; } if (doStay || !wayPoint) { isMoving = false; FireOutputs("EndMove"); //deleting entity if need to, after reaching final way point if (!doStay && !wayPoint && doDeleteAfterMovement && !sceneWeak.expired()) { auto scene = sceneWeak.lock(); scene->RemoveEntity(GetEntity()); } } }
Now we can use just made component in practice.
One of things that can be made is door or secret wall activated by player actions and this door will move a little bit inward and then to the side inside of wall. After that invisible now door will be removed.
- Create a walls with a empty place between them.
- Create couple of Empty/pivots and attach WayPoints to them.
- First WayPoint place a same place where door will be, but offset a bit deep into.
- In Scene tab grab and drag 2nd WayPoint to Nex Point field of 1st WayPoint.
- Place 2nd WayPoint insde of the wall.
- Create a door between walls. Attach WayMover component to it.
- Grab and drag 1st WayPoint to door's WayMover Next Point field.
- Enable "Del after Move" in WayMover component
- Create a box before door, make its collision type a trigger:
- Add Collision Trigger component to it.
- Open Flowgraph (2nd button at left side of the Editor).
- Drag and Drop trigger and door to it from Scene tab.
In different order, but same result in video format:
Result should looks something like that in game:
New components and map: NewComponentsTutorFiles.zip
On GitHub: https://github.com/Dreikblack/CppTutorialProject/tree/2-making-and-using-components
- 1
0 Comments
Recommended Comments
There are no comments to display.