Merge pull request #2176 from thesquib/2ddemos
Updated 2D samples from pull request #641
This commit is contained in:
commit
54ed4c917f
33
Source/Samples/49_Urho2DIsometricDemo/CMakeLists.txt
Normal file
33
Source/Samples/49_Urho2DIsometricDemo/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright (c) 2008-2014 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.
|
||||
#
|
||||
|
||||
# Define target name
|
||||
set (TARGET_NAME 49_Urho2DIsometricDemo)
|
||||
|
||||
# Define source files
|
||||
define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES} ../Utilities2D/Sample2D.h ../Utilities2D/Sample2D.cpp ../Utilities2D/Mover.h ../Utilities2D/Mover.cpp)
|
||||
|
||||
# Setup target with resource copying
|
||||
setup_main_executable ()
|
||||
|
||||
# Setup test cases
|
||||
setup_test ()
|
201
Source/Samples/49_Urho2DIsometricDemo/Character2D.cpp
Normal file
201
Source/Samples/49_Urho2DIsometricDemo/Character2D.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
|
||||
#include <Urho3D/Urho2D/AnimationSet2D.h>
|
||||
#include <Urho3D/Core/Context.h>
|
||||
#include <Urho3D/Input/Input.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 "Character2D.h"
|
||||
|
||||
#include <Urho3D/DebugNew.h>
|
||||
|
||||
// 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<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("Move Speed Scale", float, moveSpeedScale_, 1.0f, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Camera Zoom", float, zoom_, 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);
|
||||
}
|
||||
|
||||
void Character2D::Update(float timeStep)
|
||||
{
|
||||
// Handle wounded/killed states
|
||||
if (killed_)
|
||||
return;
|
||||
|
||||
if (wounded_)
|
||||
{
|
||||
HandleWoundedState(timeStep);
|
||||
return;
|
||||
}
|
||||
|
||||
AnimatedSprite2D* animatedSprite = GetComponent<AnimatedSprite2D>();
|
||||
Input* input = GetSubsystem<Input>();
|
||||
|
||||
// Set direction
|
||||
Vector3 moveDir = Vector3::ZERO; // Reset
|
||||
float speedX = Clamp(MOVE_SPEED_X / zoom_, 0.4f, 1.0f);
|
||||
float speedY = speedX;
|
||||
|
||||
if (input->GetKeyDown('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('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('W') || input->GetKeyDown(KEY_UP))
|
||||
moveDir = moveDir + Vector3::UP * speedY;
|
||||
if (input->GetKeyDown('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)
|
||||
{
|
||||
RigidBody2D* body = GetComponent<RigidBody2D>();
|
||||
AnimatedSprite2D* 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;
|
||||
UI* 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(-5.0f, 11.0f, 0.0f));
|
||||
else
|
||||
node_->SetPosition(Vector3(18.8f, 9.2f, 0.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void Character2D::HandleDeath()
|
||||
{
|
||||
RigidBody2D* body = GetComponent<RigidBody2D>();
|
||||
AnimatedSprite2D* animatedSprite = GetComponent<AnimatedSprite2D>();
|
||||
|
||||
// Set state to 'killed'
|
||||
killed_ = true;
|
||||
|
||||
// Update UI elements
|
||||
UI* 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
|
||||
Input* 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() != "dead")
|
||||
animatedSprite->SetAnimation("dead");
|
||||
}
|
70
Source/Samples/49_Urho2DIsometricDemo/Character2D.h
Normal file
70
Source/Samples/49_Urho2DIsometricDemo/Character2D.h
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Urho3D/Scene/LogicComponent.h>
|
||||
|
||||
// All Urho3D classes reside in namespace Urho3D
|
||||
using namespace Urho3D;
|
||||
|
||||
const float MOVE_SPEED_X = 4.0f;
|
||||
const int LIFES = 3;
|
||||
|
||||
/// Character2D component controling Imp behavior.
|
||||
class Character2D : public LogicComponent
|
||||
{
|
||||
URHO3D_OBJECT(Character2D, LogicComponent);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Character2D(Context* context);
|
||||
|
||||
/// Register object factory and attributes.
|
||||
static void RegisterObject(Context* context);
|
||||
|
||||
/// Handle update. Called by LogicComponent base class.
|
||||
virtual void Update(float timeStep) override;
|
||||
|
||||
/// Handle player state/behavior when wounded.
|
||||
void HandleWoundedState(float timeStep);
|
||||
|
||||
/// Handle death of the player.
|
||||
void HandleDeath();
|
||||
|
||||
/// Flag when player is wounded.
|
||||
bool wounded_;
|
||||
/// Flag when player is dead.
|
||||
bool killed_;
|
||||
/// Timer for particle emitter duration.
|
||||
float timer_;
|
||||
/// Number of coins in the current level.
|
||||
int maxCoins_;
|
||||
/// Counter for remaining coins to pick.
|
||||
int remainingCoins_;
|
||||
/// Counter for remaining lifes.
|
||||
int remainingLifes_;
|
||||
/// Scaling factor based on tiles' aspect ratio (definitively set at tile map creation).
|
||||
float moveSpeedScale_;
|
||||
/// Camera's zoom (used to scale movement speed based on camera zoom).
|
||||
float zoom_;
|
||||
};
|
353
Source/Samples/49_Urho2DIsometricDemo/Urho2DIsometricDemo.cpp
Normal file
353
Source/Samples/49_Urho2DIsometricDemo/Urho2DIsometricDemo.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
|
||||
#include <Urho3D/Urho2D/AnimationSet2D.h>
|
||||
#include <Urho3D/UI/Button.h>
|
||||
#include <Urho3D/Graphics/Camera.h>
|
||||
#include <Urho3D/Urho2D/CollisionBox2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionChain2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionCircle2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionPolygon2D.h>
|
||||
#include <Urho3D/Core/CoreEvents.h>
|
||||
#include <Urho3D/Graphics/DebugRenderer.h>
|
||||
#include <Urho3D/Urho2D/Drawable2D.h>
|
||||
#include <Urho3D/Engine/Engine.h>
|
||||
#include <Urho3D/UI/Font.h>
|
||||
#include <Urho3D/Graphics/Graphics.h>
|
||||
#include <Urho3D/Graphics/GraphicsEvents.h>
|
||||
#include <Urho3D/Input/Input.h>
|
||||
#include <Urho3D/Graphics/Octree.h>
|
||||
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
|
||||
#include <Urho3D/Graphics/Renderer.h>
|
||||
#include <Urho3D/Resource/ResourceCache.h>
|
||||
#include <Urho3D/Urho2D/RigidBody2D.h>
|
||||
#include <Urho3D/Scene/Scene.h>
|
||||
#include <Urho3D/UI/Text.h>
|
||||
#include <Urho3D/Urho2D/TileMap2D.h>
|
||||
#include <Urho3D/Urho2D/TileMapLayer2D.h>
|
||||
#include <Urho3D/Urho2D/TmxFile2D.h>
|
||||
#include <Urho3D/UI/UIEvents.h>
|
||||
#include <Urho3D/Graphics/Zone.h>
|
||||
#include <Urho3D/Urho2D/PhysicsEvents2D.h>
|
||||
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
|
||||
|
||||
#include <Urho3D/DebugNew.h>
|
||||
|
||||
#include "Character2D.h"
|
||||
#include "Utilities2D/Sample2D.h"
|
||||
#include "Utilities2D/Mover.h"
|
||||
#include "Urho2DIsometricDemo.h"
|
||||
|
||||
Urho2DIsometricDemo::Urho2DIsometricDemo(Context* context) :
|
||||
Sample(context),
|
||||
zoom_(2.0f),
|
||||
drawDebug_(false)
|
||||
{
|
||||
// Register factory for the Character2D component so it can be created via CreateComponent
|
||||
Character2D::RegisterObject(context);
|
||||
// Register factory and attributes for the Mover component so it can be created via CreateComponent, and loaded / saved
|
||||
Mover::RegisterObject(context);
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::Setup()
|
||||
{
|
||||
Sample::Setup();
|
||||
engineParameters_[EP_SOUND] = true;
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::Start()
|
||||
{
|
||||
// Execute base class startup
|
||||
Sample::Start();
|
||||
|
||||
sample2D_ = new Sample2D(context_);
|
||||
|
||||
// Set filename for load/save functions
|
||||
sample2D_->demoFilename_ = "Isometric2D";
|
||||
|
||||
// Create the scene content
|
||||
CreateScene();
|
||||
|
||||
// Create the UI content
|
||||
sample2D_->CreateUIContent("ISOMETRIC 2.5D DEMO", character2D_->remainingLifes_, character2D_->remainingCoins_);
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
Button* playButton = static_cast<Button*>(ui->GetRoot()->GetChild("PlayButton", true));
|
||||
SubscribeToEvent(playButton, E_RELEASED, URHO3D_HANDLER(Urho2DIsometricDemo, HandlePlayButton));
|
||||
|
||||
// Hook up to the frame update events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::CreateScene()
|
||||
{
|
||||
scene_ = new Scene(context_);
|
||||
sample2D_->scene_ = scene_;
|
||||
|
||||
// Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_->CreateComponent<Octree>();
|
||||
scene_->CreateComponent<DebugRenderer>();
|
||||
PhysicsWorld2D* physicsWorld = scene_->CreateComponent<PhysicsWorld2D>();
|
||||
physicsWorld->SetGravity(Vector2(0.0f, 0.0f)); // Neutralize gravity as the character will always be grounded
|
||||
|
||||
// Create camera
|
||||
cameraNode_ = scene_->CreateChild("Camera");
|
||||
Camera* camera = cameraNode_->CreateComponent<Camera>();
|
||||
camera->SetOrthographic(true);
|
||||
|
||||
Graphics* graphics = GetSubsystem<Graphics>();
|
||||
camera->SetOrthoSize((float)graphics->GetHeight() * PIXEL_SIZE);
|
||||
camera->SetZoom(2.0f * Min((float)graphics->GetWidth() / 1280.0f, (float)graphics->GetHeight() / 800.0f)); // Set zoom according to user's resolution to ensure full visibility (initial zoom (2.0) is set for full visibility at 1280x800 resolution)
|
||||
|
||||
// Setup the viewport for displaying the scene
|
||||
SharedPtr<Viewport> viewport(new Viewport(context_, scene_, camera));
|
||||
Renderer* renderer = GetSubsystem<Renderer>();
|
||||
renderer->SetViewport(0, viewport);
|
||||
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
|
||||
// Create tile map from tmx file
|
||||
TmxFile2D* tmxFile = cache->GetResource<TmxFile2D>("Urho2D/Tilesets/atrium.tmx");
|
||||
SharedPtr<Node> tileMapNode(scene_->CreateChild("TileMap"));
|
||||
TileMap2D* tileMap = tileMapNode->CreateComponent<TileMap2D>();
|
||||
tileMap->SetTmxFile(tmxFile);
|
||||
const TileMapInfo2D& info = tileMap->GetInfo();
|
||||
|
||||
// Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
Node* spriteNode = sample2D_->CreateCharacter(info, 0.0f, Vector3(-5.0f, 11.0f, 0.0f), 0.15f);
|
||||
character2D_ = spriteNode->CreateComponent<Character2D>(); // Create a logic component to handle character behavior
|
||||
// Scale character's speed on the Y axis according to tiles' aspect ratio
|
||||
character2D_->moveSpeedScale_ = info.tileHeight_ / info.tileWidth_;
|
||||
character2D_->zoom_ = camera->GetZoom();
|
||||
|
||||
// Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
TileMapLayer2D* tileMapLayer = tileMap->GetLayer(tileMap->GetNumLayers() - 1);
|
||||
sample2D_->CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
|
||||
|
||||
// Instantiate enemies at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
sample2D_->PopulateMovingEntities(tileMap->GetLayer(tileMap->GetNumLayers() - 2));
|
||||
|
||||
// Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
|
||||
TileMapLayer2D* coinsLayer = tileMap->GetLayer(tileMap->GetNumLayers() - 3);
|
||||
sample2D_->PopulateCoins(coinsLayer);
|
||||
|
||||
// Init coins counters
|
||||
character2D_->remainingCoins_ = coinsLayer->GetNumObjects();
|
||||
character2D_->maxCoins_ = coinsLayer->GetNumObjects();
|
||||
|
||||
// Check when scene is rendered
|
||||
SubscribeToEvent(E_ENDRENDERING, URHO3D_HANDLER(Urho2DIsometricDemo, HandleSceneRendered));
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node* hitNode = static_cast<Node*>(eventData[PhysicsBeginContact2D::P_NODEA].GetPtr());
|
||||
if (hitNode->GetName() == "Imp")
|
||||
hitNode = static_cast<Node*>(eventData[PhysicsBeginContact2D::P_NODEB].GetPtr());
|
||||
String nodeName = hitNode->GetName();
|
||||
Node* character2DNode = scene_->GetChild("Imp", true);
|
||||
|
||||
// Handle coins picking
|
||||
if (nodeName == "Coin")
|
||||
{
|
||||
hitNode->Remove();
|
||||
character2D_->remainingCoins_ -= 1;
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
if (character2D_->remainingCoins_ == 0)
|
||||
{
|
||||
Text* instructions = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
|
||||
instructions->SetText("!!! You have all the coins !!!");
|
||||
}
|
||||
Text* coinsText = static_cast<Text*>(ui->GetRoot()->GetChild("CoinsText", true));
|
||||
coinsText->SetText(String(character2D_->remainingCoins_)); // Update coins UI counter
|
||||
sample2D_->PlaySoundEffect("Powerup.wav");
|
||||
}
|
||||
|
||||
// Handle interactions with enemies
|
||||
if (nodeName == "Orc")
|
||||
{
|
||||
AnimatedSprite2D* animatedSprite = character2DNode->GetComponent<AnimatedSprite2D>();
|
||||
float deltaX = character2DNode->GetPosition().x_ - hitNode->GetPosition().x_;
|
||||
|
||||
// Orc killed if character is fighting in its direction when the contact occurs
|
||||
if (animatedSprite->GetAnimation() == "attack" && (deltaX < 0 == animatedSprite->GetFlipX()))
|
||||
{
|
||||
static_cast<Mover*>(hitNode->GetComponent<Mover>())->emitTime_ = 1;
|
||||
if (!hitNode->GetChild("Emitter", true))
|
||||
{
|
||||
hitNode->GetComponent("RigidBody2D")->Remove(); // Remove Orc's body
|
||||
sample2D_->SpawnEffect(hitNode);
|
||||
sample2D_->PlaySoundEffect("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
// Player killed if not fighting in the direction of the Orc when the contact occurs
|
||||
else
|
||||
{
|
||||
if (!character2DNode->GetChild("Emitter", true))
|
||||
{
|
||||
character2D_->wounded_ = true;
|
||||
if (nodeName == "Orc")
|
||||
{
|
||||
Mover* orc = static_cast<Mover*>(hitNode->GetComponent<Mover>());
|
||||
orc->fightTimer_ = 1;
|
||||
}
|
||||
sample2D_->SpawnEffect(character2DNode);
|
||||
sample2D_->PlaySoundEffect("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandleSceneRendered(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
UnsubscribeFromEvent(E_ENDRENDERING);
|
||||
// Save the scene so we can reload it later
|
||||
sample2D_->SaveScene(true);
|
||||
// Pause the scene as long as the UI is hiding it
|
||||
scene_->SetUpdateEnabled(false);
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::SubscribeToEvents()
|
||||
{
|
||||
// Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Urho2DIsometricDemo, HandleUpdate));
|
||||
|
||||
// Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent(E_POSTUPDATE, URHO3D_HANDLER(Urho2DIsometricDemo, HandlePostUpdate));
|
||||
|
||||
// Subscribe to PostRenderUpdate to draw debug geometry
|
||||
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(Urho2DIsometricDemo, HandlePostRenderUpdate));
|
||||
|
||||
// Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent(E_SCENEUPDATE);
|
||||
|
||||
// Subscribe to Box2D contact listeners
|
||||
SubscribeToEvent(E_PHYSICSBEGINCONTACT2D, URHO3D_HANDLER(Urho2DIsometricDemo, HandleCollisionBegin));
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
using namespace Update;
|
||||
|
||||
// Zoom in/out
|
||||
if (cameraNode_)
|
||||
sample2D_->Zoom(cameraNode_->GetComponent<Camera>());
|
||||
|
||||
Input* input = GetSubsystem<Input>();
|
||||
|
||||
// Toggle debug geometry with 'Z' key
|
||||
if (input->GetKeyPress(KEY_Z))
|
||||
drawDebug_ = !drawDebug_;
|
||||
|
||||
// Check for loading / saving the scene
|
||||
if (input->GetKeyPress(KEY_F5))
|
||||
sample2D_->SaveScene(false);
|
||||
|
||||
if (input->GetKeyPress(KEY_F7))
|
||||
ReloadScene(false);
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (!character2D_)
|
||||
return;
|
||||
|
||||
Node* character2DNode = character2D_->GetNode();
|
||||
cameraNode_->SetPosition(Vector3(character2DNode->GetPosition().x_, character2DNode->GetPosition().y_, -10.0f)); // Camera tracks character
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (drawDebug_)
|
||||
{
|
||||
PhysicsWorld2D* physicsWorld = scene_->GetComponent<PhysicsWorld2D>();
|
||||
physicsWorld->DrawDebugGeometry();
|
||||
|
||||
Node* tileMapNode = scene_->GetChild("TileMap", true);
|
||||
TileMap2D* map = tileMapNode->GetComponent<TileMap2D>();
|
||||
map->DrawDebugGeometry(scene_->GetComponent<DebugRenderer>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::ReloadScene(bool reInit)
|
||||
{
|
||||
String filename = sample2D_->demoFilename_;
|
||||
if (!reInit)
|
||||
filename += "InGame";
|
||||
|
||||
File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/" + filename + ".xml", FILE_READ);
|
||||
scene_->LoadXML(loadFile);
|
||||
// After loading we have to reacquire the weak pointer to the Character2D component, as it has been recreated
|
||||
// Simply find the character's scene node by name as there's only one of them
|
||||
Node* character2DNode = scene_->GetChild("Imp", true);
|
||||
if (character2DNode)
|
||||
character2D_ = character2DNode->GetComponent<Character2D>();
|
||||
|
||||
// Set what number to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false)
|
||||
int lifes = character2D_->remainingLifes_;
|
||||
int coins = character2D_->remainingCoins_;
|
||||
if (reInit)
|
||||
{
|
||||
lifes = LIFES;
|
||||
coins = character2D_->maxCoins_;
|
||||
}
|
||||
|
||||
// Update lifes UI
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
Text* lifeText = static_cast<Text*>(ui->GetRoot()->GetChild("LifeText", true));
|
||||
lifeText->SetText(String(lifes));
|
||||
|
||||
// Update coins UI
|
||||
Text* coinsText = static_cast<Text*>(ui->GetRoot()->GetChild("CoinsText", true));
|
||||
coinsText->SetText(String(coins));
|
||||
}
|
||||
|
||||
void Urho2DIsometricDemo::HandlePlayButton(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Remove fullscreen UI and unfreeze the scene
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
if (static_cast<Text*>(ui->GetRoot()->GetChild("FullUI", true)))
|
||||
{
|
||||
ui->GetRoot()->GetChild("FullUI", true)->Remove();
|
||||
scene_->SetUpdateEnabled(true);
|
||||
}
|
||||
else
|
||||
// Reload scene
|
||||
ReloadScene(true);
|
||||
|
||||
// Hide Instructions and Play/Exit buttons
|
||||
Text* instructionText = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
|
||||
instructionText->SetText("");
|
||||
Button* exitButton = static_cast<Button*>(ui->GetRoot()->GetChild("ExitButton", true));
|
||||
exitButton->SetVisible(false);
|
||||
Button* playButton = static_cast<Button*>(ui->GetRoot()->GetChild("PlayButton", true));
|
||||
playButton->SetVisible(false);
|
||||
|
||||
// Hide mouse cursor
|
||||
Input* input = GetSubsystem<Input>();
|
||||
input->SetMouseVisible(false);
|
||||
}
|
||||
|
||||
URHO3D_DEFINE_APPLICATION_MAIN(Urho2DIsometricDemo)
|
83
Source/Samples/49_Urho2DIsometricDemo/Urho2DIsometricDemo.h
Normal file
83
Source/Samples/49_Urho2DIsometricDemo/Urho2DIsometricDemo.h
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Sample.h"
|
||||
#include "Utilities2D/Sample2D.h"
|
||||
|
||||
class Character2D;
|
||||
class Sample2D;
|
||||
|
||||
/// Urho2D tile map example.
|
||||
/// This sample demonstrates:
|
||||
/// - Creating an isometric 2D scene with tile map
|
||||
/// - Displaying the scene using the Renderer subsystem
|
||||
/// - Handling keyboard to move a character and zoom 2D camera
|
||||
/// - Generating physics shapes from the tmx file's objects
|
||||
/// - Displaying debug geometry for physics and tile map
|
||||
/// Note that this sample uses some functions from Sample2D utility class.
|
||||
class Urho2DIsometricDemo : public Sample
|
||||
{
|
||||
URHO3D_OBJECT(Urho2DIsometricDemo, Sample);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Urho2DIsometricDemo(Context* context);
|
||||
|
||||
/// Setup after engine initialization and before running the main loop.
|
||||
virtual void Start();
|
||||
/// Setup before engine initialization. Modifies the engine parameters.
|
||||
virtual void Setup();
|
||||
|
||||
private:
|
||||
/// Construct the scene content.
|
||||
void CreateScene();
|
||||
/// Construct an instruction text to the UI.
|
||||
void CreateInstructions();
|
||||
/// Subscribe to application-wide logic update events.
|
||||
void SubscribeToEvents();
|
||||
/// Handle the logic update event.
|
||||
void HandleUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the logic post update event.
|
||||
void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the post render update event.
|
||||
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the end rendering event.
|
||||
void HandleSceneRendered(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle reloading the scene.
|
||||
void ReloadScene(bool reInit);
|
||||
/// Handle the contact begin event (Box2D contact listener).
|
||||
void HandleCollisionBegin(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle 'PLAY' button released event.
|
||||
void HandlePlayButton(StringHash eventType, VariantMap& eventData);
|
||||
|
||||
/// The controllable character component.
|
||||
WeakPtr<Character2D> character2D_;
|
||||
/// Camera's zoom (used to scale movement speed based on camera zoom).
|
||||
float zoom_;
|
||||
/// Flag for drawing debug geometry.
|
||||
bool drawDebug_;
|
||||
|
||||
/// Sample2D utility object.
|
||||
SharedPtr<Sample2D> sample2D_;
|
||||
};
|
33
Source/Samples/50_Urho2DPlatformer/CMakeLists.txt
Normal file
33
Source/Samples/50_Urho2DPlatformer/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
#
|
||||
# Copyright (c) 2008-2014 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.
|
||||
#
|
||||
|
||||
# Define target name
|
||||
set (TARGET_NAME 50_Urho2DPlatformer)
|
||||
|
||||
# Define source files
|
||||
define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES} ../Utilities2D/Sample2D.h ../Utilities2D/Sample2D.cpp ../Utilities2D/Mover.h ../Utilities2D/Mover.cpp)
|
||||
|
||||
# Setup target with resource copying
|
||||
setup_main_executable ()
|
||||
|
||||
# Setup test cases
|
||||
setup_test ()
|
237
Source/Samples/50_Urho2DPlatformer/Character2D.cpp
Normal file
237
Source/Samples/50_Urho2DPlatformer/Character2D.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#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
|
||||
Input* input = GetSubsystem<Input>();
|
||||
RigidBody2D* body = GetComponent<RigidBody2D>();
|
||||
AnimatedSprite2D* animatedSprite = GetComponent<AnimatedSprite2D>();
|
||||
bool onGround = false;
|
||||
bool jump = false;
|
||||
|
||||
// Collision detection (AABB query)
|
||||
Vector2 characterHalfSize = Vector2(0.16f, 0.16f);
|
||||
PhysicsWorld2D* 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('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('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('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)
|
||||
{
|
||||
RigidBody2D* body = GetComponent<RigidBody2D>();
|
||||
AnimatedSprite2D* 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;
|
||||
UI* 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()
|
||||
{
|
||||
RigidBody2D* body = GetComponent<RigidBody2D>();
|
||||
AnimatedSprite2D* animatedSprite = GetComponent<AnimatedSprite2D>();
|
||||
|
||||
// Set state to 'killed'
|
||||
killed_ = true;
|
||||
|
||||
// Update UI elements
|
||||
UI* 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
|
||||
Input* 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");
|
||||
}
|
72
Source/Samples/50_Urho2DPlatformer/Character2D.h
Normal file
72
Source/Samples/50_Urho2DPlatformer/Character2D.h
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Urho3D/Scene/LogicComponent.h>
|
||||
|
||||
// All Urho3D classes reside in namespace Urho3D
|
||||
using namespace Urho3D;
|
||||
|
||||
const float MOVE_SPEED = 23.0f;
|
||||
const int LIFES = 3;
|
||||
|
||||
/// Character2D component controling Imp behavior.
|
||||
class Character2D : public LogicComponent
|
||||
{
|
||||
URHO3D_OBJECT(Character2D, LogicComponent);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Character2D(Context* context);
|
||||
|
||||
/// Register object factory and attributes.
|
||||
static void RegisterObject(Context* context);
|
||||
|
||||
/// Handle update. Called by LogicComponent base class.
|
||||
virtual void Update(float timeStep) override;
|
||||
/// Handle player state/behavior when wounded.
|
||||
void HandleWoundedState(float timeStep);
|
||||
/// Handle death of the player.
|
||||
void HandleDeath();
|
||||
|
||||
/// Flag when player is wounded.
|
||||
bool wounded_;
|
||||
/// Flag when player is dead.
|
||||
bool killed_;
|
||||
/// Timer for particle emitter duration.
|
||||
float timer_;
|
||||
/// Number of coins in the current level.
|
||||
int maxCoins_;
|
||||
/// Counter for remaining coins to pick.
|
||||
int remainingCoins_;
|
||||
/// Counter for remaining lifes.
|
||||
int remainingLifes_;
|
||||
/// Indicate when the player is climbing a ladder or a rope.
|
||||
bool isClimbing_;
|
||||
/// Used only for ropes, as they are split into 2 shapes.
|
||||
bool climb2_;
|
||||
/// Indicate when the player is above a climbable object, so we can still jump anyway.
|
||||
bool aboveClimbable_;
|
||||
/// Indicate when the player is climbing a slope, so we can apply force to its body.
|
||||
bool onSlope_;
|
||||
};
|
447
Source/Samples/50_Urho2DPlatformer/Urho2DPlatformer.cpp
Normal file
447
Source/Samples/50_Urho2DPlatformer/Urho2DPlatformer.cpp
Normal file
@ -0,0 +1,447 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#include <Urho3D/Audio/Audio.h>
|
||||
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
|
||||
#include <Urho3D/Urho2D/AnimationSet2D.h>
|
||||
#include <Urho3D/UI/Button.h>
|
||||
#include <Urho3D/Graphics/Camera.h>
|
||||
#include <Urho3D/Urho2D/CollisionBox2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionChain2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionCircle2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionPolygon2D.h>
|
||||
#include <Urho3D/Core/CoreEvents.h>
|
||||
#include <Urho3D/Graphics/DebugRenderer.h>
|
||||
#include <Urho3D/Urho2D/Drawable2D.h>
|
||||
#include <Urho3D/Engine/Engine.h>
|
||||
#include <Urho3D/UI/Font.h>
|
||||
#include <Urho3D/Graphics/Graphics.h>
|
||||
#include <Urho3D/Graphics/GraphicsEvents.h>
|
||||
#include <Urho3D/Input/Input.h>
|
||||
#include <Urho3D/Graphics/Octree.h>
|
||||
#include <Urho3D/Urho2D/PhysicsEvents2D.h>
|
||||
#include <Urho3D/Urho2D/PhysicsWorld2D.h>
|
||||
#include <Urho3D/Graphics/Renderer.h>
|
||||
#include <Urho3D/Resource/ResourceCache.h>
|
||||
#include <Urho3D/Urho2D/RigidBody2D.h>
|
||||
#include <Urho3D/Scene/Scene.h>
|
||||
#include <Urho3D/Scene/SceneEvents.h>
|
||||
#include <Urho3D/Core/StringUtils.h>
|
||||
#include <Urho3D/UI/Text.h>
|
||||
#include <Urho3D/Urho2D/TileMap2D.h>
|
||||
#include <Urho3D/Urho2D/TileMapLayer2D.h>
|
||||
#include <Urho3D/Urho2D/TmxFile2D.h>
|
||||
#include <Urho3D/UI/UI.h>
|
||||
#include <Urho3D/UI/UIEvents.h>
|
||||
#include <Urho3D/Graphics/Zone.h>
|
||||
|
||||
#include <Urho3D/DebugNew.h>
|
||||
|
||||
#include "Character2D.h"
|
||||
#include "Utilities2D/Sample2D.h"
|
||||
#include "Utilities2D/Mover.h"
|
||||
#include "Urho2DPlatformer.h"
|
||||
|
||||
|
||||
URHO3D_DEFINE_APPLICATION_MAIN(Urho2DPlatformer)
|
||||
|
||||
Urho2DPlatformer::Urho2DPlatformer(Context* context) :
|
||||
Sample(context),
|
||||
drawDebug_(false)
|
||||
{
|
||||
// Register factory for the Character2D component so it can be created via CreateComponent
|
||||
Character2D::RegisterObject(context);
|
||||
// Register factory and attributes for the Mover component so it can be created via CreateComponent, and loaded / saved
|
||||
Mover::RegisterObject(context);
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::Setup()
|
||||
{
|
||||
Sample::Setup();
|
||||
engineParameters_[EP_SOUND] = true;
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::Start()
|
||||
{
|
||||
// Execute base class startup
|
||||
Sample::Start();
|
||||
|
||||
sample2D_ = new Sample2D(context_);
|
||||
|
||||
// Set filename for load/save functions
|
||||
sample2D_->demoFilename_ = "Platformer2D";
|
||||
|
||||
// Create the scene content
|
||||
CreateScene();
|
||||
|
||||
// Create the UI content
|
||||
sample2D_->CreateUIContent("PLATFORMER 2D DEMO", character2D_->remainingLifes_, character2D_->remainingCoins_);
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
Button* playButton = static_cast<Button*>(ui->GetRoot()->GetChild("PlayButton", true));
|
||||
SubscribeToEvent(playButton, E_RELEASED, URHO3D_HANDLER(Urho2DPlatformer, HandlePlayButton));
|
||||
|
||||
// Hook up to the frame update events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::CreateScene()
|
||||
{
|
||||
scene_ = new Scene(context_);
|
||||
sample2D_->scene_ = scene_;
|
||||
|
||||
// Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_->CreateComponent<Octree>();
|
||||
scene_->CreateComponent<DebugRenderer>();
|
||||
/*PhysicsWorld2D* physicsWorld =*/ scene_->CreateComponent<PhysicsWorld2D>();
|
||||
|
||||
// Create camera
|
||||
cameraNode_ = scene_->CreateChild("Camera");
|
||||
Camera* camera = cameraNode_->CreateComponent<Camera>();
|
||||
camera->SetOrthographic(true);
|
||||
|
||||
Graphics* graphics = GetSubsystem<Graphics>();
|
||||
camera->SetOrthoSize((float)graphics->GetHeight() * PIXEL_SIZE);
|
||||
camera->SetZoom(2.0f * Min((float)graphics->GetWidth() / 1280.0f, (float)graphics->GetHeight() / 800.0f)); // Set zoom according to user's resolution to ensure full visibility (initial zoom (2.0) is set for full visibility at 1280x800 resolution)
|
||||
|
||||
// Setup the viewport for displaying the scene
|
||||
SharedPtr<Viewport> viewport(new Viewport(context_, scene_, camera));
|
||||
Renderer* renderer = GetSubsystem<Renderer>();
|
||||
renderer->SetViewport(0, viewport);
|
||||
|
||||
// Set background color for the scene
|
||||
Zone* zone = renderer->GetDefaultZone();
|
||||
zone->SetFogColor(Color(0.2f, 0.2f, 0.2f));
|
||||
|
||||
// Create tile map from tmx file
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
SharedPtr<Node> tileMapNode(scene_->CreateChild("TileMap"));
|
||||
TileMap2D* tileMap = tileMapNode->CreateComponent<TileMap2D>();
|
||||
tileMap->SetTmxFile(cache->GetResource<TmxFile2D>("Urho2D/Tilesets/Ortho.tmx"));
|
||||
const TileMapInfo2D& info = tileMap->GetInfo();
|
||||
|
||||
// Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
Node* spriteNode = sample2D_->CreateCharacter(info, 0.8f, Vector3(1.0f, 8.0f, 0.0f), 0.2f);
|
||||
character2D_ = spriteNode->CreateComponent<Character2D>(); // Create a logic component to handle character behavior
|
||||
|
||||
// Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
TileMapLayer2D* tileMapLayer = tileMap->GetLayer(tileMap->GetNumLayers() - 1);
|
||||
sample2D_->CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
|
||||
|
||||
// Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
sample2D_->PopulateMovingEntities(tileMap->GetLayer(tileMap->GetNumLayers() - 2));
|
||||
|
||||
// Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
|
||||
TileMapLayer2D* coinsLayer = tileMap->GetLayer(tileMap->GetNumLayers() - 3);
|
||||
sample2D_->PopulateCoins(coinsLayer);
|
||||
|
||||
// Init coins counters
|
||||
character2D_->remainingCoins_ = coinsLayer->GetNumObjects();
|
||||
character2D_->maxCoins_ = coinsLayer->GetNumObjects();
|
||||
|
||||
//Instantiate triggers (for ropes, ladders, lava, slopes...) at each placeholder of "Triggers" layer (placeholders for triggers are Rectangle objects)
|
||||
sample2D_->PopulateTriggers(tileMap->GetLayer(tileMap->GetNumLayers() - 4));
|
||||
|
||||
// Create background
|
||||
sample2D_->CreateBackgroundSprite(info, 3.5, "Textures/HeightMap.png", true);
|
||||
|
||||
// Check when scene is rendered
|
||||
SubscribeToEvent(E_ENDRENDERING, URHO3D_HANDLER(Urho2DPlatformer, HandleSceneRendered));
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandleSceneRendered(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
UnsubscribeFromEvent(E_ENDRENDERING);
|
||||
// Save the scene so we can reload it later
|
||||
sample2D_->SaveScene(true);
|
||||
// Pause the scene as long as the UI is hiding it
|
||||
scene_->SetUpdateEnabled(false);
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::SubscribeToEvents()
|
||||
{
|
||||
// Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Urho2DPlatformer, HandleUpdate));
|
||||
|
||||
// Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent(E_POSTUPDATE, URHO3D_HANDLER(Urho2DPlatformer, HandlePostUpdate));
|
||||
|
||||
// Subscribe to PostRenderUpdate to draw debug geometry
|
||||
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(Urho2DPlatformer, HandlePostRenderUpdate));
|
||||
|
||||
// Subscribe to Box2D contact listeners
|
||||
SubscribeToEvent(E_PHYSICSBEGINCONTACT2D, URHO3D_HANDLER(Urho2DPlatformer, HandleCollisionBegin));
|
||||
SubscribeToEvent(E_PHYSICSENDCONTACT2D, URHO3D_HANDLER(Urho2DPlatformer, HandleCollisionEnd));
|
||||
|
||||
// Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent(E_SCENEUPDATE);
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node* hitNode = static_cast<Node*>(eventData[PhysicsBeginContact2D::P_NODEA].GetPtr());
|
||||
if (hitNode->GetName() == "Imp")
|
||||
hitNode = static_cast<Node*>(eventData[PhysicsBeginContact2D::P_NODEB].GetPtr());
|
||||
String nodeName = hitNode->GetName();
|
||||
Node* character2DNode = scene_->GetChild("Imp", true);
|
||||
|
||||
// Handle ropes and ladders climbing
|
||||
if (nodeName == "Climb")
|
||||
{
|
||||
if (character2D_->isClimbing_) // If transition between rope and top of rope (as we are using split triggers)
|
||||
character2D_->climb2_ = true;
|
||||
else
|
||||
{
|
||||
character2D_->isClimbing_ = true;
|
||||
RigidBody2D* body = character2DNode->GetComponent<RigidBody2D>();
|
||||
body->SetGravityScale(0.0f); // Override gravity so that the character doesn't fall
|
||||
// Clear forces so that the character stops (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
body->SetLinearVelocity(Vector2(0.0f, 0.0f));
|
||||
body->SetAwake(false);
|
||||
body->SetAwake(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeName == "CanJump")
|
||||
character2D_->aboveClimbable_ = true;
|
||||
|
||||
// Handle coins picking
|
||||
if (nodeName == "Coin")
|
||||
{
|
||||
hitNode->Remove();
|
||||
character2D_->remainingCoins_ -= 1;
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
if (character2D_->remainingCoins_ == 0)
|
||||
{
|
||||
Text* instructions = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
|
||||
instructions->SetText("!!! Go to the Exit !!!");
|
||||
}
|
||||
Text* coinsText = static_cast<Text*>(ui->GetRoot()->GetChild("CoinsText", true));
|
||||
coinsText->SetText(String(character2D_->remainingCoins_)); // Update coins UI counter
|
||||
sample2D_->PlaySoundEffect("Powerup.wav");
|
||||
}
|
||||
|
||||
// Handle interactions with enemies
|
||||
if (nodeName == "Enemy" || nodeName == "Orc")
|
||||
{
|
||||
AnimatedSprite2D* animatedSprite = character2DNode->GetComponent<AnimatedSprite2D>();
|
||||
float deltaX = character2DNode->GetPosition().x_ - hitNode->GetPosition().x_;
|
||||
|
||||
// Orc killed if character is fighting in its direction when the contact occurs (flowers are not destroyable)
|
||||
if (nodeName == "Orc" && animatedSprite->GetAnimation() == "attack" && (deltaX < 0 == animatedSprite->GetFlipX()))
|
||||
{
|
||||
static_cast<Mover*>(hitNode->GetComponent<Mover>())->emitTime_ = 1;
|
||||
if (!hitNode->GetChild("Emitter", true))
|
||||
{
|
||||
hitNode->GetComponent("RigidBody2D")->Remove(); // Remove Orc's body
|
||||
sample2D_->SpawnEffect(hitNode);
|
||||
sample2D_->PlaySoundEffect("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
// Player killed if not fighting in the direction of the Orc when the contact occurs, or when colliding with a flower
|
||||
else
|
||||
{
|
||||
if (!character2DNode->GetChild("Emitter", true))
|
||||
{
|
||||
character2D_->wounded_ = true;
|
||||
if (nodeName == "Orc")
|
||||
{
|
||||
Mover* orc = static_cast<Mover*>(hitNode->GetComponent<Mover>());
|
||||
orc->fightTimer_ = 1;
|
||||
}
|
||||
sample2D_->SpawnEffect(character2DNode);
|
||||
sample2D_->PlaySoundEffect("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle exiting the level when all coins have been gathered
|
||||
if (nodeName == "Exit" && character2D_->remainingCoins_ == 0)
|
||||
{
|
||||
// Update UI
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
Text* instructions = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
|
||||
instructions->SetText("!!! WELL DONE !!!");
|
||||
instructions->SetPosition(IntVector2(0, 0));
|
||||
// Put the character outside of the scene and magnify him
|
||||
character2DNode->SetPosition(Vector3(-20.0f, 0.0f, 0.0f));
|
||||
character2DNode->SetScale(1.5f);
|
||||
}
|
||||
|
||||
// Handle falling into lava
|
||||
if (nodeName == "Lava")
|
||||
{
|
||||
RigidBody2D* body = character2DNode->GetComponent<RigidBody2D>();
|
||||
body->ApplyForceToCenter(Vector2(0.0f, 1000.0f), true);
|
||||
if (!character2DNode->GetChild("Emitter", true))
|
||||
{
|
||||
character2D_->wounded_ = true;
|
||||
sample2D_->SpawnEffect(character2DNode);
|
||||
sample2D_->PlaySoundEffect("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle climbing a slope
|
||||
if (nodeName == "Slope")
|
||||
character2D_->onSlope_ = true;
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandleCollisionEnd(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node* hitNode = static_cast<Node*>(eventData[PhysicsEndContact2D::P_NODEA].GetPtr());
|
||||
if (hitNode->GetName() == "Imp")
|
||||
hitNode = static_cast<Node*>(eventData[PhysicsEndContact2D::P_NODEB].GetPtr());
|
||||
String nodeName = hitNode->GetName();
|
||||
Node* character2DNode = scene_->GetChild("Imp", true);
|
||||
|
||||
// Handle leaving a rope or ladder
|
||||
if (nodeName == "Climb")
|
||||
{
|
||||
if (character2D_->climb2_)
|
||||
character2D_->climb2_ = false;
|
||||
else
|
||||
{
|
||||
character2D_->isClimbing_ = false;
|
||||
RigidBody2D* body = character2DNode->GetComponent<RigidBody2D>();
|
||||
body->SetGravityScale(1.0f); // Restore gravity
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeName == "CanJump")
|
||||
character2D_->aboveClimbable_ = false;
|
||||
|
||||
// Handle leaving a slope
|
||||
if (nodeName == "Slope")
|
||||
{
|
||||
character2D_->onSlope_ = false;
|
||||
// Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
RigidBody2D* body = character2DNode->GetComponent<RigidBody2D>();
|
||||
body->SetLinearVelocity(Vector2::ZERO);
|
||||
body->SetAwake(false);
|
||||
body->SetAwake(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
using namespace Update;
|
||||
|
||||
// Zoom in/out
|
||||
if (cameraNode_)
|
||||
sample2D_->Zoom(cameraNode_->GetComponent<Camera>());
|
||||
|
||||
Input* input = GetSubsystem<Input>();
|
||||
|
||||
// Toggle debug geometry with 'Z' key
|
||||
if (input->GetKeyPress(KEY_Z))
|
||||
drawDebug_ = !drawDebug_;
|
||||
|
||||
// Check for loading / saving the scene
|
||||
if (input->GetKeyPress(KEY_F5))
|
||||
sample2D_->SaveScene(false);
|
||||
if (input->GetKeyPress(KEY_F7))
|
||||
ReloadScene(false);
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (!character2D_)
|
||||
return;
|
||||
|
||||
Node* character2DNode = character2D_->GetNode();
|
||||
cameraNode_->SetPosition(Vector3(character2DNode->GetPosition().x_, character2DNode->GetPosition().y_, -10.0f)); // Camera tracks character
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (drawDebug_)
|
||||
{
|
||||
PhysicsWorld2D* physicsWorld = scene_->GetComponent<PhysicsWorld2D>();
|
||||
physicsWorld->DrawDebugGeometry();
|
||||
|
||||
Node* tileMapNode = scene_->GetChild("TileMap", true);
|
||||
TileMap2D* map = tileMapNode->GetComponent<TileMap2D>();
|
||||
map->DrawDebugGeometry(scene_->GetComponent<DebugRenderer>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::ReloadScene(bool reInit)
|
||||
{
|
||||
String filename = sample2D_->demoFilename_;
|
||||
if (!reInit)
|
||||
filename += "InGame";
|
||||
|
||||
File loadFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/" + filename + ".xml", FILE_READ);
|
||||
scene_->LoadXML(loadFile);
|
||||
// After loading we have to reacquire the weak pointer to the Character2D component, as it has been recreated
|
||||
// Simply find the character's scene node by name as there's only one of them
|
||||
Node* character2DNode = scene_->GetChild("Imp", true);
|
||||
if (character2DNode)
|
||||
character2D_ = character2DNode->GetComponent<Character2D>();
|
||||
|
||||
// Set what number to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false)
|
||||
int lifes = character2D_->remainingLifes_;
|
||||
int coins = character2D_->remainingCoins_;
|
||||
if (reInit)
|
||||
{
|
||||
lifes = LIFES;
|
||||
coins = character2D_->maxCoins_;
|
||||
}
|
||||
|
||||
// Update lifes UI
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
Text* lifeText = static_cast<Text*>(ui->GetRoot()->GetChild("LifeText", true));
|
||||
lifeText->SetText(String(lifes));
|
||||
|
||||
// Update coins UI
|
||||
Text* coinsText = static_cast<Text*>(ui->GetRoot()->GetChild("CoinsText", true));
|
||||
coinsText->SetText(String(coins));
|
||||
}
|
||||
|
||||
void Urho2DPlatformer::HandlePlayButton(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Remove fullscreen UI and unfreeze the scene
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
if (static_cast<Text*>(ui->GetRoot()->GetChild("FullUI", true)))
|
||||
{
|
||||
ui->GetRoot()->GetChild("FullUI", true)->Remove();
|
||||
scene_->SetUpdateEnabled(true);
|
||||
}
|
||||
else
|
||||
// Reload scene
|
||||
ReloadScene(true);
|
||||
|
||||
// Hide Instructions and Play/Exit buttons
|
||||
Text* instructionText = static_cast<Text*>(ui->GetRoot()->GetChild("Instructions", true));
|
||||
instructionText->SetText("");
|
||||
Button* exitButton = static_cast<Button*>(ui->GetRoot()->GetChild("ExitButton", true));
|
||||
exitButton->SetVisible(false);
|
||||
Button* playButton = static_cast<Button*>(ui->GetRoot()->GetChild("PlayButton", true));
|
||||
playButton->SetVisible(false);
|
||||
|
||||
// Hide mouse cursor
|
||||
Input* input = GetSubsystem<Input>();
|
||||
input->SetMouseVisible(false);
|
||||
}
|
87
Source/Samples/50_Urho2DPlatformer/Urho2DPlatformer.h
Normal file
87
Source/Samples/50_Urho2DPlatformer/Urho2DPlatformer.h
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Sample.h"
|
||||
#include "Utilities2D/Sample2D.h"
|
||||
|
||||
class Character2D;
|
||||
class Sample2D;
|
||||
|
||||
/// Urho2D platformer example.
|
||||
/// This sample demonstrates:
|
||||
/// - Creating an orthogonal 2D scene from tile map file
|
||||
/// - Displaying the scene using the Renderer subsystem
|
||||
/// - Handling keyboard to move a character and zoom 2D camera
|
||||
/// - Generating physics shapes from the tmx file's objects
|
||||
/// - Mixing physics and translations to move the character
|
||||
/// - Using Box2D Contact listeners to handle the gameplay
|
||||
/// - Displaying debug geometry for physics and tile map
|
||||
/// Note that this sample uses some functions from Sample2D utility class.
|
||||
class Urho2DPlatformer : public Sample
|
||||
{
|
||||
URHO3D_OBJECT(Urho2DPlatformer, Sample);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Urho2DPlatformer(Context* context);
|
||||
|
||||
/// Setup after engine initialization and before running the main loop.
|
||||
virtual void Start();
|
||||
/// Setup before engine initialization. Modifies the engine parameters.
|
||||
virtual void Setup();
|
||||
|
||||
private:
|
||||
/// Construct the scene content.
|
||||
void CreateScene();
|
||||
/// Construct an instruction text to the UI.
|
||||
void CreateInstructions();
|
||||
/// Subscribe to application-wide logic update events.
|
||||
void SubscribeToEvents();
|
||||
/// Handle the logic update event.
|
||||
void HandleUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the logic post update event.
|
||||
void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the post render update event.
|
||||
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the end rendering event.
|
||||
void HandleSceneRendered(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the contact begin event (Box2D contact listener).
|
||||
void HandleCollisionBegin(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle the contact end event (Box2D contact listener).
|
||||
void HandleCollisionEnd(StringHash eventType, VariantMap& eventData);
|
||||
/// Handle reloading the scene.
|
||||
void ReloadScene(bool reInit);
|
||||
/// Handle 'PLAY' button released event.
|
||||
void HandlePlayButton(StringHash eventType, VariantMap& eventData);
|
||||
|
||||
/// The controllable character component.
|
||||
WeakPtr<Character2D> character2D_;
|
||||
/// Flag for drawing debug geometry.
|
||||
bool drawDebug_;
|
||||
/// Scaling factor based on tiles' aspect ratio.
|
||||
float moveSpeedScale_;
|
||||
|
||||
/// Sample2D utility object.
|
||||
SharedPtr<Sample2D> sample2D_;
|
||||
};
|
166
Source/Samples/Utilities2D/Mover.cpp
Normal file
166
Source/Samples/Utilities2D/Mover.cpp
Normal file
@ -0,0 +1,166 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
|
||||
#include <Urho3D/Urho2D/AnimationSet2D.h>
|
||||
#include <Urho3D/Core/Context.h>
|
||||
#include <Urho3D/IO/MemoryBuffer.h>
|
||||
#include <Urho3D/Scene/Scene.h>
|
||||
#include <Urho3D/Scene/SceneEvents.h>
|
||||
|
||||
#include <Urho3D/DebugNew.h>
|
||||
|
||||
#include "Mover.h"
|
||||
|
||||
Mover::Mover(Context* context) :
|
||||
LogicComponent(context),
|
||||
speed_(0.8f),
|
||||
currentPathID_(1),
|
||||
emitTime_(0.0f),
|
||||
fightTimer_(0.0f),
|
||||
flip_(0.0f)
|
||||
{
|
||||
// Only the scene update event is needed: unsubscribe from the rest for optimization
|
||||
SetUpdateEventMask(USE_UPDATE);
|
||||
}
|
||||
|
||||
void Mover::RegisterObject(Context* context)
|
||||
{
|
||||
context->RegisterFactory<Mover>();
|
||||
|
||||
// These macros register the class attribute 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_MIXED_ACCESSOR_ATTRIBUTE("Path", GetPathAttr, SetPathAttr, PODVector<unsigned char>, Variant::emptyBuffer, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Speed", float, speed_, 0.8f, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Current Path ID", int, currentPathID_, 1, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Emit Time", float, emitTime_, 0.0f, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Fight Timer", float, fightTimer_, 0.0f, AM_DEFAULT);
|
||||
URHO3D_ATTRIBUTE("Flip Animation", float, flip_, 0.0f, AM_DEFAULT);
|
||||
}
|
||||
|
||||
void Mover::SetPathAttr(const PODVector<unsigned char>& value)
|
||||
{
|
||||
if (value.Empty())
|
||||
return;
|
||||
|
||||
MemoryBuffer buffer(value);
|
||||
while (!buffer.IsEof())
|
||||
path_.Push(buffer.ReadVector2());
|
||||
}
|
||||
|
||||
PODVector<unsigned char> Mover::GetPathAttr() const
|
||||
{
|
||||
VectorBuffer buffer;
|
||||
|
||||
for (unsigned i = 0; i < path_.Size(); ++i)
|
||||
buffer.WriteVector2(path_[i]);
|
||||
|
||||
return buffer.GetBuffer();
|
||||
}
|
||||
|
||||
void Mover::Update(float timeStep)
|
||||
{
|
||||
if (path_.Size() < 2)
|
||||
return;
|
||||
|
||||
// Handle Orc states (idle/wounded/fighting)
|
||||
if (node_->GetName() == "Orc")
|
||||
{
|
||||
AnimatedSprite2D* animatedSprite = node_->GetComponent<AnimatedSprite2D>();
|
||||
String anim = "run";
|
||||
|
||||
// Handle wounded state
|
||||
if (emitTime_ > 0.0f)
|
||||
{
|
||||
emitTime_ += timeStep;
|
||||
anim = "dead";
|
||||
|
||||
// Handle dead
|
||||
if (emitTime_ >= 3.0f)
|
||||
{
|
||||
node_->Remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle fighting state
|
||||
if (fightTimer_ > 0.0f)
|
||||
{
|
||||
anim = "attack";
|
||||
flip_ = GetScene()->GetChild("Imp", true)->GetPosition().x_ - node_->GetPosition().x_;
|
||||
fightTimer_ += timeStep;
|
||||
if (fightTimer_ >= 3.0f)
|
||||
fightTimer_ = 0.0f; // Reset
|
||||
}
|
||||
// Flip Orc animation according to speed, or player position when fighting
|
||||
animatedSprite->SetFlipX(flip_ >= 0.0f);
|
||||
}
|
||||
// Animate
|
||||
if (animatedSprite->GetAnimation() != anim)
|
||||
animatedSprite->SetAnimation(anim);
|
||||
}
|
||||
|
||||
// Don't move if fighting or wounded
|
||||
if (fightTimer_ > 0.0f || emitTime_ > 0.0f)
|
||||
return;
|
||||
|
||||
// Set direction and move to target
|
||||
Vector2 dir = path_[currentPathID_] - node_->GetPosition2D();
|
||||
Vector2 dirNormal = dir.Normalized();
|
||||
node_->Translate(Vector3(dirNormal.x_, dirNormal.y_, 0.0f) * Abs(speed_) * timeStep);
|
||||
flip_ = dir.x_;
|
||||
|
||||
// Check for new target to reach
|
||||
if (Abs(dir.Length()) < 0.1f)
|
||||
{
|
||||
if (speed_ > 0.0f)
|
||||
{
|
||||
if (currentPathID_ + 1 < path_.Size())
|
||||
currentPathID_ = currentPathID_ + 1;
|
||||
else
|
||||
{
|
||||
// If loop, go to first waypoint, which equates to last one (and never reverse)
|
||||
if (path_[currentPathID_] == path_[0])
|
||||
{
|
||||
currentPathID_ = 1;
|
||||
return;
|
||||
}
|
||||
// Reverse path if not looping
|
||||
currentPathID_ = currentPathID_ - 1;
|
||||
speed_ = -speed_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentPathID_ - 1 >= 0)
|
||||
currentPathID_ = currentPathID_ - 1;
|
||||
else
|
||||
{
|
||||
currentPathID_ = 1;
|
||||
speed_ = -speed_;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
Source/Samples/Utilities2D/Mover.h
Normal file
64
Source/Samples/Utilities2D/Mover.h
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Urho3D/Scene/LogicComponent.h>
|
||||
|
||||
// All Urho3D classes reside in namespace Urho3D
|
||||
using namespace Urho3D;
|
||||
|
||||
/// Mover logic component
|
||||
/// - Handles entity (enemy, platform...) translation along a path (set of Vector2 points)
|
||||
/// - Supports looping paths and animation flip
|
||||
/// - Default speed is 0.8 if 'Speed' property is not set in the tmx file objects
|
||||
class Mover : public LogicComponent
|
||||
{
|
||||
URHO3D_OBJECT(Mover, LogicComponent);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Mover(Context* context);
|
||||
|
||||
/// Register object factory and attributes.
|
||||
static void RegisterObject(Context* context);
|
||||
|
||||
/// Handle scene update. Called by LogicComponent base class.
|
||||
virtual void Update(float timeStep);
|
||||
/// Return path attribute.
|
||||
PODVector<unsigned char> GetPathAttr() const;
|
||||
/// Set path attribute.
|
||||
void SetPathAttr(const PODVector<unsigned char>& value);
|
||||
|
||||
/// Path.
|
||||
PODVector<Vector2> path_;
|
||||
/// Movement speed.
|
||||
float speed_;
|
||||
/// ID of the current path point.
|
||||
int currentPathID_;
|
||||
/// Timer for particle emitter duration.
|
||||
float emitTime_;
|
||||
/// Timer used for handling "attack" animation.
|
||||
float fightTimer_;
|
||||
/// Flip animation based on direction, or player position when fighting.
|
||||
float flip_;
|
||||
};
|
545
Source/Samples/Utilities2D/Sample2D.cpp
Normal file
545
Source/Samples/Utilities2D/Sample2D.cpp
Normal file
@ -0,0 +1,545 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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/Urho3D.h>
|
||||
|
||||
#include <Urho3D/Urho2D/AnimatedSprite2D.h>
|
||||
#include <Urho3D/Urho2D/AnimationSet2D.h>
|
||||
#include <Urho3D/UI/BorderImage.h>
|
||||
#include <Urho3D/UI/Button.h>
|
||||
#include <Urho3D/Graphics/Camera.h>
|
||||
#include <Urho3D/Urho2D/CollisionBox2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionChain2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionCircle2D.h>
|
||||
#include <Urho3D/Urho2D/CollisionPolygon2D.h>
|
||||
#include <Urho3D/Core/Context.h>
|
||||
#include <Urho3D/Core/CoreEvents.h>
|
||||
#include <Urho3D/Engine/Engine.h>
|
||||
#include <Urho3D/IO/File.h>
|
||||
#include <Urho3D/IO/FileSystem.h>
|
||||
#include <Urho3D/UI/Font.h>
|
||||
#include <Urho3D/Input/Input.h>
|
||||
#include <Urho3D/Urho2D/ParticleEffect2D.h>
|
||||
#include <Urho3D/Urho2D/ParticleEmitter2D.h>
|
||||
#include <Urho3D/Math/Random.h>
|
||||
#include <Urho3D/Resource/ResourceCache.h>
|
||||
#include <Urho3D/Urho2D/RigidBody2D.h>
|
||||
#include <Urho3D/Scene/Scene.h>
|
||||
#include <Urho3D/Audio/Sound.h>
|
||||
#include <Urho3D/Audio/SoundSource.h>
|
||||
#include <Urho3D/Core/StringUtils.h>
|
||||
#include <Urho3D/UI/Text.h>
|
||||
#include <Urho3D/Graphics/Texture2D.h>
|
||||
#include <Urho3D/Urho2D/TileMap2D.h>
|
||||
#include <Urho3D/Urho2D/TileMapLayer2D.h>
|
||||
#include <Urho3D/Urho2D/TmxFile2D.h>
|
||||
#include <Urho3D/UI/UI.h>
|
||||
#include <Urho3D/UI/UIEvents.h>
|
||||
#include <Urho3D/Scene/ValueAnimation.h>
|
||||
#include <Urho3D/UI/Window.h>
|
||||
|
||||
#include "Utilities2D/Mover.h"
|
||||
#include "Sample2D.h"
|
||||
|
||||
|
||||
Sample2D::Sample2D(Context* context) :
|
||||
Object(context)
|
||||
{
|
||||
}
|
||||
|
||||
Sample2D::~Sample2D()
|
||||
{
|
||||
}
|
||||
|
||||
void Sample2D::CreateCollisionShapesFromTMXObjects(Node* tileMapNode, TileMapLayer2D* tileMapLayer, TileMapInfo2D info)
|
||||
{
|
||||
// Create rigid body to the root node
|
||||
RigidBody2D* body = tileMapNode->CreateComponent<RigidBody2D>();
|
||||
body->SetBodyType(BT_STATIC);
|
||||
|
||||
// Generate physics collision shapes and rigid bodies from the tmx file's objects located in "Physics" layer
|
||||
for (int i = 0; i < tileMapLayer->GetNumObjects(); ++i)
|
||||
{
|
||||
TileMapObject2D* tileMapObject = tileMapLayer->GetObject(i); // Get physics objects
|
||||
|
||||
// Create collision shape from tmx object
|
||||
switch (tileMapObject->GetObjectType())
|
||||
{
|
||||
case OT_RECTANGLE:
|
||||
{
|
||||
CreateRectangleShape(tileMapNode, tileMapObject, tileMapObject->GetSize(), info);
|
||||
}
|
||||
break;
|
||||
|
||||
case OT_ELLIPSE:
|
||||
{
|
||||
CreateCircleShape(tileMapNode, tileMapObject, tileMapObject->GetSize().x_ / 2, info); // Ellipse is built as a Circle shape as it doesn't exist in Box2D
|
||||
}
|
||||
break;
|
||||
|
||||
case OT_POLYGON:
|
||||
{
|
||||
CreatePolygonShape(tileMapNode, tileMapObject);
|
||||
}
|
||||
break;
|
||||
|
||||
case OT_POLYLINE:
|
||||
{
|
||||
CreatePolyLineShape(tileMapNode, tileMapObject);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollisionBox2D* Sample2D::CreateRectangleShape(Node* node, TileMapObject2D* object, Vector2 size, TileMapInfo2D info)
|
||||
{
|
||||
CollisionBox2D* shape = node->CreateComponent<CollisionBox2D>();
|
||||
shape->SetSize(size);
|
||||
if (info.orientation_ == O_ORTHOGONAL)
|
||||
shape->SetCenter(object->GetPosition() + size / 2);
|
||||
else
|
||||
{
|
||||
shape->SetCenter(object->GetPosition() + Vector2(info.tileWidth_ / 2, 0.0f));
|
||||
shape->SetAngle(45.0f); // If our tile map is isometric then shape is losange
|
||||
}
|
||||
shape->SetFriction(0.8f);
|
||||
if (object->HasProperty("Friction"))
|
||||
shape->SetFriction(ToFloat(object->GetProperty("Friction")));
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionCircle2D* Sample2D::CreateCircleShape(Node* node, TileMapObject2D* object, float radius, TileMapInfo2D info)
|
||||
{
|
||||
CollisionCircle2D* shape = node->CreateComponent<CollisionCircle2D>();
|
||||
Vector2 size = object->GetSize();
|
||||
if (info.orientation_ == O_ORTHOGONAL)
|
||||
shape->SetCenter(object->GetPosition() + size / 2);
|
||||
else
|
||||
{
|
||||
shape->SetCenter(object->GetPosition() + Vector2(info.tileWidth_ / 2, 0.0f));
|
||||
}
|
||||
|
||||
shape->SetRadius(radius);
|
||||
shape->SetFriction(0.8f);
|
||||
if (object->HasProperty("Friction"))
|
||||
shape->SetFriction(ToFloat(object->GetProperty("Friction")));
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionPolygon2D* Sample2D::CreatePolygonShape(Node* node, TileMapObject2D* object)
|
||||
{
|
||||
CollisionPolygon2D* shape = node->CreateComponent<CollisionPolygon2D>();
|
||||
int numVertices = object->GetNumPoints();
|
||||
shape->SetVertexCount(numVertices);
|
||||
for (int i = 0; i < numVertices; ++i)
|
||||
shape->SetVertex(i, object->GetPoint(i));
|
||||
shape->SetFriction(0.8f);
|
||||
if (object->HasProperty("Friction"))
|
||||
shape->SetFriction(ToFloat(object->GetProperty("Friction")));
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionChain2D* Sample2D::CreatePolyLineShape(Node* node, TileMapObject2D* object)
|
||||
{
|
||||
CollisionChain2D* shape = node->CreateComponent<CollisionChain2D>();
|
||||
int numVertices = object->GetNumPoints();
|
||||
shape->SetVertexCount(numVertices);
|
||||
for (int i = 0; i < numVertices; ++i)
|
||||
shape->SetVertex(i, object->GetPoint(i));
|
||||
shape->SetFriction(0.8f);
|
||||
if (object->HasProperty("Friction"))
|
||||
shape->SetFriction(ToFloat(object->GetProperty("Friction")));
|
||||
return shape;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateCharacter(TileMapInfo2D info, float friction, Vector3 position, float scale)
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* spriteNode = scene_->CreateChild("Imp");
|
||||
spriteNode->SetPosition(position);
|
||||
spriteNode->SetScale(scale);
|
||||
AnimatedSprite2D* animatedSprite = spriteNode->CreateComponent<AnimatedSprite2D>();
|
||||
// Get scml file and Play "idle" anim
|
||||
AnimationSet2D* animationSet = cache->GetResource<AnimationSet2D>("Urho2D/imp/imp.scml");
|
||||
animatedSprite->SetAnimationSet(animationSet);
|
||||
animatedSprite->SetAnimation("idle");
|
||||
animatedSprite->SetLayer(3); // Put character over tile map (which is on layer 0) and over Orcs (which are on layer 2)
|
||||
RigidBody2D* impBody = spriteNode->CreateComponent<RigidBody2D>();
|
||||
impBody->SetBodyType(BT_DYNAMIC);
|
||||
impBody->SetAllowSleep(false);
|
||||
CollisionCircle2D* shape = spriteNode->CreateComponent<CollisionCircle2D>();
|
||||
shape->SetRadius(1.1f); // Set shape size
|
||||
shape->SetFriction(friction); // Set friction
|
||||
shape->SetRestitution(0.1f); // Bounce
|
||||
|
||||
return spriteNode;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateTrigger()
|
||||
{
|
||||
Node* node = scene_->CreateChild(); // Clones will be renamed according to object type
|
||||
RigidBody2D* body = node->CreateComponent<RigidBody2D>();
|
||||
body->SetBodyType(BT_STATIC);
|
||||
CollisionBox2D* shape = node->CreateComponent<CollisionBox2D>(); // Create box shape
|
||||
shape->SetTrigger(true);
|
||||
return node;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateEnemy()
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* node = scene_->CreateChild("Enemy");
|
||||
StaticSprite2D* staticSprite = node->CreateComponent<StaticSprite2D>();
|
||||
staticSprite->SetSprite(cache->GetResource<Sprite2D>("Urho2D/Aster.png"));
|
||||
RigidBody2D* body = node->CreateComponent<RigidBody2D>();
|
||||
body->SetBodyType(BT_STATIC);
|
||||
CollisionCircle2D* shape = node->CreateComponent<CollisionCircle2D>(); // Create circle shape
|
||||
shape->SetRadius(0.25f); // Set radius
|
||||
return node;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateOrc()
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* node = scene_->CreateChild("Orc");
|
||||
node->SetScale(scene_->GetChild("Imp", true)->GetScale());
|
||||
AnimatedSprite2D* animatedSprite = node->CreateComponent<AnimatedSprite2D>();
|
||||
AnimationSet2D* animationSet = cache->GetResource<AnimationSet2D>("Urho2D/Orc/Orc.scml");
|
||||
animatedSprite->SetAnimationSet(animationSet);
|
||||
animatedSprite->SetAnimation("run"); // Get scml file and Play "run" anim
|
||||
animatedSprite->SetLayer(2); // Make orc always visible
|
||||
RigidBody2D* body = node->CreateComponent<RigidBody2D>();
|
||||
CollisionCircle2D* shape = node->CreateComponent<CollisionCircle2D>();
|
||||
shape->SetRadius(1.3f); // Set shape size
|
||||
shape->SetTrigger(true);
|
||||
return node;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateCoin()
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* node = scene_->CreateChild("Coin");
|
||||
node->SetScale(0.5);
|
||||
AnimatedSprite2D* animatedSprite = node->CreateComponent<AnimatedSprite2D>();
|
||||
AnimationSet2D* animationSet = cache->GetResource<AnimationSet2D>("Urho2D/GoldIcon.scml");
|
||||
animatedSprite->SetAnimationSet(animationSet); // Get scml file and Play "idle" anim
|
||||
animatedSprite->SetAnimation("idle");
|
||||
animatedSprite->SetLayer(4);
|
||||
RigidBody2D* body = node->CreateComponent<RigidBody2D>();
|
||||
body->SetBodyType(BT_STATIC);
|
||||
CollisionCircle2D* shape = node->CreateComponent<CollisionCircle2D>(); // Create circle shape
|
||||
shape->SetRadius(0.32f); // Set radius
|
||||
shape->SetTrigger(true);
|
||||
return node;
|
||||
}
|
||||
|
||||
Node* Sample2D::CreateMovingPlatform()
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* node = scene_->CreateChild("MovingPlatform");
|
||||
node->SetScale(Vector3(3.0f, 1.0f, 0.0f));
|
||||
StaticSprite2D* staticSprite = node->CreateComponent<StaticSprite2D>();
|
||||
staticSprite->SetSprite(cache->GetResource<Sprite2D>("Urho2D/Box.png"));
|
||||
RigidBody2D* body = node->CreateComponent<RigidBody2D>();
|
||||
body->SetBodyType(BT_STATIC);
|
||||
CollisionBox2D* shape = node->CreateComponent<CollisionBox2D>(); // Create box shape
|
||||
shape->SetSize(Vector2(0.32f, 0.32f)); // Set box size
|
||||
shape->SetFriction(0.8f); // Set friction
|
||||
return node;
|
||||
}
|
||||
|
||||
void Sample2D::PopulateMovingEntities(TileMapLayer2D* movingEntitiesLayer)
|
||||
{
|
||||
// Create enemy (will be cloned at each placeholder)
|
||||
Node* enemyNode = CreateEnemy();
|
||||
Node* orcNode = CreateOrc();
|
||||
Node* platformNode = CreateMovingPlatform();
|
||||
|
||||
// Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points)
|
||||
for (int i=0; i < movingEntitiesLayer->GetNumObjects(); ++i)
|
||||
{
|
||||
// Get placeholder object
|
||||
TileMapObject2D* movingObject = movingEntitiesLayer->GetObject(i); // Get placeholder object
|
||||
if (movingObject->GetObjectType() == OT_POLYLINE)
|
||||
{
|
||||
// Clone the enemy and position it at placeholder point
|
||||
Node* movingClone;
|
||||
Vector2 offset = Vector2(0.0f, 0.0f);
|
||||
if (movingObject->GetType() == "Enemy")
|
||||
{
|
||||
movingClone = enemyNode->Clone();
|
||||
offset = Vector2(0.0f, -0.32f);
|
||||
}
|
||||
else if (movingObject->GetType() == "Orc")
|
||||
movingClone = orcNode->Clone();
|
||||
else if (movingObject->GetType() == "MovingPlatform")
|
||||
movingClone = platformNode->Clone();
|
||||
else
|
||||
continue;
|
||||
movingClone->SetPosition2D(movingObject->GetPoint(0) + offset);
|
||||
|
||||
// Create script object that handles entity translation along its path
|
||||
Mover* mover = movingClone->CreateComponent<Mover>();
|
||||
|
||||
// Set path from points
|
||||
PODVector<Vector2> path = CreatePathFromPoints(movingObject, offset);
|
||||
mover->path_ = path;
|
||||
|
||||
// Override default speed
|
||||
if (movingObject->HasProperty("Speed"))
|
||||
mover->speed_ = ToFloat(movingObject->GetProperty("Speed"));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove nodes used for cloning purpose
|
||||
enemyNode->Remove();
|
||||
orcNode->Remove();
|
||||
platformNode->Remove();
|
||||
}
|
||||
|
||||
void Sample2D::PopulateCoins(TileMapLayer2D* coinsLayer)
|
||||
{
|
||||
// Create coin (will be cloned at each placeholder)
|
||||
Node* coinNode = CreateCoin();
|
||||
|
||||
// Instantiate coins to pick at each placeholder
|
||||
for (int i=0; i < coinsLayer->GetNumObjects(); ++i)
|
||||
{
|
||||
TileMapObject2D* coinObject = coinsLayer->GetObject(i); // Get placeholder object
|
||||
Node* coinClone = coinNode->Clone();
|
||||
coinClone->SetPosition2D(coinObject->GetPosition() + coinObject->GetSize() / 2 + Vector2(0.0f, 0.16f));
|
||||
|
||||
}
|
||||
|
||||
// Remove node used for cloning purpose
|
||||
coinNode->Remove();
|
||||
}
|
||||
|
||||
void Sample2D::PopulateTriggers(TileMapLayer2D* triggersLayer)
|
||||
{
|
||||
// Create trigger node (will be cloned at each placeholder)
|
||||
Node* triggerNode = CreateTrigger();
|
||||
|
||||
// Instantiate triggers at each placeholder (Rectangle objects)
|
||||
for (int i=0; i < triggersLayer->GetNumObjects(); ++i)
|
||||
{
|
||||
TileMapObject2D* triggerObject = triggersLayer->GetObject(i); // Get placeholder object
|
||||
if (triggerObject->GetObjectType() == OT_RECTANGLE)
|
||||
{
|
||||
Node* triggerClone = triggerNode->Clone();
|
||||
triggerClone->SetName(triggerObject->GetType());
|
||||
CollisionBox2D* shape = triggerClone->GetComponent<CollisionBox2D>();
|
||||
shape->SetSize(triggerObject->GetSize());
|
||||
triggerClone->SetPosition2D(triggerObject->GetPosition() + triggerObject->GetSize() / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float Sample2D::Zoom(Camera* camera)
|
||||
{
|
||||
Input* input = GetSubsystem<Input>();
|
||||
float zoom_ = camera->GetZoom();
|
||||
|
||||
if (input->GetMouseMoveWheel() != 0)
|
||||
zoom_ = Clamp(zoom_ + input->GetMouseMoveWheel() * 0.1f, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
camera->SetZoom(zoom_);
|
||||
|
||||
if (input->GetKeyDown(KEY_PAGEUP))
|
||||
{
|
||||
zoom_ = Clamp(zoom_ * 1.01f, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
camera->SetZoom(zoom_);
|
||||
}
|
||||
|
||||
if (input->GetKeyDown(KEY_PAGEDOWN))
|
||||
{
|
||||
zoom_ = Clamp(zoom_ * 0.99f, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
camera->SetZoom(zoom_);
|
||||
}
|
||||
|
||||
return zoom_;
|
||||
}
|
||||
|
||||
PODVector<Vector2> Sample2D::CreatePathFromPoints(TileMapObject2D* object, Vector2 offset)
|
||||
{
|
||||
PODVector<Vector2> path;
|
||||
for (int i=0; i < object->GetNumPoints(); ++i)
|
||||
path.Push(object->GetPoint(i) + offset);
|
||||
return path;
|
||||
}
|
||||
|
||||
void Sample2D::CreateUIContent(String demoTitle, int remainingLifes, int remainingCoins)
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
UI* ui = GetSubsystem<UI>();
|
||||
|
||||
// Set the default UI style and font
|
||||
ui->GetRoot()->SetDefaultStyle(cache->GetResource<XMLFile>("UI/DefaultStyle.xml"));
|
||||
Font* font = cache->GetResource<Font>("Fonts/Anonymous Pro.ttf");
|
||||
|
||||
// We create in-game UIs (coins and lifes) first so that they are hidden by the fullscreen UI (we could also temporary hide them using SetVisible)
|
||||
|
||||
// Create the UI for displaying the remaining coins
|
||||
BorderImage* coinsUI = ui->GetRoot()->CreateChild<BorderImage>("Coins");
|
||||
coinsUI->SetTexture(cache->GetResource<Texture2D>("Urho2D/GoldIcon.png"));
|
||||
coinsUI->SetSize(50, 50);
|
||||
coinsUI->SetImageRect(IntRect(0, 64, 60, 128));
|
||||
coinsUI->SetAlignment(HA_LEFT, VA_TOP);
|
||||
coinsUI->SetPosition(5, 5);
|
||||
Text* coinsText = coinsUI->CreateChild<Text>("CoinsText");
|
||||
coinsText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
coinsText->SetFont(font, 24);
|
||||
coinsText->SetTextEffect(TE_SHADOW);
|
||||
coinsText->SetText(String(remainingCoins));
|
||||
|
||||
// Create the UI for displaying the remaining lifes
|
||||
BorderImage* lifeUI = ui->GetRoot()->CreateChild<BorderImage>("Life");
|
||||
lifeUI->SetTexture(cache->GetResource<Texture2D>("Urho2D/imp/imp_all.png"));
|
||||
lifeUI->SetSize(70, 80);
|
||||
lifeUI->SetAlignment(HA_RIGHT, VA_TOP);
|
||||
lifeUI->SetPosition(-5, 5);
|
||||
Text* lifeText = lifeUI->CreateChild<Text>("LifeText");
|
||||
lifeText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
lifeText->SetFont(font, 24);
|
||||
lifeText->SetTextEffect(TE_SHADOW);
|
||||
lifeText->SetText(String(remainingLifes));
|
||||
|
||||
// Create the fullscreen UI for start/end
|
||||
Window* fullUI = ui->GetRoot()->CreateChild<Window>("FullUI");
|
||||
fullUI->SetStyleAuto();
|
||||
fullUI->SetSize(ui->GetRoot()->GetWidth(), ui->GetRoot()->GetHeight());
|
||||
fullUI->SetEnabled(false); // Do not react to input, only the 'Exit' and 'Play' buttons will
|
||||
|
||||
// Create the title
|
||||
BorderImage* title = fullUI->CreateChild<BorderImage>("Title");
|
||||
title->SetMinSize(fullUI->GetWidth(), 50);
|
||||
title->SetTexture(cache->GetResource<Texture2D>("Textures/HeightMap.png"));
|
||||
title->SetFullImageRect();
|
||||
title->SetAlignment(HA_CENTER, VA_TOP);
|
||||
Text* titleText = title->CreateChild<Text>("TitleText");
|
||||
titleText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
titleText->SetFont(font, 24);
|
||||
titleText->SetText(demoTitle);
|
||||
|
||||
// Create the image
|
||||
BorderImage* spriteUI = fullUI->CreateChild<BorderImage>("Sprite");
|
||||
spriteUI->SetTexture(cache->GetResource<Texture2D>("Urho2D/imp/imp_all.png"));
|
||||
spriteUI->SetSize(238, 271);
|
||||
spriteUI->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
spriteUI->SetPosition(0, - ui->GetRoot()->GetHeight() / 4);
|
||||
|
||||
// Create the 'EXIT' button
|
||||
Button* exitButton = ui->GetRoot()->CreateChild<Button>("ExitButton");
|
||||
exitButton->SetStyleAuto();
|
||||
exitButton->SetFocusMode(FM_RESETFOCUS);
|
||||
exitButton->SetSize(100, 50);
|
||||
exitButton->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
exitButton->SetPosition(-100, 0);
|
||||
Text* exitText = exitButton->CreateChild<Text>("ExitText");
|
||||
exitText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
exitText->SetFont(font, 24);
|
||||
exitText->SetText("EXIT");
|
||||
SubscribeToEvent(exitButton, E_RELEASED, URHO3D_HANDLER(Sample2D, HandleExitButton));
|
||||
|
||||
// Create the 'PLAY' button
|
||||
Button* playButton = ui->GetRoot()->CreateChild<Button>("PlayButton");
|
||||
playButton->SetStyleAuto();
|
||||
playButton->SetFocusMode(FM_RESETFOCUS);
|
||||
playButton->SetSize(100, 50);
|
||||
playButton->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
playButton->SetPosition(100, 0);
|
||||
Text* playText = playButton->CreateChild<Text>("PlayText");
|
||||
playText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
playText->SetFont(font, 24);
|
||||
playText->SetText("PLAY");
|
||||
// SubscribeToEvent(playButton, E_RELEASED, HANDLER(Urho2DPlatformer, HandlePlayButton));
|
||||
|
||||
// Create the instructions
|
||||
Text* instructionText = ui->GetRoot()->CreateChild<Text>("Instructions");
|
||||
instructionText->SetText("Use WASD keys or Arrows to move\nPageUp/PageDown/MouseWheel to zoom\nF5/F7 to save/reload scene\n'Z' to toggle debug geometry\nSpace to fight");
|
||||
instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
|
||||
instructionText->SetTextAlignment(HA_CENTER); // Center rows in relation to each other
|
||||
instructionText->SetAlignment(HA_CENTER, VA_CENTER);
|
||||
instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
|
||||
|
||||
// Show mouse cursor
|
||||
Input* input = GetSubsystem<Input>();
|
||||
input->SetMouseVisible(true);
|
||||
}
|
||||
|
||||
void Sample2D::HandleExitButton(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
Engine* engine = GetSubsystem<Engine>();
|
||||
engine->Exit();
|
||||
}
|
||||
|
||||
void Sample2D::SaveScene(bool initial)
|
||||
{
|
||||
String filename = demoFilename_;
|
||||
if (!initial)
|
||||
filename += "InGame";
|
||||
File saveFile(context_, GetSubsystem<FileSystem>()->GetProgramDir() + "Data/Scenes/" + filename + ".xml", FILE_WRITE);
|
||||
scene_->SaveXML(saveFile);
|
||||
}
|
||||
|
||||
void Sample2D::CreateBackgroundSprite(TileMapInfo2D info, float scale, String texture, bool animate)
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* node = scene_->CreateChild("Background");
|
||||
node->SetPosition(Vector3(info.GetMapWidth(), info.GetMapHeight(), 0) / 2);
|
||||
node->SetScale(scale);
|
||||
StaticSprite2D* sprite = node->CreateComponent<StaticSprite2D>();
|
||||
sprite->SetSprite(cache->GetResource<Sprite2D>(texture));
|
||||
SetRandomSeed(Time::GetSystemTime()); // Randomize from system clock
|
||||
sprite->SetColor(Color(Random(0.0f, 1.0f), Random(0.0f, 1.0f), Random(0.0f, 1.0f), 1.0f));
|
||||
|
||||
// Create rotation animation
|
||||
if (animate)
|
||||
{
|
||||
SharedPtr<ValueAnimation> animation(new ValueAnimation(context_));
|
||||
animation->SetKeyFrame(0, Variant(Quaternion(0.0f, 0.0f, 0.0f)));
|
||||
animation->SetKeyFrame(1, Variant(Quaternion(0.0f, 0.0f, 180.0f)));
|
||||
animation->SetKeyFrame(2, Variant(Quaternion(0.0f, 0.0f, 0.0f)));
|
||||
node->SetAttributeAnimation("Rotation", animation, WM_LOOP, 0.05f);
|
||||
}
|
||||
}
|
||||
|
||||
void Sample2D::SpawnEffect(Node* node)
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
Node* particleNode = node->CreateChild("Emitter");
|
||||
particleNode->SetScale(0.5 / node->GetScale().x_);
|
||||
ParticleEmitter2D* particleEmitter = particleNode->CreateComponent<ParticleEmitter2D>();
|
||||
particleEmitter->SetLayer(2);
|
||||
particleEmitter->SetEffect(cache->GetResource<ParticleEffect2D>("Urho2D/sun.pex"));
|
||||
}
|
||||
|
||||
void Sample2D::PlaySoundEffect(String soundName)
|
||||
{
|
||||
ResourceCache* cache = GetSubsystem<ResourceCache>();
|
||||
SoundSource* source = scene_->CreateComponent<SoundSource>();
|
||||
Sound* sound = cache->GetResource<Sound>("Sounds/" + soundName);
|
||||
if (sound != NULL) {
|
||||
source->SetAutoRemoveMode(REMOVE_COMPONENT);
|
||||
source->Play(sound);
|
||||
}
|
||||
}
|
137
Source/Samples/Utilities2D/Sample2D.h
Normal file
137
Source/Samples/Utilities2D/Sample2D.h
Normal file
@ -0,0 +1,137 @@
|
||||
//
|
||||
// Copyright (c) 2008-2014 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Urho3D/Core/Object.h>
|
||||
|
||||
namespace Urho3D
|
||||
{
|
||||
|
||||
class Node;
|
||||
class Scene;
|
||||
|
||||
}
|
||||
|
||||
class Character2D;
|
||||
|
||||
// All Urho3D classes reside in namespace Urho3D
|
||||
using namespace Urho3D;
|
||||
|
||||
const float CAMERA_MIN_DIST = 0.1f;
|
||||
const float CAMERA_MAX_DIST = 6.0f;
|
||||
|
||||
const float MOVE_SPEED_SCALE = 1.0f; // Scaling factor based on tiles' aspect ratio
|
||||
|
||||
/// Convenient functions for Urho2D samples:
|
||||
/// - Generate collision shapes from a tmx file objects
|
||||
/// - Create Spriter Imp character
|
||||
/// - Create enemies, coins and platforms to tile map placeholders
|
||||
/// - Handle camera zoom using PageUp, PageDown and MouseWheel
|
||||
/// - Create UI instructions
|
||||
/// - Create a particle emitter attached to a given node
|
||||
/// - Play a non-looping sound effect
|
||||
/// - Load/Save the scene
|
||||
/// - Create XML patch instructions for screen joystick layout
|
||||
class Sample2D : public Object
|
||||
{
|
||||
URHO3D_OBJECT(Sample2D, Object);
|
||||
|
||||
public:
|
||||
/// Construct.
|
||||
Sample2D(Context* context);
|
||||
/// Destruct.
|
||||
~Sample2D();
|
||||
|
||||
/// Generate physics collision shapes from the tmx file's objects located in tileMapLayer.
|
||||
void CreateCollisionShapesFromTMXObjects(Node* tileMapNode, TileMapLayer2D* tileMapLayer, TileMapInfo2D info);
|
||||
/// Build collision shape from Tiled 'Rectangle' objects.
|
||||
CollisionBox2D* CreateRectangleShape(Node* node, TileMapObject2D* object, Vector2 size, TileMapInfo2D info);
|
||||
/// Build collision shape from Tiled 'Ellipse' objects.
|
||||
CollisionCircle2D* CreateCircleShape(Node* node, TileMapObject2D* object, float radius, TileMapInfo2D info);
|
||||
/// Build collision shape from Tiled 'Polygon' objects.
|
||||
CollisionPolygon2D* CreatePolygonShape(Node* node, TileMapObject2D* object);
|
||||
/// Build collision shape from Tiled 'Poly Line' objects.
|
||||
CollisionChain2D* CreatePolyLineShape(Node* node, TileMapObject2D* object);
|
||||
/// Create Imp Spriter character.
|
||||
Node* CreateCharacter(TileMapInfo2D info, float friction, Vector3 position, float scale);
|
||||
/// Create a trigger (will be cloned at each tmx placeholder).
|
||||
Node* CreateTrigger();
|
||||
/// Create an enemy (will be cloned at each tmx placeholder).
|
||||
Node* CreateEnemy();
|
||||
/// Create an Orc (will be cloned at each tmx placeholder).
|
||||
Node* CreateOrc();
|
||||
/// Create a coin (will be cloned at each tmx placeholder).
|
||||
Node* CreateCoin();
|
||||
/// Create a moving platform (will be cloned at each tmx placeholder).
|
||||
Node* CreateMovingPlatform();
|
||||
/// Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points).
|
||||
void PopulateMovingEntities(TileMapLayer2D* movingEntitiesLayer);
|
||||
/// Instantiate coins to pick at each placeholder.
|
||||
void PopulateCoins(TileMapLayer2D* coinsLayer);
|
||||
/// Instantiate triggers at each placeholder (Rectangle objects).
|
||||
void PopulateTriggers(TileMapLayer2D* triggersLayer);
|
||||
/// Read input and zoom the camera.
|
||||
float Zoom(Camera* camera);
|
||||
/// Create path from tmx object's points.
|
||||
PODVector<Vector2> CreatePathFromPoints(TileMapObject2D* object, Vector2 offset);
|
||||
/// Create the UI content.
|
||||
void CreateUIContent(String demoTitle, int remainingLifes, int remainingCoins);
|
||||
/// Handle 'EXIT' button released event.
|
||||
void HandleExitButton(StringHash eventType, VariantMap& eventData);
|
||||
/// Save the scene.
|
||||
void SaveScene(bool initial);
|
||||
/// Create a background 2D sprite, optionally rotated by a ValueAnimation object.
|
||||
void CreateBackgroundSprite(TileMapInfo2D info, float scale, String texture, bool animate);
|
||||
/// Create a particle emitter attached to the given node.
|
||||
void SpawnEffect(Node* node);
|
||||
/// Play a non-looping sound effect.
|
||||
void PlaySoundEffect(String soundName);
|
||||
|
||||
/// Filename used in load/save functions.
|
||||
String demoFilename_;
|
||||
/// The scene.
|
||||
Scene* scene_;
|
||||
|
||||
protected:
|
||||
/// Return XML patch instructions for screen joystick layout.
|
||||
virtual String GetScreenJoystickPatchString() const { return
|
||||
"<patch>"
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />"
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Fight</replace>"
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">"
|
||||
" <element type=\"Text\">"
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />"
|
||||
" <attribute name=\"Text\" value=\"SPACE\" />"
|
||||
" </element>"
|
||||
" </add>"
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />"
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Jump</replace>"
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">"
|
||||
" <element type=\"Text\">"
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />"
|
||||
" <attribute name=\"Text\" value=\"UP\" />"
|
||||
" </element>"
|
||||
" </add>"
|
||||
"</patch>";
|
||||
}
|
||||
};
|
183
bin/Data/LuaScripts/49_Urho2DIsometricDemo.lua
Normal file
183
bin/Data/LuaScripts/49_Urho2DIsometricDemo.lua
Normal file
@ -0,0 +1,183 @@
|
||||
-- Urho2D tile map example.
|
||||
-- This sample demonstrates:
|
||||
-- - Creating an isometric 2D scene with tile map
|
||||
-- - Displaying the scene using the Renderer subsystem
|
||||
-- - Handling keyboard to move a character and zoom 2D camera
|
||||
-- - Generating physics shapes from the tmx file's objects
|
||||
-- - Displaying debug geometry for physics and tile map
|
||||
-- Note that this sample uses some functions from Sample2D utility class.
|
||||
|
||||
require "LuaScripts/Utilities/Sample"
|
||||
require "LuaScripts/Utilities/2D/Sample2D"
|
||||
|
||||
|
||||
function Start()
|
||||
-- Set filename for load/save functions
|
||||
demoFilename = "Isometric2D"
|
||||
|
||||
-- Execute the common startup for samples
|
||||
SampleStart()
|
||||
|
||||
-- Create the scene content
|
||||
CreateScene()
|
||||
|
||||
-- Create the UI content
|
||||
CreateUIContent("ISOMETRIC 2.5D DEMO")
|
||||
|
||||
-- Hook up to the frame update events
|
||||
SubscribeToEvents()
|
||||
end
|
||||
|
||||
function CreateScene()
|
||||
scene_ = Scene()
|
||||
|
||||
-- Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_:CreateComponent("Octree")
|
||||
scene_:CreateComponent("DebugRenderer")
|
||||
local physicsWorld = scene_:CreateComponent("PhysicsWorld2D")
|
||||
physicsWorld.gravity = Vector2.ZERO -- Neutralize gravity as the character will always be grounded
|
||||
|
||||
-- Create camera
|
||||
cameraNode = Node()
|
||||
local camera = cameraNode:CreateComponent("Camera")
|
||||
camera.orthographic = true
|
||||
camera.orthoSize = graphics.height * PIXEL_SIZE
|
||||
zoom = 2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (2) is set for full visibility at 1280x800 resolution)
|
||||
camera.zoom = zoom
|
||||
|
||||
-- Setup the viewport for displaying the scene
|
||||
renderer:SetViewport(0, Viewport:new(scene_, camera))
|
||||
renderer.defaultZone.fogColor = Color(0.2, 0.2, 0.2) -- Set background color for the scene
|
||||
|
||||
-- Create tile map from tmx file
|
||||
local tmxFile = cache:GetResource("TmxFile2D", "Urho2D/Tilesets/atrium.tmx")
|
||||
local tileMapNode = scene_:CreateChild("TileMap")
|
||||
local tileMap = tileMapNode:CreateComponent("TileMap2D")
|
||||
tileMap.tmxFile = tmxFile
|
||||
local info = tileMap.info
|
||||
|
||||
-- Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
CreateCharacter(info, true, 0, Vector3(-5, 11, 0), 0.15)
|
||||
|
||||
-- Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
local tileMapLayer = tileMap:GetLayer(tileMap.numLayers - 1)
|
||||
CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
|
||||
|
||||
-- Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
PopulateMovingEntities(tileMap:GetLayer(tileMap.numLayers - 2))
|
||||
|
||||
-- Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
|
||||
PopulateCoins(tileMap:GetLayer(tileMap.numLayers - 3))
|
||||
|
||||
-- Check when scene is rendered
|
||||
SubscribeToEvent("EndRendering", HandleSceneRendered)
|
||||
end
|
||||
|
||||
function HandleSceneRendered()
|
||||
UnsubscribeFromEvent("EndRendering")
|
||||
SaveScene(true) -- Save the scene so we can reload it later
|
||||
scene_.updateEnabled = false -- Pause the scene as long as the UI is hiding it
|
||||
end
|
||||
|
||||
function SubscribeToEvents()
|
||||
-- Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent("Update", "HandleUpdate")
|
||||
|
||||
-- Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent("PostUpdate", "HandlePostUpdate")
|
||||
|
||||
-- Subscribe to PostRenderUpdate to draw physics shapes
|
||||
SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
|
||||
|
||||
-- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent("SceneUpdate")
|
||||
end
|
||||
|
||||
function HandleUpdate(eventType, eventData)
|
||||
-- Zoom in/out
|
||||
Zoom(cameraNode:GetComponent("Camera"))
|
||||
|
||||
-- Toggle debug geometry with spacebar
|
||||
if input:GetKeyPress(KEY_Z) then drawDebug = not drawDebug end
|
||||
|
||||
-- Check for loading / saving the scene
|
||||
if input:GetKeyPress(KEY_F5) then
|
||||
SaveScene()
|
||||
end
|
||||
if input:GetKeyPress(KEY_F7) then
|
||||
ReloadScene(false)
|
||||
end
|
||||
end
|
||||
|
||||
function HandlePostUpdate(eventType, eventData)
|
||||
if character2DNode == nil or cameraNode == nil then
|
||||
return
|
||||
end
|
||||
cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10) -- Camera tracks character
|
||||
end
|
||||
|
||||
function HandlePostRenderUpdate(eventType, eventData)
|
||||
if drawDebug then
|
||||
scene_:GetComponent("PhysicsWorld2D"):DrawDebugGeometry(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Character2D script object class
|
||||
Character2D = ScriptObject()
|
||||
|
||||
function Character2D:Start()
|
||||
self.wounded = false
|
||||
self.killed = false
|
||||
self.timer = 0
|
||||
self.maxCoins = 0
|
||||
self.remainingCoins = 0
|
||||
self.remainingLifes = 3
|
||||
end
|
||||
|
||||
function Character2D:Update(timeStep)
|
||||
local node = self.node
|
||||
local animatedSprite = node:GetComponent("AnimatedSprite2D")
|
||||
|
||||
-- Set direction
|
||||
local moveDir = Vector3.ZERO -- Reset
|
||||
local speedX = Clamp(MOVE_SPEED_X / zoom, 0.4, 1)
|
||||
local speedY = speedX
|
||||
|
||||
if input:GetKeyDown(KEY_LEFT) or input:GetKeyDown(KEY_A) then
|
||||
moveDir = moveDir + Vector3.LEFT * speedX
|
||||
animatedSprite.flipX = false -- Flip sprite (reset to default play on the X axis)
|
||||
end
|
||||
if input:GetKeyDown(KEY_RIGHT) or input:GetKeyDown(KEY_D) then
|
||||
moveDir = moveDir + Vector3.RIGHT * speedX
|
||||
animatedSprite.flipX = true -- Flip sprite (flip animation on the X axis)
|
||||
end
|
||||
|
||||
if not moveDir:Equals(Vector3.ZERO) then
|
||||
speedY = speedX * MOVE_SPEED_SCALE
|
||||
end
|
||||
|
||||
if input:GetKeyDown(KEY_UP) or input:GetKeyDown(KEY_W) then
|
||||
moveDir = moveDir + Vector3.UP * speedY
|
||||
end
|
||||
if input:GetKeyDown(KEY_DOWN) or input:GetKeyDown(KEY_S) then
|
||||
moveDir = moveDir + Vector3.DOWN * speedY
|
||||
end
|
||||
|
||||
-- Move
|
||||
if not moveDir:Equals(Vector3.ZERO) then
|
||||
node:Translate(moveDir * timeStep)
|
||||
end
|
||||
|
||||
-- Animate
|
||||
if input:GetKeyDown(KEY_SPACE) then
|
||||
if animatedSprite.animation ~= "attack" then
|
||||
animatedSprite:SetAnimation("attack", LM_FORCE_LOOPED)
|
||||
end
|
||||
elseif not moveDir:Equals(Vector3.ZERO) then
|
||||
if animatedSprite.animation ~= "run" then
|
||||
animatedSprite:SetAnimation("run")
|
||||
end
|
||||
elseif animatedSprite.animation ~= "idle" then
|
||||
animatedSprite:SetAnimation("idle")
|
||||
end
|
||||
end
|
453
bin/Data/LuaScripts/50_Urho2DPlatformer.lua
Normal file
453
bin/Data/LuaScripts/50_Urho2DPlatformer.lua
Normal file
@ -0,0 +1,453 @@
|
||||
-- Urho2D platformer example.
|
||||
-- This sample demonstrates:
|
||||
-- - Creating an orthogonal 2D scene from tile map file
|
||||
-- - Displaying the scene using the Renderer subsystem
|
||||
-- - Handling keyboard to move a character and zoom 2D camera
|
||||
-- - Generating physics shapes from the tmx file's objects
|
||||
-- - Mixing physics and translations to move the character
|
||||
-- - Using Box2D Contact listeners to handle the gameplay
|
||||
-- - Displaying debug geometry for physics and tile map
|
||||
-- Note that this sample uses some functions from Sample2D utility class.
|
||||
|
||||
require "LuaScripts/Utilities/Sample"
|
||||
require "LuaScripts/Utilities/2D/Sample2D"
|
||||
|
||||
|
||||
function Start()
|
||||
-- Set filename for load/save functions
|
||||
demoFilename = "Platformer2D"
|
||||
|
||||
-- Execute the common startup for samples
|
||||
SampleStart()
|
||||
|
||||
-- Create the scene content
|
||||
CreateScene()
|
||||
|
||||
-- Create the UI content
|
||||
CreateUIContent("PLATFORMER 2D DEMO")
|
||||
|
||||
-- Hook up to the frame update events
|
||||
SubscribeToEvents()
|
||||
end
|
||||
|
||||
function CreateScene()
|
||||
scene_ = Scene()
|
||||
|
||||
-- Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_:CreateComponent("Octree")
|
||||
scene_:CreateComponent("DebugRenderer")
|
||||
scene_:CreateComponent("PhysicsWorld2D")
|
||||
|
||||
-- Create camera
|
||||
cameraNode = Node()
|
||||
local camera = cameraNode:CreateComponent("Camera")
|
||||
camera.orthographic = true
|
||||
camera.orthoSize = graphics.height * PIXEL_SIZE
|
||||
camera.zoom = 1.8 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.8) is set for full visibility at 1280x800 resolution)
|
||||
|
||||
-- Setup the viewport for displaying the scene
|
||||
renderer:SetViewport(0, Viewport:new(scene_, camera))
|
||||
renderer.defaultZone.fogColor = Color(0.2, 0.2, 0.2) -- Set background color for the scene
|
||||
|
||||
-- Create tile map from tmx file
|
||||
local tileMapNode = scene_:CreateChild("TileMap")
|
||||
local tileMap = tileMapNode:CreateComponent("TileMap2D")
|
||||
tileMap.tmxFile = cache:GetResource("TmxFile2D", "Urho2D/Tilesets/Ortho.tmx")
|
||||
local info = tileMap.info
|
||||
|
||||
-- Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
CreateCharacter(info, true, 0.8, Vector3(1, 8, 0), 0.2)
|
||||
|
||||
-- Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
local tileMapLayer = tileMap:GetLayer(tileMap.numLayers - 1)
|
||||
CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
|
||||
|
||||
-- Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
PopulateMovingEntities(tileMap:GetLayer(tileMap.numLayers - 2))
|
||||
|
||||
-- Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
|
||||
PopulateCoins(tileMap:GetLayer(tileMap.numLayers - 3))
|
||||
|
||||
-- Instantiate triggers (for ropes, ladders, lava, slopes...) at each placeholder of "Triggers" layer (placeholders for triggers are Rectangle objects)
|
||||
PopulateTriggers(tileMap:GetLayer(tileMap.numLayers - 4))
|
||||
|
||||
-- Create background
|
||||
CreateBackgroundSprite(info, 3.5, "Textures/HeightMap.png", true)
|
||||
|
||||
-- Check when scene is rendered
|
||||
SubscribeToEvent("EndRendering", HandleSceneRendered)
|
||||
end
|
||||
|
||||
function HandleSceneRendered()
|
||||
UnsubscribeFromEvent("EndRendering")
|
||||
SaveScene(true) -- Save the scene so we can reload it later
|
||||
scene_.updateEnabled = false -- Pause the scene as long as the UI is hiding it
|
||||
end
|
||||
|
||||
function SubscribeToEvents()
|
||||
-- Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent("Update", "HandleUpdate")
|
||||
|
||||
-- Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent("PostUpdate", "HandlePostUpdate")
|
||||
|
||||
-- Subscribe to PostRenderUpdate to draw debug geometry
|
||||
SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
|
||||
|
||||
-- Subscribe to Box2D contact listeners
|
||||
SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin")
|
||||
SubscribeToEvent("PhysicsEndContact2D", "HandleCollisionEnd")
|
||||
|
||||
-- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent("SceneUpdate")
|
||||
end
|
||||
|
||||
function HandleUpdate(eventType, eventData)
|
||||
-- Zoom in/out
|
||||
if cameraNode ~= nil then
|
||||
Zoom(cameraNode:GetComponent("Camera"))
|
||||
end
|
||||
|
||||
-- Toggle debug geometry with 'Z' key
|
||||
if input:GetKeyPress(KEY_Z) then drawDebug = not drawDebug end
|
||||
|
||||
-- Check for loading / saving the scene
|
||||
if input:GetKeyPress(KEY_F5) then
|
||||
SaveScene(false)
|
||||
end
|
||||
if input:GetKeyPress(KEY_F7) then
|
||||
ReloadScene(false)
|
||||
end
|
||||
end
|
||||
|
||||
function HandlePostUpdate(eventType, eventData)
|
||||
if character2DNode == nil or cameraNode == nil then
|
||||
return
|
||||
end
|
||||
cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10) -- Camera tracks character
|
||||
end
|
||||
|
||||
function HandlePostRenderUpdate(eventType, eventData)
|
||||
if drawDebug then
|
||||
scene_:GetComponent("PhysicsWorld2D"):DrawDebugGeometry()
|
||||
|
||||
local tileMapNode = scene_:GetChild("TileMap", true)
|
||||
local map = tileMapNode:GetComponent("TileMap2D")
|
||||
map:DrawDebugGeometry(scene_:GetComponent("DebugRenderer"), false)
|
||||
end
|
||||
end
|
||||
|
||||
function HandleCollisionBegin(eventType, eventData)
|
||||
-- Get colliding node
|
||||
local hitNode = eventData["NodeA"]:GetPtr("Node")
|
||||
if hitNode.name == "Imp" then
|
||||
hitNode = eventData["NodeB"]:GetPtr("Node")
|
||||
end
|
||||
local nodeName = hitNode.name
|
||||
local character = character2DNode:GetScriptObject()
|
||||
|
||||
-- Handle ropes and ladders climbing
|
||||
if nodeName == "Climb" then
|
||||
if character.isClimbing then -- If transition between rope and top of rope (as we are using split triggers)
|
||||
character.climb2 = true
|
||||
else
|
||||
character.isClimbing = true
|
||||
|
||||
-- Override gravity so that the character doesn't fall
|
||||
local body = character2DNode:GetComponent("RigidBody2D")
|
||||
body.gravityScale = 0
|
||||
|
||||
-- Clear forces so that the character stops (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
body.linearVelocity = Vector2.ZERO
|
||||
body.awake = false
|
||||
body.awake = true
|
||||
end
|
||||
end
|
||||
|
||||
if nodeName == "CanJump" then
|
||||
character.aboveClimbable = true
|
||||
end
|
||||
|
||||
-- Handle coins picking
|
||||
if nodeName == "Coin" then
|
||||
hitNode:Remove()
|
||||
character.remainingCoins = character.remainingCoins - 1
|
||||
if character.remainingCoins == 0 then
|
||||
ui.root:GetChild("Instructions", true).text = "!!! Go to the Exit !!!"
|
||||
end
|
||||
ui.root:GetChild("CoinsText", true).text = character.remainingCoins -- Update coins UI counter
|
||||
PlaySound("Powerup.wav")
|
||||
end
|
||||
|
||||
-- Handle interactions with enemies
|
||||
if nodeName == "Enemy" or nodeName == "Orc" then
|
||||
local animatedSprite = character2DNode:GetComponent("AnimatedSprite2D")
|
||||
local deltaX = character2DNode.position.x - hitNode.position.x
|
||||
|
||||
-- Orc killed if character is fighting in its direction when the contact occurs (flowers are not destroyable)
|
||||
if nodeName == "Orc" and animatedSprite.animation == "attack" and (deltaX < 0 == animatedSprite.flipX) then
|
||||
hitNode:GetScriptObject().emitTime = 1
|
||||
if not hitNode:GetChild("Emitter", true) then
|
||||
hitNode:GetComponent("RigidBody2D"):Remove() -- Remove Orc's body
|
||||
SpawnEffect(hitNode)
|
||||
PlaySound("BigExplosion.wav")
|
||||
end
|
||||
|
||||
-- Player killed if not fighting in the direction of the Orc when the contact occurs, or when colliding with a flower
|
||||
else
|
||||
if not character2DNode:GetChild("Emitter", true) then
|
||||
character.wounded = true
|
||||
if nodeName == "Orc" then
|
||||
hitNode:GetScriptObject().fightTimer = 1
|
||||
end
|
||||
SpawnEffect(character2DNode)
|
||||
PlaySound("BigExplosion.wav")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle exiting the level when all coins have been gathered
|
||||
if nodeName == "Exit" and character.remainingCoins == 0 then
|
||||
-- Update UI
|
||||
local instructions = ui.root:GetChild("Instructions", true)
|
||||
instructions.text = "!!! WELL DONE !!!"
|
||||
instructions.position = IntVector2.ZERO
|
||||
|
||||
-- Put the character outside of the scene and magnify him
|
||||
character2DNode.position = Vector3(-20, 0, 0)
|
||||
character2DNode:SetScale(1.2)
|
||||
end
|
||||
|
||||
-- Handle falling into lava
|
||||
if nodeName == "Lava" then
|
||||
local body = character2DNode:GetComponent("RigidBody2D")
|
||||
body:ApplyLinearImpulse(Vector2(0, 1) * MOVE_SPEED, body.massCenter, true) -- Violently project character out of lava
|
||||
if not character2DNode:GetChild("Emitter", true) then
|
||||
character.wounded = true
|
||||
SpawnEffect(character2DNode)
|
||||
PlaySound("BigExplosion.wav")
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle climbing a slope
|
||||
if nodeName == "Slope" then
|
||||
character.onSlope = true
|
||||
end
|
||||
end
|
||||
|
||||
function HandleCollisionEnd(eventType, eventData)
|
||||
-- Get colliding node
|
||||
local hitNode = eventData["NodeA"]:GetPtr("Node")
|
||||
if hitNode.name == "Imp" then
|
||||
hitNode = eventData["NodeB"]:GetPtr("Node")
|
||||
end
|
||||
local nodeName = hitNode.name
|
||||
local character = character2DNode:GetScriptObject()
|
||||
|
||||
-- Handle leaving a rope or ladder
|
||||
if nodeName == "Climb" then
|
||||
if character.climb2 then
|
||||
character.climb2 = false
|
||||
else
|
||||
character.isClimbing = false
|
||||
local body = character2DNode:GetComponent("RigidBody2D")
|
||||
body.gravityScale = 1 -- Restore gravity
|
||||
end
|
||||
end
|
||||
|
||||
if nodeName == "CanJump" then
|
||||
character.aboveClimbable = false
|
||||
end
|
||||
|
||||
-- Handle leaving a slope
|
||||
if nodeName == "Slope" then
|
||||
character.onSlope = false
|
||||
-- Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
local body = character2DNode:GetComponent("RigidBody2D")
|
||||
body.linearVelocity = Vector2.ZERO
|
||||
body.awake = false
|
||||
body.awake = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Character2D script object class
|
||||
Character2D = ScriptObject()
|
||||
|
||||
function Character2D:Start()
|
||||
self.wounded = false
|
||||
self.killed = false
|
||||
self.timer = 0
|
||||
self.maxCoins = 0
|
||||
self.remainingCoins = 0
|
||||
self.remainingLifes = 3
|
||||
self.isClimbing = false
|
||||
self.climb2 = false -- Used only for ropes, as they are split into 2 shapes
|
||||
self.aboveClimbable = false
|
||||
self.onSlope = false
|
||||
end
|
||||
|
||||
function Character2D:Save(serializer)
|
||||
self.isClimbing = false -- Overwrite before auto-deserialization
|
||||
end
|
||||
|
||||
function Character2D:Update(timeStep)
|
||||
if character2DNode == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Handle wounded/killed states
|
||||
if self.killed then
|
||||
return
|
||||
end
|
||||
|
||||
if self.wounded then
|
||||
self:HandleWoundedState(timeStep)
|
||||
return
|
||||
end
|
||||
|
||||
-- Set temporary variables
|
||||
local node = self.node
|
||||
local body = node:GetComponent("RigidBody2D")
|
||||
local animatedSprite = node:GetComponent("AnimatedSprite2D")
|
||||
local onGround = false
|
||||
local jump = false
|
||||
|
||||
-- Collision detection (AABB query)
|
||||
local characterHalfSize = Vector2(0.16, 0.16)
|
||||
local collidingBodies = scene_:GetComponent("PhysicsWorld2D"):GetRigidBodies(Rect(node.worldPosition2D - characterHalfSize - Vector2(0, 0.1), node.worldPosition2D + characterHalfSize))
|
||||
|
||||
if table.maxn(collidingBodies) > 1 and not self.isClimbing then
|
||||
onGround = true
|
||||
end
|
||||
|
||||
-- Set direction
|
||||
local moveDir = Vector2.ZERO -- Reset
|
||||
|
||||
if input:GetKeyDown(KEY_LEFT) or input:GetKeyDown(KEY_A) then
|
||||
moveDir = moveDir + Vector2.LEFT
|
||||
animatedSprite.flipX = false -- Flip sprite (reset to default play on the X axis)
|
||||
end
|
||||
|
||||
if input:GetKeyDown(KEY_RIGHT) or input:GetKeyDown(KEY_D) then
|
||||
moveDir = moveDir + Vector2.RIGHT
|
||||
animatedSprite.flipX = true -- Flip sprite (flip animation on the X axis)
|
||||
end
|
||||
|
||||
-- Jump
|
||||
if (onGround or self.aboveClimbable) and (input:GetKeyPress(KEY_UP) or input:GetKeyPress(KEY_W)) then
|
||||
jump = true
|
||||
end
|
||||
|
||||
-- Climb
|
||||
if self.isClimbing then
|
||||
if not self.aboveClimbable and (input:GetKeyDown(KEY_UP) or input:GetKeyDown(KEY_W)) then
|
||||
moveDir = moveDir + Vector2.UP
|
||||
end
|
||||
|
||||
if input:GetKeyDown(KEY_DOWN) or input:GetKeyDown(KEY_S) then
|
||||
moveDir = moveDir + Vector2.DOWN
|
||||
end
|
||||
end
|
||||
|
||||
-- Move
|
||||
if not moveDir:Equals(Vector2.ZERO) or jump then
|
||||
if self.onSlope then
|
||||
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(moveDir * timeStep * 1.8)
|
||||
end
|
||||
if jump then
|
||||
body:ApplyLinearImpulse(Vector2(0, 0.17) * MOVE_SPEED, body.massCenter, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Animate
|
||||
if input:GetKeyDown(KEY_SPACE) then
|
||||
if animatedSprite.animation ~= "attack" then
|
||||
animatedSprite:SetAnimation("attack", LM_FORCE_LOOPED)
|
||||
animatedSprite.speed = 1.5
|
||||
end
|
||||
elseif not moveDir:Equals(Vector2.ZERO) then
|
||||
if animatedSprite.animation ~= "run" then
|
||||
animatedSprite:SetAnimation("run")
|
||||
end
|
||||
elseif animatedSprite.animation ~= "idle" then
|
||||
animatedSprite:SetAnimation("idle")
|
||||
end
|
||||
end
|
||||
|
||||
function Character2D:HandleWoundedState(timeStep)
|
||||
local node = self.node
|
||||
local body = node:GetComponent("RigidBody2D")
|
||||
local animatedSprite = node:GetComponent("AnimatedSprite2D")
|
||||
|
||||
-- Play "hit" animation in loop
|
||||
if animatedSprite.animation ~= "hit" then
|
||||
animatedSprite:SetAnimation("hit", LM_FORCE_LOOPED);
|
||||
end
|
||||
|
||||
-- Update timer
|
||||
self.timer = self.timer + timeStep
|
||||
|
||||
-- End of timer
|
||||
if self.timer > 2 then
|
||||
-- Reset timer
|
||||
self.timer = 0
|
||||
|
||||
-- Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
body.linearVelocity = Vector2.ZERO
|
||||
body.awake = false
|
||||
body.awake = true
|
||||
|
||||
-- Remove particle emitter
|
||||
node:GetChild("Emitter", true):Remove()
|
||||
|
||||
-- Update lifes UI and counter
|
||||
self.remainingLifes = self.remainingLifes - 1
|
||||
ui.root:GetChild("LifeText", true).text = self.remainingLifes -- Update lifes UI counter
|
||||
|
||||
-- Reset wounded state
|
||||
self.wounded = false
|
||||
|
||||
-- Handle death
|
||||
if self.remainingLifes == 0 then
|
||||
self:HandleDeath()
|
||||
return
|
||||
end
|
||||
|
||||
-- Re-position the character to the nearest point
|
||||
if node.position.x < 15 then
|
||||
node.position = Vector3(1, 8, 0)
|
||||
else
|
||||
node.position = Vector3(18.8, 9.2, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Character2D:HandleDeath()
|
||||
local node = self.node
|
||||
local body = node:GetComponent("RigidBody2D")
|
||||
local animatedSprite = node:GetComponent("AnimatedSprite2D")
|
||||
|
||||
-- Set state to 'killed'
|
||||
self.killed = true
|
||||
|
||||
-- Update UI elements
|
||||
local instructions = ui.root:GetChild("Instructions", true)
|
||||
instructions.text = "!!! GAME OVER !!!"
|
||||
ui.root:GetChild("ExitButton", true).visible = true
|
||||
ui.root:GetChild("PlayButton", true).visible = true
|
||||
|
||||
-- Show mouse cursor so that we can click
|
||||
input.mouseVisible = true
|
||||
|
||||
-- Put character outside of the scene and magnify him
|
||||
node.position = Vector3(-20, 0, 0)
|
||||
node:SetScale(1.2)
|
||||
|
||||
-- Play death animation once
|
||||
if animatedSprite.animation ~= "dead2" then
|
||||
animatedSprite:SetAnimation("dead2")
|
||||
end
|
||||
end
|
121
bin/Data/LuaScripts/Utilities/2D/Mover.lua
Normal file
121
bin/Data/LuaScripts/Utilities/2D/Mover.lua
Normal file
@ -0,0 +1,121 @@
|
||||
-- Mover script object class
|
||||
-- - Handles entity (enemy, platform...) translation along a path (set of Vector2 points)
|
||||
-- - Supports looping paths and animation flip
|
||||
-- - Default speed is 0.8 if 'Speed' property is not set in the tmx file objects
|
||||
|
||||
Mover = ScriptObject()
|
||||
|
||||
function Mover:Start()
|
||||
self.speed = 0.8
|
||||
self.path = {}
|
||||
self.currentPathID = 2
|
||||
self.emitTime = 0
|
||||
self.fightTimer = 0
|
||||
self.flip = 0
|
||||
end
|
||||
|
||||
function Mover:Load(deserializer)
|
||||
self:SetPathAttr(deserializer:ReadBuffer())
|
||||
end
|
||||
|
||||
function Mover:Save(serializer)
|
||||
serializer:WriteBuffer(self:GetPathAttr())
|
||||
end
|
||||
|
||||
function Mover:SetPathAttr(buffer)
|
||||
if buffer.size == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
while not buffer.eof do
|
||||
table.insert(self.path, buffer:ReadVector2())
|
||||
end
|
||||
end
|
||||
|
||||
function Mover:GetPathAttr()
|
||||
local ret = VectorBuffer()
|
||||
|
||||
for i=1, table.maxn(self.path) do
|
||||
ret:WriteVector2(self.path[i])
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function Mover:Update(timeStep)
|
||||
if table.maxn(self.path) < 2 then
|
||||
return
|
||||
end
|
||||
|
||||
local node = self.node
|
||||
|
||||
-- Handle Orc states (idle/wounded/fighting)
|
||||
if node.name == "Orc" then
|
||||
local animatedSprite = node:GetComponent("AnimatedSprite2D")
|
||||
local anim = "run"
|
||||
|
||||
-- Handle wounded state
|
||||
if self.emitTime > 0 then
|
||||
self.emitTime = self.emitTime + timeStep
|
||||
anim = "dead"
|
||||
|
||||
-- Handle dead
|
||||
if self.emitTime >= 3 then
|
||||
self.node:Remove()
|
||||
return
|
||||
end
|
||||
else
|
||||
-- Handle fighting state
|
||||
if self.fightTimer > 0 then
|
||||
anim = "attack"
|
||||
self.flip = character2DNode.position.x - node.position.x
|
||||
self.fightTimer = self.fightTimer + timeStep
|
||||
if self.fightTimer >= 3 then
|
||||
self.fightTimer = 0 -- Reset
|
||||
end
|
||||
end
|
||||
-- Flip Orc animation according to speed, or player position when fighting
|
||||
animatedSprite.flipX = self.flip >= 0
|
||||
end
|
||||
-- Animate
|
||||
if animatedSprite.animation ~= anim then
|
||||
animatedSprite:SetAnimation(anim)
|
||||
end
|
||||
end
|
||||
|
||||
-- Don't move if fighting or wounded
|
||||
if self.fightTimer > 0 or self.emitTime > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Set direction and move to target
|
||||
local dir = self.path[self.currentPathID] - node.position2D
|
||||
local dirNormal = dir:Normalized()
|
||||
node:Translate(Vector3(dirNormal.x, dirNormal.y, 0) * Abs(self.speed) * timeStep)
|
||||
self.flip = dir.x
|
||||
|
||||
if Abs(dir:Length()) < 0.1 then
|
||||
if self.speed > 0 then
|
||||
if self.currentPathID + 1 <= table.maxn(self.path) then
|
||||
self.currentPathID = self.currentPathID + 1
|
||||
else
|
||||
-- If loop, go to first waypoint, which equates to last one (and never reverse)
|
||||
if self.path[self.currentPathID] == self.path[1] then
|
||||
self.currentPathID = 1
|
||||
return
|
||||
end
|
||||
-- Reverse path if not looping
|
||||
self.currentPathID = self.currentPathID - 1
|
||||
self.speed = -self.speed
|
||||
end
|
||||
else
|
||||
if self.currentPathID - 1 > 0 then
|
||||
self.currentPathID = self.currentPathID - 1
|
||||
else
|
||||
-- Reverse path
|
||||
self.currentPathID = 2
|
||||
self.speed = -self.speed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
497
bin/Data/LuaScripts/Utilities/2D/Sample2D.lua
Normal file
497
bin/Data/LuaScripts/Utilities/2D/Sample2D.lua
Normal file
@ -0,0 +1,497 @@
|
||||
-- Convenient functions for Urho2D samples:
|
||||
-- - Generate collision shapes from a tmx file objects
|
||||
-- - Create Spriter Imp character
|
||||
-- - Load Mover script object class from file
|
||||
-- - Create enemies, coins and platforms to tile map placeholders
|
||||
-- - Handle camera zoom using PageUp, PageDown and MouseWheel
|
||||
-- - Create UI interface
|
||||
-- - Create a particle emitter attached to a given node
|
||||
-- - Play a non-looping sound effect
|
||||
-- - Create a background sprite
|
||||
-- - Set global variables
|
||||
-- - Set XML patch instructions for screen joystick
|
||||
|
||||
CAMERA_MIN_DIST = 0.1
|
||||
CAMERA_MAX_DIST = 6
|
||||
|
||||
MOVE_SPEED = 23 -- Movement speed as world units per second
|
||||
MOVE_SPEED_X = 2.5 -- Movement speed for isometric maps
|
||||
MOVE_SPEED_SCALE = 1 -- Scaling factor based on tiles' aspect ratio
|
||||
|
||||
LIFES = 3
|
||||
zoom = 2 -- Speed is scaled according to zoom
|
||||
demoFilename = ""
|
||||
character2DNode = nil
|
||||
|
||||
|
||||
function CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
|
||||
-- Create rigid body to the root node
|
||||
local body = tileMapNode:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_STATIC
|
||||
|
||||
-- Generate physics collision shapes from the tmx file's objects located in "Physics" layer
|
||||
for i=0, tileMapLayer:GetNumObjects() -1 do
|
||||
local tileMapObject = tileMapLayer:GetObject(i) -- Get physics objects (TileMapObject2D)
|
||||
local objectType = tileMapObject.objectType
|
||||
|
||||
-- Create collision shape from tmx object
|
||||
local shape
|
||||
|
||||
if objectType == OT_RECTANGLE then
|
||||
shape = tileMapNode:CreateComponent("CollisionBox2D")
|
||||
local size = tileMapObject.size
|
||||
shape.size = size
|
||||
if info.orientation == O_ORTHOGONAL then
|
||||
shape.center = tileMapObject.position + size / 2
|
||||
else
|
||||
shape.center = tileMapObject.position + Vector2(info.tileWidth / 2, 0)
|
||||
shape.angle = 45 -- If our tile map is isometric then shape is losange
|
||||
end
|
||||
|
||||
elseif objectType == OT_ELLIPSE then
|
||||
shape = tileMapNode:CreateComponent("CollisionCircle2D") -- Ellipse is built as a circle shape as there's no equivalent in Box2D
|
||||
local size = tileMapObject.size
|
||||
shape.radius = size.x / 2
|
||||
if info.orientation == O_ORTHOGONAL then
|
||||
shape.center = tileMapObject.position + size / 2
|
||||
else
|
||||
shape.center = tileMapObject.position + Vector2(info.tileWidth / 2, 0)
|
||||
end
|
||||
|
||||
elseif objectType == OT_POLYGON then
|
||||
shape = tileMapNode:CreateComponent("CollisionPolygon2D")
|
||||
|
||||
elseif objectType == OT_POLYLINE then
|
||||
shape = tileMapNode:CreateComponent("CollisionChain2D")
|
||||
|
||||
else break
|
||||
end
|
||||
|
||||
if objectType == OT_POLYGON or objectType == OT_POLYLINE then -- Build shape from vertices
|
||||
local numVertices = tileMapObject.numPoints
|
||||
shape.vertexCount = numVertices
|
||||
for i=0, numVertices - 1 do
|
||||
shape:SetVertex(i, tileMapObject:GetPoint(i))
|
||||
end
|
||||
end
|
||||
|
||||
shape.friction = 0.8
|
||||
if tileMapObject:HasProperty("Friction") then
|
||||
shape.friction = ToFloat(tileMapObject:GetProperty("Friction"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CreateCharacter(info, createObject, friction, position, scale)
|
||||
character2DNode = scene_:CreateChild("Imp")
|
||||
character2DNode.position = position
|
||||
character2DNode:SetScale(scale)
|
||||
local animatedSprite = character2DNode:CreateComponent("AnimatedSprite2D")
|
||||
local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/imp/imp.scml")
|
||||
animatedSprite.animationSet = animationSet
|
||||
animatedSprite.animation = "idle"
|
||||
animatedSprite:SetLayer(3) -- Put character over tile map (which is on layer 0) and over Orcs (which are on layer 1)
|
||||
--
|
||||
local body = character2DNode:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_DYNAMIC
|
||||
body.allowSleep = false
|
||||
|
||||
local shape = character2DNode:CreateComponent("CollisionCircle2D")
|
||||
shape.radius = 1.1 -- Set shape size
|
||||
shape.friction = friction -- Set friction
|
||||
shape.restitution = 0.1 -- Slight bounce
|
||||
if createObject then
|
||||
character2DNode:CreateScriptObject("Character2D") -- Create a ScriptObject to handle character behavior
|
||||
end
|
||||
|
||||
-- Scale character's speed on the Y axis according to tiles' aspect ratio (for isometric only)
|
||||
MOVE_SPEED_SCALE = info.tileHeight / info.tileWidth
|
||||
end
|
||||
|
||||
function CreateTrigger()
|
||||
local node = scene_:CreateChild("Trigger") -- Clones will be renamed according to object type
|
||||
local body = node:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_STATIC
|
||||
local shape = node:CreateComponent("CollisionBox2D") -- Create box shape
|
||||
shape.trigger = true
|
||||
return node
|
||||
end
|
||||
|
||||
function CreateEnemy()
|
||||
local node = scene_:CreateChild("Enemy")
|
||||
local staticSprite = node:CreateComponent("StaticSprite2D")
|
||||
staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Aster.png")
|
||||
local body = node:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_STATIC
|
||||
local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
|
||||
shape.radius = 0.25 -- Set radius
|
||||
return node
|
||||
end
|
||||
|
||||
function CreateOrc()
|
||||
local node = scene_:CreateChild("Orc")
|
||||
node.scale = character2DNode.scale -- Use same scale as player character
|
||||
local animatedSprite = node:CreateComponent("AnimatedSprite2D")
|
||||
-- Get scml file and Play "run" anim
|
||||
local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/Orc/Orc.scml")
|
||||
animatedSprite.animationSet = animationSet
|
||||
animatedSprite.animation = "run"
|
||||
animatedSprite:SetLayer(2) -- Make orc always visible
|
||||
local body = node:CreateComponent("RigidBody2D")
|
||||
local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
|
||||
shape.radius = 1.3 -- Set shape size
|
||||
shape.trigger = true
|
||||
return node
|
||||
end
|
||||
|
||||
function CreateCoin()
|
||||
local node = scene_:CreateChild("Coin")
|
||||
node:SetScale(0.5)
|
||||
local animatedSprite = node:CreateComponent("AnimatedSprite2D")
|
||||
-- Get scml file and Play "idle" anim
|
||||
local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml")
|
||||
animatedSprite.animationSet = animationSet
|
||||
animatedSprite.animation = "idle"
|
||||
animatedSprite:SetLayer(2)
|
||||
local body = node:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_STATIC
|
||||
local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
|
||||
shape.radius = 0.32 -- Set radius
|
||||
shape.trigger = true
|
||||
return node
|
||||
end
|
||||
|
||||
function CreateMovingPlatform()
|
||||
local node = scene_:CreateChild("MovingPlatform")
|
||||
node.scale = Vector3(3, 1, 0)
|
||||
local staticSprite = node:CreateComponent("StaticSprite2D")
|
||||
staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Box.png")
|
||||
local body = node:CreateComponent("RigidBody2D")
|
||||
body.bodyType = BT_STATIC
|
||||
local shape = node:CreateComponent("CollisionBox2D") -- Create box shape
|
||||
shape.size = Vector2(0.32, 0.32) -- Set box size
|
||||
shape.friction = 0.8 -- Set friction
|
||||
return node
|
||||
end
|
||||
|
||||
function PopulateMovingEntities(movingEntitiesLayer)
|
||||
-- Create enemy, Orc and moving platform nodes (will be cloned at each placeholder)
|
||||
local enemyNode = CreateEnemy()
|
||||
local orcNode = CreateOrc()
|
||||
local platformNode = CreateMovingPlatform()
|
||||
|
||||
-- Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points)
|
||||
for i=0, movingEntitiesLayer:GetNumObjects() -1 do
|
||||
-- Get placeholder object (TileMapObject2D)
|
||||
local movingObject = movingEntitiesLayer:GetObject(i)
|
||||
if movingObject.objectType == OT_POLYLINE then
|
||||
-- Clone the moving entity node and position it at placeholder point
|
||||
local movingClone = nil
|
||||
local offset = Vector2.ZERO
|
||||
if movingObject.type == "Enemy" then
|
||||
movingClone = enemyNode:Clone()
|
||||
offset = Vector2(0, -0.32)
|
||||
elseif movingObject.type == "Orc" then
|
||||
movingClone = orcNode:Clone()
|
||||
elseif movingObject.type == "MovingPlatform" then
|
||||
movingClone = platformNode:Clone()
|
||||
else
|
||||
break
|
||||
end
|
||||
movingClone.position2D = movingObject:GetPoint(0) + offset
|
||||
|
||||
-- Create script object that handles entity translation along its path (load from file)
|
||||
local mover = movingClone:CreateScriptObject("LuaScripts/Utilities/2D/Mover.lua", "Mover")
|
||||
|
||||
-- Set path from points
|
||||
mover.path = CreatePathFromPoints(movingObject, offset)
|
||||
|
||||
-- Override default speed
|
||||
if movingObject:HasProperty("Speed") then
|
||||
mover.speed = movingObject:GetProperty("Speed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove nodes used for cloning purpose
|
||||
enemyNode:Remove()
|
||||
orcNode:Remove()
|
||||
platformNode:Remove()
|
||||
end
|
||||
|
||||
function PopulateCoins(coinsLayer)
|
||||
-- Create coin (will be cloned at each placeholder)
|
||||
local coinNode = CreateCoin()
|
||||
|
||||
-- Instantiate coins to pick at each placeholder
|
||||
for i=0, coinsLayer:GetNumObjects() -1 do
|
||||
local coinObject = coinsLayer:GetObject(i) -- Get placeholder object (TileMapObject2D)
|
||||
local coinClone = coinNode:Clone()
|
||||
coinClone.position2D = coinObject.position + coinObject.size / 2 + Vector2(0, 0.16)
|
||||
end
|
||||
|
||||
-- Init coins counters
|
||||
local character = character2DNode:GetScriptObject()
|
||||
character.remainingCoins = coinsLayer.numObjects
|
||||
character.maxCoins = coinsLayer.numObjects
|
||||
|
||||
-- Remove node used for cloning purpose
|
||||
coinNode:Remove()
|
||||
end
|
||||
|
||||
function PopulateTriggers(triggersLayer)
|
||||
-- Create trigger node (will be cloned at each placeholder)
|
||||
local triggerNode = CreateTrigger()
|
||||
|
||||
-- Instantiate triggers at each placeholder (Rectangle objects)
|
||||
for i=0, triggersLayer:GetNumObjects() -1 do
|
||||
local triggerObject = triggersLayer:GetObject(i) -- Get placeholder object (TileMapObject2D)
|
||||
if triggerObject.objectType == OT_RECTANGLE then
|
||||
local triggerClone = triggerNode:Clone()
|
||||
triggerClone.name = triggerObject.type
|
||||
triggerClone:GetComponent("CollisionBox2D").size = triggerObject.size
|
||||
triggerClone.position2D = triggerObject.position + triggerObject.size / 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Zoom(camera)
|
||||
if input.mouseMoveWheel then
|
||||
zoom = Clamp(camera.zoom + input.mouseMoveWheel * 0.1, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
|
||||
camera.zoom = zoom
|
||||
end
|
||||
|
||||
if input:GetKeyDown(KEY_PAGEUP) then
|
||||
zoom = Clamp(camera.zoom * 1.01, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
|
||||
camera.zoom = zoom
|
||||
end
|
||||
|
||||
if input:GetKeyDown(KEY_PAGEDOWN) then
|
||||
zoom = Clamp(camera.zoom * 0.99, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
|
||||
camera.zoom = zoom
|
||||
end
|
||||
end
|
||||
|
||||
function CreatePathFromPoints(object, offset)
|
||||
local path = {}
|
||||
for i=0, object.numPoints -1 do
|
||||
table.insert(path, object:GetPoint(i) + offset)
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
function CreateUIContent(demoTitle)
|
||||
-- Set the default UI style and font
|
||||
ui.root.defaultStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
|
||||
local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
|
||||
|
||||
-- We create in-game UIs (coins and lifes) first so that they are hidden by the fullscreen UI (we could also temporary hide them using SetVisible)
|
||||
|
||||
-- Create the UI for displaying the remaining coins
|
||||
local coinsUI = ui.root:CreateChild("BorderImage", "Coins")
|
||||
coinsUI.texture = cache:GetResource("Texture2D", "Urho2D/GoldIcon.png")
|
||||
coinsUI:SetSize(50, 50)
|
||||
coinsUI.imageRect = IntRect(0, 64, 60, 128)
|
||||
coinsUI:SetAlignment(HA_LEFT, VA_TOP)
|
||||
coinsUI:SetPosition(5, 5);
|
||||
local coinsText = coinsUI:CreateChild("Text", "CoinsText")
|
||||
coinsText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
coinsText:SetFont(font, 24)
|
||||
coinsText.textEffect = TE_SHADOW
|
||||
coinsText.text = character2DNode:GetScriptObject().remainingCoins
|
||||
|
||||
-- Create the UI for displaying the remaining lifes
|
||||
local lifeUI = ui.root:CreateChild("BorderImage", "Life")
|
||||
lifeUI.texture = cache:GetResource("Texture2D", "Urho2D/imp/imp_all.png")
|
||||
lifeUI:SetSize(70, 80)
|
||||
lifeUI:SetAlignment(HA_RIGHT, VA_TOP)
|
||||
lifeUI:SetPosition(-5, 5);
|
||||
local lifeText = lifeUI:CreateChild("Text", "LifeText")
|
||||
lifeText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
lifeText:SetFont(font, 24)
|
||||
lifeText.textEffect = TE_SHADOW
|
||||
lifeText.text = LIFES
|
||||
|
||||
-- Create the fullscreen UI for start/end
|
||||
local fullUI = ui.root:CreateChild("Window", "FullUI")
|
||||
fullUI:SetStyleAuto()
|
||||
fullUI:SetSize(ui.root.width, ui.root.height)
|
||||
fullUI.enabled = false -- Do not react to input, only the 'Exit' and 'Play' buttons will
|
||||
|
||||
-- Create the title
|
||||
local title = fullUI:CreateChild("BorderImage", "Title")
|
||||
title:SetMinSize(fullUI.width, 50)
|
||||
title.texture = cache:GetResource("Texture2D", "Textures/HeightMap.png")
|
||||
title:SetFullImageRect()
|
||||
title:SetAlignment(HA_CENTER, VA_TOP)
|
||||
local titleText = title:CreateChild("Text", "TitleText")
|
||||
titleText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
titleText:SetFont(font, 24)
|
||||
titleText.text = demoTitle
|
||||
|
||||
-- Create the image
|
||||
local spriteUI = fullUI:CreateChild("BorderImage", "Sprite")
|
||||
spriteUI.texture = cache:GetResource("Texture2D", "Urho2D/imp/imp_all.png")
|
||||
spriteUI:SetSize(238, 271)
|
||||
spriteUI:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
spriteUI:SetPosition(0, - ui.root.height / 4)
|
||||
|
||||
-- Create the 'EXIT' button
|
||||
local exitButton = ui.root:CreateChild("Button", "ExitButton")
|
||||
exitButton:SetStyleAuto()
|
||||
exitButton.focusMode = FM_RESETFOCUS
|
||||
exitButton:SetSize(100, 50)
|
||||
exitButton:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
exitButton:SetPosition(-100, 0)
|
||||
local exitText = exitButton:CreateChild("Text", "ExitText")
|
||||
exitText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
exitText:SetFont(font, 24)
|
||||
exitText.text = "EXIT"
|
||||
SubscribeToEvent(exitButton, "Released", "HandleExitButton")
|
||||
|
||||
-- Create the 'PLAY' button
|
||||
local playButton = ui.root:CreateChild("Button", "PlayButton")
|
||||
playButton:SetStyleAuto()
|
||||
playButton.focusMode = FM_RESETFOCUS
|
||||
playButton:SetSize(100, 50)
|
||||
playButton:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
playButton:SetPosition(100, 0)
|
||||
local playText = playButton:CreateChild("Text", "PlayText")
|
||||
playText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
playText:SetFont(font, 24)
|
||||
playText.text = "PLAY"
|
||||
SubscribeToEvent(playButton, "Released", "HandlePlayButton")
|
||||
|
||||
-- Create the instructions
|
||||
local instructionText = ui.root:CreateChild("Text", "Instructions")
|
||||
instructionText:SetFont(font, 15)
|
||||
instructionText.textAlignment = HA_CENTER -- Center rows in relation to each other
|
||||
instructionText.text = "Use WASD keys or Arrows to move\nPageUp/PageDown/MouseWheel to zoom\nF5/F7 to save/reload scene\n'Z' to toggle debug geometry\nSpace to fight"
|
||||
instructionText:SetAlignment(HA_CENTER, VA_CENTER)
|
||||
instructionText:SetPosition(0, ui.root.height / 4)
|
||||
|
||||
-- Show mouse cursor
|
||||
input.mouseVisible = true
|
||||
end
|
||||
|
||||
function HandleExitButton()
|
||||
engine:Exit()
|
||||
end
|
||||
|
||||
function HandlePlayButton()
|
||||
-- Remove fullscreen UI and unfreeze the scene
|
||||
if ui.root:GetChild("FullUI", true) then
|
||||
ui.root:GetChild("FullUI", true):Remove()
|
||||
scene_.updateEnabled = true
|
||||
else
|
||||
-- Reload the scene
|
||||
ReloadScene(true)
|
||||
end
|
||||
|
||||
-- Hide Instructions and Play/Exit buttons
|
||||
ui.root:GetChild("Instructions", true).text = ""
|
||||
ui.root:GetChild("ExitButton", true).visible = false
|
||||
ui.root:GetChild("PlayButton", true).visible = false
|
||||
|
||||
-- Hide mouse cursor
|
||||
input.mouseVisible = false
|
||||
end
|
||||
|
||||
function SaveScene(initial)
|
||||
local filename = demoFilename
|
||||
if not initial then
|
||||
filename = demoFilename .. "InGame"
|
||||
end
|
||||
|
||||
scene_:SaveXML(fileSystem:GetProgramDir() .. "Data/Scenes/" .. filename .. ".xml")
|
||||
end
|
||||
|
||||
function ReloadScene(reInit)
|
||||
local filename = demoFilename
|
||||
if not reInit then
|
||||
filename = demoFilename .. "InGame"
|
||||
end
|
||||
|
||||
scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/" .. filename .. ".xml")
|
||||
-- After loading we have to reacquire the character scene node, as it has been recreated
|
||||
-- Simply find by name as there's only one of them
|
||||
character2DNode = scene_:GetChild("Imp", true);
|
||||
if character2DNode == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Set what value to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false)
|
||||
local character = character2DNode:GetScriptObject()
|
||||
local lifes = character.remainingLifes
|
||||
local coins =character.remainingCoins
|
||||
if reInit then
|
||||
lifes = LIFES
|
||||
coins = character.maxCoins
|
||||
end
|
||||
|
||||
-- Update lifes UI and value
|
||||
local lifeText = ui.root:GetChild("LifeText", true)
|
||||
lifeText.text = lifes
|
||||
character.remainingLifes = lifes
|
||||
|
||||
-- Update coins UI and value
|
||||
local coinsText = ui.root:GetChild("CoinsText", true)
|
||||
coinsText.text = coins
|
||||
character.remainingCoins = coins
|
||||
end
|
||||
|
||||
function SpawnEffect(node)
|
||||
local particleNode = node:CreateChild("Emitter")
|
||||
particleNode:SetScale(0.5 / node.scale.x)
|
||||
local particleEmitter = particleNode:CreateComponent("ParticleEmitter2D")
|
||||
particleEmitter.effect = cache:GetResource("ParticleEffect2D", "Urho2D/sun.pex")
|
||||
end
|
||||
|
||||
function PlaySound(soundName)
|
||||
local soundNode = scene_:CreateChild("Sound")
|
||||
local source = soundNode:CreateComponent("SoundSource")
|
||||
source:Play(cache:GetResource("Sound", "Sounds/" .. soundName))
|
||||
end
|
||||
|
||||
function CreateBackgroundSprite(info, scale, texture, animate)
|
||||
local node = scene_:CreateChild("Background")
|
||||
node.position = Vector3(info.mapWidth, info.mapHeight, 0) / 2
|
||||
node:SetScale(scale)
|
||||
local sprite = node:CreateComponent("StaticSprite2D")
|
||||
sprite.sprite = cache:GetResource("Sprite2D", texture)
|
||||
SetRandomSeed(time:GetSystemTime()) -- Randomize from system clock
|
||||
sprite.color = Color(Random(0, 1), Random(0, 1), Random(0, 1), 1)
|
||||
|
||||
-- Create rotation animation
|
||||
if animate then
|
||||
local animation = ValueAnimation:new()
|
||||
animation:SetKeyFrame(0, Variant(Quaternion(0, 0, 0)))
|
||||
animation:SetKeyFrame(1, Variant(Quaternion(0, 0, 180)))
|
||||
animation:SetKeyFrame(2, Variant(Quaternion(0, 0, 0)))
|
||||
node:SetAttributeAnimation("Rotation", animation, WM_LOOP, 0.05)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Create XML patch instructions for screen joystick layout specific to this sample app
|
||||
function GetScreenJoystickPatchString()
|
||||
return
|
||||
"<patch>" ..
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" ..
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Fight</replace>" ..
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" ..
|
||||
" <element type=\"Text\">" ..
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />" ..
|
||||
" <attribute name=\"Text\" value=\"SPACE\" />" ..
|
||||
" </element>" ..
|
||||
" </add>" ..
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" ..
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Jump</replace>" ..
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" ..
|
||||
" <element type=\"Text\">" ..
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />" ..
|
||||
" <attribute name=\"Text\" value=\"UP\" />" ..
|
||||
" </element>" ..
|
||||
" </add>" ..
|
||||
"</patch>"
|
||||
end
|
341
bin/Data/Scripts/49_Urho2DIsometricDemo.as
Normal file
341
bin/Data/Scripts/49_Urho2DIsometricDemo.as
Normal file
@ -0,0 +1,341 @@
|
||||
// Urho2D tile map example.
|
||||
// This sample demonstrates:
|
||||
// - Creating an isometric 2D scene with tile map
|
||||
// - Displaying the scene using the Renderer subsystem
|
||||
// - Handling keyboard to move a 2D character and zoom 2D camera
|
||||
// - Generating physics shapes from the tmx file's objects
|
||||
// - Displaying debug geometry for physics and tile map
|
||||
// Note that this sample uses some functions from Sample2D utility class.
|
||||
|
||||
#include "Scripts/Utilities/Sample.as"
|
||||
#include "Scripts/Utilities/2D/Sample2D.as"
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Execute the common startup for samples
|
||||
SampleStart();
|
||||
|
||||
// Create the scene content
|
||||
CreateScene();
|
||||
|
||||
// Create the UI content
|
||||
CreateUIContent("ISOMETRIC 2.5D DEMO");
|
||||
|
||||
// Hook up to the frame update events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
void CreateScene()
|
||||
{
|
||||
scene_ = Scene();
|
||||
|
||||
// Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_.CreateComponent("Octree");
|
||||
scene_.CreateComponent("DebugRenderer");
|
||||
PhysicsWorld2D@ physicsWorld = scene_.CreateComponent("PhysicsWorld2D");
|
||||
physicsWorld.gravity = Vector2(0.0f, 0.0f); // Neutralize gravity as the character will always be grounded
|
||||
|
||||
// Create camera and define viewport
|
||||
cameraNode = Node();
|
||||
Camera@ camera = cameraNode.CreateComponent("Camera");
|
||||
camera.orthographic = true;
|
||||
camera.orthoSize = graphics.height * PIXEL_SIZE;
|
||||
camera.zoom = 2.0f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (2.0) is set for full visibility at 1280x800 resolution)
|
||||
renderer.viewports[0] = Viewport(scene_, camera);
|
||||
|
||||
// Create tile map from tmx file
|
||||
TmxFile2D@ tmxFile = cache.GetResource("TmxFile2D", "Urho2D/Tilesets/atrium.tmx");
|
||||
if (tmxFile is null)
|
||||
return;
|
||||
|
||||
Node@ tileMapNode = scene_.CreateChild("TileMap");
|
||||
TileMap2D@ tileMap = tileMapNode.CreateComponent("TileMap2D");
|
||||
tileMap.tmxFile = tmxFile;
|
||||
TileMapInfo2D@ info = tileMap.info;
|
||||
|
||||
// Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
CreateCharacter(info, true, 0.0f, Vector3(-5.0f, 11.0f, 0.0f), 0.15f);
|
||||
|
||||
// Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
TileMapLayer2D@ tileMapLayer = tileMap.GetLayer(tileMap.numLayers - 1);
|
||||
CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
|
||||
|
||||
// Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
PopulateMovingEntities(tileMap.GetLayer(tileMap.numLayers - 2));
|
||||
|
||||
// Instantiate coins to pick at each placeholder of "Coins" layer (in this sample, placeholders for coins are Rectangle objects)
|
||||
PopulateCoins(tileMap.GetLayer(tileMap.numLayers - 3));
|
||||
|
||||
// Check when scene is rendered
|
||||
SubscribeToEvent("EndRendering", "HandleSceneRendered");
|
||||
}
|
||||
|
||||
void HandleSceneRendered()
|
||||
{
|
||||
UnsubscribeFromEvent("EndRendering");
|
||||
// Save the scene so we can reload it later
|
||||
SaveScene(true);
|
||||
// Pause the scene as long as the UI is hiding it
|
||||
scene_.updateEnabled = false;
|
||||
}
|
||||
|
||||
void SubscribeToEvents()
|
||||
{
|
||||
// Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent("Update", "HandleUpdate");
|
||||
|
||||
// Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent("PostUpdate", "HandlePostUpdate");
|
||||
|
||||
// Subscribe to PostRenderUpdate to draw physics shapes
|
||||
SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
|
||||
|
||||
// Subscribe to Box2D contact listeners
|
||||
SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin");
|
||||
|
||||
// Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent("SceneUpdate");
|
||||
}
|
||||
|
||||
void HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Zoom in/out
|
||||
if (cameraNode !is null)
|
||||
Zoom(cameraNode.GetComponent("Camera"));
|
||||
|
||||
// Toggle debug geometry with spacebar
|
||||
if (input.keyPress[KEY_Z]) drawDebug = !drawDebug;
|
||||
|
||||
// Check for loading / saving the scene
|
||||
if (input.keyPress[KEY_F5])
|
||||
{
|
||||
SaveScene(false);
|
||||
}
|
||||
if (input.keyPress[KEY_F7])
|
||||
{
|
||||
ReloadScene(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (character2DNode is null || cameraNode is null)
|
||||
return;
|
||||
cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10.0f); // Camera tracks character
|
||||
}
|
||||
|
||||
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (drawDebug)
|
||||
{
|
||||
PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
|
||||
physicsWorld.DrawDebugGeometry();
|
||||
Node@ tileMapNode = scene_.GetChild("TileMap", true);
|
||||
TileMap2D@ map = tileMapNode.GetComponent("TileMap2D");
|
||||
map.DrawDebugGeometry(scene_.GetComponent("DebugRenderer"), false);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node@ hitNode = eventData["NodeA"].GetPtr();
|
||||
if (hitNode.name == "Imp")
|
||||
hitNode = eventData["NodeB"].GetPtr();
|
||||
String nodeName = hitNode.name;
|
||||
Character2D@ character = cast<Character2D>(character2DNode.scriptObject);
|
||||
|
||||
// Handle coins picking
|
||||
if (nodeName == "Coin")
|
||||
{
|
||||
hitNode.Remove();
|
||||
character.remainingCoins = character.remainingCoins - 1;
|
||||
if (character.remainingCoins == 0)
|
||||
{
|
||||
Text@ instructions = ui.root.GetChild("Instructions", true);
|
||||
instructions.text = "!!! You got all the coins !!!";
|
||||
}
|
||||
Text@ coinsText = ui.root.GetChild("CoinsText", true);
|
||||
coinsText.text = character.remainingCoins; // Update coins UI counter
|
||||
PlaySound("Powerup.wav");
|
||||
}
|
||||
|
||||
// Handle interactions with enemies
|
||||
if (nodeName == "Orc")
|
||||
{
|
||||
AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
|
||||
float deltaX = character2DNode.position.x - hitNode.position.x;
|
||||
|
||||
// Orc killed if character is fighting in its direction when the contact occurs
|
||||
if (animatedSprite.animation == "attack" && (deltaX < 0 == animatedSprite.flipX))
|
||||
{
|
||||
cast<Mover>(hitNode.scriptObject).emitTime = 1;
|
||||
if (hitNode.GetChild("Emitter", true) is null)
|
||||
{
|
||||
hitNode.GetComponent("RigidBody2D").Remove(); // Remove Orc's body
|
||||
SpawnEffect(hitNode);
|
||||
PlaySound("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
// Player killed if not fighting in the direction of the Orc when the contact occurs
|
||||
else
|
||||
{
|
||||
if (character2DNode.GetChild("Emitter", true) is null)
|
||||
{
|
||||
character.wounded = true;
|
||||
if (nodeName == "Orc")
|
||||
cast<Mover>(hitNode.scriptObject).fightTimer = 1;
|
||||
SpawnEffect(character2DNode);
|
||||
PlaySound("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Character2D script object class
|
||||
class Character2D : ScriptObject
|
||||
{
|
||||
bool wounded = false;
|
||||
bool killed = false;
|
||||
float timer = 0.0f;
|
||||
int maxCoins = 0;
|
||||
int remainingCoins = 0;
|
||||
int remainingLifes = 3;
|
||||
|
||||
void Update(float timeStep)
|
||||
{
|
||||
if (character2DNode is null)
|
||||
return;
|
||||
|
||||
// Handle wounded/killed states
|
||||
if (killed)
|
||||
return;
|
||||
|
||||
if (wounded)
|
||||
{
|
||||
HandleWoundedState(timeStep);
|
||||
return;
|
||||
}
|
||||
|
||||
AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
|
||||
|
||||
// Set direction
|
||||
Vector3 moveDir = Vector3(0.0f, 0.0f, 0.0f); // Reset
|
||||
float speedX = Clamp(MOVE_SPEED_X / zoom, 0.4f, 1.0f);
|
||||
float speedY = speedX;
|
||||
|
||||
if (input.keyDown['A'] || input.keyDown[KEY_LEFT])
|
||||
{
|
||||
moveDir = moveDir + Vector3(-1.0f, 0.0f, 0.0f) * speedX;
|
||||
animatedSprite.flipX = false; // Flip sprite (reset to default play on the X axis)
|
||||
}
|
||||
if (input.keyDown['D'] || input.keyDown[KEY_RIGHT])
|
||||
{
|
||||
moveDir = moveDir + Vector3(1.0f, 0.0f, 0.0f) * speedX;
|
||||
animatedSprite.flipX = true; // Flip sprite (flip animation on the X axis)
|
||||
}
|
||||
|
||||
if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
|
||||
speedY = speedX * MOVE_SPEED_SCALE;
|
||||
|
||||
if (input.keyDown['W'] || input.keyDown[KEY_UP])
|
||||
moveDir = moveDir + Vector3(0.0f, 1.0f, 0.0f) * speedY;
|
||||
if (input.keyDown['S'] || input.keyDown[KEY_DOWN])
|
||||
moveDir = moveDir + Vector3(0.0f, -1.0f, 0.0f) * speedY;
|
||||
|
||||
// Move
|
||||
if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
|
||||
character2DNode.Translate(moveDir * timeStep);
|
||||
|
||||
// Animate
|
||||
if (input.keyDown[KEY_SPACE])
|
||||
{
|
||||
if (animatedSprite.animation != "attack")
|
||||
animatedSprite.SetAnimation("attack", LM_FORCE_LOOPED);
|
||||
}
|
||||
else if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
|
||||
{
|
||||
if (animatedSprite.animation != "run")
|
||||
animatedSprite.SetAnimation("run");
|
||||
}
|
||||
else if (animatedSprite.animation != "idle")
|
||||
{
|
||||
animatedSprite.SetAnimation("idle");
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWoundedState(float timeStep)
|
||||
{
|
||||
RigidBody2D@ body = node.GetComponent("RigidBody2D");
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
|
||||
// Play "hit" animation in loop
|
||||
if (animatedSprite.animation != "hit")
|
||||
animatedSprite.SetAnimation("hit", LM_FORCE_LOOPED);
|
||||
|
||||
// Update timer
|
||||
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.linearVelocity = Vector2(0.0f, 0.0f);
|
||||
body.awake = false;
|
||||
body.awake = true;
|
||||
|
||||
// Remove particle emitter
|
||||
node.GetChild("Emitter", true).Remove();
|
||||
|
||||
// Update lifes UI and counter
|
||||
remainingLifes = remainingLifes - 1;
|
||||
Text@ lifeText = ui.root.GetChild("LifeText", true);
|
||||
lifeText.text = 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.position.x < 15.0f)
|
||||
node.position = Vector3(1.0f, 8.0f, 0.0f);
|
||||
else
|
||||
node.position = Vector3(18.8f, 9.2f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDeath()
|
||||
{
|
||||
RigidBody2D@ body = node.GetComponent("RigidBody2D");
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
|
||||
// Set state to 'killed'
|
||||
killed = true;
|
||||
|
||||
// Update UI elements
|
||||
Text@ instructions = ui.root.GetChild("Instructions", true);
|
||||
instructions.text = "!!! GAME OVER !!!";
|
||||
ui.root.GetChild("ExitButton", true).visible = true;
|
||||
ui.root.GetChild("PlayButton", true).visible = true;
|
||||
|
||||
// Show mouse cursor so that we can click
|
||||
input.mouseVisible = true;
|
||||
|
||||
// Put character outside of the scene and magnify him
|
||||
node.position = Vector3(-20.0f, 0.0f, 0.0f);
|
||||
node.SetScale(1.2f);
|
||||
|
||||
// Play death animation once
|
||||
if (animatedSprite.animation != "dead2")
|
||||
animatedSprite.SetAnimation("dead2");
|
||||
}
|
||||
}
|
477
bin/Data/Scripts/50_Urho2DPlatformer.as
Normal file
477
bin/Data/Scripts/50_Urho2DPlatformer.as
Normal file
@ -0,0 +1,477 @@
|
||||
// Urho2D platformer example.
|
||||
// This sample demonstrates:
|
||||
// - Creating an orthogonal 2D scene from tile map file
|
||||
// - Displaying the scene using the Renderer subsystem
|
||||
// - Handling keyboard to move a character and zoom 2D camera
|
||||
// - Generating physics shapes from the tmx file's objects
|
||||
// - Mixing physics and translations to move the character
|
||||
// - Using Box2D Contact listeners to handle the gameplay
|
||||
// - Displaying debug geometry for physics and tile map
|
||||
// Note that this sample uses some functions from Sample2D utility class.
|
||||
|
||||
#include "Scripts/Utilities/Sample.as"
|
||||
#include "Scripts/Utilities/2D/Sample2D.as"
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Set filename for load/save functions
|
||||
demoFilename = "Platformer2D";
|
||||
|
||||
// Execute the common startup for samples
|
||||
SampleStart();
|
||||
|
||||
// Create the scene content
|
||||
CreateScene();
|
||||
|
||||
// Create the UI content
|
||||
CreateUIContent("PLATFORMER 2D DEMO");
|
||||
|
||||
// Hook up to the frame update events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
void CreateScene()
|
||||
{
|
||||
scene_ = Scene();
|
||||
|
||||
// Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
|
||||
scene_.CreateComponent("Octree");
|
||||
scene_.CreateComponent("DebugRenderer");
|
||||
scene_.CreateComponent("PhysicsWorld2D");
|
||||
|
||||
// Create camera
|
||||
cameraNode = Node();
|
||||
Camera@ camera = cameraNode.CreateComponent("Camera");
|
||||
camera.orthographic = true;
|
||||
camera.orthoSize = graphics.height * PIXEL_SIZE;
|
||||
camera.zoom = 1.8f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.8) is set for full visibility at 1280x800 resolution)
|
||||
|
||||
// Setup the viewport for displaying the scene
|
||||
renderer.viewports[0] = Viewport(scene_, camera);
|
||||
renderer.defaultZone.fogColor = Color(0.2f, 0.2f, 0.2f); // Set background color for the scene
|
||||
|
||||
// Create tile map from tmx file
|
||||
Node@ tileMapNode = scene_.CreateChild("TileMap");
|
||||
TileMap2D@ tileMap = tileMapNode.CreateComponent("TileMap2D");
|
||||
tileMap.tmxFile = cache.GetResource("TmxFile2D", "Urho2D/Tilesets/Ortho.tmx");
|
||||
TileMapInfo2D@ info = tileMap.info;
|
||||
|
||||
// Create Spriter Imp character (from sample 33_SpriterAnimation)
|
||||
CreateCharacter(info, true, 0.8f, Vector3(1.0f, 8.0f, 0.0f), 0.2f);
|
||||
|
||||
// Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
|
||||
TileMapLayer2D@ tileMapLayer = tileMap.GetLayer(tileMap.numLayers - 1);
|
||||
CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
|
||||
|
||||
// Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
|
||||
PopulateMovingEntities(tileMap.GetLayer(tileMap.numLayers - 2));
|
||||
|
||||
// Instantiate coins to pick at each placeholder of "Coins" layer (in this sample, placeholders for coins are Rectangle objects)
|
||||
PopulateCoins(tileMap.GetLayer(tileMap.numLayers - 3));
|
||||
|
||||
// Instantiate triggers (for ropes, ladders, lava, slopes...) at each placeholder of "Triggers" layer (in this sample, placeholders for triggers are Rectangle objects)
|
||||
TileMapLayer2D@ triggersLayer = tileMap.GetLayer(tileMap.numLayers - 4);
|
||||
|
||||
// Instantiate triggers at each placeholder (Rectangle objects)
|
||||
PopulateTriggers(triggersLayer);
|
||||
|
||||
// Create background
|
||||
CreateBackgroundSprite(info, 3.5, "Textures/HeightMap.png", true);
|
||||
|
||||
// Check when scene is rendered
|
||||
SubscribeToEvent("EndRendering", "HandleSceneRendered");
|
||||
}
|
||||
|
||||
void HandleSceneRendered()
|
||||
{
|
||||
UnsubscribeFromEvent("EndRendering");
|
||||
// Save the scene so we can reload it later
|
||||
SaveScene(true);
|
||||
// Pause the scene as long as the UI is hiding it
|
||||
scene_.updateEnabled = false;
|
||||
}
|
||||
|
||||
void SubscribeToEvents()
|
||||
{
|
||||
// Subscribe HandleUpdate() function for processing update events
|
||||
SubscribeToEvent("Update", "HandleUpdate");
|
||||
|
||||
// Subscribe HandlePostUpdate() function for processing post update events
|
||||
SubscribeToEvent("PostUpdate", "HandlePostUpdate");
|
||||
|
||||
// Subscribe to PostRenderUpdate to draw debug geometry
|
||||
SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
|
||||
|
||||
// Subscribe to Box2D contact listeners
|
||||
SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin");
|
||||
SubscribeToEvent("PhysicsEndContact2D", "HandleCollisionEnd");
|
||||
|
||||
// Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
|
||||
UnsubscribeFromEvent("SceneUpdate");
|
||||
}
|
||||
|
||||
void HandleUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Zoom in/out
|
||||
if (cameraNode !is null)
|
||||
Zoom(cameraNode.GetComponent("Camera"));
|
||||
|
||||
// Toggle debug geometry with 'Z' key
|
||||
if (input.keyPress[KEY_Z]) drawDebug = !drawDebug;
|
||||
|
||||
// Check for loading / saving the scene
|
||||
if (input.keyPress[KEY_F5])
|
||||
{
|
||||
SaveScene(false);
|
||||
}
|
||||
if (input.keyPress[KEY_F7])
|
||||
ReloadScene(false);
|
||||
}
|
||||
|
||||
void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (character2DNode is null || cameraNode is null)
|
||||
return;
|
||||
cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10.0f); // Camera tracks character
|
||||
}
|
||||
|
||||
void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
if (drawDebug)
|
||||
{
|
||||
PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
|
||||
physicsWorld.DrawDebugGeometry();
|
||||
|
||||
Node@ tileMapNode = scene_.GetChild("TileMap", true);
|
||||
TileMap2D@ map = tileMapNode.GetComponent("TileMap2D");
|
||||
map.DrawDebugGeometry(scene_.GetComponent("DebugRenderer"), false);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node@ hitNode = eventData["NodeA"].GetPtr();
|
||||
if (hitNode.name == "Imp")
|
||||
hitNode = eventData["NodeB"].GetPtr();
|
||||
String nodeName = hitNode.name;
|
||||
Character2D@ character = cast<Character2D>(character2DNode.scriptObject);
|
||||
|
||||
// Handle ropes and ladders climbing
|
||||
if (nodeName == "Climb")
|
||||
{
|
||||
if (character.isClimbing) // If transition between rope and top of rope (as we are using split triggers)
|
||||
character.climb2 = true;
|
||||
else
|
||||
{
|
||||
character.isClimbing = true;
|
||||
RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
|
||||
body.gravityScale = 0.0f; // Override gravity so that the character doesn't fall
|
||||
// Clear forces so that the character stops (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
body.linearVelocity = Vector2(0.0f, 0.0f);
|
||||
body.awake = false;
|
||||
body.awake = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeName == "CanJump")
|
||||
character.aboveClimbable = true;
|
||||
|
||||
// Handle coins picking
|
||||
if (nodeName == "Coin")
|
||||
{
|
||||
hitNode.Remove();
|
||||
character.remainingCoins = character.remainingCoins - 1;
|
||||
if (character.remainingCoins == 0)
|
||||
{
|
||||
Text@ instructions = ui.root.GetChild("Instructions", true);
|
||||
instructions.text = "!!! Go to the Exit !!!";
|
||||
}
|
||||
Text@ coinsText = ui.root.GetChild("CoinsText", true);
|
||||
coinsText.text = character.remainingCoins; // Update coins UI counter
|
||||
PlaySound("Powerup.wav");
|
||||
}
|
||||
|
||||
// Handle interactions with enemies
|
||||
if (nodeName == "Enemy" || nodeName == "Orc")
|
||||
{
|
||||
AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
|
||||
float deltaX = character2DNode.position.x - hitNode.position.x;
|
||||
|
||||
// Orc killed if character is fighting in its direction when the contact occurs (flowers are not destroyable)
|
||||
if (nodeName == "Orc" && animatedSprite.animation == "attack" && (deltaX < 0 == animatedSprite.flipX))
|
||||
{
|
||||
cast<Mover>(hitNode.scriptObject).emitTime = 1;
|
||||
if (hitNode.GetChild("Emitter", true) is null)
|
||||
{
|
||||
hitNode.GetComponent("RigidBody2D").Remove(); // Remove Orc's body
|
||||
SpawnEffect(hitNode);
|
||||
PlaySound("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
// Player killed if not fighting in the direction of the Orc when the contact occurs, or when colliding with a flower
|
||||
else
|
||||
{
|
||||
if (character2DNode.GetChild("Emitter", true) is null)
|
||||
{
|
||||
character.wounded = true;
|
||||
if (nodeName == "Orc")
|
||||
cast<Mover>(hitNode.scriptObject).fightTimer = 1;
|
||||
SpawnEffect(character2DNode);
|
||||
PlaySound("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle exiting the level when all coins have been gathered
|
||||
if (nodeName == "Exit" && character.remainingCoins == 0)
|
||||
{
|
||||
// Update UI
|
||||
Text@ instructions = ui.root.GetChild("Instructions", true);
|
||||
instructions.text = "!!! WELL DONE !!!";
|
||||
instructions.position = IntVector2(0, 0);
|
||||
|
||||
// Put the character outside of the scene and magnify him
|
||||
character2DNode.position = Vector3(-20.0f, 0.0f, 0.0f);
|
||||
character2DNode.SetScale(1.2f);
|
||||
}
|
||||
|
||||
// Handle falling into lava
|
||||
if (nodeName == "Lava")
|
||||
{
|
||||
RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
|
||||
body.ApplyLinearImpulse(Vector2(0.0f, 1.0f) * MOVE_SPEED, body.massCenter, true); // Violently project character out of lava
|
||||
if (character2DNode.GetChild("Emitter", true) is null)
|
||||
{
|
||||
character.wounded = true;
|
||||
SpawnEffect(character2DNode);
|
||||
PlaySound("BigExplosion.wav");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle climbing a slope
|
||||
if (nodeName == "Slope")
|
||||
character.onSlope = true;
|
||||
}
|
||||
|
||||
void HandleCollisionEnd(StringHash eventType, VariantMap& eventData)
|
||||
{
|
||||
// Get colliding node
|
||||
Node@ hitNode = eventData["NodeA"].GetPtr();
|
||||
if (hitNode.name == "Imp")
|
||||
hitNode = eventData["NodeB"].GetPtr();
|
||||
String nodeName = hitNode.name;
|
||||
Character2D@ character = cast<Character2D>(character2DNode.scriptObject);
|
||||
|
||||
// Handle leaving a rope or ladder
|
||||
if (nodeName == "Climb")
|
||||
{
|
||||
if (character.climb2)
|
||||
character.climb2 = false;
|
||||
else
|
||||
{
|
||||
character.isClimbing = false;
|
||||
RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
|
||||
body.gravityScale = 1.0f; // Restore gravity
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeName == "CanJump")
|
||||
character.aboveClimbable = false;
|
||||
|
||||
// Handle leaving a slope
|
||||
if (nodeName == "Slope")
|
||||
{
|
||||
character.onSlope = false;
|
||||
// Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
|
||||
RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
|
||||
body.linearVelocity = Vector2(0.0f, 0.0f);
|
||||
body.awake = false;
|
||||
body.awake = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Character2D script object class
|
||||
class Character2D : ScriptObject
|
||||
{
|
||||
bool wounded = false;
|
||||
bool killed = false;
|
||||
float timer = 0.0f;
|
||||
int maxCoins = 0;
|
||||
int remainingCoins = 0;
|
||||
int remainingLifes = 3;
|
||||
bool isClimbing = false;
|
||||
bool climb2 = false; // Used only for ropes, as they are split into 2 shapes
|
||||
bool aboveClimbable = false;
|
||||
bool onSlope = false;
|
||||
|
||||
void Save(Serializer& serializer)
|
||||
{
|
||||
isClimbing = false; // Overwrite before auto-deserialization
|
||||
}
|
||||
|
||||
void Update(float timeStep)
|
||||
{
|
||||
if (character2DNode is null)
|
||||
return;
|
||||
|
||||
// Handle wounded/killed states
|
||||
if (killed)
|
||||
return;
|
||||
|
||||
if (wounded)
|
||||
{
|
||||
HandleWoundedState(timeStep);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set temporary variables
|
||||
RigidBody2D@ body = node.GetComponent("RigidBody2D");
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
bool onGround = false;
|
||||
bool jump = false;
|
||||
|
||||
// Collision detection (AABB query)
|
||||
Vector2 characterHalfSize = Vector2(0.16f, 0.16f);
|
||||
PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
|
||||
RigidBody2D@[]@ collidingBodies = physicsWorld.GetRigidBodies(Rect(node.worldPosition2D - characterHalfSize - Vector2(0.0f, 0.1f), node.worldPosition2D + characterHalfSize));
|
||||
|
||||
if (collidingBodies.length > 1 && !isClimbing)
|
||||
onGround = true;
|
||||
|
||||
// Set direction
|
||||
Vector2 moveDir = Vector2(0.0f, 0.0f); // Reset
|
||||
|
||||
if (input.keyDown['A'] || input.keyDown[KEY_LEFT])
|
||||
{
|
||||
moveDir = moveDir + Vector2(-1.0f, 0.0f);
|
||||
animatedSprite.flipX = false; // Flip sprite (reset to default play on the X axis);
|
||||
}
|
||||
|
||||
if (input.keyDown['D'] || input.keyDown[KEY_RIGHT])
|
||||
{
|
||||
moveDir = moveDir + Vector2(1.0f, 0.0f);
|
||||
animatedSprite.flipX = true; // Flip sprite (flip animation on the X axis)
|
||||
}
|
||||
|
||||
// Jump
|
||||
if ((onGround || aboveClimbable) && (input.keyPress['W'] || input.keyPress[KEY_UP]))
|
||||
jump = true;
|
||||
|
||||
// Climb
|
||||
if (isClimbing)
|
||||
{
|
||||
if (!aboveClimbable && (input.keyDown[KEY_UP] || input.keyDown[KEY_W]))
|
||||
moveDir = moveDir + Vector2(0.0f, 1.0f);
|
||||
|
||||
if (input.keyDown[KEY_DOWN] || input.keyDown[KEY_S])
|
||||
moveDir = moveDir + Vector2(0.0f, -1.0f);
|
||||
}
|
||||
|
||||
// Move
|
||||
if (!moveDir.Equals(Vector2(0.0f, 0.0f)) || 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.massCenter, true);
|
||||
}
|
||||
|
||||
// Animate
|
||||
if (input.keyDown[KEY_SPACE])
|
||||
{
|
||||
if (animatedSprite.animation != "attack")
|
||||
{
|
||||
animatedSprite.SetAnimation("attack", LM_FORCE_LOOPED);
|
||||
animatedSprite.speed = 1.5f;
|
||||
}
|
||||
}
|
||||
else if (!moveDir.Equals(Vector2(0.0f, 0.0f)))
|
||||
{
|
||||
if (animatedSprite.animation != "run")
|
||||
animatedSprite.SetAnimation("run");
|
||||
}
|
||||
else if (animatedSprite.animation != "idle")
|
||||
{
|
||||
animatedSprite.SetAnimation("idle");
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWoundedState(float timeStep)
|
||||
{
|
||||
RigidBody2D@ body = node.GetComponent("RigidBody2D");
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
|
||||
// Play "hit" animation in loop
|
||||
if (animatedSprite.animation != "hit")
|
||||
animatedSprite.SetAnimation("hit", LM_FORCE_LOOPED);
|
||||
|
||||
// Update timer
|
||||
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.linearVelocity = Vector2(0.0f, 0.0f);
|
||||
body.awake = false;
|
||||
body.awake = true;
|
||||
|
||||
// Remove particle emitter
|
||||
node.GetChild("Emitter", true).Remove();
|
||||
|
||||
// Update lifes UI and counter
|
||||
remainingLifes = remainingLifes - 1;
|
||||
Text@ lifeText = ui.root.GetChild("LifeText", true);
|
||||
lifeText.text = 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.position.x < 15.0f)
|
||||
node.position = Vector3(1.0f, 8.0f, 0.0f);
|
||||
else
|
||||
node.position = Vector3(18.8f, 9.2f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDeath()
|
||||
{
|
||||
RigidBody2D@ body = node.GetComponent("RigidBody2D");
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
|
||||
// Set state to 'killed'
|
||||
killed = true;
|
||||
|
||||
// Update UI elements
|
||||
Text@ instructions = ui.root.GetChild("Instructions", true);
|
||||
instructions.text = "!!! GAME OVER !!!";
|
||||
ui.root.GetChild("ExitButton", true).visible = true;
|
||||
ui.root.GetChild("PlayButton", true).visible = true;
|
||||
|
||||
// Show mouse cursor so that we can click
|
||||
input.mouseVisible = true;
|
||||
|
||||
// Put character outside of the scene and magnify him
|
||||
node.position = Vector3(-20.0f, 0.0f, 0.0f);
|
||||
node.SetScale(1.2f);
|
||||
|
||||
// Play death animation once
|
||||
if (animatedSprite.animation != "dead2")
|
||||
animatedSprite.SetAnimation("dead2");
|
||||
}
|
||||
}
|
134
bin/Data/Scripts/Utilities/2D/Mover.as
Normal file
134
bin/Data/Scripts/Utilities/2D/Mover.as
Normal file
@ -0,0 +1,134 @@
|
||||
// Mover script object class
|
||||
// - handles entity (enemy, platform...) translation along a path (set of Vector2 points)
|
||||
// - supports looping paths and animation flip
|
||||
// - default speed is 0.8 if 'Speed' property is not set in the tmx file objects
|
||||
|
||||
class Mover : ScriptObject
|
||||
{
|
||||
float speed = 0.8f;
|
||||
Array<Vector2> path;
|
||||
int currentPathID = 1;
|
||||
float emitTime = 0.0f;
|
||||
float fightTimer = 0.0f;
|
||||
float flip = 0.0f;
|
||||
uint bufferSize = 0;
|
||||
|
||||
void Load(Deserializer& deserializer)
|
||||
{
|
||||
bufferSize = deserializer.ReadUInt(); // Get buffer size
|
||||
SetPathAttr(deserializer.ReadVectorBuffer(bufferSize));
|
||||
}
|
||||
|
||||
void Save(Serializer& serializer)
|
||||
{
|
||||
serializer.WriteVectorBuffer(GetPathAttr(serializer));
|
||||
}
|
||||
|
||||
void SetPathAttr(VectorBuffer buffer)
|
||||
{
|
||||
if (buffer.size == 0)
|
||||
return;
|
||||
|
||||
while (!buffer.eof)
|
||||
path.Push(buffer.ReadVector2());
|
||||
}
|
||||
|
||||
VectorBuffer GetPathAttr(Serializer& serializer)
|
||||
{
|
||||
VectorBuffer buffer = VectorBuffer();
|
||||
|
||||
for (uint i=0; i < path.length; ++i)
|
||||
buffer.WriteVector2(path[i]);
|
||||
|
||||
bufferSize = buffer.size;
|
||||
serializer.WriteUInt(bufferSize);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void Update(float timeStep)
|
||||
{
|
||||
if (path.length < 2)
|
||||
return;
|
||||
|
||||
// Handle Orc states (idle/wounded/fighting)
|
||||
if (node.name == "Orc")
|
||||
{
|
||||
AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
|
||||
String anim = "run";
|
||||
|
||||
// Handle wounded state
|
||||
if (emitTime > 0.0f)
|
||||
{
|
||||
emitTime += timeStep;
|
||||
anim = "dead";
|
||||
|
||||
// Handle dead
|
||||
if (emitTime >= 3.0f)
|
||||
{
|
||||
node.Remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle fighting state
|
||||
if (fightTimer > 0.0f)
|
||||
{
|
||||
anim = "attack";
|
||||
flip = character2DNode.position.x - node.position.x;
|
||||
fightTimer += timeStep;
|
||||
if (fightTimer >= 3.0f)
|
||||
fightTimer = 0.0f; // Reset
|
||||
}
|
||||
// Flip Orc animation according to speed, or player position when fighting
|
||||
animatedSprite.flipX = flip >= 0.0f;
|
||||
}
|
||||
// Animate
|
||||
if (animatedSprite.animation != anim)
|
||||
animatedSprite.SetAnimation(anim);
|
||||
}
|
||||
|
||||
// Don't move if fighting or wounded
|
||||
if (fightTimer > 0.0f || emitTime > 0.0f)
|
||||
return;
|
||||
|
||||
// Set direction and move to target
|
||||
Vector2 dir = path[currentPathID] - node.position2D;
|
||||
Vector2 dirNormal = dir.Normalized();
|
||||
node.Translate(Vector3(dirNormal.x, dirNormal.y, 0.0f) * Abs(speed) * timeStep);
|
||||
flip = dir.x;
|
||||
|
||||
// Check for new target to reach
|
||||
if (Abs(dir.length) < 0.1f)
|
||||
{
|
||||
if (speed > 0.0f)
|
||||
{
|
||||
if (currentPathID + 1 < path.length)
|
||||
currentPathID = currentPathID + 1;
|
||||
else
|
||||
{
|
||||
// If loop, go to first waypoint, which equates to last one (and never reverse)
|
||||
if (path[currentPathID] == path[0])
|
||||
{
|
||||
currentPathID = 1;
|
||||
return;
|
||||
}
|
||||
// Reverse path if not looping
|
||||
currentPathID = currentPathID - 1;
|
||||
speed = -speed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentPathID - 1 >= 0)
|
||||
currentPathID = currentPathID - 1;
|
||||
else
|
||||
{
|
||||
currentPathID = 1;
|
||||
speed = -speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
569
bin/Data/Scripts/Utilities/2D/Sample2D.as
Normal file
569
bin/Data/Scripts/Utilities/2D/Sample2D.as
Normal file
@ -0,0 +1,569 @@
|
||||
// Convenient functions for Urho2D samples:
|
||||
// - Generate collision shapes from a tmx file objects
|
||||
// - Create Spriter Imp character
|
||||
// - Load Mover script object class from file
|
||||
// - Create enemies, coins and platforms to tile map placeholders
|
||||
// - Handle camera zoom using PageUp, PageDown and MouseWheel
|
||||
// - Create UI interface
|
||||
// - Create a particle emitter attached to a given node
|
||||
// - Play a non-looping sound effect
|
||||
// - Load/Save the scene
|
||||
// - Set global variables
|
||||
// - Set XML patch instructions for screen joystick
|
||||
|
||||
#include "Scripts/Utilities/2D/Mover.as"
|
||||
|
||||
float CAMERA_MIN_DIST = 0.1f;
|
||||
float CAMERA_MAX_DIST = 6.0f;
|
||||
|
||||
const float MOVE_SPEED = 23.0f; // Movement speed as world units per second
|
||||
const float MOVE_SPEED_X = 1.5f; // Movement speed as world units per second
|
||||
float MOVE_SPEED_SCALE = 1.0f; // Scaling factor based on tiles' aspect ratio
|
||||
|
||||
const int LIFES = 3;
|
||||
float zoom = 2.0f; // Speed is scaled according to zoom
|
||||
String demoFilename = "";
|
||||
Node@ character2DNode;
|
||||
|
||||
|
||||
void CreateCollisionShapesFromTMXObjects(Node@ tileMapNode, TileMapLayer2D@ tileMapLayer, TileMapInfo2D@ info)
|
||||
{
|
||||
// Create rigid body to the root node
|
||||
RigidBody2D@ body = tileMapNode.CreateComponent("RigidBody2D");
|
||||
body.bodyType = BT_STATIC;
|
||||
|
||||
// Generate physics collision shapes and rigid bodies from the tmx file's objects located in "Physics" layer
|
||||
for (uint i = 0; i < tileMapLayer.numObjects; ++i)
|
||||
{
|
||||
TileMapObject2D@ tileMapObject = tileMapLayer.GetObject(i); // Get physics objects
|
||||
|
||||
// Create collision shape from tmx object
|
||||
switch (tileMapObject.objectType)
|
||||
{
|
||||
case OT_RECTANGLE:
|
||||
CreateRectangleShape(tileMapNode, tileMapObject, tileMapObject.size, info);
|
||||
continue;
|
||||
case OT_ELLIPSE:
|
||||
CreateCircleShape(tileMapNode, tileMapObject, tileMapObject.size.x / 2, info); // Ellipse is built as a Circle shape as it doesn't exist in Box2D
|
||||
continue;
|
||||
case OT_POLYGON:
|
||||
CreatePolygonShape(tileMapNode, tileMapObject);
|
||||
continue;
|
||||
case OT_POLYLINE:
|
||||
CreatePolyLineShape(tileMapNode, tileMapObject);
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollisionBox2D@ CreateRectangleShape(Node@ node, TileMapObject2D@ object, Vector2 size, TileMapInfo2D@ info)
|
||||
{
|
||||
CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D");
|
||||
shape.size = size;
|
||||
if (info.orientation == O_ORTHOGONAL)
|
||||
shape.center = object.position + size / 2;
|
||||
else
|
||||
{
|
||||
shape.center = object.position + Vector2(info.tileWidth / 2, 0.0f);
|
||||
shape.angle = 45.0f; // If our tile map is isometric then shape is losange
|
||||
}
|
||||
shape.friction = 0.8f;
|
||||
if (object.HasProperty("Friction"))
|
||||
shape.friction = object.GetProperty("Friction").ToFloat();
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionCircle2D@ CreateCircleShape(Node@ node, TileMapObject2D@ object, float radius, TileMapInfo2D@ info)
|
||||
{
|
||||
CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D");
|
||||
Vector2 size = object.size;
|
||||
shape.radius = radius;
|
||||
if (info.orientation == O_ORTHOGONAL)
|
||||
shape.center = object.position + size / 2;
|
||||
else
|
||||
{
|
||||
shape.center = object.position + Vector2(info.tileWidth / 2, 0.0f);
|
||||
}
|
||||
shape.friction = 0.8f;
|
||||
if (object.HasProperty("Friction"))
|
||||
shape.friction = object.GetProperty("Friction").ToFloat();
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionPolygon2D@ CreatePolygonShape(Node@ node, TileMapObject2D@ object)
|
||||
{
|
||||
CollisionPolygon2D@ shape = node.CreateComponent("CollisionPolygon2D");
|
||||
uint numVertices = object.numPoints;
|
||||
shape.vertexCount = numVertices;
|
||||
for (uint i = 0; i < numVertices; ++i)
|
||||
shape.SetVertex(i, object.GetPoint(i));
|
||||
shape.friction = 0.8f;
|
||||
if (object.HasProperty("Friction"))
|
||||
shape.friction = object.GetProperty("Friction").ToFloat();
|
||||
return shape;
|
||||
}
|
||||
|
||||
CollisionChain2D@ CreatePolyLineShape(Node@ node, TileMapObject2D@ object)
|
||||
{
|
||||
CollisionChain2D@ shape = node.CreateComponent("CollisionChain2D");
|
||||
uint numVertices = object.numPoints;
|
||||
shape.vertexCount = numVertices;
|
||||
for (uint i = 0; i < numVertices; ++i)
|
||||
shape.SetVertex(i, object.GetPoint(i));
|
||||
shape.friction = 0.8f;
|
||||
if (object.HasProperty("Friction"))
|
||||
shape.friction = object.GetProperty("Friction").ToFloat();
|
||||
return shape;
|
||||
}
|
||||
|
||||
void CreateCharacter(TileMapInfo2D@ info, bool createObject, float friction, Vector3 position, float scale)
|
||||
{
|
||||
character2DNode = scene_.CreateChild("Imp");
|
||||
character2DNode.position = position;
|
||||
character2DNode.SetScale(scale);
|
||||
AnimatedSprite2D@ animatedSprite = character2DNode.CreateComponent("AnimatedSprite2D");
|
||||
|
||||
AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
|
||||
if (spriterAnimationSet is null)
|
||||
return;
|
||||
|
||||
animatedSprite.animationSet = spriterAnimationSet;
|
||||
animatedSprite.SetAnimation("idle"); // Get scml file and Play "idle" anim
|
||||
animatedSprite.layer = 3; // Put character over tile map (which is on layer 0) and over Orcs (which are on layer 2)
|
||||
RigidBody2D@ characterBody = character2DNode.CreateComponent("RigidBody2D");
|
||||
characterBody.bodyType = BT_DYNAMIC;
|
||||
characterBody.allowSleep = false;
|
||||
CollisionCircle2D@ shape = character2DNode.CreateComponent("CollisionCircle2D");
|
||||
shape.radius = 1.1f; // Set shape size
|
||||
shape.friction = friction; // Set friction
|
||||
shape.restitution = 0.1f; // Bounce
|
||||
if (createObject)
|
||||
character2DNode.CreateScriptObject(scriptFile, "Character2D"); // Create a ScriptObject to handle character behavior
|
||||
|
||||
// Scale character's speed on the Y axis according to tiles' aspect ratio (for isometric only)
|
||||
MOVE_SPEED_SCALE = info.tileHeight / info.tileWidth;
|
||||
}
|
||||
|
||||
Node@ CreateTrigger()
|
||||
{
|
||||
Node@ node = scene_.CreateChild(); // Clones will be renamed according to object type
|
||||
RigidBody2D@ body = node.CreateComponent("RigidBody2D");
|
||||
body.bodyType = BT_STATIC;
|
||||
CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D"); // Create box shape
|
||||
shape.trigger = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Node@ CreateEnemy()
|
||||
{
|
||||
Node@ node = scene_.CreateChild("Enemy");
|
||||
StaticSprite2D@ staticSprite = node.CreateComponent("StaticSprite2D");
|
||||
staticSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Aster.png");
|
||||
RigidBody2D@ body = node.CreateComponent("RigidBody2D");
|
||||
body.bodyType = BT_STATIC;
|
||||
CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape
|
||||
shape.radius = 0.25f; // Set radius
|
||||
return node;
|
||||
}
|
||||
|
||||
Node@ CreateOrc()
|
||||
{
|
||||
Node@ node = scene_.CreateChild("Orc");
|
||||
node.scale = character2DNode.scale; // Use same scale as player character
|
||||
AnimatedSprite2D@ animatedSprite = node.CreateComponent("AnimatedSprite2D");
|
||||
|
||||
AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/Orc/Orc.scml");
|
||||
if (spriterAnimationSet is null)
|
||||
return null;
|
||||
|
||||
animatedSprite.animationSet = spriterAnimationSet;
|
||||
animatedSprite.SetAnimation("run"); // Get scml file and Play "run" anim
|
||||
animatedSprite.layer = 2; // Make orc always visible
|
||||
RigidBody2D@ body = node.CreateComponent("RigidBody2D");
|
||||
CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape
|
||||
shape.radius = 1.3f; // Set shape size
|
||||
shape.trigger = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Node@ CreateCoin()
|
||||
{
|
||||
Node@ node = scene_.CreateChild("Coin");
|
||||
node.SetScale(0.5);
|
||||
AnimatedSprite2D@ animatedSprite = node.CreateComponent("AnimatedSprite2D");
|
||||
animatedSprite.layer = 4;
|
||||
AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml");
|
||||
if (spriterAnimationSet is null)
|
||||
return null;
|
||||
|
||||
animatedSprite.animationSet = spriterAnimationSet;
|
||||
animatedSprite.SetAnimation("idle"); // Get scml file and Play "idle" anim
|
||||
RigidBody2D@ body = node.CreateComponent("RigidBody2D");
|
||||
body.bodyType = BT_STATIC;
|
||||
CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape
|
||||
shape.radius = 0.32f; // Set radius
|
||||
shape.trigger = true;
|
||||
return node;
|
||||
}
|
||||
|
||||
Node@ CreateMovingPlatform()
|
||||
{
|
||||
Node@ node = scene_.CreateChild("MovingPlatform");
|
||||
node.scale = Vector3(3.0f, 1.0f, 0.0f);
|
||||
StaticSprite2D@ staticSprite = node.CreateComponent("StaticSprite2D");
|
||||
staticSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Box.png");
|
||||
RigidBody2D@ body = node.CreateComponent("RigidBody2D");
|
||||
body.bodyType = BT_STATIC;
|
||||
CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D"); // Create box shape
|
||||
shape.size = Vector2(0.32f, 0.32f); // Set box size
|
||||
shape.friction = 0.8f; // Set friction
|
||||
return node;
|
||||
}
|
||||
|
||||
void PopulateMovingEntities(TileMapLayer2D@ movingEntitiesLayer)
|
||||
{
|
||||
// Create enemy, Orc and moving platform nodes (will be cloned at each placeholder)
|
||||
Node@ enemyNode = CreateEnemy();
|
||||
Node@ orcNode = CreateOrc();
|
||||
Node@ platformNode = CreateMovingPlatform();
|
||||
|
||||
// Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points)
|
||||
for (uint i=0; i < movingEntitiesLayer.numObjects; ++i)
|
||||
{
|
||||
// Get placeholder object
|
||||
TileMapObject2D@ movingObject = movingEntitiesLayer.GetObject(i); // Get placeholder object
|
||||
if (movingObject.objectType == OT_POLYLINE)
|
||||
{
|
||||
// Clone the moving entity node and position it at placeholder point
|
||||
Node@ movingClone;
|
||||
Vector2 offset = Vector2(0.0f, 0.0f);
|
||||
if (movingObject.type == "Enemy")
|
||||
{
|
||||
movingClone = enemyNode.Clone();
|
||||
offset = Vector2(0.0f, -0.32f);
|
||||
}
|
||||
else if (movingObject.type == "Orc")
|
||||
movingClone = orcNode.Clone();
|
||||
else if (movingObject.type == "MovingPlatform")
|
||||
movingClone = platformNode.Clone();
|
||||
else
|
||||
continue;
|
||||
movingClone.position2D = movingObject.GetPoint(0) + offset;
|
||||
|
||||
// Create script object that handles entity translation along its path (load from file included)
|
||||
Mover@ mover = cast<Mover>(movingClone.CreateScriptObject(scriptFile, "Mover"));
|
||||
|
||||
// Set path from points
|
||||
mover.path = CreatePathFromPoints(movingObject, offset);
|
||||
|
||||
// Override default speed
|
||||
if (movingObject.HasProperty("Speed"))
|
||||
mover.speed = movingObject.GetProperty("Speed").ToFloat();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove nodes used for cloning purpose
|
||||
enemyNode.Remove();
|
||||
orcNode.Remove();
|
||||
platformNode.Remove();
|
||||
}
|
||||
|
||||
void PopulateCoins(TileMapLayer2D@ coinsLayer)
|
||||
{
|
||||
// Create coin (will be cloned at each placeholder)
|
||||
Node@ coinNode = CreateCoin();
|
||||
|
||||
// Instantiate coins to pick at each placeholder
|
||||
for (uint i=0; i < coinsLayer.numObjects; ++i)
|
||||
{
|
||||
TileMapObject2D@ coinObject = coinsLayer.GetObject(i); // Get placeholder object
|
||||
Node@ coinClone = coinNode.Clone();
|
||||
coinClone.position2D = coinObject.position + coinObject.size / 2 + Vector2(0.0f, 0.16f);
|
||||
}
|
||||
|
||||
// Init coins counters
|
||||
Character2D@ character = cast<Character2D>(character2DNode.scriptObject);
|
||||
character.remainingCoins = coinsLayer.numObjects;
|
||||
character.maxCoins = coinsLayer.numObjects;
|
||||
|
||||
// Remove node used for cloning purpose
|
||||
coinNode.Remove();
|
||||
}
|
||||
|
||||
void PopulateTriggers(TileMapLayer2D@ triggersLayer)
|
||||
{
|
||||
// Create trigger node (will be cloned at each placeholder)
|
||||
Node@ triggerNode = CreateTrigger();
|
||||
|
||||
// Instantiate triggers at each placeholder (Rectangle objects)
|
||||
for (uint i=0; i < triggersLayer.numObjects; ++i)
|
||||
{
|
||||
TileMapObject2D@ triggerObject = triggersLayer.GetObject(i); // Get placeholder object
|
||||
if (triggerObject.objectType == OT_RECTANGLE)
|
||||
{
|
||||
Node@ triggerClone = triggerNode.Clone();
|
||||
triggerClone.name = triggerObject.type;
|
||||
CollisionBox2D@ shape = triggerClone.GetComponent("CollisionBox2D");
|
||||
shape.size = triggerObject.size;
|
||||
triggerClone.position2D = triggerObject.position + triggerObject.size / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Zoom(Camera@ camera)
|
||||
{
|
||||
if (input.mouseMoveWheel != 0)
|
||||
camera.zoom = Clamp(camera.zoom + input.mouseMoveWheel * 0.1, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
|
||||
if (input.keyDown[KEY_PAGEUP])
|
||||
{
|
||||
zoom = Clamp(camera.zoom * 1.01f, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
camera.zoom = zoom;
|
||||
}
|
||||
|
||||
if (input.keyDown[KEY_PAGEDOWN])
|
||||
{
|
||||
zoom = Clamp(camera.zoom * 0.99f, CAMERA_MIN_DIST, CAMERA_MAX_DIST);
|
||||
camera.zoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2[] CreatePathFromPoints(TileMapObject2D@ object, Vector2 offset)
|
||||
{
|
||||
Array<Vector2> path;
|
||||
for (uint i=0; i < object.numPoints; ++i)
|
||||
path.Push(object.GetPoint(i) + offset);
|
||||
return path;
|
||||
}
|
||||
|
||||
void CreateUIContent(String demoTitle)
|
||||
{
|
||||
// Set the default UI style and font
|
||||
ui.root.defaultStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
|
||||
Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
|
||||
|
||||
// We create in-game UIs (coins and lifes) first so that they are hidden by the fullscreen UI (we could also temporary hide them using SetVisible)
|
||||
|
||||
// Create the UI for displaying the remaining coins
|
||||
BorderImage@ coinsUI = ui.root.CreateChild("BorderImage", "Coins");
|
||||
coinsUI.texture = cache.GetResource("Texture2D", "Urho2D/GoldIcon.png");
|
||||
coinsUI.SetSize(50, 50);
|
||||
coinsUI.imageRect = IntRect(0, 64, 60, 128);
|
||||
coinsUI.SetAlignment(HA_LEFT, VA_TOP);
|
||||
coinsUI.SetPosition(5, 5);
|
||||
Text@ coinsText = coinsUI.CreateChild("Text", "CoinsText");
|
||||
coinsText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
coinsText.SetFont(font, 24);
|
||||
coinsText.textEffect = TE_SHADOW;
|
||||
coinsText.text = cast<Character2D>(character2DNode.scriptObject).remainingCoins;
|
||||
|
||||
// Create the UI for displaying the remaining lifes
|
||||
BorderImage@ lifeUI = ui.root.CreateChild("BorderImage", "Life");
|
||||
lifeUI.texture = cache.GetResource("Texture2D", "Urho2D/imp/imp_all.png");
|
||||
lifeUI.SetSize(70, 80);
|
||||
lifeUI.SetAlignment(HA_RIGHT, VA_TOP);
|
||||
lifeUI.SetPosition(-5, 5);
|
||||
Text@ lifeText = lifeUI.CreateChild("Text", "LifeText");
|
||||
lifeText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
lifeText.SetFont(font, 24);
|
||||
lifeText.textEffect = TE_SHADOW;
|
||||
lifeText.text = LIFES;
|
||||
|
||||
// Create the fullscreen UI for start/end
|
||||
Window@ fullUI = ui.root.CreateChild("Window", "FullUI");
|
||||
fullUI.SetStyleAuto();
|
||||
fullUI.SetSize(ui.root.width, ui.root.height);
|
||||
fullUI.enabled = false; // Do not react to input, only the 'Exit' and 'Play' buttons will
|
||||
|
||||
// Create the title
|
||||
BorderImage@ title = fullUI.CreateChild("BorderImage", "Title");
|
||||
title.SetMinSize(fullUI.width, 50);
|
||||
title.texture = cache.GetResource("Texture2D", "Textures/HeightMap.png");
|
||||
title.SetFullImageRect();
|
||||
title.SetAlignment(HA_CENTER, VA_TOP);
|
||||
Text@ titleText = title.CreateChild("Text", "TitleText");
|
||||
titleText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
titleText.SetFont(font, 24);
|
||||
titleText.text = demoTitle;
|
||||
|
||||
// Create the image
|
||||
BorderImage@ spriteUI = fullUI.CreateChild("BorderImage", "Sprite");
|
||||
spriteUI.texture = cache.GetResource("Texture2D", "Urho2D/imp/imp_all.png");
|
||||
spriteUI.SetSize(238, 271);
|
||||
spriteUI.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
spriteUI.SetPosition(0, - ui.root.height / 4);
|
||||
|
||||
// Create the 'EXIT' button
|
||||
Button@ exitButton = ui.root.CreateChild("Button", "ExitButton");
|
||||
exitButton.SetStyleAuto();
|
||||
exitButton.focusMode = FM_RESETFOCUS;
|
||||
exitButton.SetSize(100, 50);
|
||||
exitButton.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
exitButton.SetPosition(-100, 0);
|
||||
Text@ exitText = exitButton.CreateChild("Text", "ExitText");
|
||||
exitText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
exitText.SetFont(font, 24);
|
||||
exitText.text = "EXIT";
|
||||
SubscribeToEvent(exitButton, "Released", "HandleExitButton");
|
||||
|
||||
// Create the 'PLAY' button
|
||||
Button@ playButton = ui.root.CreateChild("Button", "PlayButton");
|
||||
playButton.SetStyleAuto();
|
||||
playButton.focusMode = FM_RESETFOCUS;
|
||||
playButton.SetSize(100, 50);
|
||||
playButton.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
playButton.SetPosition(100, 0);
|
||||
Text@ playText = playButton.CreateChild("Text", "PlayText");
|
||||
playText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
playText.SetFont(font, 24);
|
||||
playText.text = "PLAY";
|
||||
SubscribeToEvent(playButton, "Released", "HandlePlayButton");
|
||||
|
||||
// Create the instructions
|
||||
Text@ instructionText = ui.root.CreateChild("Text", "Instructions");
|
||||
instructionText.SetFont(font, 15);
|
||||
instructionText.textAlignment = HA_CENTER; // Center rows in relation to each other
|
||||
instructionText.text = "Use WASD keys or Arrows to move\nPageUp/PageDown/MouseWheel to zoom\nF5/F7 to save/reload scene\n'Z' to toggle debug geometry\nSpace to fight";
|
||||
instructionText.SetAlignment(HA_CENTER, VA_CENTER);
|
||||
instructionText.SetPosition(0, ui.root.height / 4);
|
||||
|
||||
// Show mouse cursor
|
||||
input.mouseVisible = true;
|
||||
}
|
||||
|
||||
void HandleExitButton()
|
||||
{
|
||||
engine.Exit();
|
||||
}
|
||||
|
||||
void HandlePlayButton()
|
||||
{
|
||||
// Remove fullscreen UI and unfreeze the scene
|
||||
if (ui.root.GetChild("FullUI", true) !is null)
|
||||
{
|
||||
ui.root.GetChild("FullUI", true).Remove();
|
||||
scene_.updateEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reload scene
|
||||
ReloadScene(true);
|
||||
}
|
||||
|
||||
// Hide Instructions and Play/Exit buttons
|
||||
Text@ instructionText = ui.root.GetChild("Instructions", true);
|
||||
instructionText.text = "";
|
||||
Button@ exitButton = ui.root.GetChild("ExitButton", true);
|
||||
exitButton.visible = false;
|
||||
Button@ playButton = ui.root.GetChild("PlayButton", true);
|
||||
playButton.visible = false;
|
||||
|
||||
// Hide mouse cursor
|
||||
input.mouseVisible = false;
|
||||
}
|
||||
|
||||
void SaveScene(bool initial)
|
||||
{
|
||||
String filename = demoFilename;
|
||||
if (!initial)
|
||||
filename = demoFilename + "InGame";
|
||||
|
||||
File saveFile(fileSystem.programDir + "Data/Scenes/" + filename + ".xml", FILE_WRITE);
|
||||
scene_.SaveXML(saveFile);
|
||||
}
|
||||
|
||||
void ReloadScene(bool reInit)
|
||||
{
|
||||
String filename = demoFilename;
|
||||
if (!reInit)
|
||||
filename = demoFilename + "InGame";
|
||||
|
||||
File loadFile(fileSystem.programDir + "Data/Scenes/" + filename + ".xml", FILE_READ);
|
||||
scene_.LoadXML(loadFile);
|
||||
// After loading we have to reacquire the character2D scene node, as it has been recreated
|
||||
// Simply find by name as there's only one of them
|
||||
character2DNode = scene_.GetChild("Imp", true);
|
||||
if (character2DNode is null)
|
||||
return;
|
||||
|
||||
// Set what value to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false)
|
||||
Character2D@ character = cast<Character2D>(character2DNode.scriptObject);
|
||||
int lifes = character.remainingLifes;
|
||||
int coins = character.remainingCoins;
|
||||
if (reInit)
|
||||
{
|
||||
lifes = LIFES;
|
||||
coins = character.maxCoins;
|
||||
}
|
||||
|
||||
// Update lifes UI and variable
|
||||
Text@ lifeText = ui.root.GetChild("LifeText", true);
|
||||
lifeText.text = lifes;
|
||||
character.remainingLifes = lifes;
|
||||
|
||||
// Update coins UI and variable
|
||||
Text@ coinsText = ui.root.GetChild("CoinsText", true);
|
||||
coinsText.text = coins;
|
||||
character.remainingCoins = coins;
|
||||
}
|
||||
|
||||
void SpawnEffect(Node@ node)
|
||||
{
|
||||
Node@ particleNode = node.CreateChild("Emitter");
|
||||
particleNode.SetScale(0.5 / node.scale.x);
|
||||
ParticleEmitter2D@ particleEmitter = particleNode.CreateComponent("ParticleEmitter2D");
|
||||
particleEmitter.effect = cache.GetResource("ParticleEffect2D", "Urho2D/sun.pex");
|
||||
particleEmitter.layer = 2;
|
||||
}
|
||||
|
||||
void PlaySound(String soundName)
|
||||
{
|
||||
Node@ soundNode = scene_.CreateChild("Sound");
|
||||
SoundSource@ source = soundNode.CreateComponent("SoundSource");
|
||||
source.Play(cache.GetResource("Sound", "Sounds/" + soundName));
|
||||
}
|
||||
|
||||
void CreateBackgroundSprite(TileMapInfo2D@ info, float scale, String texture, bool animate)
|
||||
{
|
||||
Node@ node = scene_.CreateChild("Background");
|
||||
node.position = Vector3(info.mapWidth, info.mapHeight, 0) / 2;
|
||||
node.SetScale(scale);
|
||||
StaticSprite2D@ sprite = node.CreateComponent("StaticSprite2D");
|
||||
sprite.sprite = cache.GetResource("Sprite2D", texture);
|
||||
SetRandomSeed(time.systemTime); // Randomize from system clock
|
||||
sprite.color = Color(Random(0.0f, 1.0f), Random(0.0f, 1.0f), Random(0.0f, 1.0f), 1.0f);
|
||||
|
||||
// Create rotation animation
|
||||
if (animate)
|
||||
{
|
||||
ValueAnimation@ animation = ValueAnimation();
|
||||
animation.SetKeyFrame(0, Variant(Quaternion(0.0f, 0.0f, 0.0f)));
|
||||
animation.SetKeyFrame(1, Variant(Quaternion(0.0f, 0.0f, 180.0f)));
|
||||
animation.SetKeyFrame(2, Variant(Quaternion(0.0f, 0.0f, 0.0f)));
|
||||
node.SetAttributeAnimation("Rotation", animation, WM_LOOP, 0.05f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create XML patch instructions for screen joystick layout specific to this sample app
|
||||
String patchInstructions =
|
||||
"<patch>" +
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" +
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Fight</replace>" +
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" +
|
||||
" <element type=\"Text\">" +
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />" +
|
||||
" <attribute name=\"Text\" value=\"SPACE\" />" +
|
||||
" </element>" +
|
||||
" </add>" +
|
||||
" <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" +
|
||||
" <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Jump</replace>" +
|
||||
" <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" +
|
||||
" <element type=\"Text\">" +
|
||||
" <attribute name=\"Name\" value=\"KeyBinding\" />" +
|
||||
" <attribute name=\"Text\" value=\"UP\" />" +
|
||||
" </element>" +
|
||||
" </add>" +
|
||||
"</patch>";
|
BIN
bin/Data/Urho2D/GoldIcon.png
Normal file
BIN
bin/Data/Urho2D/GoldIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
7
bin/Data/Urho2D/Orc/License.txt
Normal file
7
bin/Data/Urho2D/Orc/License.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (C) 2013 by TreeFortress (https://github.com/treefortress/SpriterAS/)
|
||||
|
||||
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.
|
1536
bin/Data/Urho2D/Orc/Orc.scml
Normal file
1536
bin/Data/Urho2D/Orc/Orc.scml
Normal file
File diff suppressed because it is too large
Load Diff
13
bin/Data/Urho2D/Orc/Orc.xml
Normal file
13
bin/Data/Urho2D/Orc/Orc.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<TextureAtlas imagePath="Orc_Atlas.png">
|
||||
<SubTexture name="orc_0000_eyes" x="100" y="477" width="71" height="33"/>
|
||||
<SubTexture name="orc_0000_eyes_closed" x="0" y="481" width="71" height="9"/>
|
||||
<SubTexture name="orc_0001_head" x="0" y="291" width="109" height="126"/>
|
||||
<SubTexture name="orc_0001_head_angry" x="110" y="291" width="109" height="126"/>
|
||||
<SubTexture name="orc_0002_thing_!" x="220" y="291" width="69" height="146"/>
|
||||
<SubTexture name="orc_0003_hand1" x="179" y="205" width="86" height="85"/>
|
||||
<SubTexture name="orc_0004_body" x="0" y="0" width="178" height="290"/>
|
||||
<SubTexture name="orc_0005_hand2" x="179" y="0" width="96" height="204"/>
|
||||
<SubTexture name="orc_0006_foot1" x="0" y="418" width="99" height="62"/>
|
||||
<SubTexture name="orc_0007_foot2" x="100" y="418" width="82" height="58"/>
|
||||
</TextureAtlas>
|
BIN
bin/Data/Urho2D/Orc/Orc_Atlas.png
Normal file
BIN
bin/Data/Urho2D/Orc/Orc_Atlas.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
4
bin/Data/Urho2D/Orc/Orc_Atlas.xml
Normal file
4
bin/Data/Urho2D/Orc/Orc_Atlas.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<texture>
|
||||
<mipmap enable="false" />
|
||||
<quality low="0" />
|
||||
</texture>
|
15
bin/Data/Urho2D/Tilesets/Licenses.txt
Normal file
15
bin/Data/Urho2D/Tilesets/Licenses.txt
Normal file
@ -0,0 +1,15 @@
|
||||
=============================
|
||||
Isometric Dungeon (atrium):
|
||||
|
||||
Copyright Clint Bellanger
|
||||
https://github.com/clintbellanger/flare-game
|
||||
|
||||
All of Flare's art and data files are released under CC-BY-SA 3.0. Later versions are permitted.
|
||||
|
||||
=============================
|
||||
Orthographic tileset:
|
||||
|
||||
Copyright Kenney
|
||||
www.kenney.nl
|
||||
|
||||
Licensed under CC-0.
|
BIN
bin/Data/Urho2D/Tilesets/Ortho.png
Normal file
BIN
bin/Data/Urho2D/Tilesets/Ortho.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
338
bin/Data/Urho2D/Tilesets/Ortho.tmx
Normal file
338
bin/Data/Urho2D/Tilesets/Ortho.tmx
Normal file
File diff suppressed because one or more lines are too long
3
bin/Data/Urho2D/Tilesets/Ortho.xml
Normal file
3
bin/Data/Urho2D/Tilesets/Ortho.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<texture>
|
||||
<filter mode="nearest" />
|
||||
</texture>
|
138
bin/Data/Urho2D/Tilesets/atrium.tmx
Normal file
138
bin/Data/Urho2D/Tilesets/atrium.tmx
Normal file
File diff suppressed because one or more lines are too long
BIN
bin/Data/Urho2D/Tilesets/tiled_dungeon.png
Normal file
BIN
bin/Data/Urho2D/Tilesets/tiled_dungeon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 469 KiB |
3
bin/Data/Urho2D/Tilesets/tiled_dungeon.xml
Normal file
3
bin/Data/Urho2D/Tilesets/tiled_dungeon.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<texture>
|
||||
<filter mode="nearest" />
|
||||
</texture>
|
BIN
bin/Data/Urho2D/imp/imp_all.png
Normal file
BIN
bin/Data/Urho2D/imp/imp_all.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
Loading…
Reference in New Issue
Block a user