2d XNA游戏的侧滚动背景,迫切需要帮助

时间:2013-06-01 14:50:23

标签: c# xna 2d monogame side-scroller

我正在尝试在XNA(monogame)中制作一个简单的侧滚动游戏作为学习练习,但我有一些问题让我的头围绕滚动水平。基本上,我正在尝试制作一个简单的游戏,其中水平从左向右滚动,玩家在跳过障碍物时保持静止。

我最初使用旧的平台游戏初学者套件,并将其大部分功能剥离出来,如播放器,宝石和敌人。基本上,剩下的就是从文本文件加载级别的功能。它循环遍历文件的每一行,确定存在哪种类型的图块,然后为图块绘制新的纹理2D。

我已经按照一些关于从右到左制作背景滚动的教程,但是我无法自己滚动它们。

我想创建一个固定视图,其中包含播放器,然后向左移动世界其他地方。

我通常不会粘贴这么多来源,因为我怀疑任何人都会不屑一顾(这里是希望)但这里是关卡类,主(程序)类(播放器类没有真正的功能)它只是将精灵绘制到选定的vector2。

等级:

using System;
using System.Collections.Generic;
using System.IO;

使用Microsoft.Xna.Framework;    使用Microsoft.Xna.Framework.Graphics;    使用Microsoft.Xna.Framework.Content;

namespace WP8_Game

{        公共课级别        {         //关卡的物理结构。         私人瓷砖[,]瓷砖;         私有Layer []层;         int mLineIndex;

    // The layer which entities are drawn on top of.
    private const int EntityLayer = 2;

    private Vector2 cameraPosition;

    // Level content.        
    public ContentManager Content
    {
        get { return content; }
    }
    ContentManager content;

    #region Loading

    public Level(IServiceProvider serviceProvider, Stream fileStream, int lineIndex)
    {
         // Create a new content manager to load content used just by this level.
        content = new ContentManager(serviceProvider, "Content");

        mLineIndex = lineIndex;

        LoadTiles(fileStream);

        layers = new Layer[3];
        layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
        layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
        layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);
    }

    /// <summary>
    /// Iterates over every tile in the structure file and loads its
    /// appearance and behavior. This method also validates that the
    /// file is well-formed with a player start point, exit, etc.
    /// </summary>
    /// <param name="fileStream">
    /// A stream containing the tile data.
    /// </param>
    private void LoadTiles(Stream fileStream)
    {
        // Load the level and ensure all of the lines are the same length.
        int width;
        List<string> lines = new List<string>();
        using (StreamReader reader = new StreamReader(fileStream))
        {
            string line = reader.ReadLine();
            width = line.Length;
            while (line != null)
            {
                lines.Add(line);
                if (line.Length != width)
                    throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
                line = reader.ReadLine();
            }
        }

        // Allocate the tile grid.
        tiles = new Tile[width, lines.Count];

        // Loop over every tile position,
        for (int y = 0; y < Height; ++y)
        {
            for (int x = 0; x < Width; ++x)
            {
                // to load each tile.
                char tileType = lines[y][x];
                tiles[x, y] = LoadTile(tileType, x, y);
            }
        }
    }

    /// <summary>
    /// Width of level measured in tiles.
    /// </summary>
    public int Width
    {
        get { return tiles.GetLength(0); }
    }

    /// <summary>
    /// Height of the level measured in tiles.
    /// </summary>
    public int Height
    {
        get { return tiles.GetLength(1); }
    }

