// Copyright (c) 2008-2023 the Urho3D project // License: MIT #include #include #include #include #include #include #include #include #include #include "Character2D.h" #include // Character2D logic component Character2D::Character2D(Context* context) : LogicComponent(context), wounded_(false), killed_(false), timer_(0.0f), maxCoins_(0), remainingCoins_(0), remainingLifes_(3), moveSpeedScale_(1.0f), zoom_(0.0f) { } void Character2D::RegisterObject(Context* context) { context->RegisterFactory(); // 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("Move Speed Scale", moveSpeedScale_, 1.0f, AM_DEFAULT); URHO3D_ATTRIBUTE("Camera Zoom", zoom_, 0.0f, AM_DEFAULT); URHO3D_ATTRIBUTE("Coins In Level", maxCoins_, 0, AM_DEFAULT); URHO3D_ATTRIBUTE("Remaining Coins", remainingCoins_, 0, AM_DEFAULT); URHO3D_ATTRIBUTE("Remaining Lifes", remainingLifes_, 3, AM_DEFAULT); } void Character2D::Update(float timeStep) { // Handle wounded/killed states if (killed_) return; if (wounded_) { HandleWoundedState(timeStep); return; } auto* animatedSprite = GetComponent(); auto* input = GetSubsystem(); // Set direction Vector3 moveDir = Vector3::ZERO; // Reset float speedX = Clamp(MOVE_SPEED_X / zoom_, 0.4f, 1.0f); float speedY = speedX; if (input->GetKeyDown(KEY_A) || input->GetKeyDown(KEY_LEFT)) { moveDir = moveDir + Vector3::LEFT * speedX; animatedSprite->SetFlipX(false); // Flip sprite (reset to default play on the X axis) } if (input->GetKeyDown(KEY_D) || input->GetKeyDown(KEY_RIGHT)) { moveDir = moveDir + Vector3::RIGHT * speedX; animatedSprite->SetFlipX(true); // Flip sprite (flip animation on the X axis) } if (!moveDir.Equals(Vector3::ZERO)) speedY = speedX * moveSpeedScale_; if (input->GetKeyDown(KEY_W) || input->GetKeyDown(KEY_UP)) moveDir = moveDir + Vector3::UP * speedY; if (input->GetKeyDown(KEY_S) || input->GetKeyDown(KEY_DOWN)) moveDir = moveDir + Vector3::DOWN * speedY; // Move if (!moveDir.Equals(Vector3::ZERO)) node_->Translate(moveDir * timeStep); // Animate if (input->GetKeyDown(KEY_SPACE)) { if (animatedSprite->GetAnimation() != "attack") animatedSprite->SetAnimation("attack", LM_FORCE_LOOPED); } else if (!moveDir.Equals(Vector3::ZERO)) { if (animatedSprite->GetAnimation() != "run") animatedSprite->SetAnimation("run"); } else if (animatedSprite->GetAnimation() != "idle") { animatedSprite->SetAnimation("idle"); } } void Character2D::HandleWoundedState(float timeStep) { auto* body = GetComponent(); auto* animatedSprite = GetComponent(); // 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(); Text* lifeText = static_cast(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(-5.0f, 11.0f, 0.0f)); else node_->SetPosition(Vector3(18.8f, 9.2f, 0.0f)); } } void Character2D::HandleDeath() { auto* body = GetComponent(); auto* animatedSprite = GetComponent(); // Set state to 'killed' killed_ = true; // Update UI elements auto* ui = GetSubsystem(); Text* instructions = static_cast(ui->GetRoot()->GetChild("Instructions", true)); instructions->SetText("!!! GAME OVER !!!"); static_cast(ui->GetRoot()->GetChild("ExitButton", true))->SetVisible(true); static_cast(ui->GetRoot()->GetChild("PlayButton", true))->SetVisible(true); // Show mouse cursor so that we can click auto* input = GetSubsystem(); 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() != "dead") animatedSprite->SetAnimation("dead"); }