Skip to content

yama-lei/AllStarMemeClash

Repository files navigation

author date
Yama-lei
2025-05-04

AllStarsMemeClash Docs 全明星meme大乱斗

image-20250507084643077

游戏封面

PreFace:

文档还未写完,游戏正在开发。

[toc]

项目结构和逻辑

文件树

D:.
│  GameManager.cpp
│  GameManager.h
│  GameScene.cpp
│  GameScene.h
│  Kinflash.pro
│  Kinflash.pro.user
│  main.cpp
│  MainMenu.cpp
│  MainMenu.h
│  MainMenu.ui
│  mainwindow.cpp
│  mainwindow.h
│  mainwindow.ui
│  PA2 manual.pdf
│  Player.cpp
│  Player.h
│  Prop.cpp
│  Prop.h
│  README.md
│  resource.qrc
│  ScorePanel.cpp
│  ScorePanel.h
│  ScorePanel.ui
└─images
    │  background.jpg
    │  bg.png
    │  bg2.png
    │  blue.png
    │  cover.png
    │  cover.psd
    │  green.png
    │  yellow.png
    │
    ├─effect
    │      fast.png
    │      fc965.png
    │      feet.png
    │      healthdown.png
    │      healthup.png
    │      light.png
    │      strength.png
    │      strengthUp.png
    │
    ├─figures
    │  │  
    │  │
    │  ├─doro
    │  │
    │  └─nailong
    │          moving3.gif
    │          standing.gif
    │
    └─Props  # 道具相关的资源

抽象层级

4e9539a56bf5b1d79c600261535d6f0

类和数据结构

主要类及其功能

GameManager

游戏管理器类,是整个游戏的控制中心,负责场景切换、游戏启动和结束等功能。

class GameManager : public QObject
{
    
    QStackedWidget* stackedWidget;  
    MainMenu* mainMenu;           
    GameScene* gameScene;          
    void startGame();                void switchToMainMenu();  
}

GameScene

游戏场景类,负责管理游戏内的所有元素,包括玩家、NPC、道具等。处理游戏更新、碰撞检测、输入事件等。

class GameScene : public QWidget
{
    QGraphicsScene* scene;         
    QGraphicsView* view;           
    User* user;                 
    QList<Player*> players;       
    QList<Prop*> props;             
    void updateGame();             
    void initProps();              
    void shrinkSafetyZone();       
}

Player (基类)

角色基类,定义了角色的基本属性和行为。包括生命值、速度、攻击力等。

class Player : public QGraphicsObject
{
    int playerBlood;               
    int numOfKinves;               
    int attackPower;               
    Direction direction;           
    QMovie* currentGif;            
    void updateState();            
    void attack(Player* other);    
    void addBlood(int b);          
    void goDie();                
}

User (继承自Player)

用户控制的角色类,处理键盘输入和用户特有的行为。

注: 用户和NPC的区别在与,用户的移动是事件驱动的;NPC的移动是时间刷新驱动的

NPC (继承自Player)

电脑控制的角色类,包含AI逻辑。

class NPC : public Player
{
    Player* target;           
    void setTarget(Player* enemy); 
    QPoint nextStep(qreal time);   
    //这个是我后面提到的,新的NPC逻辑
}

Prop (基类)

道具基类,定义了道具的基本属性和行为。

class Prop : public QGraphicsObject
{
    QPixmap image;                    void paint();                  
    QRectF boundingRect() const;   
}

各种道具类 (继承自Prop)

实现了各种不同功能的道具,如BloodBottle(血瓶)、Knife(刀)、Boot(加速靴)等。

类之间的关系

  • GameManager管理GameScene和MainMenu,负责界面切换
  • GameScene包含多个Player(包括一个User和多个NPC)
  • GameScene还包含多个Prop对象
  • User和NPC都继承自Player,拥有不同的行为逻辑
  • 各种道具类继承自Prop基类,实现不同的道具效果

算法

算法部分

随机位置生成算法:

使用极坐标变换,和qt随机数生成函数,均匀地生成随机点:

QPointF GameScene::randomPositionInCircle(QPointF center, qreal maxRadius)
{
    double alpha = QRandomGenerator::global()->generateDouble() * 6.28;
    qreal radius = QRandomGenerator::global()->generateDouble() * maxRadius;
    qreal x = radius * qSin(alpha);
    qreal y = radius * qCos(alpha);
    return QPointF(x, y) + center;
}

