Προς το περιεχόμενο

Game Programming: AI direction


Προτεινόμενες αναρτήσεις

Δημοσ.

Έχω φτιάξει ένα AI που κινείται αυτόματα προς την κατεύθυνση του "εχθρού" με χρήση των atan2(), cos() και sin()...

// aix,aiy και tx,ty είναι οι 2Δ θέσεις του ΑΙ και του targeted "εχθρού", αντίστοιχα
float angleFromTarget = atan2f( ty-aiy, tx-aix );    // rads: [-PI, PI]
aix += xspeed * cosf(angleFromTarget);
aiy += yspeed * sinf(angleFromTarget);
Το πρόβλημά μου είναι πως θέλω να αποθηκεύσω την κατεύθυνση της κίνησης του AI και ως μια από τις τιμές του παρακάτω enumeration...

#define VALID_DIRECTION(dir)	((dir) > NO_DIRECTION && (dir) < MAX_DIRECTIONS )
enum Direction {
	NO_DIRECTION = -1,

	SOUTH        = 0,
	SOUTH_WEST,
	WEST,
	NORTH_WEST,
	NORTH,
	NORTH_EAST,
	EAST,
	SOUTH_EAST,

	MAX_DIRECTIONS
};
Υπάρχει κάποιος γρήγορος τρόπος; Προς το παρόν κάνω normalize χειροκίνητα τα rads που επιστρέφει η atan2f() από [-π, π], σε μοίρες [0,360] με origin την πάνω-αριστερή γωνιά (δηλαδή το 0,0 να είναι πάνω αριστερά κι όχι στο κέντρο της οθόνης), τις οποίες περνάω μετά σε μια switch...

	float deg = rads_to_degrees( angleFromTarget ); // degrees [-180, 180]
	float normDegrees = deg <= 0			// normalized: [0, 360]
				? fmodf(360.f - deg, 360.f)
				: 360.f - deg;
	enum Direction dir = degrees_to_direction(normDegrees);
	...

/* --------------------------------------------- */
enum Direction degrees_to_direction( float deg )
{
	if ( FLOAT_IS_ZERO(deg) || FLOAT_EQUAL(deg, 360) ) {
		return EAST;
	}
	if ( deg < 90 ) {
		return NORTH_EAST;
	}
	if ( FLOAT_EQUAL(deg, 90) ) {
		return NORTH;
	}
	if ( deg < 180 ) {
		return NORTH_WEST;
	}
	if ( FLOAT_EQUAL(deg, 180) ) {
		return WEST;
	}
	if ( deg < 270 ) {
		return SOUTH_WEST;
	}
	if ( FLOAT_EQUAL(deg, 270) ) {
		return SOUTH;
	}
	if ( deg < 360) {
		return SOUTH_EAST;
	}

	return NO_DIRECTION;
}
Αφενός έχω την αίσθηση πως το normalization που κάνω είναι αχρείαστα πολύπλοκο (δεν κατάφερα να βρω κάποιον καλύτερο τύπο αυτή τη στιγμή) και αφετέρου όλες αυτές οι μετατροπές γίνονται μέσα στο game-loop για κάθε AI, οπότε αναρωτιέμαι μήπως υπάρχει πιο απλός/ελαφρύς τρόπος να πάρω το αποτέλεσμα που θέλω.

 

ΥΓ. Σημείωση πως ακόμα δεν χρησιμοποιώ vectors για τους 2D υπολογισμούς, αφενός διότι ακόμα πειραματίζομαι με την Allegro (με αργούς αλλά σταθερούς ρυθμούς :P) και αφετέρου διότι δεν θυμάμαι Χριστό από vectors (σίγουρα όμως θα πάω σε vectors όταν ξεκινήσω κανονικά). Μπορώ το παραπάνω να το απλοποιήσω με χρήση vectors?

Δημοσ.

Η μόνη απλούστερη προσέγγιση από αυτό που κάνεις, είναι να διαχωρίσεις τελείως την κίνηση στους 2 άξονες. Κάθε αντικείμενο έχει standard vertical και horizontal speeds.

 

Σε κάθε Loop, όπου αυτό χρειάζεται, τσεκάρεις το position του αντικειμένου στον κάθε άξονα σε σχέση με αυτού του εχθρού:

 

Αν το χ μου είναι μικρότερο από το enemy_x τότε θα το αυξήσω όσο το hor_speed και το αντίθετο

Ομοίως και για το y.

 

Η κατεύθυνση του Α.Ι. με αυτό τον τρόπο μπορεί να έχει μόνο 8 τιμές και προκύπτει άμεσα από την παραπάνω διαδικασία.

 

