• XNA之RPG游戏开发教程之五


    上一节我们说到游戏中的地图绘制,我们在TileMap类中为GameplayScreen绘制地图,但要模拟真实的游戏地图,需要做到地图的滚动,以前我们经常会用现成的图片,利用图片的滚动模拟背景移动。现在要实现游戏地图的个性化,要用Tile绘制地图,就需要一个2D的Camera类,站在player的Position上会有一个视野范围,就在这个视野范围内绘制地图,用这种方法来实现动态地图的模拟。接下来建立一个Camera类如下

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    namespace XRpgLibrary.TileEngine
    {
    public class Camera
     {
     #region Field Region
    Vector2 position;//Camera位置
    float speed;//Camera的移动速度
    float zoom;//Camera的放大缩小倍数
    Rectangle viewportRectangle;
     #endregion
     #region Property Region
    public Vector2 Position
     {
    get { return position; }
    private set { position = value; }
     }
    public float Speed
     {
    get { return speed; }
    set
     {
     speed = (float)MathHelper.Clamp(speed, 1f, 16f);//该函数的作用是将speed值限定在1和16之间,大于16,返回16,小于1,返回1,在两者之间返回true
     }
     }
    public float Zoom
     {get { return zoom; }
     }
     #endregion
     #region Constructor Region
    public Camera(Rectangle viewportRect)//用视野矩阵来初始化相机
     {
     speed = 4f;
     zoom = 1f;
     viewportRectangle = viewportRect;
     }
    public Camera(Rectangle viewportRect, Vector2 position)
     {
     speed = 4f;
     zoom = 1f;
     viewportRectangle = viewportRect;
     Position = position;
     }
     #endregion
     #region Method Region//根据键盘控制移动,实际上将Camera与Player联系在一起
    public void Update(GameTime gameTime)
     {
    if (InputHandler.KeyDown(Keys.Left))
     position.X -= speed;
    else if (InputHandler.KeyDown(Keys.Right))
     position.X += speed;
    if (InputHandler.KeyDown(Keys.Up))
     position.Y -= speed;
    else if (InputHandler.KeyDown(Keys.Down))
     position.Y += speed;
     }
     #endregion
     }
    }

    接下来就是为游戏中添加玩家player类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using XRpgLibrary;
    using XRpgLibrary.TileEngine;
    namespace EyesOfTheDragon.Components
    {
    public class Player
     {
     #region Field Region
    Camera camera;//Camera对象,将其与玩家联系起来
    Game1 gameRef;//Game对象,将其与玩家联系起来
     #endregion
     #region Property Region
    public Camera Camera
     {
    get { return camera; }
    set { camera = value; }
     }
     #endregion
     #region Constructor Region在构建玩家的同时构建Camera
    public Player(Game game)
     { 
    gameRef
    = (Game1)game; camera = new Camera(gameRef.ScreenRectangle); } #endregion #region Method Region public void Update(GameTime gameTime) { camera.Update(gameTime); } public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { } #endregion } }

    接着就是将Player对象添加到GamePlayScreen中去,这样就会为游戏页面实例化一个玩家对象,同时实例化一个Camera对象来实现地图的滚动

    using EyesOfTheDragon.Components;//添加空间引用
    #region Field Region
    Engine engine = new Engine(32, 32);
    Tileset tileset; 
    TileMap map;
    Player player;//定义对象
    #endregion
    #region Constructor Region
    public GamePlayScreen(Game game, GameStateManager manager)
     : base(game, manager)
    {
     player = new Player(game);//构造函数中实例化对象
    }
    #endregion

    在游戏页面的更新中也要更新玩家

    public override void Update(GameTime gameTime)
    {
     player.Update(gameTime);
     base.Update(gameTime);
    }

    接下来要更改的就是TileMap类,上节说过,该类是地图绘制的核心类,地图的绘制在Draw方法中实现,上节中是通过不断更改矩形变量destination的X,Y坐标来绘制每个tile,现在要用Camera来更新地图,所以要将Camera作为参数传入Draw方法中,由其来决定绘制tile的X,Y坐标,更新如下

    public void Draw(SpriteBatch spriteBatch, Camera camera)
    {
    Rectangle destination = new Rectangle(0, 0, Engine.TileWidth, Engine.TileHeight);
    Tile tile;
    foreach (MapLayer layer in mapLayers)
     {
    for (int y = 0; y < layer.Height; y++)
     {
      destination.Y = y * Engine.TileHeight - (int)camera.Position.Y;//获得绘制tile的Y坐标
    for (int x = 0; x < layer.Width; x++)
     {
     tile = layer.GetTile(x, y);
    if (tile.TileIndex == -1 || tile.Tileset == -1)
    continue;
     destination.X = x * Engine.TileWidth - (int)camera.Position.X;//获得绘制tile的X坐标
     spriteBatch.Draw(
     tilesets[tile.Tileset].Texture,
     destination,
     tilesets[tile.Tileset].SourceRectangles[tile.TileIndex],
    Color.White);
     }
     }
     }
    }

    这里关于游戏中摄像机的原理,自己解释不太清楚,可以从http://blog.csdn.net/beyondma/article/details/6766914这里获得相关的理论知识

    到这里我们运行程序,移动Camera绘制地图,会发现有绘出地图的现象,即Camera移出了窗体边界。这就需要根据地图的大小来控制Camera的移动区域,需要在TileMap类中添加地图宽度,高度的对外属性,在Camera类中根据这些属性来控制Camera的移动区域,地图的左边和上边最好控制,只要保证Camera的Position的X,Y坐标都是非负就能保证其没有越过左边界和上边界,至于下边界和右边界就用地图的宽度和高度来控制

    TileMap类中的更新代码如下

    #region Field Region
    List<Tileset> tilesets;
    List<MapLayer> mapLayers;
    static int mapWidth;//地图的宽度(tile的个数)
    static int mapHeight;//地图的高度
    #endregion
    #region Property Region
    public static int WidthInPixels返回地图宽度方向的像素值
    {
      get { return mapWidth * Engine.TileWidth; }
    }
    public static int HeightInPixels
    {
      get { return mapHeight * Engine.TileHeight; }
    }
    #endregion
    #region Constructor Region
    public TileMap(List<Tileset> tilesets, List<MapLayer> layers)
    {
     this.tilesets = tilesets;
     this.mapLayers = layers;
     mapWidth = mapLayers[0].Width;//以游戏最底层的MapLayer的高度,宽度来初始化
     mapHeight = mapLayers[0].Height;
    for (int i = 1; i < layers.Count; i++)
     {
    //检测到各个层之间的宽度,高度不一致,抛出异常
    if (mapWidth != mapLayers[i].Width || mapHeight != mapLayers[i].Height) throw new Exception("Map layer size exception"); } } public TileMap(Tileset tileset, MapLayer layer) { tilesets = new List<Tileset>(); tilesets.Add(tileset); mapLayers = new List<MapLayer>();
    mapLayers.Add(layer); mapWidth
    = mapLayers[0].Width;//初始化 mapHeight = mapLayers[0].Height; } #endregion

    Camera类中的更新如下

    public void Update(GameTime gameTime)
    {
    Vector2 motion = Vector2.Zero;//定义一个向量对象,用来确定Camera的移动方向
    if (InputHandler.KeyDown(Keys.Left))
     motion.X = -speed;
    else if (InputHandler.KeyDown(Keys.Right))
     motion.X = speed;
    if (InputHandler.KeyDown(Keys.Up))
     motion.Y = -speed;
    else if (InputHandler.KeyDown(Keys.Down))
     motion.Y = speed;
    if (motion != Vector2.Zero)
     motion.Normalize();//对向量进行标准化,然后再乘以Speed才能真的模拟玩家的移动
     position += motion * speed;
     LockCamera();//对Camera的位置进行设定
    }
    private void LockCamera()
    {
     position.X = MathHelper.Clamp(position.X,
     0,
    TileMap.WidthInPixels - viewportRectangle.Width);//将Camera的X坐标限定在0和(地图宽度-Camera视野宽度)之间,特别注意要减去Camera视野宽度,这样就不会出现移出游戏窗体的现象
     position.Y = MathHelper.Clamp(position.Y,
     0,
    TileMap.HeightInPixels - viewportRectangle.Height);
    }
    #endregion

    最后我们要实现多层地图的绘制,首先要在TileMap类中添加一个AddLayer方法来添加一层地图

    public void AddLayer(MapLayer layer)
    {
    //首先判断每层的宽度,高度是否相同
    if (layer.Width != mapWidth && layer.Height != mapHeight) throw new Exception("Map layer size exception"); mapLayers.Add(layer); }

    接着就是在GamePlayScreen类中添加这一层地图

    protected override void LoadContent()
    { 
     Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
     tileset = new Tileset(tilesetTexture, 8, 8, 32, 32);
     MapLayer layer = new MapLayer(40, 40);
    for (int y = 0; y < layer.Height; y++)
     {
    for (int x = 0; x < layer.Width; x++)
     {
      Tile tile = new Tile(0, 0);
      layer.SetTile(x, y, tile);
     }
    } map
    = new TileMap(tileset, layer); MapLayer splatter = new MapLayer(40, 40); Random random = new Random();
    //在40*40的图层上添加80个tile对象
    for (int i = 0; i < 80; i++) { int x = random.Next(0, 40); int y = random.Next(0, 40);
    //在2到14这些图片中随机选择一个
    int index = random.Next(2, 14); Tile tile = new Tile(index, 0);//利用tile索引来初始化tile对象 splatter.SetTile(x, y, tile);//往新的图层上添加tile对象 } map.AddLayer(splatter); base.LoadContent(); }

    接着我们想在游戏中添加一些除了草地,植物以为的城市tile,这就需要在GameplayScreen类中新添加一个tileset对象,将从 http://xnagpa.net/xna4/downloads/tilesets2.zip获得的tilesets2.png添加到Content中,更新GamePlayScreen的LoadContent()方法如下

    protected override void LoadContent()
    {
    base.LoadContent();
      Texture2D tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset1");
     Tileset tileset1 = new Tileset(tilesetTexture, 8, 8, 32, 32);
     tilesetTexture = Game.Content.Load<Texture2D>(@"Tilesets\tileset2");
     Tileset tileset2 = new Tileset(tilesetTexture, 8, 8, 32, 32);//新建立的tileset对象
     List<Tileset> tilesets = new List<Tileset>();
     tilesets.Add(tileset1);
     tilesets.Add(tileset2);
    MapLayer layer = new MapLayer(40, 40);
    for (int y = 0; y < layer.Height; y++) { for (int x = 0; x < layer.Width; x++) { Tile tile = new Tile(0, 0); layer.SetTile(x, y, tile); } } MapLayer splatter = new MapLayer(40, 40); Random random = new Random(); for (int i = 0; i < 80; i++) { int x = random.Next(0, 40); int y = random.Next(0, 40); int index = random.Next(2, 14); Tile tile = new Tile(index, 0);//这里的0代表的是第一个tileset集合 splatter.SetTile(x, y, tile); }
    //在第二个tileset集合中选择对象设定固定位置的tile splatter.SetTile(
    1, 0, new Tile(0, 1)); splatter.SetTile(2, 0, new Tile(2, 1)); splatter.SetTile(3, 0, new Tile(0, 1)); List<MapLayer> mapLayers = new List<MapLayer>(); mapLayers.Add(layer); mapLayers.Add(splatter); map = new TileMap(tilesets, mapLayers); }
    public override void Draw(GameTime gameTime)
    {
     GameRef.SpriteBatch.Begin(
     SpriteSortMode.Immediate,
     BlendState.AlphaBlend,
     SamplerState.PointClamp,
     null,
     null,
     null,
     Matrix.Identity);
     map.Draw(GameRef.SpriteBatch, player.Camera);//绘制地图
    base.Draw(gameTime); GameRef.SpriteBatch.End(); }

    OK,今天主要是实现了利用摄像机原理来更新地图,同时实现了多层地图。

  • 相关阅读:
    Solution 「AGC 031E」Snuke the Phantom Thief
    Solution Set 「LOCAL」冲刺省选 Round XXIX
    Solution Set 「LOCAL」冲刺省选 Round XXVIII
    Solution 「LOCAL」Minimal DFA
    Solution 「ZJOI 2015」「洛谷 P3343」地震后的幻想乡
    Solution Set 「LOCAL」冲刺省选 Round X
    Solution Set 「LOCAL」冲刺省选 Round VII
    Solution 「WC 2014」「洛谷 P3920」紫荆花之恋
    虚拟机下CentOS7开启SSH连接
    linux c 共享内存 消息队列 信号量
  • 原文地址:https://www.cnblogs.com/zcftech/p/2999531.html
Copyright © 2020-2023  润新知