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

That seems likely.

Is your GitHub up to date?
I can’t really make any more suggestions unless I know what the current situation is.

static_cast is for converting between two types.
‘cast’ pretty much means ‘convert’ when used in a programming context.

C++ will do a lot of its conversions implicitly (due to partial compatibility with C),
but it’s better to be explicit when doing casts because they can introduce bugs.

For example, if you were to convert a uint8_t with a value of 200 to an int8_t,
you’d get -56 instead because int8_t can’t represent 200 and the bit pattern that represents 200 in a uint8_t is the same bit pattern that represents -56 in an int8_t.
(This is what I mean by representation being important.
Bits are just bits until you try to interpret them as a particular type of data.)

You can add your own type conversions for any struct/classes that you introduce,
but to understand that you need to first understand member functions,
and you haven’t learnt about those yet.

1 Like

Oh okay, thanks! Ill look into that more first too, then.
And i just updated everything on my github.

I can’t compile RTS.ino.
I’m getting conflicting definitions of rock from units.h, Resources.h and GameSprites.h/Sprites.h.

It looks like units.h is a duplicate of Resources.h, so I removed that.
I renamed Sprites.h to GameSprites.h because GameSprites.h couldn’t be found.
But now rock is Resources.h is conflicting with rock in GameSprites.h,
which is to be expected, you can’t have two variables sharing the same name (the ‘one definition rule’).

Sorry, I forgot I had changed the rock and tree sprite names to rockSprite and treeSprite, and forgot to update (it was conflicting with the rock/tree structs i made) so i fixed that. I also forgot i renamed my Sprites header file to GameSprites so it wouldnt conflict with the sprites class. And i was in the middle of something hoping i could update the files real quick and accidentally posted a duplicate of resources.h in the units.h, so i fixed that as well. Sorry about that -_-

1 Like

The current version compiles.

I’ll get to reading it in a moment, but firstly, I get this warning:

In file included from I:\...\RTS.ino:5:0:

sketch\units.h: In function 'void addPersonAt(uint16_t, uint16_t)':

sketch\units.h:102:36: warning: narrowing conversion of 'x' from 'uint16_t {aka unsigned int}' to 'int16_t {aka int}' inside { } [-Wnarrowing]

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

                                    ^

I’ll leave you to guess at what it means for a moment.
(There’s a similar warning for y.)


It seems you’ve got adding people working at the moment using the global cursor.

I’ve got it working using the local cursor.

But before I explain that, I’d like to point out that addPersonAt shouldn’t contain if (arduboy.justPressed(B_BUTTON)).

You should generally separate button handling code from the action performed when the button is pressed in case you want to do that action in some other context.

If you move the button handling code outside the function then you have more freedom as to when you add a new person.

So afterwards the code will look 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;
    }
}

In the function and:

  if (arduboy.justPressed(B_BUTTON))
    addPersonAt(cursorGlobalPos.x,cursorGlobalPos.y);

Outside the function.

(This is before fixing the above warning, which I’ll explain afterwards because I want to give you chance to have a guess at what the warning is about.)

Then after that, to get it to use the playerCursor instead (so that you don’t need cursorGlobalPos) you just do:

  if (arduboy.justPressed(B_BUTTON))
    addPersonAt(playerCursor.x + camera.x, playerCursor.y + camera.y);

As I said earlier, if camera.x and camera.y represent the camera’s position in the world, then doing globalX - camera.x makes the coordinate local to the camera and doing localX + camera.x moves a camera coordinate into the world.

You could even make a set of functions:

Point toLocal(Point globalPoint)
{
	return { globalPoint.x - camera.x, globalPoint.y - camera.y };
}

Point toGlobal(Point localPoint)
{
	return { localPoint.x + camera.x, localPoint.y + camera.y };
}
1 Like

Ah, thank you, is the error from a type conversion between unsigned and signed integers? (Or 8 and 16 bit, now that I’m looking at it)

And thanks, that makes much more sense- I was looking to eventually make it so the units spawn from a building so can definitely see where it would be better fit to put the button press outside the function.

I’m not sure I follow with the global and local to camera coordinates though, so will have to think on that a bit.

Yes, but indirectly.

