Perfect GunMayhem Remake

A 2D Shooting PVP Game Based on Cocos2d-x

Zhengte Cai (蔡政特), Haoquan Zhang (张皓泉), Qinxiao Quan (全秦霄), Weijie Li (李伟杰)
South China University of Technology

GitHub Repo of GunMayhem Remake
Description of another image

Description of another image

Abstract

This project is our course design of Advanced Language Programming (C++) in 2022. We completed the GunMayhem Remake only by our team members, covering all aspects, including source code, game atrwork, and music assets. You can check all code details and run the executable demo in the GitHub repo.

General Design

Description of another image

Functions

1. Game Start Interface: Introduces a user-friendly game launch interface with BGM, volume control, pause/resume, and exit options.
2. Room Management: Allows players to create/join rooms, including support for AI opponents.
3. Weapon System: Features a diverse arsenal, including a pistol, sniper rifle, machine gun, and a special explosive kit, each with unique effects.
4. Weapon Drop Mechanism: Enables players to acquire different weapons by picking up dropped treasure chests.
5. Control Mechanism: Offers intuitive keyboard and mouse controls for character movement, jumping, gun attacks, and explosive actions.
6. Infinite Life: Provides players with unlimited lives for a continuous gaming experience.
7. Special Effects: Enhances gameplay with captivating visual effects for attacks and explosions.
8. Timing System: Incorporates a timing mechanism for game events and actions.
9. Critical Strikes and Hit Display: Highlights critical strikes and displays hit information to enhance combat feedback.
10. Scoring and Leaderboards: Implements a scoring system with leaderboards to track and compare player performance.

Runtime Environment

Environment Version
Visual Studio Both VS2019 and VS2022 are available
Cocos-2dx 4.0
Windows Windows 10, Windows 11

Key Feature Implementation

Cocos2d engine modification

Engine pain point: The built-in action in the engine can only achieve a fixed trajectory, and the relative position of the object cannot be changed during the execution of the action (for example, if it needs to be flipped during the game, the corresponding action will not be flipped). The reason is that the logic of the animation movement of the original engine is to first record the initial state of the action object at the beginning of the action , and then calculate the required change amount (distance or angle) according to the action, and then update the state of the object (position) based on these two values. and angle), the problem is that the initial state and the change amount do not change during the execution of the action . During the action cycle , the object will change according to the planned route, and will not change according to the actual flip situation.

Modification scheme: When the action is updated, change the initial state and change amount of the object according to whether the object is flipped or not. Take MoveBy::create(float t,Vec2 x) as an example (t is the action period, x is the offset coordinate), the original engine first obtains the initial coordinate value of the object at the beginning of the action, and then at each update, Calculate the offset at this moment in the action cycle according to the total offset of the action, and finally update the position of the object. What needs to be modified is the initial coordinate value and offset in this action. According to the flipping of the action object, the initial coordinate value and corresponding offset of the action can be changed in real time.

void MoveBy::update(float t)
{
    if (_target)
    {
        Vec2 currentPos = _target->getPosition();
        Vec2 diff = currentPos - _previousPosition; // offset
        _startPosition = _startPosition + diff;
        Vec2 Delta = _positionDelta;
        _target->isLeft() ? Delta.x = -_positionDelta.x : Delta.x = _positionDelta.x; // change the offset
        Vec2 start = _startPosition; // initial position
        _target->isLeft() ^ isLeft ? start.x = -_startPosition.x : start.x = _startPosition.x; // change the offset coordinates
        Vec2 newPos = start + (Delta * t); // update position
        _target->setPosition3D(newPos);
        _previousPosition = newPos;
    }
}

Moving between different floors

The core logic for character movement across floors involves incrementing the floor when the character jumps, decrementing it when the character falls, and adjusting it when the character steps in the air. If the character falls outside the boundary, the floor is also decremented to determine the character's current floor.

However, if the character is descending without jumping up steps, additional checks are needed. If the character passes through a layer, the floor is decremented. In cases of "stepping on air" and "falling" with a double jump, the floor is decremented. After the double jump, the game checks if the character wears the upper layer floor; if so, it increments the floor.

The subsequent AI movement judgment relies on the character's floor value. To facilitate this, a floor_actual is introduced, updated only once after the character lands. This ensures that the character's floor is not immediately changed during jumps and falls, enhancing AI logic.

Description of another image

Skills

Description of another image

The icons of skills. From left to right, they are: Double Team, Extra Life, Jetpack, Shield, Speed Up, Super Jump, and Minimization.

Key Skills 1: SpeedUp

Description of another image

In addition to speeding up the character's movement speed and acceleration, one of the highlights of this skill is the afterimage special effect. The specific method is to use a linked list structure to store each afterimage

