Merge pull request #2176 from thesquib/2ddemos

Updated 2D samples from pull request #641
This commit is contained in:
Eugene Kozlov 2017-12-02 20:33:18 +03:00 committed by GitHub
commit 54ed4c917f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 7360 additions and 0 deletions

View 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 ()

View 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");
}

View 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_;
};

View 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)

View 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_;
};

View 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 ()

View 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");
}

View 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_;
};

View 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);
}

View 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_;
};

View 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_;
}
}
}
}

View 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_;
};

View 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);
}
}

View 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>";
}
};

View 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

View 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

View 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

View 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

View 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");
}
}

View 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");
}
}

View 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;
}
}
}
}
}

View 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>";

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View 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

File diff suppressed because it is too large Load Diff

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -0,0 +1,4 @@
<texture>
<mipmap enable="false" />
<quality low="0" />
</texture>

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
<texture>
<filter mode="nearest" />
</texture>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

View File

@ -0,0 +1,3 @@
<texture>
<filter mode="nearest" />
</texture>

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB