Как правильно обработать столкновение между игроком и двумя перекрывающимися стенами XNA - PullRequest
0 голосов
/ 05 августа 2020

Я пытаюсь создать 2D-игру для своего студенческого проекта, используя MonoGame (который использует структуру XNA), и у меня было много проблем, связанных с столкновением между двумя перекрывающимися прямоугольниками и прямоугольником Hitbox игрока. Игрок останавливается (что не является желаемым результатом), если он движется по диагонали вдоль одного прямоугольника стены, когда встречается другой перпендикулярный прямоугольник стены, который не находится на пути.

Когда игрок входит в стену, вызывается метод CollisionHandler для класса Player, и сталкивающийся прямоугольник вместе с перечисляемой стороной (по существу, каким образом стена проверяет наличие столкновений) передаются через параметры. Метод имеет некоторые условные выражения, а затем изменяет позицию игрока. Это код для этого:

public void CollisionHandler(Rectangle Wall, Side TestSide) // Assuming Hitbox and Wall are Intersecting
{
    if ((TestSide == Side.Up && Direction.Y > 0) // If the Wall is testing for collision Upwards and the Player is moving Downwards through it
        || (TestSide == Side.Down && Direction.Y < 0)) // or if the Wall is testing for collision Downwards and vice versa
    {
        Position.Y -= Direction.Y * MovementSpeed; // Y movement is reversed 
    }
    if ((TestSide == Side.Left && Direction.X > 0) // If the Wall is testing for collision to it's Left and the Player is moving to the Right through it
        || (TestSide == Side.Right && Direction.X < 0)) // or if the Wall is testing for collision to it's Right and vice versa
    {
        Position.X -= Direction.X * MovementSpeed; // X movement is reversed
    }
}

(Направление - это нормализованный вектор2 новой позиции игрока минус их предыдущая позиция)

Проблема в том, что игрок перемещается в угол двух перекрытия стен. Например, вот две из стен, которые у меня есть на карте:

Walls.Add(new Rectangle(360, 240, 1, 120)); // Side = Side.Left
Walls.Add(new Rectangle(360, 240, 120, 1)); // Side = Side.Up

Когда игрок движется по диагонали вниз и вправо (направление будет примерно (0,707, 0,707)), Ориентированная стена будет пересекать Hitbox игрока, несмотря на то, что Hitbox игрока находится за стеной и над ней, что приводит к остановке игрока.

Я много раз пытался бороться с этим, обычно изменяя внешний вид стен made, и на самом деле это самая последняя итерация того, как представлены стены. До этого они были просто большими прямоугольниками над каждой внутриигровой плиткой с атрибутом CanEnter, равным false.

Я ломал себе голову в течение нескольких недель, пытаясь понять как я могу предотвратить это, но на самом деле ничего не работает, и теперь мой проект полностью остановлен из-за проблемы. Я был бы очень признателен за помощь или советы о том, как решить эту проблему.

1 Ответ

0 голосов
/ 02 сентября 2020

Есть много вещей, которые вы не указали, но я думаю, что могу сделать обоснованное предположение: вы изменили позицию, как только получили ввод, а затем ничего не сделали, кроме "отмены" изменения позиции при обнаружении столкновение, которое привело к тому, что вектор положения оказался там, где он был. Вы можете легко решить эту проблему, выполнив часть «отменить» как минимум дважды.

Итак, вместо того, где вы сделали

Position.Y -= Direction.Y * MovementSpeed;

, вы должны сделать что-то вроде

Position.Y -= Direction.Y * MovementSpeed * 2;

Далее вам нужно будет закрепить HitBox точно на краю стены. Если этого не сделать, ваш прямоугольник Hitbox может выступать за пределы стены, а вы, вероятно, этого не захотите. установка вектора направления из входных данных и проверка столкновения. Кроме того, я надеюсь, что вы используете движение, основанное на ускорении, поскольку использование только постоянной скорости для движения может показаться грубым, тогда как использование ускорения сделает вашу игру плавной.

При этом я могу предложить много улучшения в вашем коде

Я думаю, вы используете List Walls для хранения прямоугольника для каждой стороны стены, и я думаю, что вы без нужды усложняете проблему. Вам нужна «Комната» с 4 стенами. 1022 * Это означает, что у вас есть верхняя стена с (30,30) до (430,30), левая стена с (30,30) до (30,430), правая стена с (430,30) до (430,430) и нижняя Стена от (30,430) до (430,430)

Теперь я считаю, что у вас есть Player HitBox, который снова представляет собой прямоугольник

Rectangle HitBox;

Теперь в методе Update() вам нужно проверить, HitBox и стена пересекаются, что довольно легко получить с помощью

if(HitBox.Intersects(Wall)
{
    //Handle the bouncing logic here
}

Поскольку у меня много времени, я предлагаю вам полный рабочий пример этого.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace test
{
public class Game1: Game
{
    private readonly GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private Texture2D HitboxTexture, WallTexture;
    private Rectangle HitBox, Wall;
    private Vector2 Position, Direction, Acceleration;
    float Speed;

    public Game1()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize(){base.Initialize();}

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        // TODO: use this.Content to load your game content here
        HitboxTexture = Content.Load<Texture2D>("dirt");
        WallTexture = Content.Load<Texture2D>("wall");
        Speed=2;
        HitBox=new Rectangle(0,0,40,40);
        Wall=new Rectangle(30,30,400,400);
        Position=Wall.Center.ToVector2();
    }

    protected override void Update(GameTime gameTime)
    {
        var ks=Keyboard.GetState();
        if (ks.IsKeyDown(Keys.Escape))Exit();
        
        //Get the Direction Vector from the keyboard

        Acceleration=Vector2.Zero;
        if(ks.IsKeyDown(Keys.Left))Acceleration.X=-1;
        else if(ks.IsKeyDown(Keys.Right))Acceleration.X=1;
        if(ks.IsKeyDown(Keys.Up))Acceleration.Y=-1;
        else if(ks.IsKeyDown(Keys.Down))Acceleration.Y=1;
        
        Acceleration*=Speed;

        Acceleration=Acceleration - Direction*0.54f;//Velocity = Acceleration - Friction

        Direction+=Acceleration;

        Position+=Direction; //Postition
        //Check for Intersectrion
        if(Wall.Intersects(HitBox))
        {
            //Intersection is true. Now add the bouncing logic

            //First, clip the box so that it is inside the wall
            if(HitBox.Left<Wall.Left)Position.X=Wall.X+1;
            if(HitBox.Top<Wall.Top)Position.Y=Wall.Y+1;
            if(HitBox.Right>Wall.Right)Position.X=Wall.Right-HitBox.Width -1;
            if(HitBox.Bottom>Wall.Bottom)Position.Y=Wall.Bottom-HitBox.Height-1;

            //Now, make the direction change

            if(HitBox.Left<Wall.Left || HitBox.Right>Wall.Right) Direction.X*=-1;
            if(HitBox.Top<Wall.Top || HitBox.Bottom>Wall.Bottom) Direction.Y*=-1;
            
            Direction*=2;//For True rebound
        }
        

        HitBox.X=(int)Position.X;
        HitBox.Y=(int)Position.Y;

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        Color color=gameTime.IsRunningSlowly?Color.Red:Color.CornflowerBlue;
        GraphicsDevice.Clear(color);

        // TODO: Add your drawing code here
        _spriteBatch.Begin();
        _spriteBatch.Draw(WallTexture, Wall, Color.White);
        _spriteBatch.Draw(HitboxTexture, HitBox, Color.White);
        _spriteBatch.End();

        base.Draw(gameTime);
    }
}
}
...