#c# #xna #2d #sprite #collision
#c# #xna #2d #спрайт #столкновение
Вопрос:
Я работаю над 2D-игрой на C # и XNA, и мне удалось добиться обнаружения столкновений по пикселям при работающих спрайтах. Проблема, которая меня озадачила прямо сейчас, заключается в том, как вычислить местоположение, в котором сталкиваются два спрайта.
Причина, по которой мне нужна эта информация, заключается в том, что природа этой игры требует, чтобы объекты вращались и реагировали несколько точно в соответствии с физикой. Моя текущая среда тестирования включает в себя два квадрата. Любые предложения о том, как найти точку контакта, будут хорошо приняты.
Также, если у кого-нибудь есть какие-либо предложения о том, как рассчитать расстояние для перемещения спрайтов при столкновении, чтобы они не перекрывались, это тоже было бы здорово.
Код, который я использую для обнаружения столкновений, взят отсюда:
http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed
Спасибо всем!
Ответ №1:
В первой части о получении точек соприкосновения код, на который вы ссылались, проделал тяжелую работу, вместо того, чтобы возвращать true, верните координату путем преобразования обратно в мировое пространство.
Вот слегка измененный код из http://xbox.create.msdn.com/en-US/education/catalog/tutorial/collision_2d_perpixel_transformed
public static IEnumerable<Vector2> IntersectPixels(
Matrix transformA, int widthA, int heightA, Color[] dataA,
Matrix transformB, int widthB, int heightB, Color[] dataB)
{
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space
Matrix transformAToB = transformA * Matrix.Invert(transformB);
// When a point moves in A's local space, it moves in B's local space with a
// fixed direction and distance proportional to the movement in A.
// This algorithm steps through A one pixel at a time along A's X and Y axes
// Calculate the analogous steps in B:
Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);
// Calculate the top left corner of A in B's local space
// This variable will be reused to keep track of the start of each row
Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);
// For each row of pixels in A
for(int yA = 0; yA < heightA; yA )
{
// Start at the beginning of the row
Vector2 posInB = yPosInB;
// For each pixel in this row
for(int xA = 0; xA < widthA; xA )
{
// Round to the nearest pixel
int xB = (int)Math.Round(posInB.X);
int yB = (int)Math.Round(posInB.Y);
// If the pixel lies within the bounds of B
if(0 <= xB amp;amp; xB < widthB amp;amp;
0 <= yB amp;amp; yB < heightB)
{
// Get the colors of the overlapping pixels
Color colorA = dataA[xA yA * widthA];
Color colorB = dataB[xB yB * widthB];
// If both pixels are not completely transparent,
if(colorA.A != 0 amp;amp; colorB.A != 0)
{
// then an intersection has been found
yield return Vector2.Transform(new Vector2(xA, yA),transformA);
}
}
// Move to the next pixel in the row
posInB = stepX;
}
// Move to the next row
yPosInB = stepY;
}
// No intersection found
}
Что касается второй части, обычным методом является добавление небольшой силы, противоположной направлению столкновения, чтобы оттолкнуть их. Эта статья о физике игр является хорошим учебным пособием, и существует несколько готовых физических движков, которые являются надежными, как Farseer.
Пример кода предназначен для преобразованных спрайтов, если вам не нужна эта функция, вы, вероятно, могли бы упростить код. Если вы не используете физический движок, чтобы они не перекрывались при перемещении одного, возможно, потребуется переместить другой и так далее, физический движок позаботится об этом за вас.
Редактировать: Вот несколько небольших изменений в примере MSDN, чтобы каждая точка контакта была выделена зеленым пикселем.
Добавьте эти поля
//Contact points are cleared and re-added each update
List<Vector2> contactPoints = new List<Vector2>();
//Texture for contact display
Texture2D pixelTex;
Добавить в LoadContent()
куда-нибудь
pixelTex = new Texture2D(GraphicsDevice, 1, 1);
pixelTex.SetData<Color>(new[] { Color.White });
Замените конец Update()
этим
// Update each block
personHit = false;
contactPoints.Clear();
for(int i = 0; i < blocks.Count; i )
{
// Animate this block falling
blocks[i].Position = new Vector2(0.0f, BlockFallSpeed);
blocks[i].Rotation = BlockRotateSpeed;
// Build the block's transform
Matrix blockTransform =
Matrix.CreateTranslation(new Vector3(-blockOrigin, 0.0f)) *
// Matrix.CreateScale(block.Scale) * would go here
Matrix.CreateRotationZ(blocks[i].Rotation) *
Matrix.CreateTranslation(new Vector3(blocks[i].Position, 0.0f));
// Calculate the bounding rectangle of this block in world space
Rectangle blockRectangle = CalculateBoundingRectangle(
new Rectangle(0, 0, blockTexture.Width, blockTexture.Height),
blockTransform);
// The per-pixel check is expensive, so check the bounding rectangles
// first to prevent testing pixels when collisions are impossible.
if(personRectangle.Intersects(blockRectangle))
{
contactPoints.AddRange(IntersectPixels(personTransform, personTexture.Width,
personTexture.Height, personTextureData,
blockTransform, blockTexture.Width,
blockTexture.Height, blockTextureData));
// Check collision with person
if(contactPoints.Count != 0)
{
personHit = true;
}
}
// Remove this block if it have fallen off the screen
if(blocks[i].Position.Y >
Window.ClientBounds.Height blockOrigin.Length())
{
blocks.RemoveAt(i);
// When removing a block, the next block will have the same index
// as the current block. Decrement i to prevent skipping a block.
i--;
}
}
base.Update(gameTime);
Добавить в Draw()
перед SpriteBatch.End()
foreach(Vector2 p in contactPoints)
{
spriteBatch.Draw(pixelTex, new Rectangle((int)p.X, (int)p.Y, 1, 1), Color.FromNonPremultiplied(120, 255, 100, 255));
}
Комментарии:
1. Спасибо за ваш ответ, однако мне трудно применить это изменение. Прежде всего, я не знаком с «yield» и почему он будет использоваться в данном случае. (посмотрел это, но на MSDN, но мне трудно это понять) Кроме того, что именно это вернет в этом случае и как бы я с этим справился. до этого я вызывал функцию в качестве «параметра» для инструкции if. но я не думаю, что это сработает в данном случае.
2. Я использовал yield, потому что может быть более одной точки контакта. Результатом является IEnumerable, поэтому вы можете использовать
foreach
в результате или использовать методы расширения Linq toArray, ToList. Вы могли бы добавить точки в список и вернуть это, но при необходимости используйте выборку yield, поэтому Linq вы можете использоватьIntersectPixels(...).Any()
в качестве замены исходного метода, который возвращает bool. Проверьте редактирование для примера.3. Сэр, вы мой герой. Я считаю, что я должен вам сэндвич с индейкой.