Anchor behavior for native games (#275)

* Add AnchorBehavior

* Improve the AnchorBehavior

* Add an icon to the AnchorBehavior

* Code cleaning in AnchorBehavior

* Add more icons for the AnchorBehavior
This commit is contained in:
Victor Levasseur
2016-07-24 15:45:09 +02:00
committed by Florian Rival
parent d12924daaf
commit d7c897b488
9 changed files with 494 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

View File

@@ -0,0 +1,315 @@
/**
GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#include <memory>
#include <iostream>
#include <set>
#include "AnchorBehavior.h"
#include "GDCore/Tools/Localization.h"
#include "GDCpp/Extensions/Builtin/MathematicalTools.h"
#include "GDCpp/Runtime/Project/Layout.h"
#include "GDCpp/Runtime/Serialization/SerializerElement.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCpp/Runtime/RuntimeObject.h"
#include "GDCpp/Runtime/CommonTools.h"
#include <SFML/Window.hpp>
#include "GDCore/CommonTools.h"
#include <iostream>
#include <cmath>
#include <algorithm>
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
AnchorBehavior::AnchorBehavior() :
m_leftEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_rightEdgeAnchor(ANCHOR_HORIZONTAL_NONE),
m_topEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_bottomEdgeAnchor(ANCHOR_VERTICAL_NONE),
m_invalidDistances(true),
m_leftEdgeDistance(0.f),
m_rightEdgeDistance(0.f),
m_topEdgeDistance(0.f),
m_bottomEdgeDistance(0.f),
m_oldWindowSize(-1, -1)
{
}
void AnchorBehavior::OnActivate()
{
m_invalidDistances = true;
}
namespace
{
sf::Vector2f mapFloatPixelToCoords(const sf::Vector2f& point, const sf::RenderTarget & target, const sf::View& view)
{
// First, convert from viewport coordinates to homogeneous coordinates
sf::Vector2f normalized;
sf::IntRect viewport = target.getViewport(view);
normalized.x = -1.f + 2.f * (point.x - static_cast<float>(viewport.left)) / static_cast<float>(viewport.width);
normalized.y = 1.f - 2.f * (point.y - static_cast<float>(viewport.top)) / static_cast<float>(viewport.height);
// Then transform by the inverse of the view matrix
return view.getInverseTransform().transformPoint(normalized);
}
sf::Vector2f mapCoordsToFloatPixel(const sf::Vector2f & point, const sf::RenderTarget & target, const sf::View & view)
{
//Note: almost the same as RenderTarget::mapCoordsToPixel except that the result is sf::Vector2f
//First, transform the point by the view matrix
sf::Vector2f normalized = view.getTransform().transformPoint(point);
//Then convert to viewport coordinates
sf::Vector2f pixel;
sf::IntRect viewport = target.getViewport(view);
pixel.x = ( normalized.x + 1.f) / 2.f * static_cast<float>(viewport.width) + static_cast<float>(viewport.left);
pixel.y = (-normalized.y + 1.f) / 2.f * static_cast<float>(viewport.height) + static_cast<float>(viewport.top);
return pixel;
}
}
void AnchorBehavior::DoStepPreEvents(RuntimeScene & scene)
{
}
void AnchorBehavior::DoStepPostEvents(RuntimeScene & scene)
{
sf::Vector2u windowSize = scene.renderWindow->getSize();
const RuntimeLayer & layer = scene.GetRuntimeLayer(object->GetLayer());
const RuntimeCamera & firstCamera = layer.GetCamera(0);
if(m_invalidDistances)
{
//Calculate the distances from the window's bounds.
sf::Vector2f topLeftPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX(), object->GetDrawableY()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
sf::Vector2f bottomRightPixel = mapCoordsToFloatPixel(
sf::Vector2f(object->GetDrawableX() + object->GetWidth(), object->GetDrawableY() + object->GetHeight()),
*(scene.renderWindow),
firstCamera.GetSFMLView());
//Left edge
if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_leftEdgeDistance = topLeftPixel.x;
else if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_leftEdgeDistance = static_cast<float>(windowSize.x) - topLeftPixel.x;
else if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_leftEdgeDistance = topLeftPixel.x / windowSize.x;
//Right edge
if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
m_leftEdgeDistance = bottomRightPixel.x;
else if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
m_rightEdgeDistance = static_cast<float>(windowSize.x) - bottomRightPixel.x;
else if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
m_rightEdgeDistance = bottomRightPixel.x / windowSize.x;
//Top edge
if(m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_topEdgeDistance = topLeftPixel.y;
else if(m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_topEdgeDistance = static_cast<float>(windowSize.y) - topLeftPixel.y;
else if(m_topEdgeAnchor = ANCHOR_VERTICAL_PROPORTIONAL)
m_topEdgeDistance = topLeftPixel.y / static_cast<float>(windowSize.y);
//Bottom edge
if(m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
m_bottomEdgeDistance = bottomRightPixel.y;
else if(m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
m_bottomEdgeDistance = static_cast<float>(windowSize.y) - bottomRightPixel.y;
else if(m_bottomEdgeAnchor = ANCHOR_VERTICAL_PROPORTIONAL)
m_bottomEdgeDistance = bottomRightPixel.y / static_cast<float>(windowSize.y);
m_invalidDistances = false;
}
else
{
//Move and resize the object if needed
sf::Vector2f topLeftPixel;
sf::Vector2f bottomRightPixel;
//Left edge
if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
topLeftPixel.x = m_leftEdgeDistance;
else if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
topLeftPixel.x = static_cast<float>(windowSize.x) - m_leftEdgeDistance;
else if(m_leftEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
topLeftPixel.x = m_leftEdgeDistance * static_cast<float>(windowSize.x);
//Top edge
if(m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
topLeftPixel.y = m_topEdgeDistance;
else if(m_topEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
topLeftPixel.y = static_cast<float>(windowSize.y) - m_topEdgeDistance;
else if(m_topEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
topLeftPixel.y = m_topEdgeDistance * static_cast<float>(windowSize.y);
//Right edge
if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_LEFT)
bottomRightPixel.x = m_rightEdgeDistance;
else if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_WINDOW_RIGHT)
bottomRightPixel.x = static_cast<float>(windowSize.x) - m_rightEdgeDistance;
else if(m_rightEdgeAnchor == ANCHOR_HORIZONTAL_PROPORTIONAL)
bottomRightPixel.x = m_rightEdgeDistance * static_cast<float>(windowSize.x);
//Bottom edge
if(m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_TOP)
bottomRightPixel.y = m_bottomEdgeDistance;
else if(m_bottomEdgeAnchor == ANCHOR_VERTICAL_WINDOW_BOTTOM)
bottomRightPixel.y = static_cast<float>(windowSize.y) - m_bottomEdgeDistance;
else if(m_bottomEdgeAnchor == ANCHOR_VERTICAL_PROPORTIONAL)
bottomRightPixel.y = m_bottomEdgeDistance * static_cast<float>(windowSize.y);
sf::Vector2f topLeftCoord = mapFloatPixelToCoords(topLeftPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
sf::Vector2f bottomRightCoord = mapFloatPixelToCoords(bottomRightPixel, (*scene.renderWindow), firstCamera.GetSFMLView());
//Move and resize the object accordingly to the anchors
if(m_rightEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetWidth(bottomRightCoord.x - topLeftCoord.x);
if(m_bottomEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetHeight(bottomRightCoord.y - topLeftCoord.y);
if(m_leftEdgeAnchor != ANCHOR_HORIZONTAL_NONE)
object->SetX(topLeftCoord.x + object->GetX() - object->GetDrawableX());
if(m_topEdgeAnchor != ANCHOR_VERTICAL_NONE)
object->SetY(topLeftCoord.y + object->GetY() - object->GetDrawableY());
}
}
void AnchorBehavior::UnserializeFrom(const gd::SerializerElement & element)
{
m_leftEdgeAnchor = static_cast<HorizontalAnchor>(element.GetIntAttribute("leftEdgeAnchor"));
m_rightEdgeAnchor = static_cast<HorizontalAnchor>(element.GetIntAttribute("rightEdgeAnchor"));
m_topEdgeAnchor = static_cast<VerticalAnchor>(element.GetIntAttribute("topEdgeAnchor"));
m_bottomEdgeAnchor = static_cast<VerticalAnchor>(element.GetIntAttribute("bottomEdgeAnchor"));
}
#if defined(GD_IDE_ONLY)
void AnchorBehavior::SerializeTo(gd::SerializerElement & element) const
{
element.SetAttribute("leftEdgeAnchor", static_cast<int>(m_leftEdgeAnchor));
element.SetAttribute("rightEdgeAnchor", static_cast<int>(m_rightEdgeAnchor));
element.SetAttribute("topEdgeAnchor", static_cast<int>(m_topEdgeAnchor));
element.SetAttribute("bottomEdgeAnchor", static_cast<int>(m_bottomEdgeAnchor));
}
namespace
{
gd::String GetAnchorAsString(AnchorBehavior::HorizontalAnchor anchor)
{
if(anchor == AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_LEFT)
return _("Window left");
else if(anchor == AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_RIGHT)
return _("Window right");
else if(anchor == AnchorBehavior::ANCHOR_HORIZONTAL_PROPORTIONAL)
return _("Proportional");
else
return _("No anchor");
}
gd::String GetAnchorAsString(AnchorBehavior::VerticalAnchor anchor)
{
if(anchor == AnchorBehavior::ANCHOR_VERTICAL_WINDOW_TOP)
return _("Window top");
else if(anchor == AnchorBehavior::ANCHOR_VERTICAL_WINDOW_BOTTOM)
return _("Window bottom");
else if(anchor == AnchorBehavior::ANCHOR_VERTICAL_PROPORTIONAL)
return _("Proportional");
else
return _("No anchor");
}
}
std::map<gd::String, gd::PropertyDescriptor> AnchorBehavior::GetProperties(gd::Project & project) const
{
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Left edge anchor")]
.SetValue(GetAnchorAsString(m_leftEdgeAnchor))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"));
properties[_("Right edge anchor")]
.SetValue(GetAnchorAsString(m_rightEdgeAnchor))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window left"))
.AddExtraInfo(_("Window right"))
.AddExtraInfo(_("Proportional"));
properties[_("Top edge anchor")]
.SetValue(GetAnchorAsString(m_topEdgeAnchor))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"));
properties[_("Bottom edge anchor")]
.SetValue(GetAnchorAsString(m_bottomEdgeAnchor))
.SetType("Choice")
.AddExtraInfo(_("No anchor"))
.AddExtraInfo(_("Window top"))
.AddExtraInfo(_("Window bottom"))
.AddExtraInfo(_("Proportional"));
return properties;
}
namespace
{
AnchorBehavior::HorizontalAnchor GetHorizontalAnchorFromString(const gd::String & value)
{
if(value == _("Window left"))
return AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_LEFT;
else if(value == _("Window right"))
return AnchorBehavior::ANCHOR_HORIZONTAL_WINDOW_RIGHT;
else if(value == _("Proportional"))
return AnchorBehavior::ANCHOR_HORIZONTAL_PROPORTIONAL;
else
return AnchorBehavior::ANCHOR_HORIZONTAL_NONE;
}
AnchorBehavior::VerticalAnchor GetVerticalAnchorFromString(const gd::String & value)
{
if(value == _("Window top"))
return AnchorBehavior::ANCHOR_VERTICAL_WINDOW_TOP;
else if(value == _("Window bottom"))
return AnchorBehavior::ANCHOR_VERTICAL_WINDOW_BOTTOM;
else if(value == _("Proportional"))
return AnchorBehavior::ANCHOR_VERTICAL_PROPORTIONAL;
else
return AnchorBehavior::ANCHOR_VERTICAL_NONE;
}
}
bool AnchorBehavior::UpdateProperty(const gd::String & name, const gd::String & value, gd::Project & project)
{
if ( name == _("Left edge anchor") )
m_leftEdgeAnchor = GetHorizontalAnchorFromString(value);
else if ( name == _("Right edge anchor") )
m_rightEdgeAnchor = GetHorizontalAnchorFromString(value);
else if ( name == _("Top edge anchor") )
m_topEdgeAnchor = GetVerticalAnchorFromString(value);
else if ( name == _("Bottom edge anchor") )
m_bottomEdgeAnchor = GetVerticalAnchorFromString(value);
else
return false;
return true;
}
#endif

View File

@@ -0,0 +1,84 @@
/**
GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#ifndef ANCHORBEHAVIOR_H
#define ANCHORBEHAVIOR_H
#include "GDCpp/Runtime/Project/Behavior.h"
#include "GDCpp/Runtime/Project/Object.h"
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/View.hpp>
#include <SFML/System/Vector2.hpp>
#include <vector>
namespace gd { class Layout; }
class RuntimeScene;
namespace gd { class SerializerElement; }
class RuntimeScenePlatformData;
/**
* \brief Allow to anchor objects to the window's bounds.
*/
class GD_EXTENSION_API AnchorBehavior : public Behavior
{
public:
enum HorizontalAnchor
{
ANCHOR_HORIZONTAL_NONE = 0,
ANCHOR_HORIZONTAL_WINDOW_LEFT = 1,
ANCHOR_HORIZONTAL_WINDOW_RIGHT = 2,
ANCHOR_HORIZONTAL_PROPORTIONAL = 3
};
enum VerticalAnchor
{
ANCHOR_VERTICAL_NONE = 0,
ANCHOR_VERTICAL_WINDOW_TOP = 1,
ANCHOR_VERTICAL_WINDOW_BOTTOM = 2,
ANCHOR_VERTICAL_PROPORTIONAL = 3
};
AnchorBehavior();
virtual ~AnchorBehavior() {};
virtual Behavior* Clone() const { return new AnchorBehavior(*this); }
/**
* \brief Unserialize the behavior
*/
virtual void UnserializeFrom(const gd::SerializerElement & element);
#if defined(GD_IDE_ONLY)
/**
* \brief Serialize the behavior
*/
virtual void SerializeTo(gd::SerializerElement & element) const;
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(gd::Project & project) const;
virtual bool UpdateProperty(const gd::String & name, const gd::String & value, gd::Project & project);
#endif
virtual void OnActivate();
private:
virtual void DoStepPreEvents(RuntimeScene & scene) override;
virtual void DoStepPostEvents(RuntimeScene & scene) override;
HorizontalAnchor m_leftEdgeAnchor;
HorizontalAnchor m_rightEdgeAnchor;
VerticalAnchor m_topEdgeAnchor;
VerticalAnchor m_bottomEdgeAnchor;
bool m_invalidDistances;
//Distances (in window's units) from the XXX edge of the object to side of the window the edge is anchored on.
//Note: If the edge anchor is set to PROPORTIONAL, then it contains the ratio of the distance to the window size.
float m_leftEdgeDistance;
float m_rightEdgeDistance;
float m_topEdgeDistance;
float m_bottomEdgeDistance;
sf::Vector2u m_oldWindowSize;
};
#endif // ANCHORBEHAVIOR_H

View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0015 NEW)
project(AnchorBehavior)
gd_add_extension_includes()
#Defines
###
gd_add_extension_definitions(AnchorBehavior)
#The targets
###
include_directories(.)
file(GLOB source_files *)
gd_add_extension_target(AnchorBehavior "${source_files}")
gdcpp_add_runtime_extension_target(AnchorBehavior_Runtime "${source_files}")
#Linker files for the IDE extension
###
gd_extension_link_libraries(AnchorBehavior)
#Linker files for the GD C++ Runtime extension
###
gdcpp_runtime_extension_link_libraries(AnchorBehavior_Runtime)

