Jump to content

C++ Ultra Beginner's Guide #2 - making and using components


Dreikblack

68 views

 Share

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:

image.png.355c2d89b135cce5094886403033f78f.png

image.png.03e76af2e254a875df6f163c346af0f5.png

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

image.png.a2c61c8b402a4062258a9f38d6ba8633.png

image.png.82d96c361a437fc2469614e5083ed825.png

 

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

image.png.62ba62e2efdcdf1ac315143b2f35cb95.png

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.

  1. Create a walls with a empty place between them.
  2. Create couple of Empty/pivots and attach WayPoints to them.
  3. First WayPoint place a same place where door will be, but offset a bit deep into.
  4. In Scene tab grab and drag 2nd WayPoint to Nex Point field of 1st WayPoint.
  5. Place 2nd WayPoint insde of the wall.
  6. Create a door between walls. Attach WayMover component to it.
  7. Grab and drag 1st WayPoint to door's WayMover Next Point field.
  8. Enable "Del after Move" in WayMover component
  9. Create a box before door, make its collision type a trigger:
  10. image.png.48f0e402b94d172fff5795c2718e2e73.png
  11. Add Collision Trigger component to it.
  12. Open Flowgraph (2nd button at left side of the Editor).
  13. 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

  • Like 1
 Share

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...