PlayerController: Throwing
Gameplay: Throwing Helpful Creatures
The code behind the main gameplay mechanics for the DigiPen Team Cloud Cat's game: Return to the Skyway.
The throwing mechanic of the game relied heavily on the physics system composed by a fellow team member and the helpful creature behavior class that I implemented. Since the player was limited to throwing only the helpful creatures, I decided to separate the implementation code into a function triggered through collision with a helpful creature over being initiated by a player key trigger. This way, the player could only grab and throw when they were colliding with a helpful creature.
As the main gameplay mechanic, this code was foundational in creating the game itself. As such, a lot of the internal variables were opened to the designer so they could adjust and refine as may aspect of the mechanic as possible. Aspects such as how strong the player could throw, how long it would take the player to charge their throw, and the actual key triggers were all left to the designers to determine and modify through the custom editor in engine.
This code mainly deals in the computer's view of the grab and throw but works closely with the associtaed player behavior to establish a correlation between the animations and douns through the state-based behavior system of the engine.
This code went through a lot of testing with the multiple playtest sessions that the designers held since there was little to no backend testing we could do beyond "does the object get propelled along the correct vector". Thankfully, since the code was abstracted and reliant only on the variables that the designers had access to, the back-end (code heavy) section of the mechanic didn't have to go through a lot of iteration.
/*****************************************************************//**
* \file PlayerController.cpp (snippet)
* \author Jennifer Assid
* \param Return to the Skyway
* \param GAM200AF21
*
* \date 8 Oct 2021
*
* \param Copyright � 2021 DigiPen (USA) Corporation.
*********************************************************************/
namespace CloudEngine
{
namespace Player
{
/**
* Handles all collisions pertaining to the player.
*
* \param collision Collision information
*/
void PlayerCollisionHandler(const Physics::Collision& collision)
{
// If the player is colliding with a creature
if ((collision.aCollider_.GetColliderProperties() == Physics::ColliderProperties::cpPlayer && collision.bCollider_.GetColliderProperties() == Physics::ColliderProperties::cpCreature) ||
(collision.aCollider_.GetColliderProperties() == Physics::ColliderProperties::cpCreature && collision.bCollider_.GetColliderProperties() == Physics::ColliderProperties::cpPlayer))
{
Physics::Collider* playerCollider;
Physics::Collider* creatureCollider;
// Determine which collider is which and assign them to their local variants
if (collision.aCollider_.GetColliderProperties() == Physics::ColliderProperties::cpPlayer)
{
playerCollider = &collision.aCollider_;
creatureCollider = &collision.bCollider_;
}
else
{
playerCollider = &collision.bCollider_;
creatureCollider = &collision.aCollider_;
}
// If the player controller is not active - return out of the function
if (playerCollider->Parent()->GetComponent<PlayerController>()->GetActive() == false) return;
// If the current camera is not the player camera - return out of the function (disable's player functionality while editor camera is active)
if (Graphics::Camera::GetActiveCamera() != playerCollider->Parent()->GetComponent<Graphics::Camera>()) return;
Components::BehaviorHelpful* creature_behavior = NULL;
// Iterate through the components of the creature and retrieve the sprecific helpful behavior component (this is to support all helpful creatures)
std::vector<Components::Component*> components = creatureCollider->Parent()->GetComponents();
for (Components::Component* component : components)
{
creature_behavior = dynamic_cast<Components::BehaviorHelpful*>(component);
if (creature_behavior) break;
}
// Pointer check
if (!creature_behavior) return;
// If the creature is not grabbed or thrown - tell the character to enter the "scare" behavior
if (!creature_behavior->GetIsGrabbed() && !creature_behavior->GetIsThrown())
creature_behavior->SetNextState(Components::Behavior_Helpful_Enum::b_Helpful_Scare);
// Get the local variables established
Components::BehaviorPlayer* behavior_player = playerCollider->Parent()->GetComponent<Components::BehaviorPlayer>();
if (!behavior_player) return; // Pointer Check
Player::PlayerController* player_controller = playerCollider->Parent()->GetComponent<Player::PlayerController>();
if (!player_controller) return; // Pointer Check
Components::Transform* player_transform = playerCollider->Parent()->GetComponent<Components::Transform>();
// If the player goes to throw the creature...
if (InputController::rightMouseButtonPressed())
{
// If the player is currently holding something
if (player_controller->GetHasGrabbed())
{
// Set the necessary variables
player_controller->SetIsThrowing(false);
player_controller->SetIsHolding(true);
// If the hold timer has been reached
if (player_controller->GetHoldCurrTimer() > player_controller->GetHoldTimer())
{
// Set the current value to the max
player_controller->SetHoldCurrTimer(player_controller->GetHoldTimer());
}
else
{
// Continue the timer
player_controller->SetHoldCurrTimer(player_controller->GetHoldCurrTimer() + 0.5f * Time::DeltaTime());
}
// Get the position of the mouse in world space
glm::vec2 mousePos = Graphics::Camera::GetActiveCamera()->GetScreenToWorldMatrix() * glm::vec4(InputController::getMousePos(), 0.0f, 1.0f);
// Determine the facing of the character based on the position of the mouse
if (mousePos.x > player_transform->GetTranslation()->x)
{
player_controller->SetFacingRight(true);
}
else
{
player_controller->SetFacingRight(false);
}
}
}
// If the right mouse is released...
if (InputController::rightMouseButtonUp())
{
// Stio the charge sound
FMODCore::Audio* instance = FMODCore::audioInstance_->getAudioInstance();
while (instance->eventIsPlaying("event:/SFX/Character/Throw_Charge"))
{
instance->StopSound("event:/SFX/Character/Throw_Charge");
}
// And the player is holding something...
if (player_controller->GetHasGrabbed() && creature_behavior->GetIsGrabbed())
{
// Pointer check
if (!playerCollider->Parent()->GetComponent<Graphics::Camera>()) return;
// Set the necessary variables
player_controller->SetIsHolding(false);
player_controller->SetIsThrowing(true);
player_controller->SetAnimalPtr(NULL);
player_controller->SetAnimalHeld(Components::Creature_Helpful_Type::c_Invalid);
// Move the creature to the player's head (this will help the visual feedback for the throw later on)
Components::Transform* creature_trans = creatureCollider->Parent()->GetComponent<Components::Transform>();
creature_trans->SetTranslation(glm::vec3(creature_trans->GetTranslation()->x, creature_trans->GetTranslation()->y + creature_trans->GetScale()->y + 1.0f, 0.0f));
// Get the vector from the creature to the mouse
glm::vec2 mousePos = Graphics::Camera::GetActiveCamera()->GetScreenToWorldMatrix() * glm::vec4(InputController::getMousePos(), 0.0f, 1.0f);
glm::vec2 creatureToMouse = mousePos - glm::vec2(creature_trans->GetTranslation()->x, creature_trans->GetTranslation()->y);
// Determine the strength of the throw vector (based on the player's hold timer and the determined max throw force)
float tf = playerCollider->Parent()->GetComponent<PlayerController>()->GetThrowForce() * (player_controller->GetHoldCurrTimer() / player_controller->GetHoldTimer());
// Normalize the vector and scale it according to the calculated length
creatureToMouse = glm::normalize(creatureToMouse);
creatureToMouse *= tf;
// Remove the creature from the player's hands (set the following values are necessary)
player_controller->SetHasGrabbed(false);
creature_behavior->SetIsGrabbed(false);
creature_behavior->SetPlayer(NULL);
// Tell the creature to enter the "throw" behavior
creature_behavior->SetNextState(Components::Behavior_Helpful_Enum::b_Helpful_Thrown);
// Exert the throw force on the creature to propel them
Physics::RigidBody* cRB = creatureCollider->Parent()->GetComponent<Physics::RigidBody>();
cRB->SetVelocity(creatureToMouse);
cRB->SetGrounded(false);
// Reset the timer
player_controller->SetHoldCurrTimer(0.0f);
}
}
// If the player goes to grab the creature...
if (InputController::leftMouseButtonTriggered())
{
// Deactivate the player controllers so the animation can play through
player_controller->SetActive(false);
// If the player is already grabbing a creature
if (player_controller->GetHasGrabbed())
{
// Remove the creature from the player's hands and drop it.
player_controller->SetHasGrabbed(false);
player_controller->SetAnimalPtr(NULL);
player_controller->SetIsHolding(false);
player_controller->SetIsThrowing(false);
player_controller->SetAnimalHeld(Components::Creature_Helpful_Type::c_Invalid);
player_controller->SetAnimalPtr(NULL);
player_controller->SetHoldCurrTimer(0.0f);
behavior_player->SetNextState(Components::Behavior_Player_Enum::b_Player_Drop); // This handles the animation / activating the controls
creature_behavior->SetIsGrabbed(false);
creature_behavior->SetPlayer(NULL);
// Set creature's next state to idle
creature_behavior->Parent()->GetComponent<Components::Transform>()->SetTranslation(*player_transform->GetTranslation());
creature_behavior->SetNextState(Components::Behavior_Helpful_Enum::b_Helpful_Idle);
}
else
{
// Put the creature in the player's hands
player_controller->SetHasGrabbed(true);
player_controller->SetAnimalHeld(creature_behavior->GetType());
player_controller->SetAnimalPtr(creature_behavior);
// Retrieve the BehaviorHelpful component from the creature (this is to support all helpful creatures)
Components::BehaviorHelpful* creature_behavior = NULL;
std::vector<Components::Component*> components = creatureCollider->Parent()->GetComponents();
for (Components::Component* component : components)
{
creature_behavior = dynamic_cast<Components::BehaviorHelpful*>(component);
if (creature_behavior) break;
}
// Pointer check
if (!creature_behavior) return;
// Set its necessary values
creature_behavior->SetIsGrabbed(true);
creature_behavior->SetPlayer(playerCollider->Parent());
// Set the plater behavior accordingly
behavior_player->SetNextState(Components::Behavior_Player_Enum::b_Player_Grab);
}
}
}
}
}
}