For the ‘fundamental’ types there are two kinds of conversions: narrowing and widening.
A narrowing conversion means that the range of representable values is being reduced.
A widening conversion means that the range of representable values is being increased.
Some conversions do both, in which case the version is classes as ‘narrowing’ because the fact values are being dropped is more important.

Converting from an unsigned value to a signed value of the same size (or smaller) is a narrowing conversion,
and that’s what’s happening here.

(In fact I think this is technically supposed to be an error, but the compiler is reducing it to a warning for some reason. Probably related to -fpermissive.)

There’s two options.
Option 1:
You can choose to manually tell the compiler “I know what I’m doing” by adding an explicit cast:

people[personCount].position = { static_cast<int16_t>(x), static_cast<int16_t>(y) };

Option 2:
You change the type of the arguments:

void addPersonAt(int16_t x, int16_t y)

Option 2 is better in this case.

Your world is like this:

Coordinates

All those coordinates are ‘global’ (or ‘world’) coordinates.

Your camera also represents the screen,
i.e. anything at camera.x, camera.y appears on the screen at 0, 0.

So to convert a global/world coordinate to a local/camera coordinate,
you subtract the camera’s position,
because that gives you the object’s position relative to the camera’s position.

The reverse is also true, taking a set of coordinates that are local to the camera,
you can make them global by adding the camera’s position.

To put it another way, if I handed you a ruler:
Ruler

And I said “how far is 7 away from 5”, you’d probably do 5 - 7 (or 7- 5), and tell me -2 (or 2), right?

RulerMarked

It’s the same thing here.

“how far away is enemy5 away from camera?”
enemy5.x - camera.x, enemy5.y - camera.y
(In this case, the minus is extra important.)

CoordinatesMarked

1 Like

Hmm, in that case it seems I would also have to change the the playerCursor type too then, wouldn’t I? At the moment it’s also unsigned, but I’m not sure if changing it would mess up the code for the rectangle, also.

And thanks, that definitely makes more sense!

1 Like

Not necessarily.

It’s unsigned, but it’s a uint8_t, and converting uint8_t to int16_t is a widening conversion.
(Because int16_t can represent every value that uint8_t can.)

Any use of arithmetic operators on ‘fundamental types’ (i.e. integer types, floating point types or bool) will try to convert each argument to int (or larger) before performing the operation,
so playerCursor.x + camera.x ends up ‘promoting’ (technical term) playerCursor.x to int (which is the same as int16_t on Arduboy).

1 Like

Oh alright, thanks! Yeah I tried changing some stuff around and just started making a mess so Ill just have to go back and go through it again. Thanks!

1 Like

Ah, okay, got the addPersonAt function working with the playerCursor now, definitely see what you mean about the local vs global camera coordinates now. Will have to think on how/if that might come into play with the rectangle, thanks!

1 Like

Aha, I got it! Thanks so much! Now that ive gotten this one hammered out a little better and more up to speed (and working, more importantly :P) Ill get back to reworking my other game. Thanks again!

1 Like

Alright, so I lied :stuck_out_tongue:
I went back to my other game and tried to start overhauling it, but at this point theres just so much for me to change (between the new camera configuration, changing some things to arrays, changing some things to Point/position, and breaking things up better into header files) its just a little overwhelming, so ill have to do it in stages… Ive gotten a good start so far, didnt just quit before i started, just have so much to do ill have to take it in strides (but i know itll pay off a lot in the long run).

That being said, i shifted back to this game and the next thing on my list was to program the unit movement via selection and button press. I tried to do this simply by checking the people state and changing their position to the cursor coordinates (forgetting about vectors) and this didnt work, of course.

So I revisited what you told me in the other thread about distance and vectors, but still seem to be having difficulty.

In my code, ive created a new header file called “Movement.h”, which contains this:

//Distance
int DistanceSquared(int x0, int y0, int x1, int y1)
{
  const int xDistance = (x1 - x0);
  const int yDistance = (y1 - y0);
  return ((xDistance * xDistance) + (yDistance * yDistance));
}
//Vector Magnitude
float Distance(int x0, int y0, int x1, int y1)
{
  return sqrt(DistanceSquared(x0, y0, x1, y1));
}

//Vector and direction
struct Direction{
  int x;
  int y;
};

Direction dir;

