How to program rectangle drag via user input (collision detection, vectors)?

Hey everyone, I was trying to do something and just haven’t been able to find the answer anywhere (or figure it out for myself)… but does anyone know of a way to calculate the height and width of a rectangle using two sets of coordinates with the drawRect function?

Essentially what I’m trying to do is make a draggable rectangle using a cursor… I have my cursor object and made a second set of coordinates to update when the a button is pressed, while the first increments via directional input.

I tried to calculate the width and height by simply subtracting them, but given the different axes it only works in one (and doesn’t work in any similar configuration I try), so I was hoping there might be a simpler way.

#include <Arduboy2.h>
Arduboy2 arduboy;

struct Cursor {
  uint8_t x1;
  uint8_t y1;
  uint8_t x2;
  uint8_t y2;
  uint8_t width;
  uint8_t height;
  uint8_t radius;
  uint8_t color;
};

struct Timer {
  unsigned long currentMillis;
  unsigned long previousMillis = 0;
};

Cursor playerCursor{64, 32, 0, 0, 0, 0, 3, 1};
Timer timer;

void setup() {
  arduboy.boot();
  arduboy.flashlight();
  arduboy.setFrameRate(75);
  arduboy.clear();
}

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }
  timer.currentMillis = millis();
  arduboy.pollButtons();
  arduboy.clear();
  drawCursor();
  handleCursorInput();
  test();
  arduboy.display();
}

void drawCursor() {
  arduboy.fillCircle(playerCursor.x1, playerCursor.y1, playerCursor.radius, playerCursor.color);
}

void moveCursor() {
  if (arduboy.pressed(UP_BUTTON) && playerCursor.y1 >= 5) {
    --playerCursor.y1;
  }

  if (arduboy.pressed(DOWN_BUTTON) && playerCursor.y1 <= 59) {
    ++playerCursor.y1;
  }

  if (arduboy.pressed(LEFT_BUTTON) && playerCursor.x1 >= 5) {
    --playerCursor.x1;
  }

  if (arduboy.pressed(RIGHT_BUTTON) && playerCursor.x1 <= 123) {
    ++playerCursor.x1;
  }
}

void handleCursorInput() {
  if (arduboy.justPressed(A_BUTTON)) {
    playerCursor.x2 = playerCursor.x1;
    playerCursor.y2 = playerCursor.y1;
  }
  moveCursor();
  if (arduboy.pressed(A_BUTTON)) {
    playerCursor.width = playerCursor.x1 - playerCursor.x2;
    playerCursor.height = playerCursor.y1 - playerCursor.y2;
    arduboy.drawRect(playerCursor.x2, playerCursor.y2, playerCursor.width, playerCursor.height);
  }
}

void test() {
  arduboy.setCursor(0, 0);
  arduboy.print(playerCursor.x1);
  arduboy.setCursor(0, 20);
  arduboy.print(playerCursor.y1);
  arduboy.setCursor(20, 0);
  arduboy.print(playerCursor.x2);
  arduboy.setCursor(20, 20);
  arduboy.print(playerCursor.y2);
}

Your code is actually working, but your problem is that you’re only drawing the rectangle when the A button is pressed so you’re not really seeing it.


That said, I’ve made a modified version:

#include <Arduboy2.h>
Arduboy2 arduboy;

struct Cursor
{
  uint8_t x;
  uint8_t y;
  uint8_t radius;
  uint8_t colour;
};

struct Timer
{
  unsigned long currentMillis;
  unsigned long previousMillis = 0;
};

Cursor playerCursor { 64, 32, 3, 1 };
Rect rectangle { 16, 16, 32, 32 };
Timer timer;

void setup()
{
	arduboy.boot();
	arduboy.flashlight();
	arduboy.setFrameRate(75);
	arduboy.clear();
}

void loop()
{
	if (!arduboy.nextFrame())
		return;

	timer.currentMillis = millis();
	arduboy.pollButtons();
	
	handleCursorInput();
	
	arduboy.clear();
	
	drawCursor();
	drawRectangle();
	drawDebugInfo();
	
	arduboy.display();
}

Rect createRectangleFromPoints(int16_t x0, int16_t y0, int16_t x1, int16_t y1)
{
	const int16_t startX = min(x0, x1);
	const int16_t startY = min(y0, y1);
	const int16_t endX = max(x0, x1);
	const int16_t endY = max(y0, y1);
	
	return { startX, startY, static_cast<uint8_t>(endX - startX), static_cast<uint8_t>(endY - startY) };
}

