最近新学了一点WPF,想使用WPF制作一款经典的打砖块游戏,在制作技能系统时,出现了Bug。
想实现的功能:我想实现的技能的系统是,在击碎砖块时候,会随机产生一个技能,然后从被击碎的砖块位置下落;下落后,如果碰到地板,8s内未被拾取时会自动消失;8s内触碰到地板且被挡板触碰,则挡板获得技能;技能使用时间10s,拥有技能期间不能获得第二个技能。
现在出现的bug是:当技能落地消失计时结束时,游戏速度会突然加快,等隔了一段时间,又恢复正常了。
尝试过的解决方案:我觉得是计时器冲突了,所以写了一个计时器类来管理技能系统需要的计时器,但是还是无法解决问题。
以下是代码部分:
SkillManager
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using static Skill;
namespace BreakTheBricks.Class
{
public class SkillManager
{
private Canvas gameCanvas;
private List<Skill> skills;
private SkillType currentSkillType;
private TimerManager timerManager;
private static Random random = new Random();
//private double jumpSpeed;
//private double moveSpeed;
private double defaultJumpSpeed;
private double defaultMoveSpeed;
private bool hasActiveSkill = false;
private Skill activeSkill = null;
public SkillManager(Canvas canvas)
{
gameCanvas = canvas;
skills = new List<Skill>();
currentSkillType = SkillType.None;
timerManager = new TimerManager();
}
public void GenerateRandomSkill(double x, double y)
{
SkillType skillType = GetRandomSkillType();
if (skillType != SkillType.None)
{
string imagePath = GetSkillImagePath(skillType);
Skill skill = new Skill(skillType, imagePath, gameCanvas);
Canvas.SetLeft(skill.Image, x);
Canvas.SetTop(skill.Image, y);
skills.Add(skill);
}
}
public void MoveAndCheckSkillImagle(Canvas canvas,Image paddle,double jumpSpeed,double moveSpeed)
{
foreach (Skill skill in skills.ToList()) // 使用ToList()创建副本,以避免在遍历时修改集合
{
double skillY = Canvas.GetTop(skill.Image);
skillY += 2; // 技能下落速度
if (skillY + skill.Image.Height <= canvas.Height) // 检查是否超过窗口底部
{
Canvas.SetTop(skill.Image, skillY);
}
else
{
timerManager.StartTimer(TimeSpan.FromSeconds(8), () => {
skills.Remove(skill);
canvas.Children.Remove(skill.Image);
});
}
// 检测技能是否与挡板碰撞
if (IsHit(paddle, skill.Image))
{
if (!hasActiveSkill)
{
ApplySkillEffects(skill,jumpSpeed,moveSpeed);
hasActiveSkill = true;
activeSkill = skill;
skills.Remove(skill);
canvas.Children.Remove(skill.Image);
timerManager.StartTimer(TimeSpan.FromSeconds(10), () =>
{
ClearPreviousSkillEffects(jumpSpeed,moveSpeed);
});
}
}
}
}
private SkillType GetRandomSkillType()
{
// 生成随机技能类型
double chance = random.NextDouble();
if (chance < 0.15)
return SkillType.Jump;
else if (chance < 0.30)
return SkillType.Speed;
else if (chance < 0.45)
return SkillType.Split;
else if (chance < 0.60)
return SkillType.All;
else
return SkillType.None; // 无技能
}
private string GetSkillImagePath(SkillType skillType)
{
// 获取对应技能类型的图片路径
switch (skillType)
{
case SkillType.Jump:
return "Jump_Skill.png";
case SkillType.Speed:
return "Speed_Skill.png";
case SkillType.Split:
return "Split_Skill.png";
case SkillType.All:
return "All_Skill.png";
default:
throw new ArgumentException("Invalid skill type", nameof(skillType));
}
}
public void ApplySkillEffects(Skill skill,double jumpSpeed,double moveSpeed)
{
// 根据技能类型应用对应的效果
switch (skill.Type)
{
case SkillType.Jump:
EnhanceJumpAbility(jumpSpeed);
break;
case SkillType.Speed:
IncreasePaddleSpeed(moveSpeed);
break;
case SkillType.Split:
SplitBall();
break;
case SkillType.All:
EnhanceJumpAbility(jumpSpeed);
IncreasePaddleSpeed(moveSpeed);
SplitBall();
break;
default:
break;
}
}
private void EnhanceJumpAbility(double jumpSpeed)
{
// 实现增强跳跃能力的逻辑
jumpSpeed *= 1.2;
}
private void IncreasePaddleSpeed(double moveSpeed)
{
// 实现增加挡板移动速度的逻辑
moveSpeed *= 1.5;
}
private void SplitBall()
{
// 实现分裂小球的逻辑
// SplitBall();
}
//清除技能
private void ClearPreviousSkillEffects(double jumpSpeed,double moveSpeed)
{
// 根据当前玩家所拥有的技能,清除对应效果
if (currentSkillType!=SkillType.None)
{
switch (currentSkillType)
{
case SkillType.Jump:
// 清除跳跃技能效果
ResetJumpAbility(jumpSpeed);
break;
case SkillType.Speed:
// 清除加速技能效果
ResetPaddleSpeed(moveSpeed);
break;
case SkillType.Split:
// 清除分裂技能效果
ResetSplitBall();
break;
// 全能型技能包含所有效果,因此需要重置所有效果
case SkillType.All:
ResetJumpAbility(jumpSpeed);
ResetPaddleSpeed(moveSpeed);
ResetSplitBall();
break;
}
}
}
private void ResetJumpAbility(double jumpSpeed)
{
// 重置跳跃能力的逻辑
jumpSpeed = defaultJumpSpeed;
}
private void ResetPaddleSpeed(double moveSpeed)
{
// 重置挡板移动速度的逻辑
moveSpeed = defaultMoveSpeed;
}
private void ResetSplitBall()
{
// 重置分裂小球的逻辑
}
private bool IsHit(FrameworkElement a, FrameworkElement b)
{
Rect rectA = new Rect(Canvas.GetLeft(a), Canvas.GetTop(a), a.ActualWidth, a.ActualHeight);
Rect rectB = new Rect(Canvas.GetLeft(b), Canvas.GetTop(b), b.ActualWidth, b.ActualHeight);
return rectA.IntersectsWith(rectB);
}
}
}
Skill
public class Skill
{
public enum SkillType
{
Jump, // 跳跃技能
Speed, // 加速技能
Split, // 分裂技能
All, // 全能型技能
None
}
public Image Image { get; private set; }
public SkillType Type { get; set; }
public Dictionary<SkillType, string> skillImaglePath;
public Skill(SkillType type, string imagePath,Canvas canvas)
{
Type = type;
Image = new Image
{
Height = 55, // 设置图片的高度
Width = 55, // 设置图片的宽度
Stretch = System.Windows.Media.Stretch.Fill, // 设置图片填充方式
Source = new BitmapImage(new Uri(imagePath, UriKind.Relative))
};
Panel.SetZIndex(Image, 90);
canvas.Children.Add(Image);
skillImaglePath = new Dictionary<SkillType, string>()
{
{ SkillType.Jump, "Jump_Skill.png" },
{ SkillType.Speed, "Speed_Skill.png" },
{ SkillType.Split, "Split_Skill.png" },
{ SkillType.All, "All_Skill.png" }
};
}
}
TimerManager
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace BreakTheBricks.Class
{
public class TimerManager
{
//private List<DispatcherTimer> timers;
private Dictionary<Guid, DispatcherTimer> timers;
public TimerManager()
{
timers = new Dictionary<Guid, DispatcherTimer>();
}
public void StartTimer(TimeSpan interval, Action callback)
{
Guid timerId = Guid.NewGuid();
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = interval;
timer.Tick += (sender, e) =>
{
callback.Invoke();
timer.Stop();
timers.Remove(timerId);
};
timer.Start();
timers.Add(timerId,timer);
}
//public void StopAllTimers()
//{
// foreach (var timer in timers)
// {
// timer.Stop();
// }
// timers.Clear();
}
}
在MainWindow中执行技能系统
namespace BreakTheBricks
{
public partial class MainWindow : Window
{
// 挡板的速度和物理变量
private double paddleHorizontalSpeed = 0;
private double paddleVerticalSpeed = 0;
private double paddleMoveSpeed = 5;
private double jumpSpeed = -10; // 负值表示向上跳跃
private double gravity = 0.5;
// 球体速度变量
private double ballXVelocity = 0;
private double ballYVelocity = 7;
//// 默认挡板跳跃速度和移动速度
//private double defaultJumpSpeed = -10; // 示例值,根据需要调整
//private double defaultMoveSpeed = 5.0; // 示例值,根据需要调整
private BrickManager brickManager;
private List<Brick> bricks;
// 游戏循环计时器
DispatcherTimer gameTimer = new DispatcherTimer();
// 游戏状态
bool gameIsRunning = false;
private SkillManager skillManager;
private int BallCount = 0;
private int Score = 0; // 存储得分
public Level1LoseWindow LevelWindow1;
public MainWindow()
{
InitializeComponent();
gameTimer.Interval = TimeSpan.FromMilliseconds(20);
CompositionTarget.Rendering += GameLoop;
gameTimer.Tick += newGameLoop;
gameTimer.Start();
brickManager = new BrickManager(GameCanvas, 3, 9, 50, 80, 110, 45, 0);
brickManager.InitializeBricks(); // 初始化砖块
bricks = brickManager.bricks;
InitializeBall();
}
private void newGameLoop(object sender, EventArgs e)
{
CheckForGameOver();
skillManager.MoveAndCheckSkillImagle(GameCanvas,Paddle,jumpSpeed,paddleMoveSpeed);
}
private void InitializeBall()
{
// 初始化球体速度
Random rand = new Random();
ballXVelocity = rand.NextDouble() * 6 - 2; // [-2, 4)
// 初始化球体位置
Canvas.SetLeft(Ball, GameCanvas.Width / 2 - Ball.Width / 2);
Canvas.SetTop(Ball, GameCanvas.Height / 2 - Ball.Height / 2);
BallCount++;
gameIsRunning = true;
skillManager = new SkillManager(GameCanvas);
}
// 键盘按下事件处理
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Left)
{
paddleHorizontalSpeed = -paddleMoveSpeed;
}
else if (e.Key == Key.Right)
{
paddleHorizontalSpeed = paddleMoveSpeed;
}
else if (e.Key == Key.Up)
{
// 仅在挡板在底部时允许跳跃
if ((Canvas.GetTop(Paddle) + Paddle.ActualHeight) >= GameCanvas.ActualHeight)
{
paddleVerticalSpeed = jumpSpeed;
}
}
}
// 键盘释放事件处理
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.Key == Key.Left || e.Key == Key.Right)
{
paddleHorizontalSpeed = 0;
}
}
// 游戏循环
private void GameLoop(object sender, EventArgs e)
{
if (!gameIsRunning)
return;
//CheckForGameOver();
UpdatePaddlePosition();
MoveBall();
CheckCollision();
//skillManager.MoveAndCheckSkillImagle(GameCanvas,Paddle,jumpSpeed,paddleMoveSpeed);
//CheckForGameOver();
// 监测砖块的数量
if (bricks.Count <= 0)
{
gameIsRunning = false; // 设置游戏运行状态为 false
//GoToLevelWindow(); // 转移窗口到 LevelWindow
}
}
private void MoveBall()
{
// 获取球当前位置
double ballX = Canvas.GetLeft(Ball);
double ballY = Canvas.GetTop(Ball);
// 球体移动
ballX += ballXVelocity;
ballY += ballYVelocity;
// 更新球体的位置
Canvas.SetLeft(Ball, ballX);
Canvas.SetTop(Ball, ballY);
}
private void CheckCollision()
{
// 获取Ball球体位置
double ballX = Canvas.GetLeft(Ball);
double ballY = Canvas.GetTop(Ball);
double ballWidth = Ball.ActualWidth;
double ballHeight = Ball.ActualHeight;
double paddleX = Canvas.GetLeft(Paddle);
double paddleY = Canvas.GetTop(Paddle);
double paddleWidth = Paddle.ActualWidth;
double paddleHeight = Paddle.ActualHeight;
// 边界碰撞检测
if (ballX <= 0 || ballX + Ball.Width >= GameCanvas.Width)
{
ballXVelocity *= -1; // 水平速度反转
}
// 检测顶部边界碰撞
if (ballY <= 0)
{
ballYVelocity *= -1; // 垂直速度反转
}
// 检测球体触底
if (ballY + Ball.Height >= GameCanvas.Height)
{
BallCount--; // 减少球体数量
brickManager.MoveBricksDownAndAddNewRow(); // 砖块下移并添加新行
InitializeBall(); // 重置球体位置和速度
return; // 结束碰撞检测函数的执行
}
// 检测球体与挡板碰撞
if (IsHit(Ball, Paddle) && ballYVelocity > 0)
{
// 获取挡板的位置
double paddleTop = Canvas.GetTop(Paddle);
// 检测球体与挡板碰撞
if (IsHit(Ball, Paddle) && ballYVelocity > 0)
{
//// 获取挡板的位置
//double paddleTop = Canvas.GetTop(Paddle);
// 确保小球从上方碰撞到挡板
if (ballY + Ball.Height >= paddleTop)
{
// 将小球的底部位置调整为挡板的顶部位置
ballY = paddleTop - Ball.Height;
// 反弹小球的运动方向
ballYVelocity *= -1;
}
}
}
// 检测球体与砖块碰撞
foreach (Brick brick in bricks.ToList()) // 使用ToList()以避免集合修改错误
{
if (IsHit(Ball, brick.Image))
{
// 当碰撞发生时,记录砖块位置
double brickX = Canvas.GetLeft(brick.Image);
double brickY = Canvas.GetTop(brick.Image);
if (brick.Type == BrickType.Blue)
{
brick.Type = BrickType.Yellow;
brick.LoadImageByType(BrickType.Blue);
Score += 200;
}
else if (brick.Type == BrickType.Yellow)
{
brick.Type = BrickType.Red;
brick.LoadImageByType(BrickType.Red);
Score += 100;
}
else if (brick.Type == BrickType.Red)
{
Score += 50;
GameCanvas.Children.Remove(brick.Image);
bricks.Remove(brick);
//创建技能
skillManager.GenerateRandomSkill(brickX,brickY);
}
ballYVelocity *= -1;
UpdateScoreDisplay();
break; // 只处理与第一个碰撞的砖块
}
}
}
// 更新挡板的位置
private void UpdatePaddlePosition()
{
// 应用重力
paddleVerticalSpeed += gravity;
// 检查是否在底部,防止挡板穿越底部
if ((Canvas.GetTop(Paddle) + Paddle.ActualHeight + paddleVerticalSpeed) > GameCanvas.ActualHeight)
{
paddleVerticalSpeed = 0;
Canvas.SetTop(Paddle, GameCanvas.ActualHeight - Paddle.ActualHeight);
}
// 更新挡板位置
double newLeft = Canvas.GetLeft(Paddle) + paddleHorizontalSpeed;
double newTop = Canvas.GetTop(Paddle) + paddleVerticalSpeed;
// 限制挡板的水平移动不超过画布范围
newLeft = Math.Max(newLeft, 0);
newLeft = Math.Min(newLeft, GameCanvas.ActualWidth - Paddle.ActualWidth);
Canvas.SetLeft(Paddle, newLeft);
Canvas.SetTop(Paddle, newTop);
}
private bool IsHit(FrameworkElement a, FrameworkElement b)
{
Rect rectA = new Rect(Canvas.GetLeft(a), Canvas.GetTop(a), a.ActualWidth, a.ActualHeight);
Rect rectB = new Rect(Canvas.GetLeft(b), Canvas.GetTop(b), b.ActualWidth, b.ActualHeight);
return rectA.IntersectsWith(rectB);
}
private void CheckForGameOver()
{
double cautionLineTop = Canvas.GetTop(CationLine);
// 检测最下层砖块的top有无超过CationLine的top
foreach (Brick brick in bricks)
{
double brickBottom = Canvas.GetTop(brick.Image) + brick.Image.Height;
if (brickBottom > cautionLineTop)
{
// 游戏失败逻辑
GameOver();
return;
}
}
}
private void GameOver()
{
// 停止游戏循环
gameTimer.Stop();
// 创建 Level1LoseWindow 的实例
Level1LoseWindow loseWindow = new Level1LoseWindow();
// 显示 Level1LoseWindow 窗口
loseWindow.Show();
// 关闭当前的 MainWindow 窗口
this.Close();
}
private void GoToLevelWindow()
{
// 停止游戏循环计时器
gameTimer.Stop();
// 设置一个定时器,等待2秒后跳转到 LevelWindow
DispatcherTimer winTimer = new DispatcherTimer();
winTimer.Interval = TimeSpan.FromSeconds(2);
winTimer.Tick += (sender, e) =>
{
((DispatcherTimer)sender).Stop(); // 停止定时器
// 创建并显示 LevelWindow 窗口
LevelWindow2 levelWindow2 = new LevelWindow2();
levelWindow2.Show();
// 关闭当前 MainWindow 窗口
this.Close();
};
winTimer.Start();
}
}
}