Gå til innhold

C#: Hvordan hitteste riktig med 8 retninger?


Anbefalte innlegg

OK, har nå hatt dette problemet ganske lenge uten ha funnet en løsning. Situasjonen: Jeg holder på med et TopDown RPG i 2D, hvor spilleren skal kunne bevege seg i åtte himmelretninger. Spilleren må selvsagt ikke kunne gå igjennom de hindringer som er på "banen", og jeg trenger derfor hittesting. Dette er så langt jeg har kommet:

//Import
[DllImport("user32")]
	public static extern int IntersectRect(ref RECT lpDestRect, ref RECT lpSrc1Rect, ref RECT lpSrc2Rect);
......
.......

public static void HittestPlayer(Form world, Rectangle[] obstacles, int numOfObstacles,ref Rectangle Player, int playerDir)
	{
// Hittesting av de ytre grensene på "Banen".
		if (Player.Location.Y < 53)// 53 = StatusPanel.Height ( Statuspanelet befinner seg på toppen av banen)
			Player.Y = 53;
		else if (Player.Location.Y + Player.Size.Height > world.Height)
			Player.Y = world.Height - Player.Size.Height;
		if (Player.Location.X < 0)
			Player.X = 0;
		else if (Player.Location.X + Player.Size.Width > world.Width)
			Player.X = world.Width - Player.Size.Width;

//Hittestingen av hindringene
		RECT dest = new RECT();
		RECT playerRECT = new RECT();
		RECT obstacleRECT = new RECT();
		playerRECT.Top = Player.Y;
		playerRECT.Left = Player.X;
		playerRECT.Bottom = Player.Y + Player.Height;
		playerRECT.Right = Player.X + Player.Width;
		for (int i = 0; i<numOfObstacles;i++)
		{ 
			obstacleRECT.Top = obstacles[i].Y;
			obstacleRECT.Left = obstacles[i].X;
			obstacleRECT.Bottom = obstacles[i].Y + obstacles[i].Height;
			obstacleRECT.Right = obstacles[i].X + obstacles[i].Width;
		  IntersectRect(ref dest,ref playerRECT,ref obstacleRECT);
		  if (dest.Top != 0 && dest.Bottom != 0)
		  {			// 1= N 2=S W=3 E=4 NW=5 SW=6 NE=7 SE=8 
			  switch (playerDir)
			  {
				  case 1:
					  Player.Y = obstacles[i].Location.Y + obstacles[i].Size.Height;
					  break;
				  case 2:
					  Player.Y = obstacles[i].Location.Y -Player.Height;
					  break;
				  case 3:
					  Player.X = obstacles[i].Location.X + obstacles[i].Size.Width;
					  break;
				  case 4:
					  Player.X = obstacles[i].Location.X -Player.Size.Width;
					  break;
				  case 5:// Her starter problemene

					  break;
				  case 6:
					  break;
				  case 7:
					  break;
				  case 8:
					  break;
}
}
}
}

Koden ovenfor virker forsåvidt, men om spilleren beveger seg diagonalt vil han selvsagt gå rett igjennom hindringene. Grunnen til at jeg ikke har lagt til noe i case 5-8, er at jeg ikke vet hvordan jeg skal skille mellom horisontal og vertikal kollisjon mellom spilleren og hindringene, noe som gjør det umulig å sette spillerposisjonen riktig. Vet noen hvordan jeg kan gjøre dette, eller har råd/forslag vil jeg veldig gjerne høre dem.

Lenke til kommentar
Videoannonse
Annonse

Du er klar over at det finnes en Rectangle.Intersects og Rectangle.Contains funksjon? :)

 

Jeg ville ihvertfall bare sjekket hva som er på neste posisjonen spilleren prøver å bevege seg i, og dersom det er noe i f.eks. Y retning, så hindrer du bare spilleren i å bevege seg i den retningen.

Lenke til kommentar

