RectTransform position

I’m trying to workout clickable area’s in a transform(Panel)

What I would like is 8 boxes contained within the Panel, but I’m having problems with the coordinates system.

the panel is 292 pixels in width/height and anchored at the bottom left, with a pivot to the bottom left also see screenshot

I’ve workout that the value should be around 41.1625F for a third of 292 pixels in screen system, but when I place the Right line, it’s way to large.

public class TestComponent : MonoBehaviour
{
RectTransform mTransform;

Rect mArea;
void Awake()
{
  mTransform = (RectTransform)this.transform;
}

    // Use this for initialization
    void Start()
{
  Single mTop = mTransform.rect.max.y;
  Single mRight = mTransform.rect.max.x;
  Single mBottom = mTransform.rect.min.y;
  Single mLeft = mTransform.rect.min.x;
  Single mWidth = mTransform.rect.width;
  Single mHeight = mTransform.rect.height;

  Vector2 mSize = new Vector2((mWidth / 3) * mTransform.localScale.x  , mHeight / 3);

  mArea = new Rect(mLeft, mBottom, 41.1625F, mHeight / 3);

  Debug.Log("Original: " + mTransform.rect.ToString());

  Debug.Log("Area: " + mArea.ToString());

  Debug.Log("mSize: " + mSize.ToString());
    }
 
    // Update is called once per frame
    void Update()
{

  // Top
  Debug.DrawLine(new Vector3(mArea.min.x, mArea.max.y, 0), new Vector3(mArea.max.x, mArea.max.y, 0), Color.red, 5F);

  // Right
  Debug.DrawLine(new Vector3(mArea.max.x, mArea.max.y, 0), new Vector3(mArea.max.x, mArea.min.y, 0), Color.cyan, 5F);

  // Bottom
  Debug.DrawLine(new Vector3(mArea.min.x, mArea.min.y, 0), new Vector3(mArea.max.x, mArea.min.y, 0), Color.blue, 5F);

  // Left
  Debug.DrawLine(new Vector3(mArea.min.x, mArea.max.y, 0), new Vector3(mArea.min.x, mArea.min.y, 0), Color.green, 5F);

    }
}

can anybody help with this, I’m assuming it’s a conversion issue.

2901086--213575--transform.png

I’ve been fiddling while waiting for an answer.

I noticed that the Camera scale was 0.4230469F

My camera is set up as follows:

UI Scale Mode = Scale With Screen Size.
Reference Resolution = 2560 x 1440 for “Nexus 6”
Screen Match Mode = Shrink
Reference Pixel Per Unit = 100

Now If I hard code the camera Scale value into the

 Vector2 mSize = new Vector2((mWidth / 3) * 0.4230469F, mHeight / 3);

mSize = 41.2, 97.3 (“I have not change height as of yet”).

so this is really close, is there a better way do do this?

Wouldn’t it be easier to use Layout Groups to divide the panel? These will fill the available area and rescale if necessary.

Hi PGJ,

What I’m trying to do is create an 8 Way touch pad if you like, I did want to use buttons as you have to release the button to effectively go to a different direction.

I’ve been figuring it out, i’ll post back here shortly when I have something to fully show, it works but there might be a better way?

There’s no need for buttons even if you use Layout groups. I usually place Images in the groups and add EventTriggers to them, for example PointerEnter EventTriggers. Works like a charm for virtual touchpads.

Here is the working script.

public class JoyPadComponent : MonoBehaviour
{
List<Rect> mAreas;
 
public RectTransform CanvasTransform;
private bool mClickState;
RectTransform mTransform;

private Single mScaleX;
private Single mScaleY;

void Awake()
{
  mAreas = new List<Rect>();
  mTransform = (RectTransform)this.transform;
}

void Start()
{
  mScaleX = CanvasTransform.localScale.x;
  mScaleY = CanvasTransform.localScale.y;

  Single mTop = mTransform.rect.max.y;
  Single mRight = mTransform.rect.max.x;
  Single mBottom = mTransform.rect.min.y;
  Single mLeft = mTransform.rect.min.x; 
  Single mWidth = mTransform.rect.width;
  Single mHeight = mTransform.rect.height;

  Single mTouchWidth = (mWidth / 3) * mScaleX;
  Single mTouchHeight = (mHeight / 3) * mScaleY;

   // North West
   mAreas.Add(new Rect(mLeft, mBottom + (mTouchHeight * 2), mTouchWidth, mTouchHeight));

   // North
   mAreas.Add(new Rect(mLeft + mTouchWidth, mBottom + (mTouchHeight * 2), mTouchWidth, mTouchHeight));

   // North East
   mAreas.Add(new Rect(mLeft + (mTouchWidth * 2), mBottom + (mTouchHeight * 2), mTouchWidth, mTouchHeight));

   // West
   mAreas.Add(new Rect(mLeft, mBottom + mTouchHeight, mTouchWidth, mTouchHeight));

   // East
   mAreas.Add(new Rect(mLeft + (mTouchWidth * 2), mBottom + mTouchHeight, mTouchWidth, mTouchHeight));

   // South West
   mAreas.Add(new Rect(mLeft, mBottom, mTouchWidth, mTouchHeight));

   // South
   mAreas.Add(new Rect(mLeft + mTouchWidth, mBottom, mTouchWidth, mTouchHeight));

   // South East
   mAreas.Add(new Rect(mLeft + (mTouchWidth * 2), mBottom, mTouchWidth, mTouchHeight));
    }
   