    /// <summary>
    /// Loads an individual tile's appearance and behavior.
    /// </summary>
    /// <param name="tileType">
    /// The character loaded from the structure file which
    /// indicates what should be loaded.
    /// </param>
    /// <param name="x">
    /// The X location of this tile in tile space.
    /// </param>
    /// <param name="y">
    /// The Y location of this tile in tile space.
    /// </param>
    /// <returns>The loaded tile.</returns>
    private Tile LoadTile(char tileType, int x, int y)
    {
        switch (tileType)
        {
            // Blank space
            case '.':
                return new Tile(null, new Vector2(x, y), TileCollision.Passable);

            // Impassable block
            case '#':
                return LoadTile("BlockA0", x, y, TileCollision.Impassable);

            // Unknown tile type character
            default:
                throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, x, y));
        }
    }

    /// <summary>
    /// Creates a new tile. The other tile loading methods typically chain to this
    /// method after performing their special logic.
    /// </summary>
    /// <param name="name">
    /// Path to a tile texture relative to the Content/Tiles directory.
    /// </param>
    /// <param name="collision">
    /// The tile collision type for the new tile.
    /// </param>
    /// <returns>The new tile.</returns>
    private Tile LoadTile(string name, int x, int y, TileCollision collision)
    {
        return new Tile(Content.Load<Texture2D>("Tiles/" + name), new Vector2(x, y), collision);
    }

    /// <summary>
    /// Unloads the level content.
    /// </summary>
    public void Dispose()
    {
        Content.Unload();
    }

    #endregion

    #region Bounds and collision


    /// <summary>
    /// Gets the bounding rectangle of a tile in world space.
    /// </summary>        
    public Rectangle GetBounds(int x, int y)
    {
        return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
    }

    #endregion


    #region Draw


    /// <summary>
    /// Draw everything in the level from background to foreground.
    /// </summary>
    public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
    {
        spriteBatch.Begin();
        for (int i = 0; i <= EntityLayer; ++i)
            layers[i].Draw(spriteBatch, cameraPosition);
        spriteBatch.End();

        ScrollCamera(spriteBatch.GraphicsDevice.Viewport, gameTime);
        Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition.X, 0.0f, 0.0f);
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, cameraTransform);
        DrawTiles(spriteBatch);



        spriteBatch.End();

        spriteBatch.Begin();
        for (int i = EntityLayer + 1; i < layers.Length; ++i)
            layers[i].Draw(spriteBatch, cameraPosition);
        spriteBatch.End();    
    }


    private void ScrollCamera(Viewport viewport, GameTime gameTime)
    {
        //Add to the camera positon, So we can see the origin
        cameraPosition.X = cameraPosition.X + (viewport.Width / 2);
        cameraPosition.Y = cameraPosition.Y + (viewport.Height / 2);



        //Smoothly move the camera towards the player
        cameraPosition.X = MathHelper.Lerp(cameraPosition.X, 10, 0.1f);
        cameraPosition.Y = MathHelper.Lerp(cameraPosition.Y, 10, 0.1f);
        //Undo the origin because it will be calculated with the Matrix (I know this isnt the best way but its what I had real quick)
        cameraPosition.X = cameraPosition.X - (viewport.Width / 2);
        cameraPosition.Y = cameraPosition.Y - (viewport.Height / 2);

        //Shake the camera, Use the mouse to scroll or anything like that, add it here (Ex, Earthquakes)

        //Round it, So it dosent try to draw in between 2 pixels
        cameraPosition.Y = (float)Math.Round(cameraPosition.Y);
        cameraPosition.X = (float)Math.Round(cameraPosition.X);


        //Clamp it off, So it stops scrolling near the edges
        cameraPosition.X = MathHelper.Clamp(cameraPosition.X, 1f, Width * Tile.Width);
        cameraPosition.Y = MathHelper.Clamp(cameraPosition.Y, 1f, Height * Tile.Height);
    }



    /// <summary>
    /// Draws each tile in the level.
    /// </summary>
    private void DrawTiles(SpriteBatch spriteBatch)
    {
        // For each tile position
        for (int y = 0; y < Height; ++y)
        {
            for (int x = 0; x < Width; ++x)
            {
                // If there is a visible tile in that position
                Texture2D texture = tiles[x, y].Texture;
                if (texture != null)
                {
                    // Draw it in screen space.
                    Vector2 position = new Vector2(x, y) * Tile.Size;
                    spriteBatch.Draw(texture, position, Color.White);
                }
            }
        }
    }
    #endregion 

    #region Update

    /// <summary>
    /// Updates all objects in the level
    /// </summary>
    public void Update(GameTime gameTime)
    {
        // For each tile position
        for (int y = 0; y < Height; ++y)
        {
            for (int x = 0; x < Width; ++x)
            {
                // If there is a visible tile in that position
                Texture2D texture = tiles[x, y].Texture;
                if (texture != null)
                {
                    // Draw it in screen space.
                   // Vector2 cameraOffset = new Vector2(10, 0);
                    tiles[x, y].Position = new Vector2(x--, y);
                }
            }
        }

    }

    #endregion

}
}