void drawCursor()
{
	arduboy.fillCircle(playerCursor.x, playerCursor.y, playerCursor.radius, playerCursor.colour);
}

void drawRectangle()
{
	arduboy.drawRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}

void moveCursor()
{
	if (arduboy.pressed(UP_BUTTON) && playerCursor.y >= 5)
		--playerCursor.y;

	if (arduboy.pressed(DOWN_BUTTON) && playerCursor.y <= 59)
		++playerCursor.y;

	if (arduboy.pressed(LEFT_BUTTON) && playerCursor.x >= 5)
		--playerCursor.x;

	if (arduboy.pressed(RIGHT_BUTTON) && playerCursor.x <= 123)
		++playerCursor.x;
}

void handleCursorInput()
{
	if (arduboy.pressed(A_BUTTON))
	{
		rectangle = createRectangleFromPoints(playerCursor.x, playerCursor.y, (rectangle.x + rectangle.width), (rectangle.y + rectangle.height));
	}	
	
	if (arduboy.pressed(B_BUTTON))
	{
		rectangle = createRectangleFromPoints(rectangle.x, rectangle.y, playerCursor.x, playerCursor.y);
	}

	moveCursor();
}

void drawDebugInfo()
{
	arduboy.setCursor(0, 0);
	arduboy.print(playerCursor.x);
	
	arduboy.setCursor(20, 0);
	arduboy.print(playerCursor.y);
	
	arduboy.setCursor(0, 20);
	arduboy.print(rectangle.x);
	
	arduboy.setCursor(20, 20);
	arduboy.print(rectangle.y);
	
	arduboy.setCursor(0, 40);
	arduboy.print(rectangle.width);
	
	arduboy.setCursor(20, 40);
	arduboy.print(rectangle.height);
}

Pressing A sets the top left coordinate.
Pressing B sets the bottom right coordinate.


Depending on what you actually want to do,
that might not be what you want.

Are you trying to simulate drawing a rectangle with a mouse?
(I.e. click & drag.)

1 Like

Ah, thank you! Yeah, I was trying to simulate dragging a rectangle with the mouse (and setting the second coordinate on the button release)

Easily fixed:

void handleCursorInput()
{
	if (arduboy.justPressed(A_BUTTON))
	{
		rectangle = createRectangleFromPoints(playerCursor.x, playerCursor.y, (rectangle.x + rectangle.width), (rectangle.y + rectangle.height));
	}	
	
	if (arduboy.pressed(A_BUTTON))
	{
		rectangle = createRectangleFromPoints(rectangle.x, rectangle.y, playerCursor.x, playerCursor.y);
	}

	moveCursor();
}

(Well, almost. It only works in one direction, but I don’t have time to fix that at the moment.)

1 Like

Ah, I was having the same problem, it would only work in one direction (x+ y+) and any other direction doesn’t (x- y+, x- y-, x+ y-). But I’ll see if I can’t get it working based off of yours. Thanks!

Edit: forgot to mention last night I also sat down and tried to math it out and change the width/height calculations accordingly (because both sets of x/y values are using actual on screen coordinates so going one direction would make one higher and the other lower but going in the opposite direction was the opposite), so I’ll try to give that consideration, also

It seems the problem is that after
rectangle.x == playerCursor.x or rectangle.y == playerCursor.y,
things stop working because the rectangle starts moving.

I think this means you’ll have to store the original click point separately from the rectangle.

Point clickPoint { 16, 16 };
void handleCursorInput()
{
	if (arduboy.justPressed(A_BUTTON))
	{
		clickPoint = { playerCursor.x, playerCursor.y };
		rectangle = createRectangleFromPoints(playerCursor.x, playerCursor.y, (rectangle.x + rectangle.width), (rectangle.y + rectangle.height));
	}	
	
	if (arduboy.pressed(A_BUTTON))
	{
		rectangle = createRectangleFromPoints(clickPoint.x, clickPoint.y, playerCursor.x, playerCursor.y);
	}

	moveCursor();
}

Yup, it works.

1 Like

Awesome, thanks so much!

1 Like

I went ahead and posted all the code to my github, but here is the start to my second project (an RTS): https://github.com/CatDadJynx/ArduboyRTS/tree/master

