Categories
Advanced Tutorials

Characters occupying seats or other furniture

What is it?

As from the Harrowing High Season you can now have a player character ‘occupy’ another entity. When an entity has been occupied, the character becomes attached to it with all movement control disabled. The character will also start playing a particular set of animations, depending on the type of occupy being performed (more on this later!). The character will remain locked to the entity and play these animations until the occupy has ended.

What can I use it for?

Occupying will allow creators to have characters that can become one with the environment. 

Do you want your character to be able to ride the ferris wheel? Then you occupy one of its carriages! And perhaps you need to lie down after all that fun? Occupy that bed!

How do I use it?

In brief you can call this via a lua script, and you can make a character (players or NPCs) occupy another entity using StartOccupy. We will cover how to use this in more detail in the tutorial below, but it’s worth noting that there are four versions of StartOccupy you can use:

character:StartOccupy(entity)

The character will occupy this entity. If this entity has any Occupy Sockets, the character is attached to the first socket (from the list returned by entity:GetOccupySockets()). If this entity has no Occupy Sockets, the character will attach to the origin of the entity, and the default OccupyAsset (Chair) will be used.

character:StartOccupy(entity, socketName)

The character will occupy the entity at the provided socket. If this socket is an Occupy Socket, the character will occupy it using the Occupy Asset associated with this socket. Otherwise the default OccupyAsset (Seat) will be used

character:StartOccupy(entity, occupyAsset)

The character will occupy the entity using the occupyAsset. If this entity has any Occupy Sockets, the character will be attached to the first socket (but will ignore the Occupy Asset associated with it), otherwise the character will attach to the entity position.

character:StartOccupy(entity, occupyAsset, socketName)

The character will occupy the entity at this socket, using the provided Occupy Asset.

Note: In each case, the entity to occupy does not need to be a chair, bed, etc. The entity can be as simple as a locator, meaning you can add a locator to anywhere in your game where you want the player to sit, lie down, etc.

A character will remain occupied on an entity until character:EndOccupy() is called.

Sitting solo on a chair

Firstly, we will look at getting your character to simply sit in a seat that they are interacting with, we have previously covered the OnInteract function, so we will be using that to drive being able to sit or stand back up. Once you have created a game, then you will need to place a chair into the game world. In my case I have picked Chair (School) from Harrowing High.

 Once you have the chair in position, select it in the entities list and then select Entity >Add >Script and then call this sittingScript. We need to add a new function to the code which will be the call to OnInteract and then we are going to toggle between the two calls StartOccupy() and EndOccupy() you can see the example code below to achieve this.

function SittingScript:OnInteract(player)
	sit = not sit
	if sit then
		player:StartOccupy(self:GetEntity())
	else
	player:EndOccupy(self:GetEntity())
	end
end

If we save the script and now preview the game you should be able to interact with the chair and either sit on it or stand back up. We can also use the same Occupy functions to control if NPCs can be seated.

Multiple seating points

We have covered how to sit on a seat that has one single socket that can be used for seating, but some meshes contain multiple sockets that we can attach to.

In this example, we will search for Bench (Stadium) and place it in our world, we will also create a new script by selecting the Entity >Add >Script and then name this randomSitPointScript.

With that we will create our new script, this script while similar to the first script will look for the sockets on the current mesh and add them to a table, it will also get the total number of sockets on that mesh. We will then pick a random number between the min and max number of sockets, finally we will tell the player character to occupy the randomly picked socket point based on our table.

Again, we want to add the below as a new function inside of the script:

function RandomSitPointScript:OnInteract(player)
	sit = not sit
	sockets = self:GetEntity():GetOccupySockets()
	totalSockets = #self:GetEntity():GetOccupySockets()
	pos = math.random(1,totalSockets)
	if sit then
		player:StartOccupy(self:GetEntity(),sockets[pos])
	else
		player:EndOccupy(self:GetEntity())
	end
end

If we save this script and then preview the game we can then try interacting with the bench and see that the character will sit in a random socket on the bench each time we want to sit on it. 

Custom seating points

The next thing we will look at is how we can customize the animation we pick when occupying a space, we will additionally use a locator rather than a socket that is already on a mesh. Using a locator just adds flexibility if you want the character to either sit or lie down on something that doesn’t have sockets setup.

The first thing to do is place a locator somewhere on the terrain in the world, the issue we then have is that you cannot directly interact with the locator unlike a mesh. To get around that we are going to use a trigger. We can add a trigger to the locator by right-clicking on the locator and then selecting Create Child > Trigger and then you can adjust the size and position of the trigger to be more suitable. We also need to change the Interactable property on the trigger to be active.

