Overview

Tokou Fight is a Play to Earn (P2E) that is a mix of skill-based cards game and a fighting game. It is NFT related, which means you have to own at least one NFT from the tokens collection on Opensea to be able to play the game and earn rewards.

The presentation of my work on this project will be split in 3 parts:

    1. Game Design

    Here I will explain every decisions we made (my Game Designer coworker and I) for the game. It's important to understand what we did, why, what were the constraints, how we managed problematics, etc..

    2. Prototype

    I will present step by step the development of the project on Unreal Engine 5.

    3. Discord

Game Design

In this section will be presented the project constraints, the market analysis, the game design and some technical documents that are related to design decisions.

Game Design Document

In-Game screens overview

Whitepaper

Prototype

I have showed a video for each mechanics advancement + Blueprint code pictures. I've shared pictures of the Blueprint code for those who'd be interested, but for those who don't know how to read it, don't worry! I explained each step of the programing logic.

1. Draw Card System

Here we have the draw card system. Cards are coming from the outer of the left screen and are placed in the hand in a fan shape.

It is a basic feature of cards, however, it was harder to code than having the logic.

This event is called for each card drawn. It checks the number of cards to draw and if the card can be drawn or not (maximum 10 cards).

For each card drawn, this function is called. Its role is to determinate each cards (and its slot*) future positions already in hand and moving them to it.

* "Slots" are actors placed in the scene that I used to move cards to it. Each card has his own slot and slot move to a location, then card smoothly follow his slot. I don't remember why I needed this slot but only moving card to a wanted location wasn't working correctly. I assume that I could have avoided this empty slot to lighten the code.

Note: This function is also called later when the player playing a card, putting a take back a card in his hand or reorganizing hand.

Let see what's happening inside this function:

First, it checks if the card number in hand is odd or even. It's important because depending on the number of cards, there are placed in the hand differently.

Second, it checks if the card is placed at the left or the right of the hand. We need this information to shift and to orientate the card so all fit in the hand and are visible. This is how we can create the fan shape.

In case the cards number in hand is odd, there is a middle card that is at the exact center of the hand.

Third, it calculates the card new location with the green function "Get New Location (Left)" or "Get New Location (Right)" and the card new rotation with the green functions "Get New Rotation (Left)" or "Get New Rotation (Right)". Let's go further:

To know the location and rotation, this function is adding an offset to the card depending of its position in the array CardsInHand. The first card of this array is placed at the left of the hand and has the biggest negative offset from the center and the last item in this array is placed at the right in the hand and has the biggest positive offset from the center.

Exemple: In case we have 6 cards in hands, the cards in the left have the index 0, 1 and 2 and the cards in the right have the index 3, 4 and 5. The index 0 will have an offset of: 3*OffsetValue*(-1). The index 4 will have an offset of 2*OffsetValue*1.

To be more precise, the offsets of the two middle cards are divided by 2, to not have a big gap between those 2 cards.

In reality, if we consider the offset is equal to 1, all the offset of the 6 cards in hand are: -2.5 / -1.5 / -0.5 / 0.5 / 1.5 / 2.5

After a slot and his card know their new location and rotation, both functions (event to be precise) are called. Screenshots above are the movement managment of the slot and the card.

Each frame (EventTick) the slot and the card will be reaching his new location and rotation, and when it is close enough to the result wanted, we stop it from mooving / rotating.

2. Drag from hand

The next step is to get our cards usable and playable. First step is to code the fact that we can click and drag them on the screen.

By clicking the left mouse button, the event "OnClickEvent" is called. By realeasing click, the event "OnReleaseEvent" is called.

The event first looking what is under the mouse cursor by tracing a line (I will show how later).

If it triggers a card, it checks if the card can be clicked or not (in case the card is in movement). If it is, it calls the card event "OnClickedCard".

The event of the card "OnClickedCard" does 4 things before calling the function that will manage the drag operation. We will see only the first two as the next ones aren't linked to the drag system.

The first block is setting the variable IsCardClicked of the card to True. This variable will be checked later to know if the card can actually be dragged.

The function "DisableClickOnAnyCards" is blocking the fact that others cards can be clicked so we can't click or drag multiples cards at the same time.

This is the EventTick of a card (BP_Card). Each frame, this event is called and do every function linked to it.

We use this event to manage our drag operation and also for the displacement of the card respectively. It means that when the card is being dragged, it stops any other displacement and when the click is released, the card will move to his latest position. When the card is close enough to his desired position, it stops moving.

Note: I figured out that the Gate was unecessary as I could have just set the variable IsCardClicked to True or False for each time I want my card to be dragged or not.

Then we need to indicate the location of the card for each frame we're dragging it. To do that, we have to trace a line under the mouse cursor and set up the distance where the card will be on that line.

The "Line Trace by Channel" function will trace a line from a 3D location (start) to another 3D location (end). With the really usefull function "Convert Mouse Location to World Space" we only need to set up a distance under the mouse to have our card location.