I dont have any more questions as of right now, and can work on it in parallel to my other project (there are no backgrounds or tiling so should be able to get away with using the map movement vs camera movement i think), just figured id put up the link so i could keep any questions pertaining to it to this thread.

Actually, sorry, I do have some other questions (surprise, right? :P)

- Is there any way i might be able to check if a unit is within the bounds of the rectangle? At the moment I have it set up so that I am using two one-dimensional arrays to represent unit location (in x and y), and I am trying to use an (empty) integer to increment.

For some reason i couldnt use two dimensional arrays because it added too much memory whereas 2 one dimensional arrays did not, but trying to iterate through one for loop (for the indices, to find the unit x and y) and two if loops (to test the min and max range) seems to be adding too much and doesnt work.
EDIT: Figured out the first part just using arduboy.collide, a Point created from the unit x/y coordinates, and the cursor rectangle, however it doesnt actually count whats in the rectangle and just keeps going up as you hold A. Will have to think more on this.

Let me go get the duck…

  • Which leads me into my second question… is there a better way to access the objects within an array when using for things like collision, other than to iterate through the indices every time? All Ive seen is using for loops to iterate, but i would think this could get messy pretty quick.

-And my third question… why do 2 dimensional arrays seem to add so much more ram than separate 1 dimensional arrays? Unless i just messed up somewhere along the line to make it do that

Here is the updated code (updated/fixed so you can drag to select the one unit in the map and deselect using B):


struct Timer {
  unsigned long currentMillis;
  unsigned long previousMillis = 0;
};

Timer timer;
Point personPos;

constexpr uint8_t personMax = 50;
uint8_t personFrame = 0;
uint8_t walk; //turn into enum class
uint16_t personX[personMax];
uint16_t personY[personMax];
uint8_t personCount;
uint8_t personSelect;
uint8_t personState[personMax];

void populatePerson() {
  personX[0] = playerCursor.x;
  personY[0] = playerCursor.y;
  personCount += 1;
}

void drawPerson() {
  for (uint8_t x = 0; x < personCount; x++) {
    Sprites::drawSelfMasked(personX[x] + camera.x, personY[x] + camera.y, personSprite, personFrame);
    personPos.x = personX[x];
    personPos.y = personY[x];
  }
}

void animatePerson() {
  if (personFrame < 2) {
    ++personFrame;
  }
  else
    personFrame = 0;
}

void personWalk() {
  for (uint8_t x = 0; x < personCount; x++) {
    if (timer.currentMillis - timer.previousMillis >= 500) {
      walk = random(0, 4);
      switch ( walk ) {
        case 0:
          ++personX[x];
          animatePerson();
          break;
        case 1:
          --personX[x];
          animatePerson();
          break;
        case 2:
          ++personY[x];
          animatePerson();
          break;
        case 3:
          --personY[x];
          animatePerson();
          break;
      }
      timer.previousMillis = timer.currentMillis;
    }
  }
}

void personSelection() {
  for (uint8_t x = 0; x < personCount; x++) {
    if (personState[x] == 0 && arduboy.collide(personPos, rectangle)) {
      personState[x] = 1;
      if (personState[x] = 1){
        ++personSelect;  
      }
    }
    if (arduboy.pressed(B_BUTTON)){
      personState[x] = 0;
      personSelect = 0;
    }
  }
}

void drawDebugInfo()
{
  arduboy.setCursor(0, 0);
  arduboy.print(playerCursor.x);

  arduboy.setCursor(20, 0);
  arduboy.print(playerCursor.y);

  arduboy.setCursor(0, 20);
  arduboy.print(rectangle.x);

  arduboy.setCursor(20, 20);
  arduboy.print(rectangle.y);

  arduboy.setCursor(0, 40);
  arduboy.print(personX[0]);

  arduboy.setCursor(20, 40);
  arduboy.print(personSelect);
}

Sorry, just having a hell of a time with this now…
So far ive made it so you can drag the rectangle and if a unit is within the rectangle it will change its state in an array (personSelect), and you can deselect all by pressed A + B, but when trying to add more units than just the first ive run into some difficulties…

At this point i tried adding this:

void addPerson() {
  if (arduboy.justPressed(B_BUTTON) && personCount < personMax) {
    ++personCount;
    for (uint8_t x = 1; x < personCount; x++) {
        personX[personCount] = playerCursor.x;
        personY[personCount] = playerCursor.y;
      }
   }
}