void vector(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
      dir.x = x1 - x0;
      dir.y = y1 - y0;
      dir.x /= Distance(x0, y0, x1, y1);
      dir.y /= Distance(x0, y0, x1, y1);
      x0 += dir.x * 10;
      y0 += dir.y * 10;
      return (x0, y0);
}

and then I added this to my unit header file:

void movePerson() {
  for (uint8_t i = 0; i < personMax; i++) {
    if (people[i].state == PersonState::selected) {
      vector(people[i].position.x, people[i].position.y, playerCursor.x, playerCursor.y);
    }
  }
}

void unitMove() {
  if (arduboy.pressed(A_BUTTON)) {
    movePerson();
  }
}

but im not sure how correct any of that is. I looked into it as much as i could (last time it came up and this time) but came up a little short, unfortunately. It compiles okay but doesnt work, just hoping im at least somewhat on the right track :stuck_out_tongue:

And sorry for popping up with more questions, was hoping to give you a break by working on my other game for a bit but seems thatll have its own questions too, but im hoping my duck will give me some answers on that front, first :P. Just figured i may as well ask about this anyway though because its something id need to work into both games.

Strictly speaking this should be called Vector.
A ‘direction’ is something like ‘forwards, backwards, left, right’ or ‘north, south, east’ west’.
A vector is a direction and a magnitude (or a ‘length’ to use the less technical term).

You can’t return an object from a function marked void.
void means “returns nothing”.
Also you’re using (x0, y0) instead of {x0, y0},
and frankly I’m not sure what this is doing anyway.
The first two lines seem to be getting the vector between two points.
The next two lines seem to be normalising the vector.
Then the next two lines seem to be multiplying that normalised vector by 10 and adding that to the first coordinate?
And then it’s trying to return a point.
Meanwhile I’m not sure why dir is a global variable…

Basically I can’t suggest anything because I have no idea what this is supposed to be doing.

I wouldn’t say “ok”, I can tell without even trying to compile that there will be a warning about trying to return a value from a void function.

If you haven’t done so, make sure to set the Arduino IDE’s warnings to ‘all’:

You can ignore any warnings about “unused variable EEPROM”,
that’s the fault of the Arduino core library and I doubt they’ll fix it any time soon.

1 Like

:upside_down_face: Of course you cant return an object from a function marked void, isnt that just obvious?

And thats fair, Im not so sure what its doing either, clearly :stuck_out_tongue: Sorry, i forgot to go back and better educate myself on function inputs and return types so ill go ahead and do that now, was just looking at what you had done for the distance and tried to do something similar… since ive learned how to use void functions a lotta stuffs sure been lookin like nails. I also didnt realize you could use local variables in functions, not just for logic, thanks!

As for what i thought it was doing, i honestly wasnt entirely sure, but as for the last two lines i was under the impression that was to set the speed (the resource i found actually had speed in place of the number 10, i was just trying different values). Sorry, i just found a mix of results when I tried digging and had a bit of a hard time following along with just what was going on.

But I also didnt know or see anything about normalising vectors either, so ill have to look more into that here as well. Sorry, i dont mean to ask you to have to explain any of this stuff to me if it isnt necessary, just needed a bit of a pointer in the right direction, sometimes sifting through a lot of new/similar info can run together a bit. Thanks again!

EDIT: Also, sorry, ive had the compiler warnings set to all for a while now, just dont know quite what im looking at yet, but it has been helping me troubleshoot more now that youve told me about the warnings from different data types, etc. Thanks!

1 Like

The primary objective of any piece of code should be to either:

  • Solve a problem
  • Solve a specific part of a problem
  • Perform a specific operation

Resembling someone else’s code should be secondary.

My distance function looks the way it does because it takes the coordinates of two points and returns the distance between those two points - its arguments and return values reflect its intended usage and what it needs to function.

It could also be written as:

// Distance
int distanceSquared(Point point0, Point point1)
{
	const int xDistance = (point1.x - point0.x);
	const int yDistance = (point1.y - point0.y);
	return ((xDistance * xDistance) + (yDistance * yDistance));
}

// Vector Magnitude
float distance(Point point0, Point point1)
{
	return sqrt(distanceSquared(point0, point1));
}

Or, if you want to mix the two:

// Distance
int distanceSquared(Point point0, Point point1)
{
	return distanceSquared(point0.x, point0.y, point1.x, point1.y);
}

