Urho3D/bin/Data/Scripts/Utilities/2D/Sample2D.as
2020-10-28 19:18:41 +08:00

571 lines
22 KiB
ActionScript

// 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, const 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, const 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, const 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, const 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(const 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(const 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);
sprite.layer = -99;
// 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>";