// // Copyright (c) 2008-2020 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 #include #include #include #include #include #include #include #include #include #include #include #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(); // 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(); auto* body = GetComponent(); auto* animatedSprite = GetComponent(); bool onGround = false; bool jump = false; // Collision detection (AABB query) Vector2 characterHalfSize = Vector2(0.16f, 0.16f); auto* physicsWorld = GetScene()->GetComponent(); PODVector 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(); 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(1.0f, 8.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() != "dead2") animatedSprite->SetAnimation("dead2"); }