但是发现大部分的随机点都是在圆心附近,说明算法有问题:

这个算法的$E(r) =0.5r$,但是$E(r^2)= 0.25r^2$,我们期望随机点按照面积均匀分布,因而将随机数进行开根号,使得有更大的可能性接近外圆周。

QPointF GameScene::randomPositionInCircle(QPointF center, qreal maxRadius)
{
   //只能生成安全区以内的

   double alpha = QRandomGenerator::global()->generateDouble() * 6.28;
   qreal radius = sqrt(QRandomGenerator::global()->generateDouble()) * maxRadius;
   qreal x = radius * qSin(alpha);
   qreal y = radius * qCos(alpha);
   return QPointF(x, y) + center;
}

刀的更新算法:

// 角色的paint函数
            qreal per = 360 / numOfKinves;
        for (int i = 0; i < numOfKinves; i++) {
            painter->drawPixmap(calculateKinvesPosition(startAlpha + per * i), kinfeImage);
            qDebug() << "Knife: ";
        }
        startAlpha = (startAlpha + 5) % 360;
//具体计算每一个刀的位置:

5.5更新:角色的刀的位置一直很奇怪

image-20250505161759552在paint函数中我多次尝试修改,但是越来越怪。随后加入上图的boundingRect画的Ellipse,进行调试,发现的确应该是刀的位置偏了。

最近用户查找算法

其实就是暴力遍历

"智能"NPC算法

  1. 智能逃跑和跟踪

    最初的时候,我没有考虑到要设计这一个功能,因此在扩展这个功能的时候下了一些功夫。NPC的AI逻辑主要包括以下几个方面:

    void NPC::updateState(qreal delta, QPointF center, qreal radius) {
        // 更新攻击冷却时间
        timeSinceLastAttack += delta;
        
        // 寻找目标逻辑
        if (target == nullptr || !target->isAlive) {
            // 通过Scene的方法获取最近的敌人
            auto enemyList = dynamic_cast<GameScene*>(scene())->getEnemyList(this);
            if (!enemyList.isEmpty()) {
                setTarget(enemyList.first().first);
            }
        }
        
        // 计算下一步移动方向
        QPointF nextStepPos = nextStep(delta);
        
        // 移动NPC
        QPointF movementVector = nextStepPos - pos();
        if (movementVector.manhattanLength() > 0) {
            movementVector = movementVector / movementVector.manhattanLength() * playerSpeed * delta / 1000;
            setPos(pos() + movementVector);
        }
        
        // 其他更新逻辑
        // ...
    }
  2. 目标追踪和战术判断

    NPC会根据自身状态和目标状态来决定是追击还是逃跑。当NPC认为自己比目标强时,会主动追击;反之则会尝试逃跑。

    QPoint NPC::nextStep(qreal time) {
        if (!target || !target->isAlive) {
            return pos().toPoint();
        }
        
        // 计算与目标的距离
        qreal distance = QLineF(pos(), target->pos()).length();
        
        // 比较战斗力决定行动策略
        qreal myStrength = getFightForce();
        qreal targetStrength = target->getFightForce();
        
        if (myStrength > targetStrength * 1.2) {
            // 如果我比目标强,追击目标
            QPointF direction = target->pos() - pos();
            // 标准化方向向量
            if (direction.manhattanLength() > 0) {
                direction = direction / direction.manhattanLength();
            }
            return (pos() + direction * 100).toPoint();
        } else {
            // 如果目标比我强,逃离目标
            QPointF direction = pos() - target->pos();
            // 标准化方向向量
            if (direction.manhattanLength() > 0) {
                direction = direction / direction.manhattanLength();
            }
            return (pos() + direction * 100).toPoint();
        }
    }
  3. 战斗力评估系统

    NPC通过综合评估血量、攻击力和刀的数量来计算自己和目标的战斗力,这是决策的重要依据:

    qreal Player::getFightForce() {
        // 综合考虑血量、攻击力和刀的数量
        return playerBlood * 0.5 + attackPower * 0.3 + numOfKinves * 0.2;
    }
  4. 攻击决策

    当NPC与目标距离足够近且攻击冷却结束时,会发起攻击:

    // 在updateState方法中
    if (target && target->isAlive) {
        // 检查攻击冷却和距离
        if (timeSinceLastAttack > 0.8 && distance < 200) {
            // 发射刀具攻击目标
            emit shootKnives(this, target);
            timeSinceLastAttack = 0;
        }
    }
  5. 道具收集行为

    如果NPC血量低或者附近有有价值的道具,会优先考虑收集道具:

    // 简化后的逻辑示意
    if (playerBlood < 5) {
        // 寻找最近的血瓶
        Prop* nearestBloodBottle = findNearestProp("BloodBottle");
        if (nearestBloodBottle) {
            // 朝血瓶移动
            moveTowards(nearestBloodBottle->pos());
            return;
        }
    }