程序:

using System;
using System.IO;

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

namespace WP8_Game
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Program : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    private Player player;

    // Meta-level game state.
    private int levelIndex = -1;
    private Level level;

    // The number of levels in the Levels directory of our content. We assume that
    // levels in our content are 0-based and that all numbers under this constant
    // have a level file present. This allows us to not need to check for the file
    // or handle exceptions, both of which can add unnecessary time to level loading.
    private const int numberOfLevels = 3;

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

        player = new Player();
    }

    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here

        base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        // Load the player resources 
        Vector2 playerPosition = new Vector2(100, 100);
        player.Initialize(Content.Load<Texture2D>("Sprites/Player/player"),  playerPosition);


        //Load the next level
        LoadNextLevel();

    }

    private void LoadNextLevel()
    {
        // move to the next level
        levelIndex = (levelIndex + 1) % numberOfLevels;

        // Unloads the content for the current level before loading the next one.
        if (level != null)
            level.Dispose();

        // Load the level.
        string levelPath = string.Format("Content/Levels/{0}.txt", levelIndex);
        using (Stream fileStream = TitleContainer.OpenStream(levelPath))
            level = new Level(Services, fileStream, levelIndex);
    }

    private void ReloadCurrentLevel()
    {
        --levelIndex;
        LoadNextLevel();
    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {

        //camera.Update(gameTime, player);

        base.Update(gameTime);
    }

    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.Black);

        // Start drawing
        spriteBatch.Begin();


        // Draw the Player
        player.Draw(spriteBatch);


        //Draw the level
        level.Draw(gameTime, spriteBatch);

        // Stop drawing
        spriteBatch.End();

        base.Draw(gameTime);
    }
}
}

任何建议都会非常感激,我迷失了,不知道从哪里开始。

2 个答案:

答案 0 :(得分:1)

您需要偏移绘制图块的位置,如下所示:

Vector2 position = new Vector2(x, y) * Tile.Size;

变成

Vector2 position = new Vector2(x * Tile.Size + cameraOffset.X, y * Tile.Size + cameraOffset.Y);

这是对DrawTiles方法的编辑。

答案 1 :(得分:0)

使用相机移动场景的更典型方法是使用变换矩阵。我所做的是创建一个具有各种属性(位置,旋转,缩放,原点)的Camera类,并有一个方法可以进行如下转换:

    public Matrix GetTransform()
    {
        var translationMatrix = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0));
        var rotationMatrix = Matrix.CreateRotationZ(Rotation);
        var scaleMatrix = Matrix.CreateScale(new Vector3(Zoom, Zoom, 1));
        var originMatrix = Matrix.CreateTranslation(new Vector3(Origin.X, Origin.Y, 0));

        return translationMatrix * rotationMatrix * scaleMatrix * originMatrix;
    }

然后当你绘制精灵批处理时,你可以简单地将矩阵传递给最后一个参数,如下所示:

_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, null, _camera.GetTransform());

当你这样做时,你不需要偏移任何东西,所以你需要取出你已经拥有的所有相机偏移代码。只需按照自己的方式绘制内容,并在绘制时转换整个视图。

最后要注意的是,如果您使用鼠标或触摸坐标,您还需要反向转换输入。