In the last tutorial we loaded and rendered an arena model for our game of Pong to take place in. In this tutorial we’ll add a ball to the scene. This will cover how to add game logic to an Entity and how to position and move objects in the scene.
Rendering the Ball
The ball can be rendered in exactly the same way as we rendered the arena in the last tutorial, except this time we’ll be using a different model: Ball.csmodel. As with the other resources, this can be found in the 3D Pong Asset Pack. Build this using the content pipeline then add the following to State::OnInit():
auto ballModel = resourcePool->LoadResource(CSCore::StorageLocation::k_package, "Models/Ball.csmodel");
CSRendering::StaticMeshComponentSPtr ballModelComponent = renderComponentFactory->CreateStaticMeshComponent(ballModel, modelsMaterial);
CSCore::EntitySPtr ballEntity = CSCore::Entity::Create();
ballEntity->AddComponent(ballModelComponent);
GetScene()->Add(ballEntity);
Note that we are using the same material as for the Arena. The renderer can batch any objects that share the same material which significantly improves performance. Any time objects can share the same material they should – the fewer materials, the more efficient your app is.
Creating a Component
Now the ball is rendered we need to bounce it around the arena. This requires some game logic. In ChilliSource any game logic that pertains to a single object should be contained within a component, so let’s create a BallMovementComponent.
We will need to define a new class which inherits from CSCore::Component, so in AppSource/ create a new header and source file: BallMovementComponent.h and BallMovementComponent.cpp.
Add the following to BallMovementComponent.h:
#ifndef _PONG_BALLMOVEMENTCOMPONENT_H_
#define _PONG_BALLMOVEMENTCOMPONENT_H_
#include
#include
namespace Pong
{
class BallMovementComponent final : public CSCore::Component
{
public:
CS_DECLARE_NAMEDTYPE(BallMovementComponent);
bool IsA(CSCore::InterfaceIDType in_interfaceId) const override;
private:
void OnAddedToScene() override;
void OnUpdate(f32 in_deltaTime) override;
};
}
#endif
In BallMovementComponent.cpp, add:
#include
namespace Pong
{
CS_DEFINE_NAMEDTYPE(BallMovementComponent);
bool BallMovementComponent::IsA(CSCore::InterfaceIDType in_interfaceId) const
{
return (BallMovementComponent::InterfaceID == in_interfaceId);
}
void BallMovementComponent::OnAddedToScene()
{
}
void BallMovementComponent::OnUpdate(f32 in_deltaTime)
{
}
}
There are a few things to note here. First of all ChilliSource/ChilliSource.h has been included. This needs to be included in all header files in a ChilliSource project. Secondly, components inherit from CSCore::QueryableInterface. This allows the type to be queried for at runtime. The requires the type to be declared as a “named type” in the class declaration and defined in the source. the IsA() method is also required. It must return true for all named types which the component implements – in this case just BallMovementComponent. Lastly, are the life cycle event methods: OnAddedToScene() and OnUpdate(). All components have access to these events, as well as several others. See the Life Cycle Events tutorial for more information.
Note that if you called your project something other than Pong, you’ll need to change the name of the namespace to your project name.
It can be convenient to forward declare classes like this. ChilliSource provides a macro for forward declaring classes which also declares shortened versions of constant, unique, weak and shared pointer variations of the type. In this case that means BallMovementComponentSPtr can be used instead of std::shared_ptr
#ifndef _PONG_FORWARDDECLARATIONS_H_
#define _PONG_FORWARDDECLARATIONS_H_
#include
namespace Pong
{
CS_FORWARDDECLARE_CLASS(BallMovementComponent);
}
#endif
This can then be included in all header files in your project instead of ChilliSource/ChilliSource.h.
Now we just need to add the new Component to our ball Entity, so include BallMovementComponent.h in State.cpp and add the following to State::OnInit():
auto ballMovementComponent = std::make_shared();
ballEntity->AddComponent(ballMovementComponent);
Moving the Ball
We’ve created our new Component, but it doesn’t do anything yet. We want it to move the ball around the arena, bouncing off the top and bottom walls. If the left and right walls are touched the ball resets back to the centre.
Positioning and Movement of an Entity is handled through the entities Transform. Transform is a special component that all Entities have by default, which describes the local and world space transforms of the object. It provides a selection of methods for moving, rotating and scaling objects but in our case we just want to translate. This is achieved using the MoveBy() method.
In the class declaration add the following private member:
CSCore::Vector3 m_velocity;
We can use the OnAddedToScene() method for initialisation:
m_velocity = CSCore::Vector3(25.0f, 25.0f, 0.0f);
GetEntity()->GetTransform().SetPosition(CSCore::Vector3::k_zero);
The OnUpdate() method is called every frame. This is the best place for our movement logic. We’ll need to move the ball, then confirm if we have hit any of the walls:
GetEntity()->GetTransform().MoveBy(m_velocity * in_deltaTime);
auto& transform = GetEntity()->GetTransform();
if ((m_velocity.y > 0.0f && transform.GetWorldPosition().y > 35.0f) || (m_velocity.y < 0.0f && transform.GetWorldPosition().y < -35.0f))
{
m_velocity.y = -m_velocity.y;
}
else if ((m_velocity.x > 0.0f && transform.GetWorldPosition().x > 57.5f) || (m_velocity.x < 0.0f && transform.GetWorldPosition().x < -57.5f))
{
GetEntity()->GetTransform().SetPosition(CSCore::Vector3::k_zero);
m_velocity.x = -m_velocity.x;
}
If you build and run this now you should see the ball bounce around the screen!