Har nå prøvd det som du sa GeirGrusom, men denne koden virker overhodet ikke av en eller annen grunn:

public enum Direction
	{
		None = 0;
		North = 1,
		South = 2,
		West = 4,
		East = 8,
		NorthWest = North | West,
		SouthWest = South | West,
		NorthEast = North | East,
		SouthEast = South | East
	}
public static void MovePlayer(ref List<Rectangle> Player, int walkingSpeed,int runningSpeed,List<Rectangle> obstacles,int numOfObstacles, ref Direction playerDir, bool hittestObstacles )
	{// 1= N 2=S W=3 E=4 NW=5 SW=6 NE=7 SE=8 
		int YSpeed;
		int XSpeed;
		if (High(GetKeyState((int)Keys.Space)) == 1)
		{
			YSpeed = runningSpeed;
			XSpeed = runningSpeed;
		}
		else
		{
			 YSpeed = walkingSpeed;
			 XSpeed = walkingSpeed;
		}
		if (hittestObstacles)
		{
			foreach (Rectangle obstacle in obstacles)
			{
				if (obstacle.X < Player.Right+XSpeed && obstacle.right > Player.X-XSpeed)
				{
					if (Player.Right  <= obstacle.X)
					{
						XSpeed = obstacle.X - Player.Right;
					}
					else if (Player.X >= obstacle.Right) )// Hvis spilleren ikke kolliderer før han beveger seg ( Fra høyre mot venstre)
					{
						XSpeed = Player.X - obstacle.Right;
					}
					else
					{}
				}
				if (obstacle.Y < Player:Bottom && obstacle.Bottom > Player.Y)
				{
					if (Player.Bottom  <= obstacle.Y)
					{
						YSpeed = obstacles[i].Y - Player.Bottom;
					}
					else if (Player.Y >= obstacle.Bottom )
					{
						YSpeed = Player.Y - obstacle.Bottom;
					}
					else
					{}
				}
			}
		}
		playerDir = Direction.None;
		if (High(GetKeyState((int)Keys.W)) == 1)
		{
			Player.Y += -1 * YSpeed;
			playerDir = Direction.North;
		}
		if (High(GetKeyState((int)Keys.S)) == 1)
		{
			Player.Y += YSpeed;
			playerDir = Direction.South;
		}
		if (High(GetKeyState((int)Keys.A)) == 1)
		{
			Player.X += -1 * XSpeed;

			if (playerDir == Direction.North)
				playerDir = Direction.NorthWest;
			else if (playerDir == Direction.South)
				playerDir = Direction.SouthWest;
			else
				playerDir = Direction.West;
		}
		if (High(GetKeyState((int)Keys.D)) == 1)
		{
			Player.X += XSpeed;

			if (playerDir == Direction.North)
				playerDir = Direction.NorthEast;
			else if (playerDir == Direction.South)
				playerDir = Direction.SouthEast;
			else
				playerDir = Direction.East;
		}
	}

Hva er det jeg gjør galt?

Edit: Visste forresten ikke om Rectangle.Intersect, takk for tipset =).

Endret av Velena
Lenke til kommentar

Vel, du burde ikke flytte spilleren før du vet at det er trygt å flytte den dit, ellers får du en "spretteffekt" hvor spilleren spretter imellom mulige posisjoner, som kan sees på som ugunstig. Dessuten kan det føre til at spilleren setter seg lett fast i hinder.

 

edit:

Så du burde prøve å regne ut neste posisjon, gi den til en funksjon som deretter sier hvor langt det var mulig å flytte spilleren (ved å for eksempel returnere en gyldig posisjon)

Endret av GeirGrusom
Lenke til kommentar
Vel, du burde ikke flytte spilleren før du vet at det er trygt å flytte den dit, ellers får du en "spretteffekt" hvor spilleren spretter imellom mulige posisjoner, som kan sees på som ugunstig.

