[英]Side Scrolling Background for 2d XNA game, Help desperately needed
我正在嘗試使用XNA(monogame)制作一個簡單的側滾動游戲,作為學習練習,但是我在滾動關卡時遇到了一些問題。 基本上,我正在嘗試制作一個簡單的游戲,其中一個關卡從左到右滾動,而玩家在跳過障礙物時保持靜止。
最初,我使用了舊的Platformer入門套件,並剝離了大部分功能,例如播放器,寶石和敵人。 本質上,剩下的就是從文本文件加載關卡的功能。 它遍歷文件的每一行,確定存在哪種類型的圖塊,然后為圖塊繪制新的Texture 2D。
我遵循了一些有關使背景從右向左滾動的教程,但是我無法讓磁貼本身滾動。
我想創建一個包含播放器的固定視圖,然后將世界其他地方向左移動。
我通常不會粘貼太多的源代碼,因為我懷疑有人會不願意去看它(這里就是希望),但是這里是關卡類,主(程序)類(玩家類沒有真正的功能,因為它只是畫圖而已)精靈到選定的向量2。
水平:
using System;
using System.Collections.Generic;
using System.IO;
使用Microsoft.Xna.Framework; 使用Microsoft.Xna.Framework.Graphics; 使用Microsoft.Xna.Framework.Content;
namespace WP8_Game
{public class Level {//級別的物理結構。 私人Tile [,] tile; 私有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);
}
}
}
任何建議將不勝感激,我很迷茫,不確定從哪里開始。
您需要偏移繪制圖塊的位置,如下所示:
Vector2 position = new Vector2(x, y) * Tile.Size;
變成
Vector2 position = new Vector2(x * Tile.Size + cameraOffset.X, y * Tile.Size + cameraOffset.Y);
這是對DrawTiles方法的編輯。
使用攝像機移動場景的更典型方法是使用變換矩陣。 我要做的是創建一個具有各種屬性(位置,旋轉,縮放,原點)的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());
當您以這種方式執行此操作時,您無需偏移任何內容,因此您需要取出所有已經獲得的相機偏移代碼。 只需按其原樣繪制事物,並在繪制時變換整個視圖。
最后要注意的一點是,如果您使用鼠標或觸摸坐標,則還需要反向轉換輸入。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.