我正在尝试在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);
}
}
}
任何建议都会非常感激,我迷失了,不知道从哪里开始。
答案 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());
当你这样做时,你不需要偏移任何东西,所以你需要取出你已经拥有的所有相机偏移代码。只需按照自己的方式绘制内容,并在绘制时转换整个视图。
最后要注意的是,如果您使用鼠标或触摸坐标,您还需要反向转换输入。