Creating a projectile that moves in a sive wave pattern

POCKETS

Terrarian
I need a bit of help here...

I'm not asking for anyone to write a single line of code for me. I'd actually like to tackle this one myself.

However, I'm lost on where to begin. I've googled this problem, and I've seen a few solutions in engines like Unity, and I'd like to adapt something like that to Terraria. I guess what I'm after are some hints on how one might begin to think about solving this problem. Maybe there's even an example out there I could take a look at? I'm sure there's probably mods out there that do this sort of thing, but I've yet to find an example of one that's open source.

REALITY CHECK: If this requires any heavy math skills, I'll have to admit defeat for now and come back to this, once I have a better understanding of what may be involved. Still, I consider this one a personal challenge. It may sound like a simple thing to do, but I'm not so sure the implementation would be.

Still, I've been able to do things while modding Terraria that I never thought I'd be able to pull off, even with my very limited C# (and Terraria modding) knowledge, so I'm actually optimistic :)
 
What I would do, is try to change the velocity of the projectile over time to look like a cosine wave. (Because sin'(x) = cos(x)) Basically, store the starting velocity somewhere in the projectile, then before every update, change the actual velocity to be the starting velocity plus a unit normal vector multiplied by the cosine of the time since the projectile was created.

Kinda like this: Projectile sin wave (It's just maths no actual code involved. :))
 
What I would do, is try to change the velocity of the projectile over time to look like a cosine wave. (Because sin'(x) = cos(x)) Basically, store the starting velocity somewhere in the projectile, then before every update, change the actual velocity to be the starting velocity plus a unit normal vector multiplied by the cosine of the time since the projectile was created.

Kinda like this: Projectile sin wave (It's just maths no actual code involved. :))

Oh hey, look at that...

Thanks for the link! I'll take a look at this and see what I can come up with :)
 
Ooh, this is an interesting one! I'll try to help as much as I can without giving the game away. ;)

Okay, so what you want is a projectile to move in a sine wave. Now, broadly speaking, there are two ways to do this:
  1. Manually update the position of the projectile each frame.
  2. Update the velocity of the projectile, which then moves the projectile.
Both of these are valid ways of going about it, but I'd personally prefer the second one, as it's more natural and also has the added benefit of working with velocity multipliers (i.e. going in and out of water). That's purely preference, though!

The largest problem here is getting the sine wave to work in any direction, but for now let's dispense with that and just consider the projectile traveling in a horizontal line. This has two benefits:
  1. All horizontal movement (x) is constant, and all vertical movement (y) is purely from (and therefore perfectly plots) the sine wave.
  2. Because our horizontal movement is constant, we can give it any value we want and not have it impact the sine wave. Even 0!
If we take this as a starting point for the projectile, then for now all we have to do is make our projectile move up and down in a sine wave, and that would give us a basis that we can expand on and won't have to modify to get the rest working. So let's start with that. Because this post is very wall-of-texty, I've broken down the different parts of this... well, essay, into different sections.
If we're going to go the "update position" road, then our position is very simple sin(something). This something needs to be something that linearly increases (or decreases) every frame. There are a couple of candidates, but since I'm not sure how much you want spoiled, I'll leave that one up in the air for now (if you get stuck, do ask though).

However, if we're doing velocities, would sin(something) also work? Yes, and no. While it would plot a sine wave, it wouldn't plot the same sine wave as the one above: the wave it plots starts at the highest point of the sine wave, rather than in the middle (which would look weird on projectiles). This is because velocity(x, y) is the derivative of position(x, y). If that doesn't make sense, consider it like this:

Imagine our projectile has just been spawned, and it starts in the 'middle' of the sine wave, going up. At that point, it's y-position would be 0. It's y-velocity, however, is 1, because it's at its maximum speed going up (if you look at a sine wave, you'll see that at this point, the slope upwards is the steepest). When the sine wave crests, its y-position is 1 (the highest point), but the y-velocity is 0. Going down again, at the mid point y-position is 0 again, but y-velocity is -1. And at last at the trench, y-position is -1, and y-velocity is 0 again. So as you can see, position and velocity are both sine waves, but they're out of sync. How much out of sync? Well, if velocity is the derivative of position, and position is a sine wave, then velocity is the derivative of a sine wave. Commonly known as... the cosine!

So, if we want velocity to plot a sine wave, we simply use the cosine (cos(something)). For a nice sine wavy function, that's all we need! However, there are two more aspects we can change: the period and the amplitude. If you're not seeing a nice clean sine wave, it's likely these are either too low or too high.
  • The period is how long the projectile takes to oscillate (read: go up and down). Given the formula sin(something * x), we can decrease (speed up) the period by increasing x (e.g. 2), and increase (slow down) the period by decreasing x (e.g 0.5).
  • The amplitude is how high (and low) the wave reaches. Given the formula sin(something) * x, we can increase the amplitude by increasing x, and decrease (flatten) the amplitude by decreasing x.
So there you have it, the second hardest part of making a sine waving projectile: actually getting the sine wave running. :p
This part is so simple it doesn't even warrant its own spoiler block, but hey, consistency is king!

As we said before, our vertical movement (whether regulated by position or velocity) is all sine wave, and our horizontal movement is constant. If we're using velocities, then all we have to do is set x-velocity to a constant. If we're using positions, then we'd have to add a constant value to our previous position every frame. Both of these are rather simple, so I won't dwell on this any longer.
Now comes the difficult part. Right now our projectile can only move in a horizontal line, which isn't ideal if we want to be able to fire in any direction. How do we ensure omnidirectionality?

Let's first consider what we already have. It moves in a straight line to the right. Can we also move it in a straight line to the left?

The answer is a very simple "yes". All we have to do is invert the x-velocity and the projectile moves in that same straight line, but in the other direction. We don't even have to touch the y-velocity.

Now, if we can turn our projectile 180 degrees, what about 90. Could we fire straight up?

If we compare a sine wave going to the right and a sine wave going upwards, what exactly is the difference?
  1. The sine wave going to the right has a constant x-velocity and a sine y-velocity.
  2. The sine wave going upwards has a sine x-velocity and a constant y-velocity.
As you can see, the only difference between the two is that their x and y velocities have been swapped around (one small but vital difference that's hard to spot in this particular case is also that y has been negated during the swap, so x.up = -y.right). And if we can invert the horizontal wave, then we can also do the same for the vertical wave. Meaning that we now have four directions we can move in: right, left, up and down. Progress, but still only four directions. What if we wanted to fire diagonally right and up at a 45 degree angle?

If we thought about this really basically, we'd just say we take half of the right-traveling wave and half of the upward-traveling wave. Would that actually work? Yes, it turns out! If we only take the 'amount' of wave in each direction that we need, we can point our projectile in any direction!
Of course this is easier said than done, as we still need to implement it, but that's all code, and as you mentioned, you prefer to do that yourself. This is really only the theory, and I did intentionally leave out a couple of things, but if you get stuck, I'm always happy to help out!
 
That wouldn't really work too well. Unless you only change the position in very fine increments, the projectile could easily clip through walls.

Other than that, yes, that's pretty much the same thing I was thinking of.
That's actually a very good point I didn't even think of (and in hindsight, that's rather embarrassing!). So yeah, velocity seems to be the way to go.
 
Thank you both for these wonderful suggestions. This math is a little beyond my skill level atm, but this is great info for the future.

Since I'm serious enough about all this, I've gone ahead and purchased a couple of textbooks (Geometry/Trig). Algebra isn't my weak point, and I did take Geometry in high school, but it's going to be a great idea to have a refresher on all my past mathematics courses. Even if it takes me a year or so, I'll try and post an update on this.

As I get deeper and deeper into modding, I'm finding it easier to do certain things, but my lack of math skills will always be a hindrance. It's time to remedy that ;-)
 
Ooh, this is an interesting one! I'll try to help as much as I can without giving the game away. ;)

Okay, so what you want is a projectile to move in a sine wave. Now, broadly speaking, there are two ways to do this:
  1. Manually update the position of the projectile each frame.
  2. Update the velocity of the projectile, which then moves the projectile.
Both of these are valid ways of going about it, but I'd personally prefer the second one, as it's more natural and also has the added benefit of working with velocity multipliers (i.e. going in and out of water). That's purely preference, though!

The largest problem here is getting the sine wave to work in any direction, but for now let's dispense with that and just consider the projectile traveling in a horizontal line. This has two benefits:
  1. All horizontal movement (x) is constant, and all vertical movement (y) is purely from (and therefore perfectly plots) the sine wave.
  2. Because our horizontal movement is constant, we can give it any value we want and not have it impact the sine wave. Even 0!
If we take this as a starting point for the projectile, then for now all we have to do is make our projectile move up and down in a sine wave, and that would give us a basis that we can expand on and won't have to modify to get the rest working. So let's start with that. Because this post is very wall-of-texty, I've broken down the different parts of this... well, essay, into different sections.
If we're going to go the "update position" road, then our position is very simple sin(something). This something needs to be something that linearly increases (or decreases) every frame. There are a couple of candidates, but since I'm not sure how much you want spoiled, I'll leave that one up in the air for now (if you get stuck, do ask though).

However, if we're doing velocities, would sin(something) also work? Yes, and no. While it would plot a sine wave, it wouldn't plot the same sine wave as the one above: the wave it plots starts at the highest point of the sine wave, rather than in the middle (which would look weird on projectiles). This is because velocity(x, y) is the derivative of position(x, y). If that doesn't make sense, consider it like this:

Imagine our projectile has just been spawned, and it starts in the 'middle' of the sine wave, going up. At that point, it's y-position would be 0. It's y-velocity, however, is 1, because it's at its maximum speed going up (if you look at a sine wave, you'll see that at this point, the slope upwards is the steepest). When the sine wave crests, its y-position is 1 (the highest point), but the y-velocity is 0. Going down again, at the mid point y-position is 0 again, but y-velocity is -1. And at last at the trench, y-position is -1, and y-velocity is 0 again. So as you can see, position and velocity are both sine waves, but they're out of sync. How much out of sync? Well, if velocity is the derivative of position, and position is a sine wave, then velocity is the derivative of a sine wave. Commonly known as... the cosine!

So, if we want velocity to plot a sine wave, we simply use the cosine (cos(something)). For a nice sine wavy function, that's all we need! However, there are two more aspects we can change: the period and the amplitude. If you're not seeing a nice clean sine wave, it's likely these are either too low or too high.
  • The period is how long the projectile takes to oscillate (read: go up and down). Given the formula sin(something * x), we can decrease (speed up) the period by increasing x (e.g. 2), and increase (slow down) the period by decreasing x (e.g 0.5).
  • The amplitude is how high (and low) the wave reaches. Given the formula sin(something) * x, we can increase the amplitude by increasing x, and decrease (flatten) the amplitude by decreasing x.
So there you have it, the second hardest part of making a sine waving projectile: actually getting the sine wave running. :p
This part is so simple it doesn't even warrant its own spoiler block, but hey, consistency is king!

As we said before, our vertical movement (whether regulated by position or velocity) is all sine wave, and our horizontal movement is constant. If we're using velocities, then all we have to do is set x-velocity to a constant. If we're using positions, then we'd have to add a constant value to our previous position every frame. Both of these are rather simple, so I won't dwell on this any longer.
Now comes the difficult part. Right now our projectile can only move in a horizontal line, which isn't ideal if we want to be able to fire in any direction. How do we ensure omnidirectionality?

Let's first consider what we already have. It moves in a straight line to the right. Can we also move it in a straight line to the left?

The answer is a very simple "yes". All we have to do is invert the x-velocity and the projectile moves in that same straight line, but in the other direction. We don't even have to touch the y-velocity.

Now, if we can turn our projectile 180 degrees, what about 90. Could we fire straight up?

If we compare a sine wave going to the right and a sine wave going upwards, what exactly is the difference?
  1. The sine wave going to the right has a constant x-velocity and a sine y-velocity.
  2. The sine wave going upwards has a sine x-velocity and a constant y-velocity.
As you can see, the only difference between the two is that their x and y velocities have been swapped around (one small but vital difference that's hard to spot in this particular case is also that y has been negated during the swap, so x.up = -y.right). And if we can invert the horizontal wave, then we can also do the same for the vertical wave. Meaning that we now have four directions we can move in: right, left, up and down. Progress, but still only four directions. What if we wanted to fire diagonally right and up at a 45 degree angle?

If we thought about this really basically, we'd just say we take half of the right-traveling wave and half of the upward-traveling wave. Would that actually work? Yes, it turns out! If we only take the 'amount' of wave in each direction that we need, we can point our projectile in any direction!
Of course this is easier said than done, as we still need to implement it, but that's all code, and as you mentioned, you prefer to do that yourself. This is really only the theory, and I did intentionally leave out a couple of things, but if you get stuck, I'm always happy to help out!
I randomly found this thread when looking for something like this. Is there a way you might be able to explain how to do the rotational sine wave in a bit more detail? Perhaps provide some examples?
 
Back
Top Bottom