用于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 阅读次数:9226