When the line is drawn, it gives us many information: actor encountered, impact point, physic material, bone name... From all this, we just need "Trace End" that is the end of the line traced.

From this location obtained, we will set our card location and rotation. "VInterp To" and "RInterp To" are functions that give a nice smooth feeling when the card move and rotate.

Note: I figured out when writting this part that we in fact don't need to trace a line and only need to use our previous function "Convert Mouse Location to World Space" and the RaycastDistance variable (the distance of the line) to get our final card location. Dumb old me.

3. Drag & drop + Rearrange cards

To proceed the drag & drop system, I set up 2 different zones: the hand zone and the center zone where cards can be played. They can be dragged and dropped in each zone and can be reorganized.

We skipped on the previous part the functions "What Under Cursor On Drag" and "Get Center Cards Positions". This is their time to shine:

Both functions "What Under Cursor On Drag" and "What Under Cursor On Drop" (when releasing click) checks by tracing a line which zone is under the cursor. These will be use to know what to do with the card depending on these info.

The next function manages each behavior depending on zones detected on drag and detected on drop.

For example, if the card is taken from the hand and dragged in the center zone, it will:

  • Remove the card item from the array "CardsInHand" to add it to the array "PlayedCards"
  • Update cards positions in hands
  • Update order in the array in "CardPlayed" depending on the positions of the cards in screen
  • Update positions (visually this time) of cards played in the center zone

4. Cards info

Now we have a functional drag and drop system for our cards, we need to give them all info (functionally with variables and so visually).

This is called just after drawing a card (Part 1). It spawns the CardSlot, which spawns the Card, then setting info with the function with the same name.

This is some variables stored in the card that might be used. They are all empty when the card spawn and some will be filled depending of the card.

Those variables are stocked in a Data Table Sheet. All the variables are set beforehand.

This function is looking inside the DataTable.

It collects all the variables of the card from the DataTable and sets all info one by one to it.

Isn't it beautiful ?

5. Energy Managment

There is an energy bar at the left bottom corner that is updating when a card is played or took back in hand.

When we play a card to the center zone, we verify if the energy bar value is greater than the energy cost of the card. If that's not the case, the card can not be played and the card comes back to the hand.

If the card can be played, it mooves the card to the center and update energy value depending of the card energy cost.

Inside the function, the value of energy is updated, the number is displayed under the jauge and the jauge fills of empties at its actual value.

For the energy bar animation, this is the same idea of the card gates system.

Each tick, it fills or empties the jauge and when it reaches the wanted value, it stops.

When a turn is finished (Part 6.), we set the new value of energy depending of the unused energy in the previous turn. We aslo do the same for enemy energy.

Then it update the energy jauge and increment the energy gain for the next turn. This variable start to 3 and it is incremented each turn. The energy unused in the previous turn is added to this variable to give the amount of energy to the next turn.

6. Next Deck / End Turn

In this part, I'll show:

  • How the deck cycles when it have no more cards in it
  • What happens when clicking the "End Turn" button

When the game starts, this function is called.

It set up the energy value (3 at the beginning) and create both player and opponent decks.

This function is clearing by security the last deck array and creating a new one.

The deck is composed of 33 cards. There are only 7 differents cards in those 33 cards (5*Melee1, 5*Melee2, 5*Range1, 5*Range2, 5*Special1, 5*Special2, 3*Super).

They are added to our Deck array and it is shuffled (I suppose that this function was made principally for card games).

So when a card is drawn, it checks if the deck has cards in it. If it's not the case, it's going to rebuild a new deck. But one more thing to do is to remove cards already in hand from this array so there is not a duplicate.

This function manages it by comparison. For each cards in hand, it checks his name and compare it to each card name in the new deck. If there is a similarity, the card is removed from the new deck.

We now have the same action as in real life when we take discarded cards and shuffle them to recreate a new deck to draw in.

By clicking he End Turn button when cards are played (or not), it will disable every cards clickability and play an animation on cards.

Then it will play an animation that will show every attacks played. For now it only shows player attack and not opponent ones.

7. Enemy Cards Managment / Attack Order

Just after having clicked the "End Button" and proceed player turn, we will make play the player opponent.

On opponent's turn, the game will check which card can be played and choose randomly one of them.

Note: In the future, an algorithm would have been implemented to choose the best combination depending on the card in hand and the situation.

The function looks all cards in hands and compare every energy cost with the player energy value. If the card can be played, it is stored in the array "Playable Card".

We're storing the chosen card from "Playable Cards" to the array "Played Cards", the player's energy value is deducted by the card cost, the card is removed from "Hand Cards" array and the function loops to find if there is another playable card.

To order the attacks list, we have the array "Turn Cards List" that will be filled by both players attacks. First it transfers all player 1 attacks into this array.

It checks who's playing first to insert correctly each player 2 attacks.

8. Multiple Turns

9. All Combos

10. Card effects + Character animation + Interface feedbacks

Discord