这套AI系统使NPC具有一定的"智能",能够根据游戏局势做出合理的决策,增加了游戏的挑战性和趣味性。虽然算法相对简单,但已经能够提供比较有趣的游戏体验。

防止角色"卡出"地图算法

 最初的时候,我没有考虑到要设计这一个功能,因此在扩展这个功能的时候下了一些功夫。
// 在Player类的updateState方法中
void Player::updateState(qreal delta, QPointF center, qreal radius)
{
    // ... 其他代码 ...
    
    // 计算新位置
    QPointF newPos = pos() + QPointF(dx, dy);
    
    // 计算到中心的距离
    qreal distance = QLineF(newPos, center).length();
    
    // 如果超出安全区域
    if (distance > radius - 50) {
        // 计算离边界最近的合法位置
        QLineF line(center, newPos);
        line.setLength(radius - 50);
        newPos = line.p2();
    }
    
    // 设置新位置
    setPos(newPos);
    
    // ... 其他代码 ...
}

这个算法的关键点:

  1. 计算角色新位置到安全区中心的距离
  2. 如果距离超过安全区半径(减去一个缓冲值),则将位置调整到安全区边界
  3. 使用QLineF的setLength方法来确保新位置在合法范围内

这样可以确保角色永远不会移动到安全区外,防止"卡出"地图的情况发生。

helper function

一些helper function用来简化算法

随机点生成

QPointF GameScene::randomPositionInCircle(QPointF center, qreal maxRadius)
{
    double alpha = QRandomGenerator::global()->generateDouble() * 6.28;
    qreal radius = sqrt(QRandomGenerator::global()->generateDouble()) * maxRadius;
    qreal x = radius * qSin(alpha);
    qreal y = radius * qCos(alpha);
    return QPointF(x, y) + center;
}

获取敌人列表并按距离排序

QList<QPair<Player*, qreal>> GameScene::getEnemyList(Player* player)
{
    QList<QPair<Player*, qreal>> result;
    for (auto p : players) {
        if (p != player && p->isAlive) {
            qreal distance = QLineF(player->pos(), p->pos()).length();
            result.append(QPair<Player*, qreal>(p, distance));
        }
    }
    //这个地方是按照距离排序
    std::sort(result.begin(), result.end(), 
        [](const QPair<Player*, qreal>& a, const QPair<Player*, qreal>& b) {
            return a.second < b.second;
        });
    return result;
}

这一部分的算法其实不是很好,每一帧、每一个角色都要调用这个函数,效率比较低;算是我设计得不太合理的地方

计算刀具位置

QPointF Player::calculateKinvesPosition(qreal alpha)
{
    // 计算角色周围刀的位置
    qreal knifeDistance = 40; // 刀距离角色的半径
    qreal radian = alpha * M_PI / 180.0;
    qreal x = knifeDistance * qCos(radian);
    qreal y = knifeDistance * qSin(radian);
    
    return QPointF(x - kinfeImage.width()/2, y - kinfeImage.height()/2);
}

计算刀具应该在角色的那个位置

singleshoot+ weakPointer 安全删除

