0
votes

I am trying to make a very simple terraria-like game in C# XNA for a school project. I have very limited time otherwise I would probably have spent more time trying to figure this out myself. I created a tilemap but I just can't figure out how to make the tiles "solid" and not passable.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using Microsoft.Xna.Framework; 
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content; 
using Microsoft.Xna.Framework.GamerServices; 
using Microsoft.Xna.Framework.Graphics; 
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace TileEngine {
     public class Game1 : Microsoft.Xna.Framework.Game
     {
         GraphicsDeviceManager graphics;
         SpriteBatch spriteBatch;

         Texture2D character;
         Vector2 cPosition;
         Rectangle cBounds, t1Bounds;

         List<Texture2D> tileTextures = new List<Texture2D>();

         int[,] tileMap = new int[,]
         {
             { 0, 1, 1, 0, 2, 1, 1, 1, 0, 1, },
             { 0, 1, 1, 0, 2, 1, 1, 1, 0, 1, },
             { 0, 1, 1, 0, 2, 1, 1, 1, 0, 1, },
             { 0, 1, 1, 0, 2, 1, 1, 1, 0, 1, },
         };

         int tileWidth = 64;
         int tileHeight = 36;

         int cameraPositionX = 0;
         int cameraPositionY = 0;

         int vSpeed = 0;

         public Game1()
         {
             graphics = new GraphicsDeviceManager(this);
             Content.RootDirectory = "Content";
         }

         protected override void Initialize()
         {
             IsMouseVisible = true;

             graphics.IsFullScreen = false;
             graphics.PreferredBackBufferWidth = 1280;
             graphics.PreferredBackBufferHeight = 720;
             graphics.ApplyChanges();

             cPosition = new Vector2(graphics.GraphicsDevice.Viewport.Width / 2 - 15, graphics.GraphicsDevice.Viewport.Height / 2 - 20);

             cBounds = new Rectangle((int)(cPosition.X), (int)(cPosition.Y), character.Width, character.Height);

             base.Initialize();
         }
         protected override void LoadContent()
         {
             // Create a new SpriteBatch, which can be used to draw textures.
             spriteBatch = new SpriteBatch(GraphicsDevice);

             Texture2D texture;

             character = Content.Load<Texture2D>("Tiles/character");

             texture = Content.Load<Texture2D>("Tiles/green");
             tileTextures.Add(texture);

             texture = Content.Load<Texture2D>("Tiles/red");
             tileTextures.Add(texture);

             texture = Content.Load<Texture2D>("Tiles/blue");
             tileTextures.Add(texture);

             cBounds = new Rectangle((int)(cPosition.X), (int)(cPosition.Y),  
 character.Width, character.Height);

             // TODO: use this.Content to load your game content here
         }
         protected override void UnloadContent()
         {
             // TODO: Unload any non ContentManager content here
         }


         protected override void Update(GameTime gameTime)
         {

             if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                 this.Exit();

             KeyboardState keyState = Keyboard.GetState();

             vSpeed += 1;
             cameraPositionY += vSpeed;

             if (keyState.IsKeyDown(Keys.Right))
                 cameraPositionX += 5;
             if (keyState.IsKeyDown(Keys.Left))
                cameraPositionX -= 5;
             if (keyState.IsKeyDown(Keys.Space))
                 vSpeed = -15;

             if (cBounds.Intersects(t1Bounds))
             {
                 cameraPositionY = 0;
                 vSpeed = 0;
             }    

             base.Update(gameTime);
         }


         protected override void Draw(GameTime gameTime)
         {
             GraphicsDevice.Clear(Color.CornflowerBlue);

             spriteBatch.Begin();

             int tileMapWidth = tileMap.GetLength(1);
              int tileMapHeight = tileMap.GetLength(0);

             spriteBatch.Draw(character, cPosition, Color.White);

             for (int x = 0; x < tileMapWidth; x++)
             {
                 for (int y = 0; y < tileMapHeight; y++)
                 {
                     int textureIndex = tileMap[y, x];
                     Texture2D texture = tileTextures[textureIndex];

                     spriteBatch.Draw(
                         texture, t1Bounds =
                         new Rectangle(
                             320 + x * tileWidth - cameraPositionX,
                             540 + y * tileHeight - cameraPositionY,
                             tileWidth,
                             tileHeight),
                         Color.White);
                 }
             }

             spriteBatch.End();

             base.Draw(gameTime);
         }
     } }

As you can see I tried to make Rectangles around all the sprites and detect when they intersect, but it doesn't seem to work. And even if I get the Rectangle-thing to work I just don't know what to do if they intersect. If I set the velocity to 0 then it will still slowly "fall" through the blocks as there is a default vertical acceleration.

1
Instead of stopping the player by setting the velocity to 0, you need to see how far the player is into the tile, also known as the intersection depth, and subtract the depth to set the player back on the tile.Cyral
You don't have anywhere where collision gets checked, except for the last tile. You probably have to make an array with all the rectangles, then a foreach loop, check for each tilerectangle if it intersects the player bounds. Keep in mind what @Cyral said too.joppiesaus

1 Answers

0
votes

First, you need to create a simple class for your tiles like this:

Class Tile
{
public int type; //Holds the ID to the specific texture.
public bool collision; //If true you should check for collision if the player is close.
public int health; //You probably need this since rock is harder to break then dirt.
}

Then create an array with Tiles. Now you have to check if the player is close to a collidable tile. You need a function that converts world space to tile space and put your player coordinates in it, check each frame for a couple of tiles around the player. If you check the complete map for collision your FPS will drop to .001, likewise for drawing all the tiles. Some pseudo code (already on tile level):

for (int y = player.y-4;y <= player.y+4;y++) 
{ //If your player is just the size of a single tile then just -2/+2 will do. 9*9 is already an expensive 81 rectangles to check.
    for (int x = player.x-4;x <= player.x+4;x++)
    {
        if (map[x,y].collision){
        if (new Rectangle(x*tilewidth,y*tileheight,tilewidth,tileheight).intersects(player.rectangle)){
        //Check farthest valid position and put player there
        }}

    }
}

The best thing to do is add in a newPosition property and before moving the player to this newPosition you have to check if the position is valid.

Other then that, if you do not have the time then the best advice is to not create a terraria like game. Even the simplest of terraria like game will be time consuming. Since you do not know the basics of collision i suggest making a pong or arkanoid clone that is pretty much how we all started out.