Making an enemy move in a sine wave pattern

Started by
12 comments, last by popcorn 13 years, 9 months ago
This post is related to this post.

How do I make an enemy move in the sine/cos wave pattern?
As before I'm using VC++ 2008 express and SDL.

I would be grateful if someone could explain the maths to me as I'm not very good at this stuff.
How about them apples?
Advertisement
x = cos(theta), y = sin(theta), theta = omega*time, omega = 2*pi*frequency ...

Have you read this: Trigonometric functions? or angular frequency? or simple harmonic motion?
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Ok I appreciate the links but I guess I need to be eased into this sort of stuff because even just a quick look at the forumlas and some of the langauge used in those articles tells me I'm going to struggle.

I mean something like this

Quote:
In ordinary Euclidean geometry, according to the triangle postulate the inside angles of every triangle total 180° (π radians).


just goes way over my head.

If I must do all this myself then I really need some lessons on basic high school maths. So if you know of sites that are at that level then I guess it would be more helpful.

Ultimately I just need to know if my enemy has a starting coordinate of say x = 10, y = 10, then where should the next position be until it either goes out the screen or it dies.
How about them apples?
I'd start with the equation in the article on harmonic motion that LessBread linked to. To keep it simple, we'll take out the phase, leaving:
offset(time) = amplitude*sin(2pi*frequency*time)
I used 'sine' instead of 'cosine' here so that at time = 0 (i.e. the beginning of the entity's motion), the offset will be zero rather than 'A'.

In this function, 'amplitude' and 'frequency' are tunable parameters; in other words, you can set them to whatever values give you the results you're after. The higher the magnitude of 'amplitude', the more the entity will move 'back and forth'; the sign of 'amplitude' will determine which direction the entity moves initially (e.g. left/right or up/down). The value of 'frequency' will determine how fast the entity moves back and forth. Finally, 'time' should (most likely) be the elapsed time in seconds since the entity began moving.

Assuming the entity's motion is along one of the Cardinal axes, then the above equation will determine the offset relative to its original X or Y coordinate, and the simple equation:
distance = time * speed
Will give you the offset in the other coordinate (where 'speed' determines how fast the entity will move 'forward').

As an example, if this were a top-to-bottom scroller with +y pointing down, you might determine the current xy position for an entity as follows:
x = start_x + offset(time);y = start_y + distance(time);
Gave this a go, I have a feeling there is something wrong though, can somebody check through this please?

Heres what it looks like:

amplitude = 1, frequency = 1, speed = 1



It can be tried here.

The source code is below:

#include "SDL.h"#include "SDL_ttf.h"#include "Enemy.h"#include "Timer.h"#include <cmath>#include <iostream>using namespace std;const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;const int SCREEN_BPP = 32;double newXPos(int amplitude, int frequency, int milliSecs){	double xPos = amplitude * sin(2 * 3.14 * frequency * (milliSecs /1000));	return xPos;}double newYPos(int milliSecs, int speed){	double yPos = (milliSecs / 1000) * speed;	return yPos;}int main( int argc, char* args[] ){    //Start SDL    SDL_Init(SDL_INIT_EVERYTHING);	SDL_Surface* screen = NULL;	screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);	SDL_WM_SetCaption("Sine Wave Test", NULL);		SDL_Event event;	bool quit = false;	Enemy enemy(300, 0, 1);	Timer timer;	double x = 0;	double y = 0;	timer.start();	while(!quit)	{		while(SDL_PollEvent(&event))		{			if(event.type == SDL_QUIT)			{				quit = true;			}    		}		//cout << secs << endl;		enemy.draw();		x = enemy.getX() + newXPos(1, 1, timer.getTicks());		y = enemy.getY() + newYPos(timer.getTicks(), enemy.getSpeed());		cout << "x: " << x << endl;		cout << "y: " << y << endl;		enemy.setXY(x, y);		SDL_Flip(screen);	}    //Quit SDL    SDL_Quit();        return 0;    }


Full source
here.
How about them apples?
Quote:Original post by popcorn
Ok I appreciate the links but I guess I need to be eased into this sort of stuff because even just a quick look at the forumlas and some of the langauge used in those articles tells me I'm going to struggle.


Sorry, but your question was very broad, so I thought some background might help.

Quote:Original post by popcorn
I mean something like this

Quote:
In ordinary Euclidean geometry, according to the triangle postulate the inside angles of every triangle total 180° (π radians).


just goes way over my head.


Unfortunately the language there obscures what would otherwise be a simple observation: the sum of the angles of a triangle is two right angles. A right angle is 90°, so the sum of the angles of a triangle is 180°. An inside angle is found inside the area bounded by the triangle.

Radians provide another way of measuring an angle. They express the angle in terms of the arc that the angle cuts out of a unit circle. The symbol typically used to represent the angle is θ, the Greek letter theta. An arc is a segment of the circumference of a circle. The length of an arc is the angle multiplied by the radius or θr. This follows from the fact that the circumference of a circle is 2πr, where r is the radius of the circle. For a unit circle, the radius is 1 and θ*1 = θ.

Euclidean geometry is ordinary geometry, the kind you would study in high school - triangles, proofs and parallel lines. Some Wikipedia entries may use overly formal language, but the articles are full of links to other articles offering further explanation. It's not necessary to absorb everything about everything, just take what you need and leave the rest.

Quote:Original post by popcorn
If I must do all this myself then I really need some lessons on basic high school maths. So if you know of sites that are at that level then I guess it would be more helpful.


I don't, but google does: trignometry

These look fairly good at a glance.

Dave's Short Trig Course
Basic Trigonometry
Trigonometry

Quote:Original post by popcorn
Ultimately I just need to know if my enemy has a starting coordinate of say x = 10, y = 10, then where should the next position be until it either goes out the screen or it dies.


jyk's answer was good.
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Here's your code with some comments added:

#include "SDL.h"#include "SDL_ttf.h"#include "Enemy.h"#include "Timer.h"#include <cmath>#include <iostream>using namespace std;const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;const int SCREEN_BPP = 32;double newXPos(int amplitude, int frequency, int milliSecs){    //double xPos = amplitude * sin(2 * 3.14 * frequency * (milliSecs /1000));    //return xPos;        // The above can just be written as:    return amplitude * sin(2 * 3.14 * frequency * (milliSecs /1000));        // Also, make sure your 'seconds' value is actually being evaluated to    // a floating-point type here and elsewhere. (As is, it looks like it will    // evaluate to an integer, which will almost certainly make the results    // incorrect in the general case.)        // Finally, I'd recommend using a named constant for pi (and for 2*pi), and    // increasing the precision as well.}double newYPos(int milliSecs, int speed){    // Same as above...    double yPos = (milliSecs / 1000) * speed;    return yPos;}int main( int argc, char* args[] ){    //Start SDL    SDL_Init(SDL_INIT_EVERYTHING);    SDL_Surface* screen = NULL;    screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);    SDL_WM_SetCaption("Sine Wave Test", NULL);        SDL_Event event;    bool quit = false;    Enemy enemy(300, 0, 1);    Timer timer;    double x = 0;    double y = 0;    timer.start();    while(!quit)    {        while(SDL_PollEvent(&event))        {            if(event.type == SDL_QUIT)            {                quit = true;            }            }        //cout << secs << endl;        enemy.draw();        // Take another look at my earlier post. The functions above are        // intended to generate offsets relative to an absolute starting        // position, not offsets relative to the current position. As        // such, this:        /*        x = enemy.getX() + newXPos(1, 1, timer.getTicks());        y = enemy.getY() + newYPos(timer.getTicks(), enemy.getSpeed());        cout << "x: " << x << endl;        cout << "y: " << y << endl;        enemy.setXY(x, y);        */                // Should probably be (you'll have to declare and initialize        // 'start_x' and 'start_y' as appropriate):        enemy.setXY(            start_x + newXPos(1, 1, timer.getTicks()),            start_y + newYPos(timer.getTicks(), enemy.getSpeed())        );        SDL_Flip(screen);    }    //Quit SDL    SDL_Quit();        return 0;    }
Try using a higher frequency, like 440, and try using a larger amplitude, like 10. Incidentally, 440 Hz is the frequency of A below middle C on the piano. The human ear generally can't detect sounds below 20 Hz or above 20,000 Hz.
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Ok I made the changes to the code according to jyk's post:
#include "SDL.h"#include "SDL_ttf.h"#include "Enemy.h"#include "Timer.h"#include <cmath>#include <iostream>using namespace std;const int SCREEN_WIDTH = 640;const int SCREEN_HEIGHT = 480;const int SCREEN_BPP = 32;double newXPos(int amplitude, int frequency, double milliSecs){	double xPos = amplitude * sin(2 * 3.14 * frequency * (milliSecs /1000));	return xPos;}double newYPos(int milliSecs, double speed){	double yPos = (milliSecs / 1000) * speed;	return yPos;}int main( int argc, char* args[] ){    //Start SDL    SDL_Init(SDL_INIT_EVERYTHING);	SDL_Surface* screen = NULL;	screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);	SDL_WM_SetCaption("Sine Wave Test", NULL);		SDL_Event event;	bool quit = false;	Enemy enemy(300, 0, 1);	Timer timer;	int x = 0;	int y = 0;	double startX = enemy.getX();	double startY = enemy.getY();	timer.start();	while(!quit)	{		while(SDL_PollEvent(&event))		{			if(event.type == SDL_QUIT)			{				quit = true;			}    		}		enemy.draw();		cout << "\tx: " << x << endl;		cout << "y: " << y << endl;		x = startX + newXPos(200, 100, timer.getTicks());		y = startY + newYPos(timer.getTicks(), enemy.getSpeed());		enemy.setXY(x, y);		SDL_Flip(screen);	}    //Quit SDL    SDL_Quit();        return 0;    }


amplitude = 200, frequency = 100, speed = 50

I get this:



Not sure if this is correct or not but I think it looks vaguely like a sin wave???

Incidentally if I use offsets relative to the current position and have

amplitude = 10, frequency = 1, speed = 1

I get this:



which looks better but is probably not right.
How about them apples?
I suggest re-reading my comments; there's at least one important point that you missed.

That's assuming, of course, that I'm right about what's causing the problems that you're seeing, but from the screenshots you posted, I imagine my guess is correct. (I'm guessing you'll also want a lower frequency, like in your second example.)

Note that once your code is fixed, applying the offsets incrementally will still give you something like in the second example. Although that's not exactly how the code is intended to be used, it does show that you can easily come up with interesting patterns by applying these functions in different ways.

This topic is closed to new replies.

Advertisement