// Distance
int distanceSquared(int x0, int y0, int x1, int y1)
{
	const int xDistance = (x1 - x0);
	const int yDistance = (y1 - y0);
	return ((xDistance * xDistance) + (yDistance * yDistance));
}

// Vector Magnitude
float distance(Point point0, Point point1)
{
	return sqrt(distanceSquared(point0, point1));
}

// Vector Magnitude
float distance(int x0, int y0, int x1, int y1)
{
	return sqrt(distanceSquared(x0, y0, x1, y1));
}

(There should be no additional cost for doing this.)

Where did you think you’d been using them so far?

setup and loop are functions too.

Multiplying the components of a vector scales the vector.
So it can be used for determining speed, but that’s not clear from the usage here.

I think part of the problem here is that your function is doing too much.
The fact you called it vector instead of having a clear name for the function should have been a big hint there.
If you’re struggling to name a function, that usually means you aren’t sure what it’s doing, and that could be because it’s doing too much.

Normalising a vector means creating a vector with a length (or ‘magnitude’) of 1 from a vector with a different magnitude.
This is done by dividing the components of the vector by the vector’s length (or ‘magnitude’).
This is very useful, but it’s expensive (because it needs the sqrt for getting the length/magnitude), so it should be done sparingly.

However, this only works with vectors that have floating point or fixed point components.
It won’t work for integer components, because a vector with a magnitude of 1 will always have components between 0 and 1, and integers can’t represent the fractional values in between those two values.

Even if you don’t understand a warning, read what it says.
Eventually if you see the same warning come up enough you might start to see a pattern in what’s causing it.
Some warnings are very specific.

Edit:

For example (when compiling a blank sketch with your Direction, DistanceSquared, Distance and vector added):

warning: return-statement with a value, in function returning ‘void’ [-fpermissive]

Tells you what you need to figure out that you’re returning something from a void function.

The fact you see an -fpermissive on the end means "this would normally be an error, but it’s a warning instead because of the evil -fpermissive flag the Arduino IDE passes to the compiler.

The other warning:

warning: left operand of comma operator has no effect [-Wunused-value]

You’re unlikely to understand because the root cause is different to the problem that gets reported
Personally speaking I could spot the problem immediately without knowing the warning because I know the syntax so well,
but I do also happen to know what the comma operator is.
(I won’t explain the comma operator now, because aside from the fact it’s pretty irrelevant, you’re almost never going to need it. It’s almost always used for hacks and/or poor solutions.)

Warnings like this are warnings that you might not understand,
but they’re still an indicator that something is wrong and you should have a think about what it could be just in case you can figure out what’s wrong.

1 Like

Yeah, I definitely wasnt sure what all it should have been doing. I was aware of the separate components (somewhat), but mostly all i could find was about magnitude, couldnt find anything regarding how to find a vector without a known angle, and could quite see how to put them together (and even now im not so sure how to use a vector once ive found it, to be perfectly honest). So things got a bit jumbled up because i was pretty confused from the start. But that definitely helps, thanks!

And ill be sure to pay closer attention to what the warnings are saying, i should have noticed something as obvious, sorry. Thanks!

It depends what information you’ve got and which vector you’re trying to find specifically.

It depends what you’re trying to do with it.
There are many different operations you can perform on a vector depending on what other objects you have lying around.

A vector is a direction with a length (more correctly ‘magnitude’),
and it’s comprised of an x component and a y component.
The easiest way to imagine it is an arrow.

In this diagram, if a is the vector (think Vector a;) then ax is the x component (think a.x) and ay is the y component (think a.y).
(Excuse the subscript notation, I borrowed this image from mathsisfun.)

This is why getting the direction of a vector actually uses Pythagoras’s theorem - the two components are the opposite and adjacent sides of a triangle, and the vector itself is the hypotenuse (the magnitude of the vector is the length of the hypotenuse).


Getting back to the operations you can perform…

Stuff to do with vectors

If you add a vector to a point (by adding the x and y components) then you end up moving the point by the amount specified by the vector.
(This is how gravity works (roughly). Gravity is a vector pointing towards the centre of the earth (or the bottom of the screen).)

Vector p = { q.x + v.x, q.y + v. y};
(There is a way to just write Vector p = (q + v);, but I’ll explain that another time when you’re more comfortable with vectors.)