so a unit will spawn via B button press, however they arent spawning at the cursor x and y position like they should be, for some reason (though they do seem to follow a pattern), but selecting them (by dragging the rectangle) does work. Not sure what i may be overlooking here.

One 2D array is not the same as 2 1D arrays unless one of the 2D array dimensions is 2.

I.e.
This:

int array[2][4];

Is (roughly) similar to this:

int array0[4];
int array1[4];

But not this:

// Different dimension
int array0[3];
int array1[4];

If you need to access every singly object in the array then no,
there’s no way to avoid iterating through the array.

If you only need to access a particular object then you only access that object via its index.

Not necessarily.

It would be better/easier to do:

struct UPoint
{
	uint16_t x;
	uint16_t y;
};

enum class PersonState : uint8_t
{
	// Whatever the valid states are
};

struct Person
{
	UPoint position;
	PersonState state;
};

// This will take the same amount of RAM
Person people[personMax];
uint8_t personCount;
uint8_t personSelect;

Then you can do things like:

people[0].position = { playerCursor.x, playerCursor.y };

Or (if the type of Person::position and playerCursor are the same):

people[0].position = playerCursor;

By the way, I think this has a bug:

void populatePerson() {
  personX[0] = playerCursor.x;
  personY[0] = playerCursor.y;
  personCount += 1;
}

I think you meant to do:

void populatePerson()
{
	// Avoid trying to add more than the maximum
	if(personCount < personMax)
	{
		// Person goes on the end of the list
		personX[personCount] = playerCursor.x;
		personY[personCount] = playerCursor.y;
		++personCount;
	}
}

Also with:

personX[x] + camera.x, personY[x] + camera.y

Usually you subtract camera.x and camera.y from a coordinate to make it local to the camera,
so that implies you’re doing something different from normal.

In personWalk you’re animating the person on every case, so instead of including it in every case, you could just put it after the switch statement:

switch (walk)
{
	case 0:
	  ++personX[x];
	  break;
	case 1:
	  --personX[x];
	  break;
	case 2:
	  ++personY[x];
	  break;
	case 3:
	  --personY[x];
	  break;
}
animatePerson();

Also (though this is really a minor issue) usually people use i (or in my case I prefer index) for the loop variable used for indexing an array.
x and y are typically reserved for coordinates.

I don’t have time to compile and run the code at the moment,
but I will be able to later.
I think I might have solved this one earlier in my comment.

One thing I will say is that the for loop doesn’t look right.

You realise that the for loop will be setting every person’s coordinate to the cursor position and not just the recently added one, right?

(As I say, if in doubt, read through the code verbatim and run the program in your mind. Code is written for humans to read, not for machines to read.)

1 Like

Ah, thanks so much! Yeah, I didn’t put the personMax in the populatePerson function because that was only meant to run once in my setup, for the initial person. But I’ll still fix that to avoid any issues.

And in my actual loop I was trying to start the index at 1 (because the initial person should be at 0)… I never had any issues with the initial person, but every person after it was having issues. If it’s still iterating through the entire array staring at person 1 each time though then that would explain it.

I’ll get everything fixed a bit better now, thanks!

If it’s only running once in setup then you don’t really need it at all.

Any initialisation you do in setup (with the exception of something relying on random numbers and a few other minor exceptions) can usually be done with global initialisation instead.


I think what you really need is something like:

void addPersonAt(uint16_t x, uint16_t y)
{
	// Avoid trying to add more than the maximum
	if(personCount < personMax)
	{
		// Person goes on the end of the list
		personX[personCount] = x;
		personY[personCount] = y;
		++personCount;
	}
}

Then you can do:

if (arduboy.justPressed(B_BUTTON))
{
	addPersonAt(static_cast<uint16_t>(playerCursor.x), static_cast<uint16_t>(playerCursor.y));
}
1 Like

Ah, okay, thanks! I was going to change things a bit more like you had above, but was wondering…

If I were trying to access just the x or y of “people[0].position” how would I do that?

people[0].position.x.

And for the record, every . is evaluated at compile time, there’s no runtime cost.

In most dynamically typed languages (e.g. JavaScript, Python) there’s a runtime cost,
but C++ is statically typed, so there’s no additional cost for accessing member variables of member variables.