(虽然不是函数,但是确实是helper

// 使用QPointer进行安全删除
QPointer<QGraphicsScene> weakScene = scene;
QPointer<Player> weakPlayer = player;    
QTimer::singleShot(1000, [weakScene, weakPlayer]() {
    // 安全检查:确保场景和玩家对象仍然有效
    if (!weakScene || !weakPlayer) {
        return;
    }
    if (weakPlayer->scene() == weakScene) {
        weakScene->removeItem(weakPlayer);
    }
});

声明: 这个方法是AI(gemini)教我的,因为在开发中,出现了多次程序崩溃的现象 AI检查后发现可能是出现删除空指针的问题;故使用这种更爱安全的办法。

开发日志

开发中常见的错误

开发过程中的常见错误

  1. xxxxIncomplete Type: 未引入相关头文件
  2. delete空指针
  3. delete之后不赋值为nullptr
  4. 在使用removeItem的时候不检查是否已经移出了scene、或者压根就没加入scene
  5. 将为初始化的指针加入stackedWidget
  6. 使用定时器+lambda表达式,但是没有考虑到this对象被销毁会出现空指针解引用的情况点击跳转前往相关错误
  7. 使用delete而非使用deleteLater
  8. 部分变量忘记初始化,忘记最后赋值
  9. 变量一多就管不过来(特别是没有很好地将游戏开发分成多个抽象层次来开发时)

开发中遇见的问题和解决方法

  1. 在使用两个gif切换的时候,两个QMovie使用同一个槽函数updateGif,来绘制currentFrame, 并且在角色更新逻辑中更改currentGif。 但是却出现了只绘制一个图像GIF的情况,让我很是苦恼。询问大模型后得到可能的原因:

    image-20250501204801815

    提示我在每一次都要重新播放Gif,但是我发现我错误的根源是在资源初始化的时候只start了一个Gif,我误以为绘制的时候会自动地初始化

  2. 程序崩溃:

    21:30:26: Starting D:\code\qt\Kinflash\build\Desktop_Qt_6_9_0_MinGW_64_bit-Debug\debug\Kinflash.exe...QPixmap: Must construct a QGuiApplication before a QPixmap 21:30:34: 进程崩溃了。在我添加了一个静态成员之后程序崩溃了,还是感谢大模型:image-20250501213326872原因分析: 因为在初始化的时候我使用了new来创建QMovie对象,但是此时QAplication还没有成功地创建,因此出现了错误。

    将初始化的过程放在程序中进行。

  3. attack算法导致程序崩溃!!!!这里五一找bug找了好几天(ai也看不出)十分地离谱:

    • 单独调用addBlood(-1)给用户自己,血量低于0之后会死亡
    • 单独调用addBlood(-1)给player,同样血量低于0之后会死亡
    • 我以为是哪里出现了double free,uninitialize pointer等等原因,加了一堆检查
    • 我甚至在想是不是发生了多线程里面讲得多线程冲突,甚至用上了mutux防止多个attack函数同时调用

    结果让我崩溃:

     qreal per = 360 / numOfKinves; 

    就是这小小的一行。。Division by Zero...... 为什么qt不告诉我

  4. delete gameScene的时候出现程序崩溃:错误原因居然是delete之后没有赋值为nullptr!!!

  5. 角色无敌??? 原来是void addKnives(int k) { numOfKinves += numOfKinves >= 15 ? 0 : k; }....

    我设置成了只有刀的数量少于15才能攻击。。

  6. 游戏界面和菜单界面相互切换的时候也是有bug,因为在重新new一个gameScene的时候没有connect这个gameScene和gameManager!! (感谢AI主人,我愿意当你的🐶)

  7. 画不出跟踪敌人的线?为什么???我明明设置了pen和painter了啊?

    Qwen: 因为你没有将角色的坐标映射到scene的坐标

    然后我使用mapToScene函数,绘制两个player的中心连线,但是又出现了问题:无法显示线。

    调试发现,函数确实被调用了,但是调用的频率相当低。

    最后干脆

  8. 道具异常状态:在加速道具中,我设置成每一次都增加0.3* playerSpeed,并且5s后减少0.3*playerSpeed,但是问题是:如果角色角色捡到一个道具,速度变为1.3,效果消失后变为1.3*0.7= 0.91. 当同时捡到多个加速道具时,bug更加严重。 更改为:增加一个常量,或者让减少的数值和增加的一致即可。

解决方法是将道具效果改为增加固定值,而不是基于当前速度的百分比:

```cpp
// 修改前
void Player::applySpeedUp() {
    qreal speedIncrease = 0.3 * playerSpeed;
    playerSpeed += speedIncrease;
    
    // 5秒后减去相同的百分比(但基于新的速度值)
    QTimer::singleShot(5000, this, [this, speedIncrease]() {
        playerSpeed -= 0.3 * playerSpeed; // 问题在这里!
    });
}

// 修改后
void Player::applySpeedUp() {
    qreal speedIncrease = 200; // 固定值增加
    playerSpeed += speedIncrease;
    
    // 5秒后减去相同的固定值
    QTimer::singleShot(5000, this, [this, speedIncrease]() {
        playerSpeed -= speedIncrease; // 确保减去的是增加的相同数值
    });
}
```

这样做可以确保多个道具效果叠加和消失时不会导致速度异常。
  1. 飞行的刀具无法显示(后来发现是被自己给捡到了。。)

  2. 标注最近的敌人绘制的线会多次画出,检查发现是因为及时将物品removeItem之后也不会直接重绘。修改方法: 将singleShoot的计时器由16ms改为0ms。

  3. 边界移动bug:之前的逻辑是,如果新的位置不在边界内,那么就不要移动;新的逻辑是:如果移出了边界,那么就移动到最近的边界上。

  4. NPC飞行的刀具随着时间积累越来越多,出现了发出好几十条的盛况。

    结果发现是多次触发了同一信号。

        if (npc->target == nullptr) {
               npc->setTarget(pair.first);
                //下面这这一行使多余的,因为在setTarget的时候已经调用过一次了
                //  emit player->shootKnives(player, pair.first);
  5. 程序崩溃: 在角色死亡或者游戏胜利的时候都会出现回到MainMenu但是线程崩溃的情况。 经过多轮调试,发现问题应该出现在下面这个函数中:  

    void GameManager::switchToMainMenu()
    {
        qDebug() << "Switch To MainMenu";
        if (gameScene) {
            // 在删除场景前断开所有信号连接!!!
            gameScene->disconnect();
            stackedWidget->removeWidget(gameScene);
            //这个地方要注意!!! stackedWidget存储的地址要给它删了,因为用不上了,你要把他delete
            gameScene->hide();
            QTimer::singleShot(1, this, [this]() {
                if (gameScene) {
                    delete gameScene;
                    gameScene = nullptr;
                }
            });
        }
        stackedWidget->setCurrentWidget(mainMenu);
    }

    发现定时器延时删除多少有点问题:1. 设置的时间是1ms,我以为是1s。 会出现不安全的情况,需要设置一个更长的时间比如100ms 2. 在lambda表达式里面,出现一个逻辑问题:我capture了this指针,删除了this所指的gameScene,但是!!this这个时候指向的已经是新的gameScene了!修改之后:

    void GameManager::switchToMainMenu()
    {
        qDebug() << "Switch To MainMenu";
        if (gameScene) {
            // 在删除场景前断开所有信号连接!!!
            gameScene->disconnect();
            stackedWidget->removeWidget(gameScene);
            //这个地方要注意!!! stackedWidget存储的地址要给它删了,因为用不上了,你要把他delete
            gameScene->hide();
            // 使用定时器延迟删除,让Qt完成所有挂起的事件处理
            auto temp = gameScene;
            QTimer::singleShot(100, this, [temp]() {
                if (temp) {
                    delete temp;
                    //temp是
                }
            });
            gameScene = nullptr;
        }
        stackedWidget->setCurrentWidget(mainMenu);
    }

    这个时候程序的bug已经完全解决了,但是ai告诉我可以直接用deleteLater,但是,在实际应用中发现,会出现程序崩溃的情况,原因是多个gameScene共享了许多资源,在删除的时候出现了问题(我猜的)


    问题还没有解决,在过去的一段时间,我总是面临着程序崩溃的情况,

    一直出现类似这样的报错:

    QGraphicsScene::removeItem: item 0x29aa639e510's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29aa60c5db0's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29aa60c64f0's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29aa60c6570's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29aa5711090's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29abdee7970's scene (0x0) is different from this scene (0x29abdf59590)
    QGraphicsScene::removeItem: item 0x29aa63e52c0's scene (0x0) is different from this scene (0x29abdf59590)
    14:59:14: 进程崩溃了。

    发现是有的东西多次删除导致程序崩溃,于是,我在每一个removeItem前都加上了检查;

    但是程序已经会崩溃2511A790


    我在分析中认为:应该是GameManager的gameScene在delete和new之间出现了问题(原先代码的逻辑是,每一次开始游戏就要重新创建一个GameScene),所以我索性就不要delete GameManager了(我尝试过用singleShot计数器或者deleteLater但是都出现了程序崩溃的情况).更改逻辑为只创建一次gameManager,在这个gameManager里面封装好,startGame和endGame以及相关逻辑,最后再统一emit gameOverSignal给GameManager,实现更好地逻辑分离,也减少了bug的可能


    但是问题还是没有解决。

    我尝试给所有的资源加载都加上了检查逻辑、给所有的removeItem都加上了检查逻辑,但是还是找不到错误!

    甚至在startGame的时候也检查了是否成功地释放了资源。

    无数的更改使得项目逻辑开始混乱,询问ai除了给出各种奇怪的建议,毫无意义。


    无意间,搜到了一篇文章QtCreator使用Heob进行程序内存泄漏检测 - 韭菜钟 - 博客园

    介绍了一个程序分析工具,于是我配置好工具,来测试我的程序究竟在哪里出错了:

    下面是两处程序的内存问题:

    第一次(忘记截图了):

    unhandled exception code: 0xC0000005 (ACCESS_VIOLATION)
      在 z_adler32_combine in D:/Qt/6.9.0/mingw_64/bin/Qt6Core.dll中
    exception on  1: 0x0
      2: D:/code/qt/Kinflash/build/Desktop_Qt_6_9_0_MinGW_64_bit-
      ....中间略去
      26: D:/code/qt/Kinflash/build/Desktop_Qt_6_9_0_MinGW_64_bit-Debug/debug/Kinflash.exe
    data execution prevention violation at 0x0000000000000000

    询问gemini2.5Pro之后得知:

    根据您提供的错误信息,这是一个ACCESS_VIOLATION(访问违规)错误,发生在空指针解引用时。该错误在Qt的定时器事件处理中发生,这通常与定时器和lambda函数有关。

    我知道了,在程序中,为了实现定时删除,我使用了大量的QTimer::singleShot(100,this,[this](){})的语句,原来是这里出错了。

    第二次:有概率在启动的时候程序崩溃(不能理解为什么是有概率,总之很奇怪。)

    image-20250504230714298

    经过研究发现,程序中有几处(特别是

  6. 又出现了程序崩溃的bug。。。以下是使用了调试工具显示的内容

    image-20250505162757406

image-20250505164046777

​ 甚至无法关闭程序。。 ​ 我以为是我刚加的flyingProp的问题,但是奇怪的就是只有当游戏胜利的时候才会崩溃,自杀不会崩溃,所以我在想是不是因为emit playerDeath的问题,但是又不是杀一个敌人就会出现的问题,而是杀完所有敌人才会崩溃

players数组越界? 不可能,每一次访问都有检查。

苦思冥想一整天之后,chatgpt给了我思路:

chatGPT: 有可能是在胜利之后处理角色死亡后再处理死亡动画的时候,角色已经给删除了,导致出现了空指针访问

结合前面多次的经历,我知道了

在处理角色死亡的

    QPointer<QGraphicsScene> weakScene = scene;
    QTimer::singleShot(1000, [weakScene, player]() {
        if (player->scene() == weakScene) {
            weakScene->removeItem(player);
        }
    });

时候应该是,因为

    QPointer<QGraphicsScene> weakScene = scene;
    QPointer<Player> weakPlayer = player;    
    QTimer::singleShot(1000, [weakScene, weakPlayer]() {
        // 安全检查:确保场景和玩家对象仍然有效
        if (!weakScene || !weakPlayer) {
            return;
        }
        if (weakPlayer->scene() == weakScene) {
            weakScene->removeItem(weakPlayer);
        }
    });

下面是chatgpt的总结:

修改后的代码成功的关键在于解决了异步操作中的对象访问安全问题:

1. **使用QPointer追踪Player对象**:
   原代码中直接在lambda中使用了原始player指针,但这个指针可能在1000毫秒的延迟期间被销毁。新代码使用QPointer包装player,可以自动检测对象是否已被销毁。

2. **添加了完整的安全检查**```cpp
   if (!weakScene || !weakPlayer) {
       return;
   }

这个检查确保在尝试访问对象前验证它们是否仍然有效,防止访问已释放的内存。

  1. 修复了闭包捕获问题: 原代码中lambda闭包捕获了原始的player指针,不能感知指针在外部是否已失效。新代码捕获了QPointer,能够正确跟踪对象状态。

在Qt多线程或异步编程中,当对象可能在不同时间点被销毁时,使用QPointer是一种标准做法。它让你可以安全地检查对象是否仍然存在,而不会导致程序崩溃。

貌似是我第N次遇见这个问题了。


设置毒区:

  1. 设置两个图片(一个正常,一个是毒区) 毒区作为背景。
  2. 将当前正常区域的访问传递给角色,限制其移动。

难点在于第二点,询问大语言模型后得知:QRectF safeZoneRect = backImage_normal->mapToScene(backImage_normal->boundingRect()).boundingRect(); 可以做到

飞行的刀具:

原先的攻击逻辑是: 只要在攻击范围内发动攻击,就一定命中,但是不符合实验手册中物理情景的要求,因而进行改变:

// 在GameScene类中实现飞行刀具逻辑
bool GameScene::handleShootKnives(Player* sender, Player* target)
{
    if (!sender || !target || !sender->isAlive || !target->isAlive) {
        return false;
    }
    
    // 检查刀的数量是否足够
    if (sender->getNumOfKnives() <= 0) {
        return false;
    }
    
    // 创建飞行的刀具对象
    QPixmap knifeImage = sender->getKnifeImage();
    KnifeToAttack* knife = new KnifeToAttack(sender->pos(), sender, knifeImage);
    
    // 计算飞行路径
    QPointF startPoint = sender->pos();
    QPointF targetPoint = target->pos();
    
    // 将飞行刀具添加到场景中
    scene->addItem(knife);
    
    // 将飞行刀具信息添加到列表中,用于更新位置
    QPair<QPointF, QPointF> path(startPoint, targetPoint);
    flyingKnives.append(QPair<QPair<QPointF, QPointF>, Prop*>(path, knife));
    
    // 播放音效
    playSound(SHOOT);
    
    return true;
}

// 更新飞行刀具的位置
void GameScene::updateFlyingProp(qreal time)
{
    QList<FlyingProp> newList;
    
    for (const auto& flyingKnife : flyingKnives) {
        QPair<QPointF, QPointF> path = flyingKnife.first;
        Prop* prop = flyingKnife.second;
        
        if (!prop || !prop->scene()) {
            continue;
        }
        
        // 计算当前位置到目标位置的向量
        QPointF direction = path.second - prop->pos();
        qreal distance = QLineF(prop->pos(), path.second).length();
        
        if (distance < 10) {
            // 刀具已到达目标位置,检查碰撞
            QList<QGraphicsItem*> items = prop->collidingItems();
            
            for (auto item : items) {
                Player* player = dynamic_cast<Player*>(item);
                if (player && player->isAlive) {
                    // 获取刀具的所有者
                    KnifeToAttack* knife = dynamic_cast<KnifeToAttack*>(prop);
                    if (knife && knife->getOwner() != player) {
                        // 对玩家造成伤害
                        player->addBlood(-1);
                        playSound(BEINGATTACKED);
                    }
                }
            }
            
            // 移除刀具
            scene->removeItem(prop);
            delete prop;
        } else {
            // 更新刀具位置
            if (direction.manhattanLength() > 0) {
                direction = direction / direction.manhattanLength();
            }
            qreal moveDistance = flyingPropSpeed * time / 1000.0;
            prop->setPos(prop->pos() + direction * moveDistance);
            
            // 保留此飞行刀具继续更新
            newList.append(flyingKnife);
        }
    }
    
    // 更新飞行刀具列表
    flyingKnives = newList;
}

这个实现方式使得攻击不再是瞬时判定,而是需要飞行时间的投射物,增加了游戏的物理真实感和策略性。玩家需要考虑目标的移动方向和速度来提前预判,使游戏体验更加丰富。

声明

AI的使用

项目在开发的过程当中一定是少不了ai的使用,本人使用ai的主要途径为

  1. 询问相关知识
  2. 询问如何实现某个方法
  3. 询问bug的解决方法
  4. 部分样式的设计(如角色移动轨迹美化是由ai生成的,我设计的轨迹十分地丑)

About

The project of our class. But I want to make it more intersting.

Resources

Stars

Watchers

Forks

Packages

No packages published