Για να μπορείς να κλειδώσεις σε ένα Position, όταν αυτό χρειάζεται, ελέγχεις πάντα (πριν γίνει η κίνηση) την τιμή του abs(x-enemy_x) και abs (y-enemy_y). Αν αυτά είναι μικρότερα των hor_speed και v_speed αντίστοιχα, τότε κλείνεις το κάθε loop (αφού έχεις φτάσει σε επιθυμητό Position) με x=enemy_x και y = enemy_y

 

Προφανώς και το αποτέλεσμα δεν θα είναι ότι πιο ρεαλιστικό... Αν θες κάτι τέτοιο θα μπορούσες να επηρεάζεις αναλόγως τα hor_speed και ver_speed. Αυτό μπορείς να το κάνεις χειροκίνητα και κατά προσέγγιση. Έτσι θα έχεις κίνηση που δεν είναι ακριβής, αλλά θα αποφύγεις την χρήση directions.

 

Υ.Γ. Πάντως τα vectors είναι η καλύτερη λύση imho.

Δημοσ.

Αυτα που ειπαν οι απο πανω εκτος και αν εχεις specific game design η κινηση να γινεται μονο οριζοντια/καθετα/διαγωνια (σαν να κινειται σε grid). Σε αυτη τη περιπτωση εισαι οκ οπως εισαι. Αν θες να τριχισεις την τριχια, μπορεις να παρακαμψεις την μετατροπη απο rads σε degrees (φτιαξε δηλ μια αντιστοιχη μεθοδο rads_to_direction και  πασαρε της το angleToTarget κατευθειαν)

Δημοσ.

Ευχαριστώ για τις απαντήσεις!

 

Να κάνω όμως μια διευκρίνηση, το θέμα μου αυτή τη στιγμή δεν είναι πως θα κινήσω το AI, αλλά πως θα μετατρέψω την κατεύθυνση της κίνησής του στην αντίστοιχη τιμή του enumeration. Πείτε δηλαδή για παράδειγμα πως θέλω να έχω ορατή μια ας την πούμε πυξίδα που θα αναγράφει προς ποια κατεύθυνση κινήθηκε το AI (Ν, NE, Ε, SE, S, SW, W, NW).

 

Για να το οπτικοποιήσω ανεξάρτητα από το υπόλοιπο game, έγραψα στα γρήγορα ένα test.c σε κονσόλα που φτιάχνει ένα πλέγμα, πετάει μέσα 10 τυχαία σημεία [0,9], και δεξιά από το πλέγμα αναγράφει για το κάθε σημείο τη γωνία και τον προσανατολισμό του σε σχέση με το κέντρο του πλέγματος (τη γωνία τη δείχνει πρώτα σε rads όπως την επιστρέφει η atan2f() και μετά σε normalized μοίρες). Αυτό που ρωτάω όμως στο νήμα είναι για την τελευταία στήλη, τον προσανατολισμό.

 

0.............................  0.( 0, 0) angle:-2.509 (143.746) NW
..............................  1.(19,19) angle: 1.107 (296.565) SE
..............................  2.(15,14) angle: 1.571 (270.000) S
..6...........................  3.(18,12) angle: 0.322 (341.565) SE
..............................  4.(14, 9) angle:-2.034 (116.565) NW
...........5..................  5.(11, 5) angle:-2.159 (123.690) NW
..............................  6.( 2, 3) angle:-2.590 (148.393) NW
..............................  7.( 6,20) angle: 2.356 (225.000) SW
.............9................  8.( 7,10) angle:-3.017 (172.875) NW
..............4...............  9.(13, 8) angle:-2.159 (123.690) NW
.......8......................
...............+..............
..................3...........
..............................
...............2..............
..............................
..............................
..............................
..............................
...................1..........
......7.......................
..............................
Center: +(15,11)
Βάζω και τον κώδικα σε spoiler, απλά αν έχει κανείς την περιέργεια (γιατί αλλιώς δεν είναι κρίσιμος).

 

 

#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <time.h>
#include <windows.h>
#include <stdarg.h>

#define NPOINTS			10
#define GRID_NCOLS		30
#define GRID_NROWS		22
#define GRID_LEN		(GRID_NCOLS) * (GRID_NROWS)
#define CH_EMPTY_CELL		'.'
#define CH_CENTER		'+'

#define PI			3.141592653589f

#define FLOAT_DELTA		0.01f
#define FLOAT_IS_ZERO( f )	( fabsf( (float)(f) ) <= FLOAT_DELTA )
#define FLOAT_EQUAL( f1, f2 )	FLOAT_IS_ZERO( (f1)-(f2) )

