Jump to content

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


Dreikblack

232 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.355c2d89b135cce5094886403033f7

image.png.03e76af2e254a875df6f163c346af0

Now open Visual Studio Code.

 

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.lua content with:

 

WayPoint = {}
-- name should always match class name for correct component work
WayPoint.name = "WayPoint"
WayPoint.nextPoint = nil
--  wait for command before moving to next WayPoint
WayPoint.doStayOnPoint = false

-- Start is called when Load() of all components was called already
function WayPoint:Start()

end

-- will be called on map load
function WayPoint:Load(properties, binstream, scene, flags, extra)
    -- internally entity saves in the Editor as String unique id
    -- can be empty if this way point is final
    if type(properties.nextPoint) == "string" then
        for _, entity in ipairs(scene.entities) do
            if properties.nextPoint == entity:GetUuid() then
                self.nextPoint = entity
                break
            end
        end
        -- self.nextPoint = scene:GetEntity(properties.nextPoint)
        if type(properties.doStayOnPoint) == "boolean" then
            self.doStayOnPoint = properties.doStayOnPoint
        end
    end
    return true
end

-- Can be used to save current component state on map save
function WayPoint:Save(properties, binstream, scene, flags, extra)
    if self.nextPoint ~= nil then
        properties.nextPoint = self.nextPoint:GetUuid()
        properties.doStayOnPoint = self.doStayOnPoint;
    end
    return true
end

-- Can be used to get copy of this component
function WayPoint:Copy()
    local t = {}
    local k
    local v
    for k, v in pairs(self) do
        t[k] = v
    end
    return t
end

-- needed for correct work, when loaded from a map
RegisterComponent("WayPoint", WayPoint)

return WayPoint

Let's create our floating object component and call it WayMover

image.png.62ba62e2efdcdf1ac315143b2f35cb

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.lua:

WayMover = {}
-- name should always match class name for correct component work
WayMover.name = "WayMover"
WayMover.moveSpeed = 4.0
WayMover.isMoving = false
WayMover.nextPoint = nil
WayMover.scene = nil

function WayMover:Copy()
    local t = {}
    local k
    local v
    for k, v in pairs(self) do
        t[k] = v
    end
    return t
end

-- will be called on map load
function WayMover:Load(properties, binstream, scene, flags, extra)
    if type(properties.moveSpeed) == "number" then
        self.moveSpeed = properties.moveSpeed
    end
    if type(properties.isMoving) == "boolean" then
        self.isMoving = properties.isMoving
    end
    if type(properties.doDeleteAfterMovement) == "boolean" then
        self.doDeleteAfterMovement = properties.doDeleteAfterMovement
    end
    if type(properties.nextPoint) == "string" then
        for _, entity in ipairs(scene.entities) do
            if properties.nextPoint == entity:GetUuid() then
                self.nextPoint = entity
                break
            end
        end   
        -- self.nextPoint = scene:GetEntity(properties.nextPoint)
        -- need scene for removing entity on doDeleteAfterMovement condition
        self.scene = scene
        return true
    end
end

-- Can be used to save current component state on map save
function WayMover:Save(properties, binstream, scene, flags, extra)
    if self.nextPoint ~= nil then
        properties.nextPoint = self.nextPoint:GetUuid()
        properties.doStayOnPoint = self.doStayOnPoint;
    end
    properties.moveSpeed = self.moveSpeed;
    properties.isMoving = self.isMoving;
    properties.doDeleteAfterMovement = self.doDeleteAfterMovement;
    return true
end

function WayMover:DoMove()
    self.isMoving = true
end

function WayMover:MoveEnd()
    local doStay = false
    if (self.nextPoint ~= nil) then
        doStay = self.nextPoint:GetComponent("WayPoint").doStayOnPoint
        self.nextPoint = self.nextPoint:GetComponent("WayPoint").nextPoint
    end
    if (doStay or self.nextPoint == nil) then
        self.isMoving = false;
        self:FireOutputs("EndMove")
        -- deleting entity if need to, after reaching final way point
        if (not doStay and self.nextPoint == nil and self.doDeleteAfterMovement and self.scene ~= nil) then
            --commented out this code for now until bind for RemoveEntity will added
            --self.scene:RemoveEntity(self.entity)
        end
    end
end

function WayMover:Update() 
	if (not self.isMoving) then
		return;
    end
	local wayPoint = self.nextPoint
	if (self.entity == nil or wayPoint == nil) then
		return
    end
	--60 HZ game loop, change to own value if different to keep same final speed
	local speed = self.moveSpeed / 60.0
	local targetPosition = wayPoint:GetPosition(true)

	--moving to point with same speed directly to point no matter which axis
	local pos = self.entity:GetPosition(true);
	local distanceX = Abs(targetPosition.x - pos.x);
	local distanceY = Abs(targetPosition.y - pos.y);
	local distanceZ = Abs(targetPosition.z - pos.z);
	local biggestDelta = distanceZ;
	if (distanceX > distanceY and distanceX > distanceZ) then
		biggestDelta = distanceX;
	elseif (distanceY > distanceX and distanceY > distanceZ) then
		biggestDelta = distanceY;
    end

	local moveX = MoveTowards(pos.x, targetPosition.x, speed * (distanceX / biggestDelta));
	local moveY = MoveTowards(pos.y, targetPosition.y, speed * (distanceY / biggestDelta));
	local moveZ = MoveTowards(pos.z, targetPosition.z, speed * (distanceZ / biggestDelta));

	self.entity:SetPosition(moveX, moveY, moveZ)
	if (self.entity:GetPosition(true) == targetPosition) then
		self:MoveEnd()
    end

end

-- needed for correct work, when loaded from a map
RegisterComponent("WayMover", WayMover)

return WayMover

 

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.48f0e402b94d172fff5795c2718e2e
  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:

In fast debug mode it might crash at this moment for unkown reason. Use Full Debug or Run mode.

Project files: LuaExample2.zip

Repository: https://github.com/Dreikblack/LuaTutorialProject/tree/2-making-and-using-components

  • Like 1
  • Thanks 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...