Men det gjør jeg da jo heller ikke? Koden var ment å sjekke om spilleren kolliderer i noe hvis han/hun beveger seg, og hvis han/hun gjorde det, trekke ifra avstanden som spilleren kolliderer med fra farten spilleren har i kollisjonsretningen.

edit:

Så du burde prøve å regne ut neste posisjon, gi den til en funksjon som deretter sier hvor langt det var mulig å flytte spilleren (ved å for eksempel returnere en gyldig posisjon)

Som sagt er det dette koden var tiltenkt å gjøre ( bare i en og samme funksjon). Har jeg tenkt feil, ettersom du svarer slik?
Lenke til kommentar

Nei, sikkert ikke, bare jeg som ikke leste godt nok igjennom.

Det ser nogenlunde riktig ut, men koden kan være enklere å få oversikt over dersom du deler den opp i flere funksjoner, og rett og slett gjør koden mindre, da kan det bli enklere å se hva du gjør feil.

 

1:

Bruk List<Rectangle> fremfor Rectangle[] på grunn av effektivitet

2:

Bruk foreach(Rectangle rect in Obstacles) fordi du slipper Array operatøren i hver forbanna linje med kode

3:

Bruk Rectangle.Bottom (og Rectangle.Right) istedet for Rectangle.Y + Rectangle.Height fordi det gjør koden din enklere å lese, som igjen gir deg bedre mulighet til å få oversikt.

4:

Lag gjerne en enumeration istedet for å deklarere verdier i en kommentar for retninger:

 

public enum Direction
{
 North = 1
 South = 2,
 East = 4,
 West = 8,
 NorthEase = North | East,
 NorthWest = North | West,
 SouthEast = South | East,
 SouthWest = South | West
}

 

For å sjekke denne om bit er satt, bruker du & operatøren:

Direction dir = Direction.NorthEast;
if((dir & Direction.East) == Direction.East)
 (...) // East bit er satt
if((dir & Direction.North) == Direction.North)
 (...) // North bit er satt

Endret av GeirGrusom
Lenke til kommentar

Bytex: Jeg bruker da ikke Numpaden i det hele tatt i koden min? Du styrer med WASD, og ja, hvis du trykker f.eks W + A vil spilleren bevege seg i Nord-vestlig retning.

 

GierGrusom: Takker igjen for gode råd. To spørsmål: 1: Hvorfor er List mer effektiv enn Array? 2: Jeg vet fortsatt ikke hva som er galt :p.

 

Edit: Ryddet på koden jeg postet tidligere.

Endret av Velena
Lenke til kommentar
Ikke at dette er et direkte svar på spørsmålet, men om du skal kunne bevege deg i åtte himmelretninger er det ikke da bedre å bruke oktagonale ruter fremfor firkantede?

Godt poeng! men er ikke det litt mer komplisert en nødvendig for et program av denne sorten?

 

Uansett angående arrays så må du nesten bare lese de tekniske artiklene rundt dette som er tilgjengelig på MSDN, det har noe med duplikate arrays og slikt å gjøre.

 

Ok, litt av poenget med å bruke enumen fikk du ikke helt med deg hehe

 

public enum Direction
	{
		None = 0;
		North = 1,
		South = 2,
		West = 4,
		East = 8,
		NorthWest = North | West,
		SouthWest = South | West,
		NorthEast = North | East,
		SouthEast = South | East
	}