If you do change to having a Person struct then addPersonAt would be more like:

void addPersonAt(uint16_t x, uint16_t y)
{
	// Avoid trying to add more than the maximum
	if(personCount < personMax)
	{
		// Person goes on the end of the list
		people[personCount].position = { x, y };
		++personCount;
	}
}

Or if you’d prefer you could do:

void addPerson(Person person)
{
	// Avoid trying to add more than the maximum
	if(personCount < personMax)
	{
		// Person goes to the end of the list
		people[personCount] = person
		++personCount;
	}
}

Either way instead of populatePerson you can do:

Person people[maxPeople]
// The first set of braces is 'all the people'
{
	// This set of braces is people[0]
	{
		// This is people[0].position
		{ 0, 0 },
		// This is people[0].state
		PersonState::Idle
	}
	
	// The rest of your people can be left out and they'll just be 'zero initialised'
};

// You start off with 1 person initialised
uint8_t personCount = 1;
1 Like

I ended up using the first (addPersonAt) example, using the cursor x and y as the function input in my loop, and got rid of the populatePerson using the zero initialization method (or the method that only initialized that one, rather). Only issue was that the people werent getting added at the correct position (because they were going off of the cursor on screen coordinates), so i had to make a global cursor position and that worked. Only issue now though is that I cant select them, because my rectangle is also using on screen coordinates, not on the same coordinate system. Will have to think on that.

I think technically it’s aggregate initialisation of the array (which is itself a form of list initialisation),
and the first element is copy initialised, while the remaining elements are default initialised and/or zero initialised.

But honestly without sitting down and figuring it out I’d have no clue,
the initialisation rules are arguably the hardest part about C++.

I may have the answer to that problem,
but first I need to understand something.

Why is this:

Sprites::drawSelfMasked(personX[x] + camera.x, personY[x] + camera.y, personSprite, personFrame);

Using + camera.x and + camera.y?

That implies that either camera.x and camera.y aren’t the camera’s top left corner in world coordinates,
or that for some reason personX and personY aren’t the person’s position in world coordinates.

Essentially mapping local/screen coordinates to global/world coordinates should be the inverse of mapping global/world coordinates to local/screen coordinates.

Usually world-to-screen is done by - camera.x and - camera.y,
thus making screen-to-world + camera.x and + camera.y,
but for some reason your game seems to be doing the inverse?

Ah, it was inverse, but i fixed that (and everything else that corresponded to it). Thanks!

Also, I changed my resources to base both off the same struct (before they were similar to how my people were set up except duplicated once for each resource). I would have thought this wouldve saved me RAM also, but somehow it added 2% (sorry, need to pay closer attention to bytes).

How might you initialize all the variables, rather than just the first, then? At the moment I have my resources set up to just initialize “Resource rock[resourceMax];” then i populate them later randomly in a for loop- is there somehow i might be able to just initialize them this way?

Changed how exactly?

It should use the same amount of RAM because the end result is the same (in terms of usage, but not in terms of the location of the data in RAM).

I can’t really say why it would happen without knowing what the change looks like precisely.

For an array of objects you’d have to specify every single object.
E.g.

Point points[8]
{
	{ 1, 4 },
	{ 2, 4 },
	{ 3, 4 },
	{ 4, 4 },
	{ 5, 4 },
	{ 6, 4 },
	{ 7, 4 },
	{ 8, 4 },
};

If you need the data to be random, then it has to be filled later.

It’s worth noting that the data won’t be random until you call arduboy.initRandomSeed().
(Which calls srandom, which seeds the random generator. The seed is a combination of noise from a disconnected analogue digital converter input and the number of microseconds elapsed since system startup.)

If you just want it to follow a sequence then you can manually generate that sequence.
(Which is effectively what you do with images - a pregenerated sequence of data.)

1 Like

Ah, okay, thanks. Actually now that I think about it, i forgot I hadnt set the state for the resources the first time around and added that when i changed it, so that would account for the RAM change. I forgot that random needed to be initialised first, so just set the resources to populate in a different function same way I had before.

Now just to fix the rectangle coordinates vs map/global coordinates issue. Im wondering if this might work using something to do with static_cast? Im not familiar with it yet, ive just noticed youve used it a lot (in the cursor/rectangle code and the star background) and tried to get a feel for what it is and seems like something that might applicable there.