#define FLOAT_LESS_EQUAL( f1, f2 )	\
	( (float)(f1) < (float)(f2) || FLOAT_EQUAL((f1),(f2)) )

#define VALID_DIRECTION(dir)	((dir) > NO_DIRECTION && (dir) < MAX_DIRECTIONS )
enum Direction {
	NO_DIRECTION = -1,

	SOUTH        = 0,
	SOUTH_WEST,
	WEST,
	NORTH_WEST,
	NORTH,
	NORTH_EAST,
	EAST,
	SOUTH_EAST,

	MAX_DIRECTIONS
};

char *dirLabels[MAX_DIRECTIONS] = {
	(char *)"S",
	(char *)"SW",
	(char *)"W",
	(char *)"NW",
	(char *)"N",
	(char *)"NE",
	(char *)"E",
	(char *)"SE"
};

/* --------------------------------------------- */
void direction_print( enum Direction dir )
{
	if ( VALID_DIRECTION(dir) ) {
		puts( dirLabels[dir] );
	}
}
/* --------------------------------------------- */
void gotoXY( int x, int y )
{
	COORD temp = {.X=x, .Y=y};
	if ( !SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), temp) ) {
		return;
	}
}
/* --------------------------------------------- */
void printfXY( int x, int y, const char *format, ... )
{
	va_list argptr;

	va_start(argptr, format);
	gotoXY(x,y);
	vprintf(format, argptr);
	fflush(stdout);

	va_end(argptr);
}
/* --------------------------------------------- */
enum Direction degrees_to_direction( float deg )
{
	if ( FLOAT_IS_ZERO(deg) || FLOAT_EQUAL(deg, 360) ) {
		return EAST;
	}
	if ( deg < 90 ) {
		return NORTH_EAST;
	}
	if ( FLOAT_EQUAL(deg, 90) ) {
		return NORTH;
	}
	if ( deg < 180 ) {
		return NORTH_WEST;
	}
	if ( FLOAT_EQUAL(deg, 180) ) {
		return WEST;
	}
	if ( deg < 270 ) {
		return SOUTH_WEST;
	}
	if ( FLOAT_EQUAL(deg, 270) ) {
		return SOUTH;
	}
	if ( deg < 360) {
		return SOUTH_EAST;
	}

