Urho3D/Source/Samples/50_Urho2DPlatformer/Character2D.cpp
2021-07-17 16:43:46 +00:00

236 lines
8.0 KiB
C++

//
// Copyright (c) 2008-2021 the Urho3D project.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
#include <Urho3D/Urho2D/AnimationSet2D.h>
#include <Urho3D/Core/Context.h>
#include <Urho3D/Input/Input.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
#include <Urho3D/Urho2D/RigidBody2D.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>
#include <Urho3D/UI/Text.h>
#include <Urho3D/UI/UI.h>
#include <Urho3D/DebugNew.h>
#include "Character2D.h"
// Character2D logic component
Character2D::Character2D(Context* context) :
LogicComponent(context),
wounded_(false),
killed_(false),
timer_(0.0f),
maxCoins_(0),
remainingCoins_(0),
remainingLifes_(3),
isClimbing_(false),
climb2_(false),
aboveClimbable_(false),
onSlope_(false)
{
}
void Character2D::RegisterObject(Context* context)
{
context->RegisterFactory<Character2D>();
// These macros register the class attributes to the Context for automatic load / save handling.
// We specify the 'Default' attribute mode which means it will be used both for saving into file, and network replication.
URHO3D_ATTRIBUTE("Wounded", bool, wounded_, false, AM_DEFAULT);
URHO3D_ATTRIBUTE("Killed", bool, killed_, false, AM_DEFAULT);
URHO3D_ATTRIBUTE("Timer", float, timer_, 0.0f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Coins In Level", int, maxCoins_, 0, AM_DEFAULT);
URHO3D_ATTRIBUTE("Remaining Coins", int, remainingCoins_, 0, AM_DEFAULT);
URHO3D_ATTRIBUTE("Remaining Lifes", int, remainingLifes_, 3, AM_DEFAULT);
// Note that we don't load/save isClimbing_ as the contact listener already sets this bool.
URHO3D_ATTRIBUTE("Is Climbing Rope", bool, climb2_, false, AM_DEFAULT);
URHO3D_ATTRIBUTE("Is Above Climbable", bool, aboveClimbable_, false, AM_DEFAULT);
URHO3D_ATTRIBUTE("Is On Slope", bool, onSlope_, false, AM_DEFAULT);
}
void Character2D::Update(float timeStep)
{
// Handle wounded/killed states
if (killed_)
return;
if (wounded_)
{
HandleWoundedState(timeStep);
return;
}
// Set temporary variables
auto* input = GetSubsystem<Input>();
auto* body = GetComponent<RigidBody2D>();
auto* animatedSprite = GetComponent<AnimatedSprite2D>();
bool onGround = false;
bool jump = false;
// Collision detection (AABB query)
Vector2 characterHalfSize = Vector2(0.16f, 0.16f);
auto* physicsWorld = GetScene()->GetComponent<PhysicsWorld2D>();
PODVector<RigidBody2D*> collidingBodies;
physicsWorld->GetRigidBodies(collidingBodies, Rect(node_->GetWorldPosition2D() - characterHalfSize - Vector2(0.0f, 0.1f), node_->GetWorldPosition2D() + characterHalfSize));
if (collidingBodies.Size() > 1 && !isClimbing_)
onGround = true;
// Set direction
Vector2 moveDir = Vector2::ZERO; // Reset
if (input->GetKeyDown(KEY_A) || input->GetKeyDown(KEY_LEFT))
{
moveDir = moveDir + Vector2::LEFT;
animatedSprite->SetFlipX(false); // Flip sprite (reset to default play on the X axis)
}
if (input->GetKeyDown(KEY_D) || input->GetKeyDown(KEY_RIGHT))
{
moveDir = moveDir + Vector2::RIGHT;
animatedSprite->SetFlipX(true); // Flip sprite (flip animation on the X axis)
}
// Jump
if ((onGround || aboveClimbable_) && (input->GetKeyPress(KEY_W) || input->GetKeyPress(KEY_UP)))
jump = true;
// Climb
if (isClimbing_)
{
if (!aboveClimbable_ && (input->GetKeyDown(KEY_UP) || input->GetKeyDown(KEY_W)))
moveDir = moveDir + Vector2(0.0f, 1.0f);
if (input->GetKeyDown(KEY_DOWN) || input->GetKeyDown(KEY_S))
moveDir = moveDir + Vector2(0.0f, -1.0f);
}
// Move
if (!moveDir.Equals(Vector2::ZERO) || jump)
{
if (onSlope_)
body->ApplyForceToCenter(moveDir * MOVE_SPEED / 2, true); // When climbing a slope, apply force (todo: replace by setting linear velocity to zero when will work)
else
node_->Translate(Vector3(moveDir.x_, moveDir.y_, 0) * timeStep * 1.8f);
if (jump)
body->ApplyLinearImpulse(Vector2(0.0f, 0.17f) * MOVE_SPEED, body->GetMassCenter(), true);
}
// Animate
if (input->GetKeyDown(KEY_SPACE))
{
if (animatedSprite->GetAnimation() != "attack")
{
animatedSprite->SetAnimation("attack", LM_FORCE_LOOPED);
animatedSprite->SetSpeed(1.5f);
}
}
else if (!moveDir.Equals(Vector2::ZERO))
{
if (animatedSprite->GetAnimation() != "run")
animatedSprite->SetAnimation("run");
}
else if (animatedSprite->GetAnimation() != "idle")
{
animatedSprite->SetAnimation("idle");
}
}
void Character2D::HandleWoundedState(float timeStep)
{
auto* body = GetComponent<RigidBody2D>();
auto* animatedSprite = GetComponent<AnimatedSprite2D>();
// Play "hit" animation in loop
if (animatedSprite->GetAnimation() != "hit")
animatedSprite->SetAnimation("hit", LM_FORCE_LOOPED);
// Update timer
timer_ += timeStep;
if (timer_ > 2.0f)
{
// Reset timer
timer_ = 0.0f;
// Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
body->SetLinearVelocity(Vector2::ZERO);
body->SetAwake(false);
body->SetAwake(true);
// Remove particle emitter
node_->GetChild("Emitter", true)->Remove();
// Update lifes UI and counter
remainingLifes_ -= 1;
auto* ui = GetSubsystem<UI>();
Text* lifeText = static_cast<Text*>(ui->GetRoot()->GetChild("LifeText", true));
lifeText->SetText(String(remainingLifes_)); // Update lifes UI counter
// Reset wounded state
wounded_ = false;
// Handle death
if (remainingLifes_ == 0)
{
HandleDeath();
return;
}
// Re-position the character to the nearest point
if (node_->GetPosition().x_ < 15.0f)
node_->SetPosition(Vector3(1.0f, 8.0f, 0.0f));
else
node_->SetPosition(Vector3(18.8f, 9.2f, 0.0f));
}
}
void Character2D::HandleDeath()
{
auto* body = GetComponent<RigidBody2D>();
auto* animatedSprite = GetComponent<AnimatedSprite2D>();
// Set state to 'killed'
killed_ = true;
// Update UI elements
auto* ui = GetSubsystem<UI>();
Text* instructions = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
instructions->SetText("!!! GAME OVER !!!");
static_cast<Text*>(ui->GetRoot()->GetChild("ExitButton", true))->SetVisible(true);
static_cast<Text*>(ui->GetRoot()->GetChild("PlayButton", true))->SetVisible(true);
// Show mouse cursor so that we can click
auto* input = GetSubsystem<Input>();
input->SetMouseVisible(true);
// Put character outside of the scene and magnify him
node_->SetPosition(Vector3(-20.0f, 0.0f, 0.0f));
node_->SetScale(1.2f);
// Play death animation once
if (animatedSprite->GetAnimation() != "dead2")
animatedSprite->SetAnimation("dead2");
}