Search code examples
c#objectxnatile

Moving Platforms in a Tile Map


I am using a tile map where if a moving platform hits a tile it should go another direction. I am using a list for both the tilemap and platform, this helps keep things neat.

The image below shows the platform, it is constantly in motion and when it collides with one of the black circles it should change directions and head the opposite way it was going.

Image of Platform and colliding tile

Unfortunately I am having a problem where I need to find the correct platform so it will go the other direction. I created the list in the main class and do not know how to call upon the list inside a another class besides the main.

Question:

How would I call upon a list that is holding multiple objects going in different directions, in different position, and is created in the main class, so that I can know if the platform is colliding with a tile?

Simply put, how do I make the platform collide with a tile?

Here is the process that the tile map must go through to be used and called:

To make the tile map two classes are used, Block class and Game1 class (the main class).

Inside the Block class the Texture, Position, and BlockState (which decides what thing it will do)

    public Block(Texture2D Texture, Vector2 Position, int BlockState)
    {
        this.Texture = Texture;
        this.Position = Position;
        this.BlockState = BlockState;
    }

Then the Block class will use a method that uses the Player class.

    public Player BlockCollision(Player player)
    { 
        Rectangle top = new Rectangle((int)Position.X + 5, (int)Position.Y - 10, Texture.Width - 10, 10);
        Rectangle bottom = new Rectangle((int)Position.X + 5, (int)Position.Y + Texture.Height, Texture.Width - 10, 10);
        Rectangle left = new Rectangle((int)Position.X - 10, (int)Position.Y + 5, 10, Texture.Height - 10);
        Rectangle right = new Rectangle((int)Position.X + Texture.Width, (int)Position.Y + 5, 10, Texture.Height - 10);

        if (BlockState == 1 || (BlockState == 2 && !player.goingUp))
        {
            if (top.Intersects(new Rectangle((int)player.Position.X, (int)player.Position.Y, player.Texture.Width, player.Texture.Height)))
            {
                if (player.Position.Y + player.Texture.Height > Position.Y && player.Position.Y + player.Texture.Height < Position.Y + Texture.Height / 2)
                {
                    player.Position.Y = player.ground = Position.Y - player.Texture.Height;
                    player.Velocity.Y = 0;
                    player.isJumping = false;
                    player.Time = 0;
                    player.botCollision = true;
                }
            }
        }

        return player;
    }

Then the Draw method.

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(Texture, new Rectangle((int)Position.X, (int)Position.Y, Texture.Width, Texture.Height), Color.White);
    }

We then move over to the main class Game1.

I create the Lists for both Blocks and the Platforms.

    List<Block> Blocks;
    List<MovingPlatform> Platforms;

Using a char system I create the map.

    List<char[,]> Levels = new List<char[,]>();
    public int tileWidth, tileHeight;

Then in the Initialize method I call on Lists and create the map.

    protected override void Initialize()
    {
        Blocks = new List<Block>();
        Platforms = new List<MovingPlatform>();

        char[,] Level1 = {{'.','.','#'},
                          {'.','.','#'},
                          {'.','.','#'}};

        Levels.Add(Level1);

        base.Initialize();
    }