View File

@@ -0,0 +1,68 @@
/**
GDevelop - Anchor Behavior Extension
Copyright (c) 2016 Victor Levasseur (victorlevasseur52@gmail.com)
This project is released under the MIT License.
*/
#include "GDCpp/Extensions/ExtensionBase.h"
#include "GDCpp/Runtime/Project/BehaviorsSharedData.h"
#include "GDCore/Tools/Version.h"
#include "AnchorBehavior.h"
void DeclareAnchorBehaviorExtension(gd::PlatformExtension & extension)
{
extension.SetExtensionInformation("AnchorBehavior",
_("Anchor"),
_("Anchor objects to the window's bounds."),
"Victor Levasseur",
"Open source (MIT License)");
gd::BehaviorMetadata & aut = extension.AddBehavior("AnchorBehavior",
_("Anchor"),
"Anchor",
_("Behavior that anchors objects to the window's bounds."),
"",
"CppPlatform/Extensions/AnchorIcon.png",
"AnchorBehavior",
std::shared_ptr<gd::Behavior>(new AnchorBehavior),
std::shared_ptr<gd::BehaviorsSharedData>(new gd::BehaviorsSharedData));
#if defined(GD_IDE_ONLY)
aut.SetIncludeFile("AnchorBehavior/AnchorBehavior.h");
#endif
}
/**
* \brief This class declares information about the extension.
*/
class AnchorBehaviorCppExtension : public ExtensionBase
{
public:
/**
* Constructor of an extension declares everything the extension contains: objects, actions, conditions and expressions.
*/
AnchorBehaviorCppExtension()
{
DeclareAnchorBehaviorExtension(*this);
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};
};
#if defined(ANDROID)
extern "C" ExtensionBase * CreateGDCppAnchorBehaviorExtension() {
return new AnchorBehaviorCppExtension;
}
#elif !defined(EMSCRIPTEN)
/**
* Used by GDevelop to create the extension class
* -- Do not need to be modified. --
*/
extern "C" ExtensionBase * GD_EXTENSION_API CreateGDExtension() {
return new AnchorBehaviorCppExtension;
}
#endif

View File

@@ -10,6 +10,7 @@ include(CMakeUtils.txt) #Functions to factor common tasks done in CMakeLists.txt
#Add all the CMakeLists:
ADD_SUBDIRECTORY(AdMobObject)
IF (NOT EMSCRIPTEN) #Only add some extensions when compiling with emscripten.
ADD_SUBDIRECTORY(AnchorBehavior)
ADD_SUBDIRECTORY(AdvancedXML)
ADD_SUBDIRECTORY(AES)
ADD_SUBDIRECTORY(Box3DObject)

View File

@@ -139,7 +139,7 @@ gd::String CodeCompilerCall::GetFullCall() const
}
else //Generate argument for linking files
{
if(!compilationForRuntime)
if(!compilationForRuntime)
args.push_back("-Wl,-rpath," + baseDir);
args.push_back("-shared");
if ( !inputFile.empty() ) args.push_back("\""+inputFile+"\"");