    // Update is called once per frame
    void Update()
{
  if (mAreas.Count > 0)
   for (int A = 0; A < mAreas.Count; A++)
    DrawRectangle(mAreas[A]);

  if (Input.GetMouseButtonDown(0) == true)
   mClickState = true;

  if (Input.GetMouseButtonUp(0) == true)
   mClickState = false;

  if (mClickState == true)
    {
     if (mAreas.Count > 0)
      for (int A = 0; A < mAreas.Count; A++)
      {
       if (mAreas[A].Contains(Input.mousePosition) == true)
        {
         Debug.Log("Hit Area " + A);
        }
      }
    }
    }

private void DrawRectangle(Rect area)
{
  // Top
  Debug.DrawLine(new Vector3(area.min.x, area.max.y, 0), new Vector3(area.max.x, area.max.y, 0), Color.red, 5F);

  // Right
  Debug.DrawLine(new Vector3(area.max.x, area.max.y, 0), new Vector3(area.max.x, area.min.y, 0), Color.cyan, 5F);

  // Bottom
  Debug.DrawLine(new Vector3(area.min.x, area.min.y, 0), new Vector3(area.max.x, area.min.y, 0), Color.blue, 5F);

  // Left
  Debug.DrawLine(new Vector3(area.min.x, area.max.y, 0), new Vector3(area.min.x, area.min.y, 0), Color.green, 5F);
}
}

now I’ve still got stuff to do with it but the basics are there, is there a better way.

Notes

If the user clicks/presses the screen in one of the areas, it will be acknowledged, now if the user still holds their finger down and moves way from the area it will ignore the areas, but if the bring it back it will carry on again, there is NO area in the centre as well.

I currently use a joystick, but users are still struggling with it, this may solve the problem.

2901218--213578--transform2.png

but will the pointer event trigger, constantly fire the event? if so I’m curious now.

No, but you can hook into; PointerUp, PointerDown, PointerEnter, and PointerExit. The you’ll get an event when the finger enters and exits. So you don’t really need constant events. Just have a flag that is set to on upon an enter event and set to off once there is an exit event.

This is of course providing that you are doing a “digital” touch pad. If you want to have “analog” values that changes depending on how far from the center the finger is, then you also need to check for movement of the finger.

I thought so, PointerDown will only fire once, I need a constant I’m press this button baby! event.

The way the game works is that you should be able to go to different directions without, needing to lift off, similar to a real joystick in fact.

If I’m not happy with it I’ll try something else, just trail and error.

That is why you want to hook into Down and Enter, that way you’ll get events even if the user only moves from one image to another without lifting his finger.

If you absolutely need constant events you can do something like this:

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class VirtualGamepad : MonoBehaviour
{
    [System.Serializable]
    public class GamepadEvent : UnityEvent { };

    public bool up = false;
    public bool down = false;
    public bool right = false;
    public bool left = false;

    // You might want diagonal events as well
    public GamepadEvent upEvent;
    public GamepadEvent downEvent;
    public GamepadEvent leftEvent;
    public GamepadEvent rightEvent;

    void Update () {
        if (up)
        {
            upEvent.Invoke();
        }

        if (down)
        {
            downEvent.Invoke();
        }

        if (left)
        {
            leftEvent.Invoke();
        }

        if (right)
        {
            rightEvent.Invoke();
        }
    }

    /// <summary>
    /// Assign to Pointer Enter and Pointer Down for up, left/up and right/up Image
    /// </summary>
    /// <param name="e"></param>
    public void UpOn(BaseEventData e)
    {
        up = true;
    }

    /// <summary>
    /// Assign to Pointer Exit and Pointer Up for up, left/up and right/up Image
    /// </summary>
    /// <param name="e"></param>
    public void UpOff(BaseEventData e)
    {
        up = false;
    }

    /// <summary>
    /// Assign to Pointer Enter and Pointer Down for down, left/down and right/down Image
    /// </summary>
    /// <param name="e"></param>
    public void DownOn(BaseEventData e)
    {
        down = true;
    }

    /// <summary>
    /// Assign to Pointer Exit and Pointer Up for down, left/down and right/down Image
    /// </summary>
    /// <param name="e"></param>
    public void DownOff(BaseEventData e)
    {
        down = false;
    }

    /// <summary>
    /// Assign to Pointer Enter and Pointer Down for left, left/down and left/up Image
    /// </summary>
    /// <param name="e"></param>
    public void LeftOn(BaseEventData e)
    {
        left = true;
    }

    /// <summary>
    /// Assign to Pointer Exit and Pointer Up for left, left/down and left/up Image
    /// </summary>
    /// <param name="e"></param>
    public void LeftOff(BaseEventData e)
    {
        left = false;
    }

    /// <summary>
    /// Assign to Pointer Enter and Pointer Down for right, right/down and right/up Image
    /// </summary>
    /// <param name="e"></param>
    public void RightOn(BaseEventData e)
    {
        right = true;
    }

    /// <summary>
    /// Assign to Pointer Exit and Pointer Up for right, right/down and right/up Image
    /// </summary>
    /// <param name="e"></param>
    public void RightOff(BaseEventData e)
    {
        right = false;
    }
}

