生前不必久睡 2024-04-21 15:55 采纳率: 50%
浏览 7
已结题

使用wpf制作打砖块游戏时遇到的一个Bug

最近新学了一点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();
        }
    }
}
  • 写回答

17条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-04-21 15:55
    关注

    引自免费微信小程序:皆我百晓生

    针对您提供的WPF打砖块游戏中技能系统出现的性能问题(游戏速度随技能出现而加快),经过对您提供的代码进行分析,我认为问题可能出在以下几个方面:

    1. 技能生成与更新频率过高:每次击碎砖块都会立即生成一个技能,并在游戏循环中频繁检查技能与挡板、地板的碰撞情况。随着技能数量增多,检查碰撞的计算负担逐渐加重,可能导致游戏速度变快。

    2. 计时器管理:虽然您已经使用了TimerManager来管理计时器,但每个技能在落地时都会创建一个新的计时器(用于技能消失)。如果技能数量很多,可能会导致大量计时器同时运行,影响性能。

    3. 技能效果叠加:当前代码设计中,挡板可以连续获取技能且技能效果会累加(如加速、跳跃能力等)。这可能导致挡板速度急剧增加,给人造成游戏速度加快的错觉。

    为解决这些问题,我提出以下优化建议:

    优化技能生成与更新

    技能生成率控制

    • 设定技能生成概率:可以在游戏设置中定义一个合理的技能生成概率(例如,每击碎10个砖块生成一个技能),通过计数器跟踪已击碎砖块数,达到阈值时才生成技能。

    • 限制同时存在的技能数量:设定一个最大同时存在的技能数量,一旦达到上限,不再生成新的技能,直至已有技能消失或被拾取。

    优化技能碰撞检测

    • 分层管理:将技能元素置于单独的Canvas层(或使用GridRowDefinitionsColumnDefinitions),与挡板、砖块分隔开。这样,只需在技能落地时检查其与挡板的碰撞,无需在游戏循环中频繁检查所有技能与挡板的碰撞。

    • 技能落地检测:在技能下落过程中,仅在技能Y坐标改变时(即每次下落)检查是否触地,而非在游戏循环中频繁检查所有技能是否触地。

    改进计时器管理

    • 复用计时器:对于技能消失的计时器,可以使用单个全局计时器,通过列表或队列管理待消失的技能及其剩余时间。计时器每次触发时,处理最早应消失的技能,更新剩余时间列表。

    规范技能效果

    • 技能效果互斥:确保同一时刻挡板只能拥有一个技能效果。当新的技能被拾取时,清除之前技能的效果。

    • 技能效果持续时间统一:将技能效果持续时间统一为固定值(例如,10秒),并在技能生效时记录开始时间。在游戏循环中检查挡板当前技能是否已过期,若过期则清除其效果。

    结合上述优化措施,您可以对现有代码进行如下调整:

    SkillManager.cs

    // ...(省略其他部分)
    
    public class SkillManager
    {
        // ...(省略其他成员)
    
        private int skillGenerationThreshold = 10; // 每击碎10个砖块生成一个技能
        private int skillLimit = 3; // 最大同时存在的技能数量
        private int skillCounter = 0;
        private Queue<Skill> pendingDisappearingSkills = new Queue<Skill>();
    
        // ...(省略其他成员)
    
        public void GenerateRandomSkill(double x, double y)
        {
            if (skills.Count < skillLimit && skillCounter % skillGenerationThreshold == 0)
            {
                // ...(省略其他生成逻辑)
            }
        }
    
        public void MoveAndCheckSkillImagle(Canvas canvas, Image paddle, double jumpSpeed, double moveSpeed)
        {
            foreach (Skill skill in skills.ToList())
            {
                double skillY = Canvas.GetTop(skill.Image);
                skillY += 2; // 技能下落速度
    
                if (skillY + skill.Image.Height <= canvas.Height) // 检查是否超过窗口底部
                {
                    Canvas.SetTop(skill.Image, skillY);
                }
                else
                {
                    // 技能落地,检查与挡板碰撞
                    if (IsHit(paddle, skill.Image))
                    {
                        if (!hasActiveSkill)
                        {
                            ApplySkillEffects(skill, jumpSpeed, moveSpeed);
                            hasActiveSkill = true;
                            activeSkill = skill;
                            skills.Remove(skill);
                            canvas.Children.Remove(skill.Image);
                            // 记录技能消失时间(假设技能持续10秒)
                            skill.ExpirationTime = DateTime.Now.AddSeconds(10);
                            pendingDisappearingSkills.Enqueue(skill);
                        }
                    }
                }
            }
        }
    
        // ...(省略其他成员)
    
        private void ProcessDisappearingSkills()
        {
            while (pendingDisappearingSkills.Count > 0 && pendingDisappearingSkills.Peek().ExpirationTime <= DateTime.Now)
            {
                Skill expiredSkill = pendingDisappearingSkills.Dequeue();
                ClearPreviousSkillEffects(expiredSkill.JumpSpeedBackup, expiredSkill.MoveSpeedBackup);
            }
        }
    
        // ...(省略其他成员)
    }
    
    // ...(省略其他类)
    

    MainWindow.xaml.cs

    // ...(省略其他部分)
    
    private void GameLoop(object sender, EventArgs e)
    {
        // ...(省略其他逻辑)
    
        skillManager.ProcessDisappearingSkills();
    }
    
    // ...(省略其他部分)
    

    以上优化措施旨在降低技能生成与更新的频率,改进计时器管理,以及规范技能效果,从而缓解游戏速度随技能数量增多而加快的问题。根据实际情况,您可能还需要进一步调整相关参数以达到理想的游戏体验。

    评论 编辑记录

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 4月24日
  • 修改了问题 4月22日
  • 创建了问题 4月21日

悬赏问题

  • ¥15 Unity 2022.3.34版本安卓打包apk失败,gradle配置问题,用的是mono2x
  • ¥15 R语言中安装bibliometrix 后运行biblioshiny出现问题
  • ¥20 关于#android#的问题:用开发助手发现找不到控件(autojs)
  • ¥15 dir815漏洞反弹shell失败
  • ¥15 支付宝小程序云函数登录获取user_id失败
  • ¥50 python for 循环速度慢
  • ¥15 CubeMX生成的代码用keil编译有报错
  • ¥15 Stata链式中介效应代码修改
  • ¥15 pip安装PyAV报错
  • ¥15 latex投稿显示click download