Просмотр исходного кода

Adds support for a Joystick control.

Chris Culy 13 лет назад
Родитель
Сommit
115e8b15b7

+ 4 - 1
gameplay/gameplay.vcxproj

@@ -48,6 +48,7 @@
     <ClCompile Include="src\gameplay-main-win32.cpp" />
     <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Joint.cpp" />
+    <ClCompile Include="src\Joystick.cpp" />
     <ClCompile Include="src\Label.cpp" />
     <ClCompile Include="src\Layout.cpp" />
     <ClCompile Include="src\Light.cpp" />
@@ -138,6 +139,7 @@
     <ClInclude Include="src\gameplay.h" />
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />
+    <ClInclude Include="src\Joystick.h" />
     <ClInclude Include="src\Keyboard.h" />
     <ClInclude Include="src\Label.h" />
     <ClInclude Include="src\Layout.h" />
@@ -233,6 +235,7 @@
     <None Include="src\Image.inl" />
     <None Include="src\MathUtil.inl" />
     <None Include="src\MathUtilNeon.inl" />
+    <None Include="src\Joystick.inl" />
     <None Include="src\Matrix.inl" />
     <None Include="src\MeshBatch.inl" />
     <None Include="src\Plane.inl" />
@@ -362,4 +365,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 10 - 4
gameplay/gameplay.vcxproj.filters

@@ -279,6 +279,9 @@
     <ClCompile Include="src\FlowLayout.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\Joystick.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -553,13 +556,13 @@
     </ClInclude>
     <ClInclude Include="src\FlowLayout.h">
       <Filter>src</Filter>
-    </ClInclude>
-    <ClInclude Include="src\ScrollLayout.h">
+    </ClInclude>
+    <ClInclude Include="src\Joystick.h">
       <Filter>src</Filter>
     </ClInclude>
     <ClInclude Include="src\MathUtil.h">
       <Filter>src</Filter>
-    </ClInclude>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\shaders\bumped-specular.vsh">
@@ -664,6 +667,9 @@
     <None Include="src\MathUtilNeon.inl">
       <Filter>src</Filter>
     </None>
+    <None Include="src\Joystick.inl">
+      <Filter>src</Filter>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\PhysicsFixedConstraint.inl">
@@ -709,4 +715,4 @@
       <Filter>src</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

+ 5 - 0
gameplay/src/Container.cpp

@@ -10,6 +10,7 @@
 #include "RadioButton.h"
 #include "Slider.h"
 #include "TextBox.h"
+#include "Joystick.h"
 #include "Game.h"
 
 namespace gameplay
@@ -135,6 +136,10 @@ void Container::addControls(Theme* theme, Properties* properties)
         {
             control = TextBox::create(controlStyle, controlSpace);
         }