http://www.c-jump.com/bcc/common/Talk3/Math/Vectors/const_images/v04_points.jpg

If you negate a vector (e.g. vector = { -vector.x, -vector.y }),
you effectively make it point in the direction opposite to what it was pointing to before.
(Only works if the components are signed types.)

If you subtract a vector from a point, you move the point in the opposite direction to what the vector is pointing.
This is equivalent to adding a negated vector for the same reason that x - y == x + -y.

If you add vectors together, you get a vector that points to where following all the vectors would take you:

VectorAdd

You can rotate a vector by an angle (though that’s a bit more tricky).

If you have two vectors then you can either find the angle between them, or get a rough indication of whether they trend towards each other, away from each other, or are at right angles to each other.

If you have two points then the line between them is in fact a vector.
Specifically, it’s the vector that one of the points would have to move by to become equal to the other point.
And the distance between them is the magnitude of that vector!

If you normalise a vector (by dividing both of the vector’s components by its magnitude) you get a vector of magnitude 1,
which has lots of useful properties.
For one thing, its end touches the unit circle,
which makes operations involving angles easy.
Or you can multiply it by any amount to get a vector at that scale.
(Essentially by normalising a vector you almost strip away its magnitude and end up focusing on its direction.)


That’s probably overwhelmed you with info,
but the point is that there’s quite a lot of stuff you can do with vectors.

They are the basic unit of movement in a 2D plane.
(And also in 3D worlds, where vectors also have a z component.)

Honestly the most complete explanation of the various vector operations that I’ve found is still Jorge Rodriguez’s Intro to Vectors, and as I’ve said before, it’s very rare for me to recommend a youtube playlist as a programming resource.

It’s not so much that it’s ‘obvious’, it’s that the compiler warnings are there to help you.

(It takes a lot of work to get to the point where seeing syntax/semantic errors causes you to physically flinch in pain. :P)

The actual wording might seem confusing at times,
but that’s because the compiler isn’t smart enough to guess what you were trying to do,
it can only report how language rules have been broken or specific discrepencies that it’s been programmed to detect (like variables being unused).

1 Like

That definitely helps more, thanks! Sorry, im still having just a bit of a hard time connecting the dots, though.

And this is what has me lost

Ultimately what Im trying to do is just make an object move to the cursor position via button press, but im confused as to how id add what i now have to a point (because i have the magnitude, which uses points distance squared).

Technically that should have been Point p, sorry for mixing the two.

Point q { 1, 4 };
Vector v { 2, 3 };
Point p { (q.x + v.x), (q.y + v.y) };

VectorAdding

The point p is defined by point q offset by vector v.

The usual arithmetic laws apply, so you if you had q and p but no v,
you could do:

Point q { 1, 4 };
Point p { 3, 7 };
Vector v { (p.x - q.x), (p.y - q.y) };

Which is effectively the inverse of the code above.

(Remember, a vector is effectively just combining two regular numbers into a single object, so most laws regarding mathematical operations still apply.)

That’s quite a bit more complicated assuming you don’t want it to happen instantly.

You’d need to give each object/person a target point that they’re supposed to be moving towards,
and each frame you get that object/person to move a bit more towards that point than they were doing so before.

For this to work properly you’re probably going to have to use floats or fixed points for your positions and vectors or movement might get a bit clunky.
It’s not impossible to use integers, but you might run into some complications.
Particularly because this is one of those things where normalisation will be useful.

You know the distance between two points,
but the distance between two points is actually the magnitude of the vector between the two points,
which means that at some point you’ve calculated the vector between those two points…

int distanceSquared(Point point0, Point point1)
{
	const int xDistance = (point1.x - point0.x);
	const int yDistance = (point1.y - point0.y);
	return ((xDistance * xDistance) + (yDistance * yDistance));
}

const int xDistance = (point1.x - point0.x);
const int yDistance = (point1.y - point0.y);

Which happens to look a bit like…

const int vx = (p.x - q.x);
const int vy = (p.y - q.y);

So…

Vector vectorBetween(Point point0, Point point1)
{
	return { (point1.x - point0.x), (point1.y - point0.y) };
}

(This is actually covered in that Youtube tutorial series in this video. There’s diagrams and C++ code.
Though the code may not necessarily make sense to you.)

1 Like