用于Sliverlight的StunEngine引擎
在重用XNA代码一文中我们已经创建了可以在Silverlight平台上运行的仿制XNA Game类,因此直接将XNA平台下的StunEngine的代码复制过来即可。至于StunEngine的代码分析可参见本网站的“XNA编程”栏目中的的“游戏引擎StunEngine”,所以本文只介绍如何基于StunEngine实现乒乓游戏,代码主要参考自§2.3让我们编写Pong,但根据StunEngine引擎的特点进行了修改。
游戏屏幕
本游戏共有2个屏幕,第一个为主菜单屏幕,对应文件为MainMenuScreen.cs,第二个为游戏屏幕,对应文件为GameplayScreen.cs,主菜单屏幕如下图所示:
主菜单屏幕
主菜单屏幕包含星空背景和XNA Pong的游戏标题,它们是Renderable2DsceneNode,两个菜单选项是按钮,它们是UIButton。由于主菜单和游戏屏幕公用星空背景图像,所以事实上有第三个Scene类BackgroundScreen.cs,它负责绘制星空背景,BackgroundScreen.cs的代码如下:
namespace Silverlight3dApp.GameScreens { /// <summary> /// 背景屏幕在其他屏幕之后 /// 无论在它之上的其他屏幕是否处于过渡都会绘制背景图像 /// </summary> class BackgroundScreen : Scene2D { /// <summary> /// 构造函数 /// </summary> public BackgroundScreen(Engine engine):base(engine ,"Background") { Texture2D background = engine.Content.Load<Texture2D>("Textures/SpaceBackground"); Renderable2DSceneNode bg = new Renderable2DSceneNode(this, background); bg.Size = new Vector2(800, 600); AddNode(bg); } /// <summary> /// 更新背景屏幕。不同于其他屏幕,当这个屏幕被其他屏幕覆盖时也无需淡出 /// 它总是被覆盖!这个重载方法将coveredByOtherScreen参数强制设为false使关闭基类的淡出 /// </summary> public override void UpdateScene(GameTime gameTime, bool otherScreenHasFocus,bool coveredByOtherScreen) { base.UpdateScene(gameTime, otherScreenHasFocus, false ); } } }
由于菜单按钮还要实现当鼠标移动其上时变成桔黄色,发出声音,移出时变回白色,当点击按钮时发出另一个声音,这些功能引擎中的UIButton类并没有提供,因此需要从UIButton类继承实现新功能的按钮类,我命名为Button.cs,代码如下:
namespace Silverlight3dApp { public class Button:UIButton { SoundEffectInstance highlight; SoundEffectInstance click; public Button(Scene setScene, UIManager setUIManager, Texture2D setTexture,SoundEffectInstance highlight,SoundEffectInstance click):base(setScene,setUIManager,setTexture) { this.highlight =highlight ; this.click = click; } protected override void OnMouseEnter(EventArgs e) { Color = new Color(255, 165, 0, 255); base.OnMouseEnter(e); } protected override void OnMouseLeave(EventArgs e) { Color = Color.White; highlight.Play(); base.OnMouseLeave(e); } protected override void OnMouseLeftButtonClick(EventArgs e) { click.Play(); base.OnMouseLeftButtonClick(e); } } }
做好了准备工作后,就可以实现主菜单了,代码如下:
namespace Silverlight3dApp.GameScreens { /// <summary> /// 游戏主菜单 /// </summary> class MainMenuScreen : Scene2D { Button btnGamePlaySingle, btnGamePlayMultiple; readonly SoundEffect Highlight; readonly SoundEffectInstance HighlightInstance; readonly SoundEffect Click; readonly SoundEffectInstance ClickInstance; static readonly Rectangle XnaPongLogoRect = new Rectangle(0, 0, 512, 110), MenuSingleplayerRect = new Rectangle(0, 110, 512, 38), MenuMultiplayerRect = new Rectangle(0, 148, 512, 38); /// <summary> /// 构造函数添加菜单内容 /// </summary> public MainMenuScreen(Engine engine): base(engine,"Main Menu") { Texture2D pongMenu = engine.Content.Load<Texture2D>("Textures/PongMenu"); // XNA Pong标题 Renderable2DSceneNode XnaPongLogo = new Renderable2DSceneNode(this, pongMenu); XnaPongLogo.Size = new Vector2(512,110); XnaPongLogo.Position = new Vector2(400 - XnaPongLogoRect.Width / 2, 150); XnaPongLogo.RectSourceOrigin = XnaPongLogoRect; AddNode(XnaPongLogo); // 加载按钮的声音 Highlight = engine.Content.Load<SoundEffect>("Sound\\Highlight"); HighlightInstance = Highlight.CreateInstance(); Click = engine.Content.Load<SoundEffect>("Sound\\Click"); ClickInstance = Click.CreateInstance(); // Singleplayer按钮 btnGamePlaySingle = new Button(this, this.UiManager, pongMenu, HighlightInstance,ClickInstance ); btnGamePlaySingle.Size = new Vector2(512, 38); btnGamePlaySingle.RectSourceOrigin = btnGamePlaySingle.RectSourceHighlight = btnGamePlaySingle.RectSourceClicked = MenuSingleplayerRect; btnGamePlaySingle.Position = new Vector2(400 - MenuSingleplayerRect.Width / 2, 300); btnGamePlaySingle.Text = null; btnGamePlaySingle.MouseLeftButtonClick += new EventHandler(btnGamePlay_MouseLeftButtonClick); AddNode(btnGamePlaySingle); // Multipleplayer按钮 btnGamePlayMultiple = new Button(this,this.UiManager , pongMenu, HighlightInstance,ClickInstance); btnGamePlayMultiple.Size = new Vector2(512, 38); btnGamePlayMultiple.RectSourceOrigin = btnGamePlayMultiple.RectSourceHighlight =btnGamePlayMultiple.RectSourceClicked=MenuMultiplayerRect; btnGamePlayMultiple.Position = new Vector2(400 - MenuMultiplayerRect.Width / 2, 350); btnGamePlayMultiple.Text = null; btnGamePlayMultiple.MouseLeftButtonClick += new EventHandler(btnGamePlay_MouseLeftButtonClick); AddNode(btnGamePlayMultiple); } void btnGamePlay_MouseLeftButtonClick(object sender, EventArgs e) { GameplayScreen gameplayScreen; if (sender == btnGamePlayMultiple) gameplayScreen = new GameplayScreen(engine, true); else gameplayScreen = new GameplayScreen(engine, false); engine.SceneManager.AddScene(gameplayScreen ); } } }
下面是游戏屏幕,如下图所示:
游戏屏幕
其中包含左上、右上角的生命值信息,红、蓝两色球拍和小球,它们都是Renderable2DsceneNode,游戏屏幕类GameplayScreen.cs的代码如下:
namespace Silverlight3dApp { /// <summary> /// 实际进行游戏的屏幕 /// </summary> class GameplayScreen:Scene2D { #region 成员变量和构造函数 /// <summary> /// 图像对应的源矩阵 /// </summary> static readonly Rectangle GameLivesRect = new Rectangle(0, 222, 100, 34), GameRedWonRect = new Rectangle(151, 222, 155, 34), GameBlueWonRect = new Rectangle(338, 222, 165, 34), GameRedPaddleRect = new Rectangle(23, 0, 22, 92), GameBluePaddleRect = new Rectangle(0, 0, 22, 92), GameBallRect = new Rectangle(1, 94, 33, 33), GameSmallBallRect = new Rectangle(37, 108, 19, 19); /// <summary> /// CPU控制的挡板的速度。如果球移动的速度比这个速度大,则CPU控制的球拍就无法跟上小球,最终你会获胜 /// </summary> const float ComputerPaddleSpeed = 220.0f; /// <summary> /// 小球的速度大小,即1秒能移动多少像素 /// </summary> const float ballSpeed = 180.0f; /// <summary> /// 随机数生成器 /// </summary> Random random = new Random(); /// <summary> /// 左边和右边玩家的生命值。如果一方的生命值为0,则另一方获胜 /// </summary> int leftPlayerLives = 3,rightPlayerLives = 3; //要绘制的图像、文字 Renderable2DSceneNode redPaddle, bluePaddle, ball, livesRed, livesBlue,blueWon,redWon; Renderable2DSceneNode [] redBallLives=new Renderable2DSceneNode [3]; Renderable2DSceneNode [] blueBallLives = new Renderable2DSceneNode [3]; /// <summary> /// 当前的小球位置 /// </summary> Vector2 ballPosition = new Vector2(367.0f, 267.0f); /// <summary> /// 小球的速度方向矢量,每个新小球都是随机的 /// 如果游戏结束则设为0 /// </summary> Vector2 ballSpeedVector = Vector2.Zero; /// <summary> /// 是否多人游戏,如果不是则CPU控制左方蓝色球拍 /// </summary> bool multiplayer = false; /// <summary> /// 游戏是否结束 /// </summary> bool isGameOver = false; readonly SoundEffect PongBallHit; readonly SoundEffectInstance PongBallHitInstance; readonly SoundEffect PongBallLost; readonly SoundEffectInstance PongBallLostInstance; /// <summary> /// 构造函数 /// </summary> public GameplayScreen(Engine engine,bool setMultiplayer):base(engine,"Gameplay") { PongBallHit = engine.Content.Load<SoundEffect>("Sound\\PongBallHit"); PongBallHitInstance = PongBallHit.CreateInstance(); PongBallLost = engine.Content.Load<SoundEffect>("Sound\\PongBallLost"); PongBallLostInstance = PongBallLost.CreateInstance(); Texture2D background = engine.Content.Load<Texture2D>("Textures/SpaceBackground"); Texture2D pongMenu = engine.Content.Load<Texture2D>("Textures/PongMenu"); Texture2D pongGame = engine.Content.Load<Texture2D>("Textures/PongGame"); // 背景 Renderable2DSceneNode bg = new Renderable2DSceneNode(this, background); bg.Size = new Vector2(800, 600); //AddNode(bg); //左边的生命值 livesRed = new Renderable2DSceneNode( this, pongMenu); livesRed.Position = new Vector2(10, 10); livesRed.Size = new Vector2(GameLivesRect.Width, GameLivesRect.Height); livesRed.RectSourceOrigin = GameLivesRect; livesRed.Color = new Color(255,0,0,255); AddNode(livesRed); for (int num = 0; num < leftPlayerLives; num++) { redBallLives[num] = new Renderable2DSceneNode(this, pongGame); redBallLives[num].Position = new Vector2(GameLivesRect.Width + GameSmallBallRect.Width * num, 18); redBallLives[num].Size = new Vector2(GameSmallBallRect.Width, GameSmallBallRect.Height); redBallLives[num].RectSourceOrigin = GameSmallBallRect; AddNode(redBallLives[num]); } //右边的生命值 livesBlue = new Renderable2DSceneNode (this, pongMenu); livesBlue.Position = new Vector2(600, 10); livesBlue.Size = new Vector2(GameLivesRect.Width, GameLivesRect.Height); livesBlue.RectSourceOrigin = GameLivesRect; livesBlue.Color = new Color(0,0,255,255); AddNode(livesBlue); for (int num = 0; num < rightPlayerLives; num++) { blueBallLives[num] = new Renderable2DSceneNode (this, pongGame); blueBallLives[num].Position = new Vector2(600 + GameLivesRect.Width + GameSmallBallRect.Width * num, 18); blueBallLives[num].Size = new Vector2(GameSmallBallRect.Width, GameSmallBallRect.Height); blueBallLives[num].RectSourceOrigin = GameSmallBallRect; AddNode(blueBallLives[num]); } //显示“蓝方获胜”的文字 blueWon = new Renderable2DSceneNode(this,pongMenu); blueWon.RectSourceOrigin = GameBlueWonRect; blueWon.Size = new Vector2(GameBlueWonRect.Width ,GameBlueWonRect .Height ); blueWon.Position = new Vector2(400.0f - blueWon.Size.X / 2, 200.0f); blueWon.Color = new Color(0, 0, 255, 255); blueWon.Visible = false; AddNode(blueWon); //显示“红方获胜”的文字 redWon = new Renderable2DSceneNode(this,pongMenu); redWon.RectSourceOrigin = GameRedWonRect; redWon.Size = new Vector2(GameRedWonRect.Width, GameRedWonRect.Height); redWon.Position = new Vector2(400.0f - redWon.Size.X / 2, 200.0f); redWon.Color = new Color(255, 0, 0, 255); redWon.Visible = false; AddNode(redWon); //小球 ball = new Renderable2DSceneNode (this, pongGame); ball.Position = new Vector2(367.0f, 267.0f); ball.Size = new Vector2 (GameBallRect .Width,GameBallRect .Height ) ; ball.RectSourceOrigin = GameBallRect; AddNode(ball); multiplayer = setMultiplayer; //左边红色球拍 redPaddle = new Renderable2DSceneNode (this, pongGame); redPaddle.Position = new Vector2(10, 254); redPaddle.Size = new Vector2(GameRedPaddleRect.Width, GameRedPaddleRect.Height); redPaddle.RectSourceOrigin=GameRedPaddleRect; if (multiplayer) { PaddleController redPaddleController = new PaddleController(Key.W,Key.S,10.0f); redPaddle.AttachController(redPaddleController); } AddNode(redPaddle); //右边蓝色球拍 bluePaddle = new Renderable2DSceneNode (this,pongGame); bluePaddle.Position = new Vector2(768, 254); bluePaddle.Size = new Vector2(GameBluePaddleRect.Width, GameBluePaddleRect.Height); bluePaddle.RectSourceOrigin = GameBluePaddleRect; AddNode(bluePaddle); PaddleController bluePaddleController = new PaddleController(Key.Up ,Key.Down,768.0f); bluePaddle.AttachController(bluePaddleController); } #endregion #region 开始和停止小球 /// <summary> /// 当游戏开始时或小球出界时重新设置小球 /// </summary> public void StartNewBall() { ballPosition = new Vector2(367.0f, 267.0f); Random rnd = new Random((int)DateTime.Now.Ticks); int direction = rnd.Next(4); //随机生成小球运动的方向(分别有右上、左上、左下、右下四个方向) ballSpeedVector = direction == 0 ? new Vector2(1, 0.8f) : direction == 1 ? new Vector2(1, -0.8f) : direction == 2 ? new Vector2(-1, 0.8f) : new Vector2(-1, -0.8f); } /// <summary> /// 当游戏结束时停止小球 /// </summary> public void StopBall() { ballSpeedVector = Vector2.Zero ; } #endregion #region 更新 public override void UpdateScene(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) { base.UpdateScene(gameTime, otherScreenHasFocus, coveredByOtherScreen); [...] } } #endregion } }
游戏逻辑
游戏逻辑位于GameplayScreen.cs类的Update方法中,代码如下,原理可参见其中的注释:
public override void UpdateScene(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen) { base.UpdateScene(gameTime, otherScreenHasFocus, coveredByOtherScreen); if (this.IsActive) { { float moveFactorPerSecond = (float)gameTime.ElapsedGameTime .TotalMilliseconds / 1000.0f; // 当小球停止并且游戏还没有结束,则重新设置小球 if (ballSpeedVector == Vector2.Zero && !isGameOver) StartNewBall(); // 如果第二个玩家由CPU控制 if (!multiplayer) { // 让CPU控制的左边红色球拍跟随小球的运动 float computerChange = ComputerPaddleSpeed * moveFactorPerSecond; if (redPaddle.Position.Y > ball.Position.Y + computerChange) redPaddle.Position -= new Vector2(0, computerChange); else if (redPaddle.Position.Y < ball.Position.Y - computerChange) redPaddle.Position += new Vector2(0, computerChange); } //避免红色球拍移出上下边界 if (redPaddle.Position.Y < 0) redPaddle.Position = new Vector2(10, 0); if (redPaddle.Position.Y > 508) redPaddle.Position = new Vector2(10, 508); #region 碰撞检测 // 检测小球是否与屏幕顶部或底部碰撞 bool hasNegativeVelocity; if (ballSpeedVector.Y < 0) hasNegativeVelocity = true; else hasNegativeVelocity = false; // 只有在小球超出上边界且向上运动时;超出下边界且向下运动时 // 才让小球回弹,这样可以避免小球速度过快时发生在边界上快速 // 小幅度碰撞的不合理情况的发生。 if ((ballPosition.Y < 0&&hasNegativeVelocity) || (ballPosition.Y > 567&&!hasNegativeVelocity)) { //如果碰撞,则将小球速度矢量的Y分量取反 ballSpeedVector.Y = -ballSpeedVector.Y; //播放声音 PongBallHitInstance.Play(); } // 创建小球、两个球拍的包围盒用于碰撞检测 BoundingBox ballBox = new BoundingBox(new Vector3(ball.Position.X, ball.Position.Y, 0), new Vector3((ball.Position.X + ball.Size.X), (ball.Position.Y + ball.Size.Y), 0)); BoundingBox redPaddleBox = new BoundingBox(new Vector3(redPaddle.Position.X, redPaddle.Position.Y, 0), new Vector3((redPaddle.Position.X + redPaddle.Size.X), (redPaddle.Position.Y + redPaddle.Size.Y), 0)); BoundingBox bluePaddleBox = new BoundingBox(new Vector3( bluePaddle.Position.X, bluePaddle.Position.Y, 0), new Vector3((bluePaddle.Position.X + bluePaddle.Size.X), (bluePaddle.Position.Y + bluePaddle.Size.Y), 0)); // 球是否与左边的红色球拍碰撞? if (ballBox.Intersects(redPaddleBox)) { // Bounce of the paddle (always make positive) ballSpeedVector.X = Math.Abs(ballSpeedVector.X); // 增加球的速度 ballSpeedVector *= 1.2f; // 如果碰撞在红色球拍右上角 if (ballBox.Intersects(new BoundingBox( new Vector3( (redPaddleBox.Max.X - 1.0f), (redPaddleBox.Min.Y - 1.0f), 0), new Vector3((redPaddleBox.Max.X + 1.0f), (redPaddleBox.Min.Y + 1.0f), 0)))) // 给对方设置一个更快的小球 ballSpeedVector.Y = -2; // 如果碰撞在红色球拍右下角 else if (ballBox.Intersects(new BoundingBox( new Vector3( (redPaddleBox.Max.X - 1.0f), (redPaddleBox.Max.Y - 1.0f), 0), new Vector3( (redPaddleBox.Max.X + 1.0f), (redPaddleBox.Max.Y + 1.0f), 0)))) // 给对方设置一个更快的小球 ballSpeedVector.Y = +2; // 播放撞击声 PongBallHitInstance.Play(); } // 球是否与右边的蓝色球拍碰撞? if (ballBox.Intersects(bluePaddleBox)) { // 使小球向左方运动 ballSpeedVector.X = -Math.Abs(ballSpeedVector.X); // 碰撞后增加一点速度 ballSpeedVector *= 1.2f; // 如果碰撞在蓝色球拍左上角 if (ballBox.Intersects(new BoundingBox( new Vector3( (bluePaddleBox.Min.X - 1.0f), (bluePaddleBox.Min.Y - 1.0f), 0), new Vector3( (bluePaddleBox.Min.X + 1.0f), (bluePaddleBox.Min.Y + 1.0f), 0)))) // 给对方设置一个更快的小球 ballSpeedVector.Y = -2; // 如果碰撞在蓝色球拍左下角 else if (ballBox.Intersects(new BoundingBox( new Vector3( (bluePaddleBox.Min.X - 1.0f), (bluePaddleBox.Max.Y - 1.0f), 0), new Vector3( (bluePaddleBox.Min.X + 1.0f), (bluePaddleBox.Max.Y + 1.0f), 0)))) // 给对方设置一个更快的小球 ballSpeedVector.Y = +2; // 播放撞击声 PongBallHitInstance.Play(); } // 更新小球位置 ballPosition += ballSpeed * moveFactorPerSecond * ballSpeedVector; ball.Position = ballPosition; // 球是否出左边界? if (ballPosition.X < 0) { // 播放声音 PongBallLostInstance.Play(); // 左边的生命值减1 leftPlayerLives--; redBallLives[leftPlayerLives].Visible = false; // 重新设置一个新的小球 StartNewBall(); } //球是否出右边界? else if (ballPosition.X > 767.0f) { ballSpeedVector.X = -ballSpeedVector.X; // 播放声音 PongBallLostInstance.Play(); // 右边的生命值减1 rightPlayerLives--; blueBallLives[rightPlayerLives].Visible = false; // 重新设置一个新的小球 StartNewBall(); } #endregion // 如果任意一方的生命值为0,则另一方获胜 if (leftPlayerLives == 0 || rightPlayerLives == 0) { StopBall(); isGameOver = true; if (leftPlayerLives == 0) blueWon.Visible = true; else redWon.Visible = true; } //如果在游戏过程中按下Escape键,则返回主菜单 if (Input.KeyboardKeyJustPressed (Key.Escape )) this.ExitScreen(); ; //如果游戏结束,按下空格键或鼠标左键可以重新开始 if (isGameOver) { if (Input.KeyboardKeyJustPressed(Key.Space) || Input.MouseLeftButtonJustPressed) { leftPlayerLives = rightPlayerLives = 3; for (int num = 0; num < leftPlayerLives; num++) { blueBallLives[num].Visible = redBallLives[num].Visible = true; } redWon.Visible = false; blueWon.Visible = false; StartNewBall(); isGameOver = false; } } } } }
控制两个球拍是使用不同参数的PaddleController类,代码如下:
namespace Silverlight3dApp { public class PaddleController : Controller { #region 构造函数和成员变量 /// <summary> /// 要控制的球拍 /// </summary> private Renderable2DSceneNode paddle; /// <summary> /// 球拍的移动速度 /// </summary> const float paddleSpeed = 200.0f; // 控制球拍上下移动的键 Key upKey; Key downKey; // 球拍的x坐标 float positionX; /// <summary> /// 创建一个新Controller /// </summary> /// <param name="setUpKey">向上运动的按键设置</param> /// <param name="setDownKey">向下运动的按键设置</param> public PaddleController(Key setUpKey,Key setDownKey,float setX): base() { upKey = setUpKey; downKey = setDownKey; positionX =setX ; } #endregion /// <summary> /// 控制逻辑 /// </summary> public override void ControllerAction(GameTime gameTime) { paddle = (Renderable2DSceneNode )controlledNode; float moveFactorPerSecond = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f; ProcessInput(moveFactorPerSecond); } private void ProcessInput(float moveFactorPerSecond) { //按upKey键向上移动 if (Input.Keyboard .IsKeyDown (upKey)) { paddle.Position -= new Vector2(0, paddleSpeed*moveFactorPerSecond); if (paddle.Position.Y < 0) paddle.Position = new Vector2(positionX, 0); } //按downKey键向下移动 if (Input.Keyboard.IsKeyDown(downKey)) { paddle.Position += new Vector2(0, paddleSpeed*moveFactorPerSecond); if(paddle .Position .Y>508) paddle.Position = new Vector2(positionX, 508); } } } }
源代码PongGame4SL.zip下载。
文件下载(已下载 2139 次)
发布时间:2012/9/25 下午10:57:45 阅读次数:8410