First time I’ve seen the “UnityEvent” I usually use a delegate/event using .net, I’m assuming this is unity’s version of an event system. how does others classes subscribe to that event.

I’ve just seen an article Here on the subject, it feels a little disconnected to me, I’ve not seen how it cleans these event up either e.g. -= method etc.

are I see AddListener, makes sense.

Thanks for the advice I’ll take it onbaord.

What I don’t really understand with unity is, they use .net, for the language, and ignore a good chunk of what it give you why?

Why bother with the language in the first place if you don’t use its strengths.

@piggybank1974 - “how does others classes subscribe to that even” - It’s actually pretty nice, if you consider that you can just add public, serialized unity event class, then put field for it to your class, and you get UI in inspector where you can add / remove subscribers to your event, similar to what you have in Button or EventTrigger component.

So if one is using UI to setup subscribers, UnityEvent is pretty neat, otherwise I don’t know if you get any benefits.

Cheers eses,

That’s probably why I’ve not seen them or use them in that fassion, I’ve use AddListener though code and though the inspector, but not seen unity events, I don’t think the tutorials section has anything on this unless I missed it and skipped though, as I know .net events I tend to prefer and use what I know, and change it later if need be.

Unity is a fantasic engine to be hold but it does do bonkers things sometimes, things like this do make me wonder if that’s the reason for the 23mb(default) compile size, I’m sure in years to come I’ll get used to all the different things hopefully, Like I say the more I know the less I feel I know. :wink:

@PGJ_1
@eses

My idea does have one caveat so far, the positions I have set down, are not local positions but screen positions I moved the position of the panel off the screen side bottom and left, and the position of the rectangle did not change I had to use the anchoredPosition.

Here is the demo project, so please tell me what you think?

I would love to know your thoughts @PGJ_1 and others.

2901650–213640–JoyPad v0.1.zip (3.12 MB)

@piggybank1974 - It’s pretty straightforward to find out local position in rectTransforms, once you know it. Manual is little bit short on this info however:

  if (canvas.renderMode == RenderMode.WorldSpace)
  RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, Camera.main, out position);
  else
  RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, Input.mousePosition, null, out position);

@piggybank1974

Couldn’t resist checking your file… been testing/learning joysticks on/off last year.

I’d keep it simple and just set bool on IPointerDownHandler and IPointerUpHandler’s methods.

That way you can ten just use RectTransform utility to get location in plane, and that way keep code short, also, added benefit is that this works for all canvas types, and with rotated joystick.

The TouchPadComponent seems to be a nice solution. I agree with @eses comments. I would be a bit concerned by the constant Contains method calls. It would be interesting to run your version and mine in the Profiler to see which is the most efficient one. I’ll try to do that later on.

UnityEvent are pretty nifty, I like that you can have them show up in the Inspector and that they expose all public methods and variables for the selected objects. I’ve been building a framework with simple, basic components that the user can string together using UnityEvent (or rather I use QuickEvent, which is an improved version of the UnityEvent). I’ll be using this framework with my younger students who arent’t all that proficient in programming yet.

@eses
@PGJ_1

I was thinking of using IPointerDownHandler and IPointerUpHandler, instead of using the update method way I was not sure if it had extra overhead.

I would of though contains method should be optimised, as your only really comparing points, if this method is not optimised you could easily role your own.

Currently I use RectTransformUtility.ScreenPointToLocalPointInRectangle, but I’ve read that sometimes it produces wrong results, I think this happens as the hero goes in the opposite direction as you are dragging, take your finger off the joystick and start again and then all is well.

I would be interested on how fast PGJ this method is, I will bet its faster than the one I currently have as I don’t need to workout angles/distances etc.

Please let us know here.

@eses
@PGJ_1

Everything was working great until I introduced my fire button to and input.mouseposition now does not work as this on gets the first touch if you like, I have recoded it to use IDragHandler, IPointerUpHandler, IPointerDownHandler, but it means you need to slightly move it to get the character to move it’s not a press and go solution

I tried

if (RectTransformUtility.ScreenPointToLocalPointInRectangle(mTransform, Input.mousePosition, Camera.main, out pos) == true)

on it’s own with out the IDragHandler but it reports back I’m in the transform even when I’m not got an clues.