public static void MovePlayer(ref Rectangle Player, int walkingSpeed,int runningSpeed, List<Rectangle> obstacles, ref Direction playerDir, bool hittestObstacles )
	{
		int YSpeed;
		int XSpeed;
		if (High(GetKeyState((int)Keys.Space)) == 1)
		{
			YSpeed = runningSpeed;
			XSpeed = runningSpeed;
		}
		else
		{
			 YSpeed = walkingSpeed;
			 XSpeed = walkingSpeed;
		}
		if (hittestObstacles)
		{
			foreach (Rectangle obstacle in obstacles)
			{
				if (obstacle.X < Player.Right+XSpeed && obstacle.right > Player.X-XSpeed)
				{
					if (Player.Right <= obstacle.X)
						XSpeed = obstacle.X - Player.Right;
					else if (Player.X >= obstacle.Right)
						XSpeed = Player.X - obstacle.Right;
				}
				if (obstacle.Y < Player.Bottom && obstacle.Bottom > Player.Y)
				{
					if (Player.Bottom  <= obstacle.Y)
						YSpeed = obstacle.Y - Player.Bottom;
					else if (Player.Y >= obstacle.Bottom )
						YSpeed = Player.Y - obstacle.Bottom;
				}
			}
		}
		playerDir = Direction.None;
		if (High(GetKeyState((int)Keys.W)) == 1)
		{
			Player.Y -= YSpeed;
			playerDir = Direction.North;
		}
		if (High(GetKeyState((int)Keys.S)) == 1)
		{
			Player.Y += YSpeed;
			playerDir = Direction.South;
		}
		if (High(GetKeyState((int)Keys.A)) == 1)
		{
			Player.X -= XSpeed;
			playerDir |= Direction.West;
		}
		if (High(GetKeyState((int)Keys.D)) == 1)
		{
			Player.X += XSpeed;

			playerDir |= Direction.East;
		}
	}

Du må kanskje kjøre en (Player + (XSpeed, YSpeed).Intersect(obstacle) for ellers kan vel dette triggre så lenge spilleren foreksempel er i samme X posisjon, men ikke i Y posisjonen?

Lenke til kommentar
Du må kanskje kjøre en (Player + (XSpeed, YSpeed).Intersect(obstacle) for ellers kan vel dette triggre så lenge spilleren foreksempel er i samme X posisjon, men ikke i Y posisjonen?
Hva mener du med (Player + (XSpeed, YSpeed).Intersect(obstacle))? Mener du at jeg skal legge til posisjonen til Player med så mye som den kolliderer med? Dette virker ikke logisk, hvis f.eks spilleren er 2 enheter fra en obstacle, og vil kollidere med 3 enheter så vil han jo ende opp 1 enhet inne i hindringen.
Lenke til kommentar

Det jeg mente meddet var bare for å sjekke hvor brukeren ville flytte seg, ikke egentlig så veldig relevant til det jeg mente.

 

Men hele idéen var at for hver hindring så må du først sjekke om hindringen faktisk intersecter der spilleren ville havnet før du sjekker om de krysser X eller Y aksene.

 

Får håpe jeg er til hjelp da, og at jeg har lest koden korrekt :S jeg har en tendens til å få dårlig oversikt over andres kode.

Lenke til kommentar

Det er verre når en skal drive mmed polygonkollisjon, da må en først sjekke om et annet polygon (forutsatt polygon-polygon kollisjon) først krysser planet til det andre polygonet, deretter må sjekke om en faktisk er innenfor plygonet, og alt må sjekkes om noen av kantene til det første polygonet treffer - mye jobb!

Nå pleier ikke polygon-polygon å være det vanligste i simple motorer, oftere sjekker en med kuber, cylindere eller kuler krysser polygoner. Det enkleste er kule-kule kollisjon hvor det eneste som kreves er å kunne pythagoras' a2 + b2 = c2 men svært sjeldent noe en bruker.

Endret av GeirGrusom
Lenke til kommentar
FlagsAttribute Class

 

Indicates that an enumeration can be treated as a bit field; that is, a set of flags.

 

trodde det da var åpenbart at om en ikke hadde denne FlagsAttributen, så kunne en ikke "treate it as a bit field"? http://msdn.microsoft.com/en-us/library/sy...sattribute.aspx. men jeg har tatt feil før jeg, så tar det ikke tungt om jeg har trodd feil :p

 

om den nummererer selv 1, 2, 4, 8 osv, så er jo det strålende da... må jeg teste ut en dag :)

Endret av Svish
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...