Each of the afterimage stores position information and a corresponding image, and stores a pointer to a previous afterimage and a pointer to a next afterimage. First set the maximum number of afterimages and update time, and use “head” and “tail” to store the head and tail pointers of the afterimage list respectively. For each update, first draw the afterimage pointed by the “head” pointer to the canvas, then create a new pointer, and pass in the current position of the character and the corresponding image, and move the position of the “head” pointer. If the number of afterimages reaches the maximum value, the “tail” pointer needs to be released, and the corresponding afterimages will also be removed from the canvas, and finally the “tail” pointer will be moved forward.

One thing that needs special attention is that every time you create a pointer, you cannot directly pass in the pointer corresponding to the current character. First, the pointer will change, and the content of the afterimage will not be recorded. Second, the direct operation on the afterimage pointer will affect the character itself (if the pointer of the character itself is passed in), there will be various errors, so the method in the project is to define a clone() function in the CharacterBase class. This function is based on the real-time Status returns a new image.

struct Shadow {
    Sprite* figure;
    Vec2 point;
    Shadow* next;
    Shadow* last;
    
    Shadow(Sprite* figure,Vec2 point, Shadow* last) {
        this->figure = figure;
        this->point = point;
        this->last = last;
        this->next = nullptr;
    }
};

Key Skills 2: Jetpack

Description of another image

The basic logic of the jetpack is to change the longitudinal acceleration of the character in the update() function, so that the character can fly upwards by pressing the up button while jumping in the air, and at the same time create a jet particle animation during the leap to realize the character jet upward special effects.

void Jetpack::update(float dt)
{
    SkillBase::update(dt);

    if (player->getPositionY() > player->map->platform->getContentSize().height + 1000) flyable = false;
    if (player->getPositionY() < player->map->platform->getContentSize().height + 500) flyable = true;

    if (player->keyMap["up"] == true && player->inTheAir && player->isDoubleJump  && flyable) {
        if (onAction)
            player->MoveDelay(true, false);
        onAction = false;
        player->status->gravitation = anti_gravitation;
        if (player->y_speed > player->status->y_maxSpeed/1.2)
            player->y_speed -= player->status->gravitation * dt;

        Emission();
    }
    else {
        player->status->gravitation = gravitation;
        onAction = true;
    }

    if (jetpack->isFlippedX() != player->isFlippedX()) {
        jetpack->setFlippedX(player->isFlippedX());
        jetpack->setPositionX(-jetpack->getPositionX());
    }
}

Key Skills 3: DoubleTeam

Description of another image

The basic logic of the split is when creating an AI in the constructor of the skill, and the initial position of the AI is created in the same position as the character, but after creating it, some parameters of the split have to be adjusted accordingly.

DoubleTeam::DoubleTeam(CharacterBase* player)
    {
        skillTpye = DOUBLE_TEAM;
        this->player = player;
        duration = 0;
        Duration = 10;
        doppelganger = AI2::create(player->getTag(), player->map);
        doppelganger->isDoppelganger = true;
        doppelganger->firstLand = false;
        doppelganger->playerName->setString("doppelganger");
        doppelganger->Live = 1;
        doppelganger->isDoubleJump = player->isDoubleJump;
        doppelganger->inTheAir = player->inTheAir;
        doppelganger->floor = player->floor;
        doppelganger->floor_actual = player->floor_actual;
        if (!doppelganger->skills.empty()) {
            for (auto& skill : doppelganger->skills) {
                delete skill;
                skill = nullptr;
            }
            doppelganger->skills.clear();
        }
        doppelganger->Flip(player->isFlippedX());
        doppelganger->GetOpponent(player->opponent);
        doppelganger->setPosition(player->getPosition());
        player->map->platform->addChild(doppelganger);
    }

UI Design & Implementation

Background Choise Menu

This interface is primarily composed of buttons. The map selection section incorporates a sliding color block effect. However, the first issue with image buttons is that, upon clicking, they quickly revert to their original state. To address this, the callback function modifies the main state image. The second issue is that the two images are on the same layer. Therefore, when the selection is made, the orange color is positioned beneath the sliding color block, resulting in a suboptimal effect. To remedy this, layer changes are implemented in the click callback function.

The interface also features a text box for entering the desired quantity of lives.

All selection information on this interface is stored in the GameManager, preparing for subsequent gameplay.

Setting Menu

Regarding the control of background music and sound effects: pre-load in the HelloWorld class, initialize background music in the StartScene class, and implement control over background music in the Setting class.

Issue: In the StartScene class, if initialized as follows, it will result in the background music restarting from the beginning each time StartScene is entered, leading to the accumulation of multiple layers of background music.


Our Staff !


GitHub Repo of GunMayhem Remake

"Alone we can do so little; together we can do so much."
- Helen Keller