+        else if (controlName == "JOYSTICK")
+        {
+            control = Joystick::create(controlStyle, controlSpace);
+        }
         else
         {
             GP_ERROR("Failed to create control; unrecognized control name '%s'.", controlName.c_str());

+ 4 - 1
gameplay/src/Control.cpp

@@ -961,8 +961,11 @@ Theme::ThemeImage* Control::getImage(const char* id, State state)
 {
     Theme::Style::Overlay* overlay = getOverlay(state);
     GP_ASSERT(overlay);
+    
     Theme::ImageList* imageList = overlay->getImageList();
-    GP_ASSERT(imageList);
+    if (!imageList)
+        return NULL;
+
     return imageList->getImage(id);
 }
 

+ 219 - 0
gameplay/src/Joystick.cpp

@@ -0,0 +1,219 @@
+#include "Base.h"
+#include "Joystick.h"
+
+#define INVALID_CONTACT_INDEX ((unsigned int)-1)
+
+namespace gameplay
+{
+
+Joystick::Joystick() : _contactIndex(INVALID_CONTACT_INDEX), _absolute(true)
+{
+}
+
+Joystick::Joystick(const Joystick& copy)
+{
+}
+
+Joystick::~Joystick()
+{
+}
+
+Joystick* Joystick::create(Theme::Style* style, Properties* properties)
+{
+    Joystick* joystick = new Joystick();
+    joystick->initialize(style, properties);
+    joystick->_consumeTouchEvents = false;
+
+    return joystick;
+}
+
+void Joystick::initialize(Theme::Style* style, Properties* properties)
+{
+    GP_ASSERT(properties);
+
+    Control::initialize(style, properties);
+
+    if (!properties->exists("radius"))
+    {
+        GP_ERROR("Failed to load joystick; required attribute 'radius' is missing.");
+        return;
+    }
+    _radius = properties->getFloat("radius");
+
+    Vector4 v;
+    if (properties->getVector4("region", &v))
+    {
+        _absolute = false;
+        _region = _bounds;
+        _bounds.x = v.x;
+        _bounds.y = v.y;
+        _bounds.width = v.z;
+        _bounds.height = v.w;
+    }
+}
+
+void Joystick::addListener(Control::Listener* listener, int eventFlags)
+{
+    if ((eventFlags & Listener::TEXT_CHANGED) == Listener::TEXT_CHANGED)
+    {
+        GP_ERROR("TEXT_CHANGED event is not applicable to this control.");
+    }
+
+    _consumeTouchEvents = true;
+
+    Control::addListener(listener, eventFlags);
+}
+
+bool Joystick::touchEvent(Touch::TouchEvent touchEvent, int x, int y, unsigned int contactIndex)
+{
+    switch (touchEvent)
+    {
+        case Touch::TOUCH_PRESS:
+        {
+            float dx = 0.0f;
+            float dy = 0.0f;
+
+            if (_absolute)
+            {
+                dx = x - _bounds.width * 0.5f;
+                dy = _bounds.height * 0.5f - y;
+            }
+            else
+            {
+                _region.x = x + _bounds.x - _region.width * 0.5f;
+                _region.y = y + _bounds.y - _region.height * 0.5f;
+            }
+
+            if ((dx >= -_radius && dx <= _radius) && (dy >= -_radius && dy <= _radius) && 
+                _contactIndex == INVALID_CONTACT_INDEX)
+            {
+                _contactIndex = contactIndex;
+                _displacement.set(0.0f, 0.0f);
+                
+                Vector2 value(0.0f, 0.0f);
+                if (_value != value)
+                {
+                    _value.set(value);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
+                }
+
+                _state = ACTIVE;
+            }
+        }
+        case Touch::TOUCH_MOVE:
+        {
+            if (_contactIndex == contactIndex)
+            {
+                float dx = x - ((!_absolute) ? _region.x - _bounds.x : 0.0f) - _region.width * 0.5f;
+                float dy = -(y - ((!_absolute) ? _region.y - _bounds.y : 0.0f) - _region.height * 0.5f);
+                if (((dx * dx) + (dy * dy)) <= (_radius * _radius))
+                {
+                    GP_ASSERT(_radius);
+                    Vector2 value(dx, dy);
+                    value.scale(1.0f / _radius);
+                    if (_value != value)
+                    {
+                        _value.set(value);
+                        notifyListeners(Control::Listener::VALUE_CHANGED);
+                    }
+                }
+                else
+                {
+                    Vector2 value(dx, dy);
+                    value.normalize();
+                    value.scale(_radius);
+                    value.normalize();
+                    if (_value != value)
+                    {
+                        _value.set(value);
+                        notifyListeners(Control::Listener::VALUE_CHANGED);
+                    }
+                }
+
+                _displacement.set(dx, dy);
+            }
+        }
+        break;
+        case Touch::TOUCH_RELEASE:
+        {
+            if (_contactIndex == contactIndex)
+            {
+                // Reset displacement and direction vectors.
+                _contactIndex = INVALID_CONTACT_INDEX;
+                _displacement.set(0.0f, 0.0f);
+                
+                Vector2 value(0.0f, 0.0f);
+                if (_value != value)
+                {
+                    _value.set(value);
+                    notifyListeners(Control::Listener::VALUE_CHANGED);
+                }
+
+                _state = NORMAL;
+            }
+        }
+        break;
+    }
+
+    return Control::touchEvent(touchEvent, x, y, contactIndex);
+}
+
+void Joystick::update(const Rectangle& clip, const Vector2& offset)
+{
+    Control::update(clip, offset);
+}
+
+void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+{
+    GP_ASSERT(spriteBatch);
+    spriteBatch->begin();
+
+    // If the joystick is not absolute, then only draw if it is active.
+    if (_absolute || (!_absolute && _state == ACTIVE))
+    {
+        if (_absolute)
+            _region = _bounds;
+
+        // Draw the outer image.
+        Theme::ThemeImage* outer = getImage("outer", _state);
+        if (outer)
+        {
+            // Get the uvs and color and draw.
+            const Theme::UVs& uvs = outer->getUVs();
+            const Vector4& color = outer->getColor();
+            spriteBatch->draw(_region.x, _region.y, _region.width, _region.height, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+        }
+
+        // Draw the inner image.
+        Theme::ThemeImage* inner = getImage("inner", _state);
+        if (inner)
+        {
+            Rectangle region = _region;
+
+            // Adjust position to reflect displacement.
+            if (((_displacement.x * _displacement.x) + (_displacement.y * _displacement.y)) <= (_radius * _radius))
+            {
+                region.x += _displacement.x;
+                region.y += -_displacement.y;
+            }
+            else
+            {
+                // Find the point on the joystick's circular bound where the
+                // vector intersects. This is the position of the inner image.
+                Vector2 delta = Vector2(_displacement.x, -_displacement.y);
+                delta.normalize();
+                delta.scale(_radius);
+                region.x += delta.x;
+                region.y += delta.y;
+            }
+        
+            // Get the uvs and color and draw.
+            const Theme::UVs& uvs = inner->getUVs();
+            const Vector4& color = inner->getColor();
+            spriteBatch->draw(region.x, region.y, _region.width, _region.height, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+        }
+    }
+    spriteBatch->end();
+}
+
+}

+ 146 - 0
gameplay/src/Joystick.h

@@ -0,0 +1,146 @@
+#ifndef JOYSTICK_H_
+#define JOYSTICK_H_
+
+#include "Control.h"
+
+namespace gameplay
+{
+
+class Joystick : public Control
+{
+    friend class Container;
+
+public:
+    
+    /**
+     * Add a listener to be notified of specific events affecting
+     * this control.  Event types can be OR'ed together.
+     * E.g. To listen to touch-press and touch-release events,
+     * pass <code>Control::Listener::TOUCH | Control::Listener::RELEASE</code>
+     * as the second parameter.
+     *
+     * @param listener The listener to add.
+     * @param eventFlags The events to listen for.
+     */
+    void addListener(Control::Listener* listener, int eventFlags);
+
+    /**
+     * Retrieves the value (2-dimensional direction) of the joystick.
+     * 
+     * @return The value of the joystick.
+     */
+    inline const Vector2& getValue() const;
+
+    /**
+     * Sets the region within which the joystick will be spontaneously created on a user's touch.
+     * 
+     * Note: This does not actually enable spontaneous joystick creation on touch input.
+     * To enable (or disable) absolute position explicitly, use #setAbsolute.
+     * 
+     * @param region The region to use.
+     */
+    inline void setRegion(const Rectangle& region);
+
+    /**
+     * Gets the region within which the joystick will be spontaneously created on a user's touch.
+     * 
+     * Note: just because the returned region is not empty does not mean that it is necessarily
+     * being used. If absolute positioning is not enabled, then it will be used (to check if
+     * absolute positioning is enabled, call #isAbsolute).
+     * 
+     * @return The region within which the joystick will be spontaneously created on a user's touch.
+     */
+    inline const Rectangle& getRegion() const;
+
+    /**
+     * Sets whether absolute positioning is enabled or not.
+     * 
+     * @param absolute Whether absolute positioning should be enabled or not.
+     */
+    inline void setAbsolute(bool absolute);
+
+    /**
+     * Retrieves whether absolute positioning is enabled or not.
+     * 
+     * @return <code>true</code> if absolute positioning is enabled; <code>false</code> otherwise.
+     */
+    inline bool isAbsolute() const;
+
+protected:
+    
+    /**
+     * Constructor.
+     */
+    Joystick();
+
+    /**
+     * Destructor.
+     */
+    virtual ~Joystick();
+
+    /**
+     * Create a joystick with a given style and properties.
+     *
+     * @param style The style to apply to this joystick.
+     * @param properties The properties to set on this joystick.
+     *
+     * @return The new joystick.
+     */
+    static Joystick* create(Theme::Style* style, Properties* properties);
+
+    /**
+     * Initialize this joystick.
+     */
+    virtual void initialize(Theme::Style* style, Properties* properties);
+
+    /**
+     * Touch callback on touch events.  Controls return true if they consume the touch event.
+     *
+     * @param evt The touch event that occurred.
+     * @param x The x position of the touch in pixels. Left edge is zero.
+     * @param y The y position of the touch in pixels. Top edge is zero.
+     * @param contactIndex The order of occurrence for multiple touch contacts starting at zero.
+     *
+     * @return Whether the touch event was consumed by the control.
+     *
+     * @see Touch::TouchEvent
+     */
+    bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
+
+    /**
+     * Called when a joystick's properties change. Updates this joystick's internal rendering properties.
+     *
+     * @param clip The clipping rectangle of this joystick's parent container.
+     * @param offset The scroll offset of this joystick's parent container.
+     */
+    void update(const Rectangle& clip, const Vector2& offset);
+
+    /**
+     * Draw the images associated with this control.
+     *
+     * @param spriteBatch The sprite batch containing this control's icons.
+     * @param clip The clipping rectangle of this control's parent container.
+     * @param offset Layout-computed positioning offset to add to the control's position.
+     */
+    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+
+private:
+
+    /**
+     * Copy constructor.
+     */
+    Joystick(const Joystick& copy);
+
+    float _radius;
+    unsigned int _contactIndex;
+    bool _absolute;
+    Vector2 _displacement;
+    Vector2 _value;
+    Rectangle _region;
+};
+
+}
+
+#include "Joystick.inl"
+
+#endif

+ 34 - 0
gameplay/src/Joystick.inl

@@ -0,0 +1,34 @@
+#include "Joystick.h"
+
+namespace gameplay
+{
+
+inline const Vector2& Joystick::getValue() const
+{
+    return _value;
+}
+
+inline void Joystick::setRegion(const Rectangle& region)
+{
+    if (_region.isEmpty())
+        _region = _bounds;
+
+    _bounds = region;
+}
+
+inline const Rectangle& Joystick::getRegion() const
+{
+    return _bounds;
+}
+
+inline void Joystick::setAbsolute(bool absolute)
+{
+    _absolute = absolute;
+}
+
+inline bool Joystick::isAbsolute() const
+{
+    return _absolute;
+}
+
+}

+ 1 - 0
gameplay/src/gameplay.h

@@ -87,4 +87,5 @@
 #include "Layout.h"
 #include "AbsoluteLayout.h"
 #include "VerticalLayout.h"
+#include "Joystick.h"