XNA之RPG游戏开发教程之八
在前一节中,我们创建了一个新的控件LeftRightSelector,主要是用来对角色进行选取,但是该控件并没有发挥其作用。本节中将添加PictureBox控件,在筛选角色信息同时,角色图片也相应改变,并且根据选取的角色来在GamePlayScreen页面上呈现;
首先在CharacterGeneratorScreen类中添加PictureBox控件和TextTure数组对象,并响应LeftRightSelector的SelctionChanged事件,实现代码如下
PictureBox characterImage;//添加的类级变量,PictureBox对象,用来呈现图片对象 Texture2D[,] characterImages;//图片对象数组 protected override void LoadContent() { base.LoadContent(); LoadImages();//加载图片信息 CreateControls(); } private void CreateControls() { Texture2D leftTexture = Game.Content.Load<Texture2D>(@"GUI\leftarrowUp"); Texture2D rightTexture = Game.Content.Load<Texture2D>(@"GUI\rightarrowUp"); Texture2D stopTexture = Game.Content.Load<Texture2D>(@"GUI\StopBar"); backgroundImage = new PictureBox( Game.Content.Load<Texture2D>(@"Backgrounds\titlescreen"), GameRef.ScreenRectangle); ControlManager.Add(backgroundImage); Label label1 = new Label(); label1.Text = "Who will search for the Eyes of the Dragon?"; label1.Size = label1.SpriteFont.MeasureString(label1.Text); label1.Position = new Vector2((GameRef.Window.ClientBounds.Width - label1.Size.X) / 2, 150); ControlManager.Add(label1); genderSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture); genderSelector.SetItems(genderItems, 125); genderSelector.Position = new Vector2(label1.Position.X, 200); genderSelector.SelectionChanged += new EventHandler(selectionChanged); ControlManager.Add(genderSelector); classSelector = new LeftRightSelector(leftTexture, rightTexture, stopTexture); classSelector.SetItems(classItems, 125); classSelector.Position = new Vector2(label1.Position.X, 250); classSelector.SelectionChanged += selectionChanged; ControlManager.Add(classSelector); LinkLabel linkLabel1 = new LinkLabel(); linkLabel1.Text = "Accept this character."; linkLabel1.Position = new Vector2(label1.Position.X, 300); linkLabel1.Selected += new EventHandler(linkLabel1_Selected); ControlManager.Add(linkLabel1);
//以下是添加的代码 characterImage = new PictureBox( characterImages[0, 0], new Rectangle(500, 200, 96, 96), new Rectangle(0, 0, 32, 32)); ControlManager.Add(characterImage);//初始化角色图片框并加入到管理类中 ControlManager.NextControl(); } private void LoadImages() { characterImages = new Texture2D[genderItems.Length, classItems.Length];//角色信息分为性别和类型,根据这两个因素将图片存储为一个图片组 for (int i = 0; i < genderItems.Length; i++) { for (int j = 0; j < classItems.Length; j++) { characterImages[i, j] = Game.Content.Load<Texture2D>(@"PlayerSprites\" + genderItems[i] + classItems[j]);//根据选择来加载图片 } } }
//selectionChanged事件的响应函数
void selectionChanged(object sender, EventArgs e) { characterImage.Image = characterImages[genderSelector.SelectedIndex, classSelector.SelectedIndex]; }
接下来就是将玩家选择的角色呈现在GamePlayScreen游戏页面上,这就需要CharacterGeneratorScreen类中提供角色选择信息的接口,代码如下
public string SelectedGender//返回角色的性别信息 { get { return genderSelector.SelectedItem; } } public string SelectedClass//返回角色的类型信息 { get { return classSelector.SelectedItem; } }
在GamePlayScreen类中根据获得的角色信息来加载相应的角色到游戏界面,其LoadContent方法如下
protected override void LoadContent() { Texture2D spriteSheet = Game.Content.Load<Texture2D>( @"PlayerSprites\" + GameRef.CharacterGeneratorScreen.SelectedGender + GameRef.CharacterGeneratorScreen.SelectedClass);//根据角色加载相应的图片
Dictionary<AnimationKey, Animation> animations = new Dictionary<AnimationKey, Animation>(); Animation animation = new Animation(3, 32, 32, 0, 0); animations.Add(AnimationKey.Down, animation); animation = new Animation(3, 32, 32, 0, 32); animations.Add(AnimationKey.Left, animation); animation = new Animation(3, 32, 32, 0, 64); animations.Add(AnimationKey.Right, animation); animation = new Animation(3, 32, 32, 0, 96); animations.Add(AnimationKey.Up, animation);//根据角色的运动状态加载相应的动画 sprite = new AnimatedSprite(spriteSheet, animations);//初始化动态精灵对象
//以上为更新的代码 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); 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); splatter.SetTile(x, y, 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); }
所以现在的游戏页面跳转逻辑是:startMenuScreen到characterGneratorScreen,根据选择的角色再在GamePlayScreen上绘制相应的角色图片。
下一步我们想实现Camera类的Zoom in 和Zoom out方法,实现对地图的放大和缩小,打开Camera类,更改update方法如下
public void Update(GameTime gameTime) { if (InputHandler.KeyReleased(Keys.PageUp) || InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One)) ZoomIn();//地图缩小 else if (InputHandler.KeyReleased(Keys.PageDown) || InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One)) ZoomOut();//地图放大
//以上是更新代码 if (mode == CameraMode.Follow) return; Vector2 motion = Vector2.Zero; if (InputHandler.KeyDown(Keys.Left) || InputHandler.ButtonDown(Buttons.RightThumbstickLeft, PlayerIndex.One)) motion.X = -speed; else if (InputHandler.KeyDown(Keys.Right) || InputHandler.ButtonDown(Buttons.RightThumbstickRight, PlayerIndex.One)) motion.X = speed; if (InputHandler.KeyDown(Keys.Up) || InputHandler.ButtonDown(Buttons.RightThumbstickUp, PlayerIndex.One)) motion.Y = -speed; else if (InputHandler.KeyDown(Keys.Down) || InputHandler.ButtonDown(Buttons.RightThumbstickDown, PlayerIndex.One)) motion.Y = speed; if (motion != Vector2.Zero) { motion.Normalize(); position += motion * speed; LockCamera(); } } private void ZoomIn() { zoom += .25f; if (zoom > 2.5f) zoom = 2.5f; } private void ZoomOut() { zoom -= .25f; if (zoom < .5f) zoom = .5f; }
到目前为止我们只是在Camera类中加入了控制放大缩小的代码以及放大缩小梯度的设置,真正实现地图的放大和缩小是在GameplayScreen类中
可以将整个地图看做是一个像素矩阵,要对该矩阵做放大,缩小变化,要按照一定的矩阵变化顺序执行:等比-》按比例放大,缩小-》旋转-》平移。这些操作Matrix类都给我们提供了现成的方法,代码如下
public Matrix Transformation//转移矩阵 { get { return Matrix.CreateScale(zoom) * Matrix.CreateTranslation(new Vector3(-Position, 0f)); }//先按照倍数放大矩阵,再朝Camera的负方向平移 } public Rectangle ViewportRectangle//给出Camera视场矩阵的接口 { get { return new Rectangle( viewportRectangle.X, viewportRectangle.Y, viewportRectangle.Width, viewportRectangle.Height); } }
上述代码是Camera类给出的对外接口,向调用它进行放大缩小的对象提供变换矩阵transformation和变化对象矩阵ViewportRectangle。回到GameplayScreen类中,将其Draw方法中的spritebatch.begin()中参数重新设置,以便实现地图的放缩
public override void Draw(GameTime gameTime) { GameRef.SpriteBatch.Begin( SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, player.Camera.Transformation);//Begin方法的7个参数重载,其中最关键的是最后一个参数Matrix,主要用于对Begin和End方法之间的所有绘制对象进行放缩变化的矩阵 map.Draw(GameRef.SpriteBatch, player.Camera); sprite.Draw(gameTime, GameRef.SpriteBatch, player.Camera); base.Draw(gameTime); GameRef.SpriteBatch.End(); }
已经设置好了放缩矩阵,那么现在就要对Begin和end方法之间要绘制的对象的Draw方法进行重写,实现其的放缩变化
先重写Tilemap类的Draw方法如下
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;//改变的地方,实际上就是将以前画图时减去Camera对象的坐标给删除了,为什么呢?因为在我们转移矩阵中已经减过了... 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;// spriteBatch.Draw( tilesets[tile.Tileset].Texture, destination, tilesets[tile.Tileset].SourceRectangles[tile.TileIndex], Color.White); } } } }
同样在AnimatedSprtie类的Draw方法中
public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Camera camera) { spriteBatch.Draw( texture, position, animations[currentAnimation].CurrentFrameRect, Color.White); }
现在我们就可以对地图进行放大和缩小了,但是会有一个现象发生,当zoom缩小到一定值后地图边缘会被蓝色屏幕覆盖,这就需要我们对Camera位置进行重新锁定,使得地图的大小面积根据zoom的值进行变化,这样就不会出现地图不能覆盖整个窗体的现象,Camera类中修改代码如下
private void LockCamera()//对Camera锁定区域的修改 { position.X = MathHelper.Clamp(position.X, 0, TileMap.WidthInPixels * zoom - viewportRectangle.Width); position.Y = MathHelper.Clamp(position.Y, 0, TileMap.HeightInPixels * zoom - viewportRectangle.Height); }
public void LockToSprite(AnimatedSprite sprite)//对Follow状态下Camera区域的修改 { position.X = (sprite.Position.X + sprite.Width / 2) * zoom - (viewportRectangle.Width / 2); position.Y = (sprite.Position.Y + sprite.Height / 2) * zoom - (viewportRectangle.Height / 2); LockCamera(); }
接着就是要将Camera和地图进行对齐
public void ZoomIn() { zoom += .25f; if (zoom > 2.5f) zoom = 2.5f; Vector2 newPosition = Position * zoom; SnapToPosition(newPosition); } public void ZoomOut() { zoom -= .25f; if (zoom < .5f) zoom = .5f; Vector2 newPosition = Position * zoom; SnapToPosition(newPosition); } private void SnapToPosition(Vector2 newPosition) { position.X = newPosition.X - viewportRectangle.Width / 2; position.Y = newPosition.Y - viewportRectangle.Height / 2; LockCamera(); }
最后就是在PlayGameScreen类中修改Update方法,实现图像的放缩
public override void Update(GameTime gameTime) { player.Update(gameTime); sprite.Update(gameTime);
//代码修改区域
//按pageup键,地图缩小 if (InputHandler.KeyReleased(Keys.PageUp) || InputHandler.ButtonReleased(Buttons.LeftShoulder, PlayerIndex.One)) { player.Camera.ZoomIn(); if (player.Camera.CameraMode == CameraMode.Follow) player.Camera.LockToSprite(sprite); } else if (InputHandler.KeyReleased(Keys.PageDown) || InputHandler.ButtonReleased(Buttons.RightShoulder, PlayerIndex.One)) { player.Camera.ZoomOut(); if (player.Camera.CameraMode == CameraMode.Follow) player.Camera.LockToSprite(sprite); }
//代码修改区域 Vector2 motion = new Vector2(); if (InputHandler.KeyDown(Keys.W) || InputHandler.ButtonDown(Buttons.LeftThumbstickUp, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Up; motion.Y = -1; } else if (InputHandler.KeyDown(Keys.S) || InputHandler.ButtonDown(Buttons.LeftThumbstickDown, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Down; motion.Y = 1; } if (InputHandler.KeyDown(Keys.A) || InputHandler.ButtonDown(Buttons.LeftThumbstickLeft, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Left; motion.X = -1; } else if (InputHandler.KeyDown(Keys.D) || InputHandler.ButtonDown(Buttons.LeftThumbstickRight, PlayerIndex.One)) { sprite.CurrentAnimation = AnimationKey.Right; motion.X = 1; } if (motion != Vector2.Zero) { sprite.IsAnimating = true; motion.Normalize(); sprite.Position += motion * sprite.Speed; sprite.LockToMap(); if (player.Camera.CameraMode == CameraMode.Follow) player.Camera.LockToSprite(sprite); } else { sprite.IsAnimating = false; } if (InputHandler.KeyReleased(Keys.F) || InputHandler.ButtonReleased(Buttons.RightStick, PlayerIndex.One)) { player.Camera.ToggleCameraMode(); if (player.Camera.CameraMode == CameraMode.Follow) player.Camera.LockToSprite(sprite); } if (player.Camera.CameraMode != CameraMode.Follow) { if (InputHandler.KeyReleased(Keys.C) || InputHandler.ButtonReleased(Buttons.LeftStick, PlayerIndex.One)) { player.Camera.LockToSprite(sprite); } } base.Update(gameTime); }
Ok,现在地图就可以放大缩小了。