Programming in Rooms.xyz (Part 3)

2023

.

04

.

23

/

Log

3

Author: Bruno Oliveira (@btco)

Welcome to Part 3 of this tutorial! In this part we’re going to cover:

  • Virtual joystick input
  • Character controls

Virtual Joystick Input

As the millennia went by, human beings have developed the capacity for several complex tasks that go beyond clicking. We are capable, for example, of pressing keys and buttons as well. How do we harness this incredible potential in our games and experiences?

In Rooms, there is the concept of a virtual joystick, which has these 6 buttons:

Joystick Buttons

In particular, the buttons are called: up, down, left, right, a, b. The combination of up/down/left/right is called the DPAD.

On a desktop device: they correspond to keys on the keyboard, namely the arrow keys, and the keys A (or Space) and B (or Left Shift).

On a mobile device: the virtual keyboard appears on the screen and the user can press the keys. They can also drag their finger on the DPAD.

Using onButtonDown() and onButtonUp()

Let’s start with an empty room with a beagle at the center.

Ensure it’s physics type is set to Upright, as we’ll control it via its velocity:

Setting this to Upright will allow him to be affected by physics velocities and forces, but will prevent him from rolling over in silly way like a potato knocked off the supermarket shelf (that would be Tumbly physics). So “Upright” is “physics, but with a bit of dignity”.

Now let’s write the code for the beagle:

SPEED = 50
JUMP_SPEED = 100

function onButtonDown(button)
  if button == "up" then
    setVelocity(0, 0, SPEED)
  elseif button == "down" then
    setVelocity(0, 0, -SPEED)
  elseif button == "left" then
    setVelocity(-SPEED, 0, 0)
  elseif button == "right" then
    setVelocity(SPEED, 0, -0)
  elseif button == "a" then
    setVelocity(0, JUMP_SPEED, 0)
  elseif button == "b" then
    say("bark")
  end
end

What’s happening here? When the user presses any button (DPAD or A/B), the onButtonDown() function gets called and the name of the button is passed as an argument, for example “up” or “a”.

Depending on the button pressed, we are setting our velocity to the corresponding direction. In the case of A, we’re setting it upwards to cause us to jump, for example.

To make it more fun you can put a table in the room and try to jump onto the table.

Check out the result here: https://rooms.xyz/btco/tutocontrols1

He’s moving weirdly, right? Well, that’s for two reasons: one is that we’re just giving him velocity when the key is pressed, but that velocity wears out because of friction, so you have to keep tapping to renew the velocity. The other is that the room is viewed at a 45° angle, which means when you press “up”, you might of expect the dog to move in the direction that you see as “forward”, which is towards the corner of the room, not towards the blue wall.

We can solve this and many other issues by using “natural X” and “natural Y” instead of discrete directions. We’ll see this next.

Full DPAD controls and onUpdate()

If you have the beagle you can continue with it; but I’ll delete it and use Tiny Bruno as my character instead for this one.

Ensure that Tiny Bruno’s physics type is set to Upright.

Now set his code to this:

-- This is called 60 times a second.
function onUpdate()
  setVelocity(input.natX * 50, 0, input.natZ * 50)
end

What is this doing? Well, onUpdate() is called by the engine 60 times a second to give the Thing a chance to update itself. It’s an expensive function that you shouldn’t define on many objects, but it’s fine for special ones like the main character.

Here, input.natX and input.natZ are the direction of the DPAD in the “natural” frame of reference, that is, where the user expects the directions to point taking into account the 45° viewing angle. So we’re setting the velocity based on these values, makig it so that Tiny Bruno’s velocity is dictated by the natural direction of the DPAD.

Give this a try and you’ll see that Tiny Bruno indeed moves in the direction you press in: https://rooms.xyz/btco/tutocontrols2

Facing the direction of motion

It’s strange because Tiny Bruno is always facing the same direction. We’d like him to face the direction he is moving. To do this, we can use another “magic” value called input.natYaw. This is the Y component of the rotation (also known as the “yaw”) that should be set to ensure it’s facing the right direction. So let’s update the code to:

-- This is called 60 times a second.
function onUpdate()
  setVelocity(input.natX * 50, 0, input.natZ * 50)
  setRotation(0, input.natYaw, 0)
end

Try it out: https://rooms.xyz/btco/tutocontrols3

Yes! Now Tiny Bruno is looking where he’s going, which is a great way to go about life in general.

Fall off ledges

Add a platform of some kind and put Tiny Bruno on top of it, and I’ll show you something weird: https://rooms.xyz/btco/tutocontrols4

Now try to walk off the platform in Preview mode.

You float. Why does it happen?

Look again at the code. We are calling setVelocity() and we are setting the X, Y and Z components of the velocity. What’s wrong with that? Well, we’re setting the Y component! This means that we are cancelling gravity. What gravity tries to do is to add to the Y component of velocity to make things fall; if we are constantly setting that to 0, we’ll defeat it and we’ll be very floaty.

How do we fix that? Simple: use nil for the Y component. This tells the engine not to touch that component, so update Tiny Bruno’s code to:

-- This is called 60 times a second.
function onUpdate()
  setVelocity(input.natX * 50, nil, input.natZ * 50)
  setRotation(0, input.natYaw, 0)
end

Notice the nil there as the second parameter of setVelocity(). That’s the Y component and nil means “don’t mess with it, let gravity do its work”.

Add Jump

For jumps what we want to say is “if the A button was just pressed, then add a velocity upwards”. We could do this will our good old friend onButtonDown() if we wanted to, but since we already have onUpdate() it’s best to handle it directly there:

-- This is called 60 times a second.
function onUpdate()
  local vy = nil
  if input.aJustPressed then
    vy = 100
  end
  setVelocity(input.natX * 50, vy, input.natZ * 50)
  setRotation(0, input.natYaw, 0)
end

So here we’re saying: if the A button was just pressed (input.aJustPressed), then we set the Y component of the velocity to 100. If not, we leave it as nil, meaning “let gravity do its work”. So if A is pressed, we jump; if not we fall.

One last snag here is that as it is, we are allowing the user to jump while in the air as well, because we don’t check that we’re on the ground or on a solid surface. I’ll leave that as an exercise to you. Hint: use onCollision() to see when you hit the ground.

Check out the result: https://dev.rooms.xyz/btco/tutocontrols5

Controlling Animation

Things can have voxel animation, that is, they can have more than one "frame" in their voxel model.

For example, look at this one → https://dev.rooms.xyz/btco/tutoanim

Click the Cat and edit its model. You'll see that it has 2 animation frames that alternate, making it look like the cat is walking.

In the Model Editor, you can edit the individual animation frames by clicking on the frame buttons at the bottom of the screen.

I'm going to add a 3rd frame to represent the cat in a sitting position:

But now the animation looks weird, because it looks like the cat takes 2 steps then sits then takes 2 steps then sits. That's OK. That's because by default all the animation frames play. But now we're about to delve into coding to make it so that only the frames we want will show.

Here's the code for the cat:

function onStart()
  -- Start in the sitting position.
  sitDown()
end

function onClick()
  -- When clicked, togglet between walking and sitting
  if isWalking then
    sitDown()
  else
    startWalking()
  end
end

function startWalking()
  -- Start moving
  startMoveBy(0, 0, -100, 20)
  -- Set animation to loop frames 1 through 2
  setAnimation("loop", 5, 1, 2)
  isWalking = true
end

function sitDown()
  -- Stop moving.
  stopMove()
  -- Set animation to only show frame #3 (sitting).
  setFrame(3)
  isWalking = false
end

Now you can click the cat to make it move and do the walk animation, and click again to make it stop and sit down:

Try it out here → https://dev.rooms.xyz/btco/tutoanim2

The End

You've reached the end of this article series. Thanks for reading it through! Remember that you can dive deeper into the API by reading the API documentation, which has all sorts of interesting functions (and boring ones, too) that you can call.