Then a void LoadLevel is called on.

    void LoadLevel(int level)
    {
        Blocks.Clear();
        Platforms.Clear();

        player.Position = Vector2.Zero;

        tileWidth = Levels[level].GetLength(1);
        tileHeight = Levels[level].GetLength(0);

        Texture2D blockSpriteA = Content.Load<Texture2D>("blockA2");

        for (int x = 0; x < tileWidth; x++)
        {
            for (int y = 0; y < tileHeight; y++)
            {

                //Background
                Blocks.Add(new Block(background, new Vector2(x * 50, y * 50), 0));

                //Impassable Blocks
                if (Levels[level][y, x] == '#')
                {
                    Blocks.Add(new Block(blockSpriteA, new Vector2(x * 50, y * 50), 1));
                }

                //Vertical Moving Platform
                if (Levels[level][y, x] == '=')
                {
                    Platforms.Add(new MovingPlatform(platform, new Vector2(x * 50, y * 50), 3.0f, true));
                }

Then In the Update I call on the lists.

        foreach (Block b in Blocks)
        {
            player = b.BlockCollision(player);
        }

        foreach (MovingPlatform m in Platforms)
        {
            player = m.BlockCollision(player);
            m.Update(gameTime);
        }

Finally the Draw method calls on them.

        foreach (Block b in Blocks)
        {
            b.Draw(spriteBatch);
        }

        foreach (MovingPlatform m in Platforms)
        {
            m.Draw(spriteBatch);
        }

Solution

  • I think I understand your question, I am not sure, but I am going to hazard an answer anyway.

    I think what you need to do is to move stuff out of the main Game class. You have a concept of a map and a bunch of stuff that belongs to a map (platforms, blocks) so it's a good idea to encapsulate this in its own class, eg:

    class Map
    {
        public List<Block> Blocks;
        public List<MovingPlatform> Platforms;
    
        void LoadLevel(int level)
        {
            ///
        }
    }
    

    Now it makes the code cleaner, but also you now have a Map object you can pass around.

    So for example to enable a MovingPlatform to have access to everything on a map, you just need to pass a reference to a map as a parameter to the MovingPlatform constructor which saves it away to a private field. Eg:

    class MovingPlatform
    {
        Map _map;
        public MovingPlatform(Map map, Vector2 position, ...)
        {
            _map = map;
        }
    
        public void Update(GameTime gameTime)
        {
            //move platform
            //detect collision
            //have access to all the blocks and other platforms on map
            //_map.Blocks;
            //_map.Platforms;
        }
    }
    

    So in the map class LoadLevel() method you'd pass in 'this' as the first parameter of the MovingPlatform constructor:

    class Map
    {
        void LoadLevel(int level)
        {
            //Vertical Moving Platform
            if (Levels[level][y, x] == '=')
            {
                Platforms.Add(new MovingPlatform(this, ...));
            }
        }
    }
    

    You can add other map specific things (even a reference to the player?) to the Map class and the MovingPlatforms will automatically have access to them.

    This is called Dependency Injection. Your MovingPlatform class has a dependency on the map and you are injecting that dependency in through the constructor.

    Edit:

    For collision you could add a Bounds property to Tile and MovingPlatform class. That makes it easier to get the current rectangle bounds of them. Also an IntersectsTile method in MovingPlatform that will return true if the platform intersects a specified tile.

    class Tile
    {
        Rectangle Bounds
        {
            get
            {
                return new Rectangle((int)Position.X, (int)Position.Y, tileWidth, tileHeight);
            }
        }
    }
    
    class MovingPlatform
    {
        Rectangle Bounds
        {
            get
            {
                return new Rectangle((int)Position.X, (int)Position.Y, platformWidth, platformHeight);
            }
        }
    
        //returns true if this platform intersects the specified tile
        bool IntersectsTile(Tile tile)
        {
            return Bounds.Intersects(tile.Bounds);
        }
    }
    

    Then in the MovingPlatfom class in the Update method where the platform is moved each frame, check if there is a collision after the movement. If there is a collision then back out the movement and reverse the platform direction, so next frame it will move the opposite way.

    class MovingPlatform
    {
        void Update(Tile[] collidableTiles)
        {
            Position += direction;
    
            //test collision
            bool isCollision = CollisionTest(collidableTiles);
            if (isCollision)
            {
                //undo the movement and change direction
                Position -= direction;
                direction = newDirection;
            }
        }
    
        //returns true if this platform intersects with any of the specified tiles
        bool CollisionTest(Tile[] tiles)
        {
            foreach (Tile tile in tiles)
                if (IntersectsTile(tile))
                    return true;
            return false;
        }
    }
    

    For the above to work you need to pass in a list of collidable tiles on the map to the MovingPlatform update. Call each MovingPlatform in the map class, passing it the list of collidable tiles.

    class Map
    {
        List<Tile> _collidableTiles;
    
        void Update(GameTime gameTime)
        {
            foreach (MovingPlatform platform in MovingPlatforms)
            {
                platform.Update(_collidableTiles);
            }
        }
    }
    

    That's one way of doing it. A better way though would be instead of passing in collidable tiles, get the MovingPlatform to grab tiles around it and test those.

    class MovingPlatform
    {
        void Update()
        {
            Position += direction;
    
            //test collision
            Tile[] tiles = _map.GetNearbyTiles(Position);
            foreach (Tile tile in tiles)
                if (tile.IsSolid)
                    if (IntersectsTile(tile))
                    {
                        //undo the movement and change direction
                        Position -= direction;
                        direction = newDirection;
                        break;
                    }
        }
    }
    

    So that way all tiles have an IsSolid property and any that are set to true will cause moving platforms to collide with them.

    class Tile
    {
        bool IsSolid;
    }