	return NO_DIRECTION;
}
/* --------------------------------------------- */
float rads_to_degrees( float rads )
{
	return rads * (180.f / PI);
}
/* --------------------------------------------- */
bool grid_printXY( int x, int y, int grid[GRID_LEN] )
{
	if ( !grid )
		return false;

	gotoXY(x,y);
	for (int i=0; i < GRID_LEN; i++) {
		if ( 0 != i && 0 == i % GRID_NCOLS )
			putchar('\n');
		putchar(grid[i]);
	}
	putchar('\n');

	return true;
}
/* --------------------------------------------- */
bool grid_set_xy( int grid[GRID_LEN], int x, int y, int c )
{
	int n = y * GRID_NCOLS + x;

	if ( !grid || n < 0 )
		return false;

	grid[n] = c;
	return true;
}
/* --------------------------------------------- */
float grid_get_center_x( int grid[GRID_LEN] )
{
	if ( !grid )
		return FLT_MAX;

	return GRID_NCOLS / 2.f;
}
/* --------------------------------------------- */
float grid_get_center_y( int grid[GRID_LEN] )
{
	if ( !grid )
		return FLT_MAX;

	return GRID_NROWS / 2.f;
}
/* --------------------------------------------- */
bool grid_init( int grid[GRID_LEN], int c, int o )
{
	if ( !grid )
		return false;

	for (int i=0; i < GRID_LEN; i++)
		grid[i] = c;
	grid[ (GRID_NROWS/2) * GRID_NCOLS + (GRID_NCOLS/2) ] = o;

	return true;
}
/* --------------------------------------------- */
int main(void)
{
	int grid[GRID_LEN] = {'\0'};

	float tx,  ty;					// target x,y
	float ox = grid_get_center_x(grid);
	float oy = grid_get_center_y(grid);

	srand(time(NULL));

	grid_init( grid, CH_EMPTY_CELL, CH_CENTER );
	grid_printXY( 0,0, grid );

	printfXY( 0, GRID_NROWS, "Center: +(%g,%g)\n", ox, oy );

	for (int i=0; i < NPOINTS; i++)
	{
		// set random point in grid (avoid collisions)
		for(; {
			tx = (float)(rand() % GRID_NCOLS);
			ty = (float)(rand() % GRID_NROWS);
			int n = (int)tx * GRID_NCOLS + (int)ty;
			if ( CH_EMPTY_CELL != grid[n] ) {
				continue;
			}
			grid_set_xy(grid, tx,ty, i+'0');
			break;
			
		};

		// calc point's direction compared to the center of the grid
		float angleFromTarget = atan2f( ty-oy, tx-ox );	// rads: [-PI, PI]
		float deg = rads_to_degrees( angleFromTarget ); // degrees [-180, 180]
		float normDegrees = deg <= 0			// normalized: [0, 360]
				? fmodf(360.f - deg, 360.f)
				: 360.f - deg;
		enum Direction dir = degrees_to_direction(normDegrees);

		// report point's information
		printfXY(
			2+GRID_NCOLS, i,
			"%d.(%2.0f,%2.0f) "
			"angle:%6.3f (%6.3f)"
			" %s",
			i, tx, ty,
			angleFromTarget, normDegrees,
			VALID_DIRECTION(dir) ? dirLabels[dir] : "--"
			);
	}
	grid_printXY( 0,0, grid );

	getchar();
	return 0;
}

 

Οπότε φίλε Alchemist, αν κατάλαβα σωστά αυτό που προτείνεις ως εναλλακτική για την μετατροπή της κατεύθυνσης σε enumerated τιμή είναι μια ακολουθία από if-else για χειροκίνητη σύγκριση των x & y των 2 αντικειμένων, κάτι δηλαδή σαν το παρακάτω (αγνόησε τη χρήση int αντί για float);

	const int EPSILON = 1;
	int ax	= plr2d_get_sprite_x( ai->plr2d );
	int ay	= plr2d_get_sprite_y( ai->plr2d );
	//float aw	= plr2d_get_sprite_width( ai->plr2d );
	//float ah	= plr2d_get_sprite_height( ai->plr2d );

	int   xdiff = abs(tx - ax);
	int   ydiff = abs(ty - ay);
	float dist = sqrtf( powf(xdiff,2) + powf(ydiff,2) );

	if ( dist < tw || dist < th ) {
		return SYS2D_NO_DIRECTION;
	}

	if ( xdiff <= EPSILON && ty < ay ) {
		return SYS2D_NORTH;
	}
	if ( xdiff <= EPSILON && ty > ay ) {
		return SYS2D_SOUTH;
	}
	if ( ydiff < EPSILON && tx < ax ) {
		return SYS2D_WEST;
	}
	if ( ydiff < EPSILON && tx > ax ) {
		return SYS2D_EAST;
	}

	if ( tx < ax && ty < ay ) {
		return SYS2D_NORTH_WEST;
	}
	if ( tx < ax && ty > ay ) {
		return SYS2D_SOUTH_WEST;
	}
	if ( tx > ax && ty < ay ) {
		return SYS2D_NORTH_EAST;
	}
	if ( tx > ax && ty > ay ) {
		return SYS2D_SOUTH_EAST;
	}

	return SYS2D_NO_DIRECTION;
Αν ναι, ήλπιζα μήπως υπήρχε κάποιος πιο αυτοματοποιημένος και ενδεχομένως πιο ελαφρύς τρόπος από αυτούς τους 2 (δεν ξέρω, π.χ. κάποιος μαθηματικός τύπος που να παίρνει τα ax,ay,tx,ty, να μας επιστρέφει τον προσανατολισμό ας πούμε ως μια float ή int τιμή και κατόπιν να κάναμε αυτή την τιμή switch ή if-else).

 

@Georgemarios, το έχω δοκιμάσει κι αυτό που λες...

 

 

/* --------------------------------------------- */
enum Direction atan2f_to_direction( float rads )
{
	const float NINETY       = PI / 2.f;
	const float ONE_EIGHTY   = PI;
	const float TWO_SEVENTY  = ONE_EIGHTY + NINETY;
	const float THREE_SIXTY  = PI + PI;
	rads += (PI + FLOAT_DELTA);

	if ( FLOAT_IS_ZERO(rads) || FLOAT_EQUAL(rads, THREE_SIXTY) ) {
		return WEST;
	}
	if ( rads < NINETY ) {
		return NORTH_WEST;
	}
	if ( FLOAT_EQUAL(rads, NINETY) ) {
		return NORTH;
	}
	if ( rads < ONE_EIGHTY ) {
		return NORTH_EAST;
	}
	if ( FLOAT_EQUAL(rads, ONE_EIGHTY) ) {
		return EAST;
	}
	if ( rads < TWO_SEVENTY ) {
		return SOUTH_EAST;
	}
	if ( FLOAT_EQUAL(rads, TWO_SEVENTY) ) {
		return SOUTH;
	}
	if ( rads < THREE_SIXTY ) {
		return SOUTH_WEST;
	}

	return NO_DIRECTION;
}

 

Αυτό προϋποθέτει την ακόλουθη αλλαγή...

		float angleFromTarget = atan2f( ty-oy, tx-ox );	// rads: [-PI, PI]
#if 1
		enum Direction dir = atan2f_to_direction(angleFromTarget);
#else
		float deg = rads_to_degrees( angleFromTarget ); // degrees [-180, 180]
		float normDegrees = deg <= 0			// normalized: [0, 360]
				? fmodf(360.f - deg, 360.f)
				: 360.f - deg;
		enum Direction dir = degrees_to_direction(normDegrees);
#endif
Δείχνει να είναι αρκετά έως πολύ πιο ελαφρύ (δεν το έχω μετρήσει) και τελικά αν δεν υπάρχει καλύτερος τρόπος αυτό μάλλον θα χρησιμοποιήσω.

 

Απλώς αν τελικά χρειαστώ έτσι κι αλλιώς τη μετατροπή από rads σε degrees (πράγμα πολύ πιθανό, π.χ. το Cocos2D API αν θυμάμαι καλά δουλεύει με μοίρες αν το θυμάμαι καλά) τότε θα πρέπει να καταφύγω στον τρόπο του αρχικού μου ποστ που μου φαντάζει πολύπλοκος και βαρύς (αν δεν υπάρχει όμως κάτι καλύτερο, so be it).

 

@παπι, @Alchemist:, πως μπορώ να μετατρέψω την φορά ενός διανύσματος σε repored text? (North, South, κλπ)?

Δημοσ.

Δες εδω το... απορω ακομα τι σκατα σκεφτομουν και το εγραψα.. http://www.insomnia.gr/topic/477835-share-3d-console/ να παρεις μερικες ιδεες

Ωραίο, του ριξα μια ματιά αλλά δεν κατάλαβα που βρίσκεται η απάντηση στην ερώτησή μου (είπαμε, δεν θυμάμαι Χριστό από διανύσματα :P).

 

Η απορία μου είναι πως μπορεί κανείς να μετατρέψει τη φορά ενός διανύσματος (direction) σε προσανατολισμό (orientation) συγκριτικά με το world-space. Σε ποιο σημείο του κώδικα που έδωσες κάνεις κάτι τέτοιο;

Δημοσ.

Kαι με το δυανυσμα παλι θα πρεπει να τσεκαρεις τις συντεταγμενες του.....

Εχεις 8 κατευθυνσεις, θες να τσεκαρεις 8 περιπτωσεις αρα θες 8 if.....

Δημοσ.

Ωραίο, του ριξα μια ματιά αλλά δεν κατάλαβα που βρίσκεται η απάντηση στην ερώτησή μου (είπαμε, δεν θυμάμαι Χριστό από διανύσματα :P).

 

Η απορία μου είναι πως μπορεί κανείς να μετατρέψει τη φορά ενός διανύσματος (direction) σε προσανατολισμό (orientation) συγκριτικά με το world-space. Σε ποιο σημείο του κώδικα που έδωσες κάνεις κάτι τέτοιο;

Α, δεν εχει καμια απαντηση. Απλα, σου το πλασαρω πλαγιως, φτιαχτω πανω σε διανυσματα.

Δημοσ.

Δεν είμαι σίγουρος ότι έχω καταλάβει καλά και είναι λίγο θέμα ότι η γωνία είναι float αλλά αν ήταν int (σε μοιρές) τότε μπορείς να πεις:

dir = 2*(deg/90) + (deg%90 == 0?0:1);

όπου dir η κατεύθυνση και deg η γωνία σε μοίρες...

 

EDIT: Γράψε λάθος... Άλλο είχα καταλάβει... :)

Δημοσ.

Kαι με το δυανυσμα παλι θα πρεπει να τσεκαρεις τις συντεταγμενες του.....

Εχεις 8 κατευθυνσεις, θες να τσεκαρεις 8 περιπτωσεις αρα θες 8 if.....

Οπότε από ότι καταλαβαίνω, καταλήγουμε πως δεν υπάρχει καλύτερος τρόπος από αυτόν που χρησιμοποιώ ήδη.

 

Παρεμπιπτόντως, ευχαριστώ όλους για τις απαντήσεις.

 

@παπι, με διανύσματα θα το κάνω το κανονικό. Προς το παρόν μαθαίνω την Allegro 5 (και ταυτόχρονα φτιάχνω κούτσου-κούτσου και κάνα-δυο abstraction layers, μπας και με διευκολύνει αν αποφασίσω να αλλάξω βιβλιοθήκη).

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...