We will also add a new script to the trigger by selecting Entity >Add >Script and then name this OccupyOnLocator. We will then add a new function to the script, this is very similar to the first script, but this time we are getting the parent object which should be your locator. In the existing script add the following function:

function OccupyOnLocator:OnInteract(player)
	sit = not sit
	--Get the parent which is the locator.
	local parent = self:GetEntity():GetParent()
	if sit then
		player:StartOccupy(parent)
	else
		player:EndOccupy(parent)
	end
end

If we preview the game and find the spot where the trigger is you can press the interact key and you will see the character sit on the spot, however there is a visual issue because the character isn’t sitting on the floor but with their legs through the floor. To improve this we are going to use a property type called occupyAsset, this will allow you to pick an animation set that will be applied when occupying the locator. To do this, we will first add the property to the script, an example of which is below:

OccupyOnLocator.Properties = {
	{name = "animationType", type = "occupyasset", },
}

We then need to modify the current line that contains the call to the StartOccupy() function to the following:

player:StartOccupy(parent,self.properties.animationType)

Don’t forget to set the new AnimationType property to be Floor as that is where we positioned this locator. When we preview and interact with the trigger this time then the character will sit in a more natural pose that makes sense given the context of what they are sitting on.

Occupy Assets

The initial release of this feature will be shipped with four different Occupy Assets – Chair, Bed, Floor and Stool. An Occupy Asset determines how a character occupies an entity. Essentially it defines which animation the character will play. You can add Occupy Assets to script properties by making a new property with type = “occupyasset”. The images below show the effect of occupying an entity with each of these Occupy Assets.
Separating Occupy Assets from the meshes being occupied allows creators the freedom of choosing exactly how a character should occupy an asset. For example, you might not always want to lie down on a bed!

And with that, we have covered how to use the occupy functions to get your characters to occupy spaces. However, there is a lot of scope for how these scripts could be improved for example you could try implementing the following:

  • Sitting on a socket that is the closest to the player.
  • Allowing multiple characters to sit on the same seat but in different positions.
  • Include a visual prompt to let your players know they can interact with the seats.

We have also included some more advanced information on how to get the most out of the feature so feel free to read more about this in detail below.

Occupy Sockets

We briefly touched on this earlier but to make life easier for creators, we have introduced Occupy Sockets to certain meshes. If you have made games in Crayta before, you may already be familiar with the notion of ‘sockets’. For example, if you want to attach a helmet to a player character, you attach it to the character’s head socket.

An Occupy Socket is a special type of socket that also specifies an Occupy Asset associated with it. In other words, a chair will have a socket on it placed at the location where the character should be attached to, with the Occupy Asset set to ‘Chair’. We’ve added these to all of the meshes in Crayta that look like they should be sat or laid on.

To help visualize this, we now render sockets as small red locator icons whenever an entity with sockets is selected. Note how the bunk bed in this image has a socket in the middle of each bed. This is where the character will be attached to when occupying this bed.

You can hover over these sockets to see more information about them. The socket on the lower bed is called “bottom”. It’s also an Occupy Socket, as it is associated with the ‘Bed’ Occupy Asset.

You can also get a list of all OccupySockets present on an entity by querying entity:GetOccupySockets() in a lua script.

Alternatively you can obtain a list of all sockets present on an entity from the new ‘Copy Socket Names’ button in the properties of any entities with sockets:

Useful Information

The following is some additional API we’ve added that can help you make the most of the new Occupy mechanic

character:IsOccupying()

This will return true if this character is currently occupying another entity.

entity:GetOccupySockets()

Get a list of all OccupySockets on this entity

entity:GetSockets()

Get a list of all sockets (including OccupySockets) on this entity

entity:GetOccupyAssetForSocket(socketName)

Get the occupy asset associated with this Occupy Socket

entity:GetSocketPosition()
entity:GetSocketRelativePosition()
entity:GetSocketRotation()
entity:GetSocketRelativeRotation()

Get the world/relative position/rotation of a socket.

Limitations

Native occupy logic
There is no native support in Crayta for handling any kind of occupying logic. For example, if 2 players try to sit on the same chair, they will happily sit on the same chair at the same time, overlapping each other!  It is your responsibility to prevent this from happening if you don’t want this behavior in your game.  

Character Physics

Character physics is disabled while the character is occupying an entity. Raycasts will still detect hits with the character, but any tangible physical object will not collide with them in any way.

Conclusion

You can see how much functionality this new feature will provide, and how many options we have given access to for creators. With that, we hope that you’re enjoying this new set of features and cannot wait to see what you come up with.