Dynamic Slices and Placing Them in the Level

One of the major features in Lumberyard is the use of slices.

Slices are basically prefabricated collections of entities and components, that can be reused in the editor. If you make it into a dynamic slice, then it can be used in your code.

In this tutorial, we will create a dynamic slice from the editor and use our raycasting to place it in the level.

Creating the Slice

First thing to do is launch the editor and open up our level. If you followed the input series and the raycast series, we called our level TutorialLevel.

Create a new entity for what we want to place with a mouse click. In this tutorial, we will place a basic box, so I call the entity “box”.
tut_place_item_entity

Next, we need to set up the box entity’s components. In this tutorial, we will use a basic cube mesh, and give it physics and a mesh collider.
tut_place_item_box_components

Now is a good time to run the game in the editor and confirm that the box has physics by running into it.

Once we confirm that, it’s time to create the slice. Right click on the box entity and select “Create slice…”. We can save it as box.slice.
tut_place_item_menu_create_slice
Now if we were merely using the slice in the editor, then we could stop here. But since we want to use this slice procedurally in our C++ code, we need to make it a dynamic slice. To do this, right click the slice we saved in the Asset Browser and select “Set Dynamic Slice.”
tut_place_item_menu_dynamic_slice

This will cause the asset browser to process our slice, and it will process it into a dynamic one.

Now we can remove the box entity now that it’s saved as a slice. Save, export and close the editor.

Assign the Slice

So next we need some way of giving our C++ code access to that slice we created. The easiest way to do this is to add it a property to our character component.

First lets add a private variable to our TutorialSeriesCharacterComponent to hold the slice.
TutCharacterComp.h:

AZ::Data::Asset<AZ::DynamicPrefabAsset> SliceToSpawn;

Next we need to expose this to the editor in our reflection function.

TutCharacterComp.cpp:

void TutorialSeriesCharacterComponent::Reflect(AZ::ReflectContext *reflection)
{
    if (auto serializationContext = azrtti_cast<AZ::SerializeContext *>(reflection))
    {
        serializationContext->Class<TutorialSeriesCharacterComponent>()
            ->Version(1)
            ->Field("Movement scale", &TutorialSeriesCharacterComponent::MovementScale)
            ->Field("Rotation Speed", &TutorialSeriesCharacterComponent::RotationSpeed)
            ->Field("Slice To Spawn", &TutorialSeriesCharacterComponent::SliceToSpawn);

        if (auto editContext = serializationContext->GetEditContext())
        {
            editContext->Class<TutorialSeriesCharacterComponent>("TutorialSeriesCharacterComponent", "Main controller component")
                ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                ->Attribute(AZ::Edit::Attributes::Category, "TutorialSeries")
                ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
                ->DataElement(nullptr, &TutorialSeriesCharacterComponent::MovementScale, "Movement scale", "How fast the character moves")
                ->DataElement(nullptr, &TutorialSeriesCharacterComponent::RotationSpeed, "Rotation Speed", "The speed multiplier to apply to mouse rotation")
                ->DataElement(nullptr, &TutorialSeriesCharacterComponent::SliceToSpawn, "Slice To Spawn", "This is the slice that will be spawned when clicking");
        }
    }
}

Time to compile to make sure everything is kosher.

Back to the Editor ™

Go ahead and run the editor again and load our level up.

Now when you click the player entity, you should property in our TutorialSeriesCharacterComponent section to assign our dynamic slice.
tut_place_item_assign_slice

Save, export, and close the editor.

Placing the Box

Finally back to c++! Woohoo!

Lets add some helper functions to our character component, under the private section.

TutCharacterComp.h:

void PlayerUseAction(PhysicsWrapper::Hit &hit);
void PerformRaycast(const AzFramework::InputChannel &inputChannel);

AZ::Vector3 GetPlacementPosition(PhysicsWrapper::Hit &hit);

TutCharacterComp.cpp:

AZ::Vector3 TutorialSeriesCharacterComponent::GetPlacementPosition(PhysicsWrapper::Hit &hit)
{
    return AZ::Vector3{hit.position.GetX(), hit.position.GetY(), hit.position.GetZ()};
}

void TutorialSeriesCharacterComponent::PlayerUseAction(PhysicsWrapper::Hit &hit)
{
    auto placement_pos = GetPlacementPosition(hit);

    auto transform = AZ::Transform::CreateTranslation(placement_pos);

    AzFramework::SliceInstantiationTicket ticket;
    EBUS_EVENT_RESULT(ticket, AzFramework::GameEntityContextRequestBus, InstantiateDynamicSlice, SliceToSpawn, transform, nullptr);
}

void TutorialSeriesCharacterComponent::PerformRaycast(const InputChannel &inputChannel)
{
    if (inputChannel.GetState() == InputChannel::State::Began)
    {
        auto hit = EBUS_EVENT_RETURN(PhysicsWrapper::Hit, PhysicsWrapper::PhysicsWrapperRequestBus, PerformRayCast);

        if (hit.hit)
        {
            auto entity = EBUS_EVENT_RETURN(AZ::Entity *, AZ::ComponentApplicationBus, FindEntity, hit.entity);

            auto input_type = inputChannel.GetInputChannelId();
            if (input_type == InputDeviceMouse::Button::Right)
            {
                PlayerUseAction(hit);
            }
        }
    }
}

The super new thing here is the PlayerUseAction function. There we call the GameEntityContextRequestBus‘s SliceToSpawn function, which takes a slice and a position and puts it in the world. For now, we are using the raycast’s hit location to determine where to put our slice.

Lets compile and run the launcher.

Placing the Box… Nicely

You will notice that the block places nicely where we click the ground, but once you try to put a box on another box, the boxes start to clip. This is because our placement position needs to take into account the size of the box we are placing.

We can do this by using the normal of the raycast hit result.

Keep in mind, that by default, origin point of each slice is the center of the bottom. Our block is a radius of 1 (cubes don’t have a radius, you say? leave me alone!) So as you will see in the code, we deal with X and Y differently than the Z axis.

TutCharacterComp.h:

AZ::Vector3 TutorialSeriesCharacterComponent::GetPlacementPosition(PhysicsWrapper::Hit &hit)
{
    auto shift_amount = hit.normal * AZ::Vector3{1.f, 1.f, hit.normal.GetZ() < 0.f ? 2.f : 0.f};
    auto position = shift_amount + hit.position;
    return position;
}

Basically we get the amount we want to shift the block before placement, and add that to the position of our raycast hit. Since the slices origin is at the bottom center, we have to deal with the Z axis differently if it is positive or negative.

Compile and run the launcher. You should see blocks place a bit nicer, though it is still not perfect.

Conclusion

Slices are a powerful way to create complex entities. If you go to the editor, you can place your slice (drag from the Asset Browser), modify it, and then update all slices with those modifications (right click slice entity and select “push to slice”). You can even make a slice that contains another slice!

There are still some problems with our code however:
1. You can still place a box in a box (like by clicking the ground twice close together.)
2. Boxes would not place nicely on an incline or at any angle.

Both of these problems can be solved a few ways. The 2 that strike me immediately are:
1. Have some type of hook system where boxes “hook” onto other boxes, and check for collisions before placing. This would mean the player can’t place a box if it would collide with something else. (Similar to base building in No Man’s Sky – OH GOD I’M MENTIONED THE GAME THAT SHALL NOT BE MENTIONED!!!!1)
2. Place boxes in a grid like Minecraft

We will eventually explore both of these options, but the Minecraft one is by far the easier solution.

Written By Greg Horvay.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s