Night Raid

Night Raid

3D Local Multiplayer Action RPG Game

Tool: Unity Engine (C#)

Team Size: 1

My Role: Gameplay Programmer, Game Designer

Development Time: 4 Months

My Contribution:

  • Implemented the combat system and a player/AI character controller.

  • Data Driven Character system that behave differently based on the Character Profile Asset.

    • Character status (HP, Atk, Def, Spd, Jump), Moveset (Animation, damage, etc)

    • Base animation state-machine, additive layers, blend tree that handle all possible actions.

    • Handle collision event, damage calculation, number UI feedback.

    • Aim-assist for player that help track enemy direction when attacking.

  • Movement system: walk, run, responsive jump, wall jump, roll, jump dash.

  • Created Character Profile System and tool to help simplify the character movesets creation process.

    • Able to define AnimationClip, Animation Speed, Damage, Physic result, Physic Start/End time, Collider Start/End time, ColliderTag, AnimationSide, Attack Modifier, Attack Response.

  • Built the Inventory system that handle weapon/equipment pickup, character status, animations.

  • Developed a finite-state-machine AI architecture and implement 11 different enemy behaviors states.

  • Designed Level, Character/Weapon Movesets, Status, and UX/UI for the game:

    • UI status (increase/decrease color), Combo, Health, Inventory, Circular experience bar, Pickup

    • Control UI for both Xbox gamepad/keyboard

    • UI transition animations scripts

  • Gameplay Feedback:

    • Damage, Invincibility, Counter,  Charge Attack character emission effect.

    • Hold frame/delay physic result when take damage.

    • Particle system: created several particles effect including normal-hit, weapon-hit, guard-hit, bullet particle, jump smoke, boss bullet.

 

Gameplay System Details:

About The Project

  • This game is a solo game project, where I am focusing on creating a coop multiplayer Action RPG gameplay.

  • The ideal experience that I wanted to create is the feeling of launching enemy into the air, doing some cool combo like in Tekken 7, a well paced looting and progression experience like in Diablo 3, and a challenging experience that you can’t mash button your way through like in Dark soul.

Why Multiplayer Combat-based RPG?

  • Dynasty Warriors on PS2 to 5Ever since I was young, combat-based gameplay was always my preference, my favorite ranging from online fighting game like Getamped. So dissecting and building the combat gameplay system was always my passion.

  • Recently I get to try Diablo 3, where for the first time in my life that I find an RPG game not boring and repetitive which really encourage me to explore and build one of my own.

  • Multiplayer part of the game is just another preference of mine, where ever since I was young I was always playing as player 1 with my younger brother as player 2, creating a game I can enjoy with friend/family is the kind of experience I want to make.

Challenges:

  • One of the challenges for my project that really well addressed in Diablo 3 is the pacing of the loot, progression, and the reward in the game. This is more of like a design challenge than a technical one, but building the system that I can wear the designer hat and balance everything is also in my consideration.

  • Figure out the equipment creation pipeline.

    • Weapon like sword, you can get away with just be parent it on the character’s arm, but equipment like armor/helmet need to be skin weighted properly and deformed correctly with the character’s joint. To figure out these creation pipeline, I need to learn more about how things work in both artist end and how unity SkinnedMeshRenderer work.

  • Create a Combat System Pipeline that let me, a sole team member, able to define the following attack data for each character, weapon, and equipment:

    • Attack Animation

    • Attack Damage

    • Physic Result (like a wide hook should move player forward abit)

    • Physic Result Start Timestep (define a timestep that the physic result occur [0-1])

    • Collider Start/End Timestep (define precisely when the hitcollider should be enable)

    • ColliderTag (which collider should be enable, Eg. LeftLeg, RightArm)

    • AnimationSide (if holding sword on right arm, but the animation is left swing, animation should be mirrored)

    • Attack Response (should enemy falldown, stun, or just get hit)

Movement System:

 
  • Movement: The first system that I wanted to tackle was the core movement system. Because I wanted this game to be a local coop game, the movement control that I feel is the most intuitive is the simple 8 direction movement like in top down game. With the use of simple Quaternion.Slerp(from, to, time), I was able create a smoother turn, while keeping the turn quite responsive.

  • Jump: Jump is one of the interesting problem to solve as the goal here is to make a responsive and precise. The ideal jump experience is probably the classic Mario’s jump where how long you hold the button influence how high the character jump.

  • The way I implement it is really simple, where I have maxJumpVelocity, curJumpScale, and jumpTimer. When the jump input is down, the jumpTimer will start and the longer you hold the jump button, the timestep [0,1] of the timer will be assign to the curJumpScale. Then the final jump velocity will be maxJumpVelocity * curJumpScale.

  • The trick is to applies this curve to the timestep before assigning to the curJumpScale. The idea of this curve is make initial jump input affect the jump immediately, while holding a little while longer to reach the peak of the jump.

  • JumpDash/WallJump: After a few play test session, I quickly decided to add JumpDash and WallJump into the game. The reason of JumpDash and WallJump is to empower player with an ability to traverse the environment and encourage the player to explore the environment without fear of falling off the cliff.

Combat System:

 
  • Requirement: Character can perform 4 light attack and 3 strong attack sequences, where run and jump attack are different attack animation. So I need to have at least 11 (4 light + 3 strong + 2 jump attack + 2 run attack) attack animations for a character.

  • HitCollider: The core of the combat system lies on the collision detection, where a simple HitCollider is being check if it collide with the CharacterHitBox that is not its parent, not invincible, and not death. This is a trivial problem, since all the collision detection are being handled internally by Unity, so the real important problem is “When to enable/disable the HitCollider?”.

  • When to enable/disable the HitCollider: For each different attack animation, the most accurate way get the enable/disable HitCollider time is to manually set it myself for each attack animation. This can be store and serialize easily by simply by creating a class that derive from Unity’s ScriptableObject Class.

  • AnimationData: If we’re going to store some attack data for each animation here, we might as well just include these following data all together:

    • AnimationClip

    • Priority Order (should not override data with higher Priority Order)

    • ClipSpeed (Adjust to make the animation feel right)

    • Attack Damage

    • Physic Result (a wide hook should move player forward abit)

    • Physic Result Start Timestep (define a timestep that the physic result occur [0-1])

    • Collider Start/End Timestep (define precisely when the hitcollider should be enable)

    • ColliderTag (which collider should be enable, Eg. LeftLeg, RightArm)

    • AnimationSide (if holding sword on right arm, but the animation is left swing, animation should be mirrored)

    • Attack Response (should enemy falldown, stun, or just get hit)

ScriptableObject's data serialized nicely as .asset file

  • This give me an ability to define a cool jump smash attack, enemy shoot projectile, and leap attack with Armored AttackModifier.

  • Weapon/Equipment Animation: This AnimationData is also used to define the attack data for each equipment and weapon. This let me able to set different animation data for each weapon/equipment.

Show More

Different attack animation for each weapon

Launching enemy into the air and follow by combos attack

  • Integrating into the Character Class: With Unity’s AnimatorOverrideController, Character's AnimationClip can be swapped at runtime. Character Class have to maintain its ActionAnimState that has OnEnter(), OnUpdate(), and OnExit() function implemented to handle the physic result, enable/disable HitCollider, and ResultAction at the right animation timestep defined in AnimationData.

  • AnimationData Load Order: Character would load the AnimationDatas in a deterministic order from CharacterData -> PickableData (Weapon, Potion, etc) -> EquipmentData (Helmet, Armor, etc), which is where PriorityOrder come really handy when trying to preserve some specific AnimationData.

  • Hold Frame and Delay Physic Result: Hold frame is something really simple and powerful, which basically just freeze the animation and physic result for several frames when the player successfully land a hit or get hit, to sell the feeling of powerful hit and give player the time to react. It's something that commonly present in fighting game like Street fighter and Skullgirls.

  • Roll and Guard: Both roll and guard are provide player with some of the action for evading/blocking the enemy's attack.

  • Counter attack: this is a mechanic that let player turn from defense to offense, by pressing the attack button at the right timing on successful guard attempt.

  • Input Buffer Queue: Input Buffer Queue is basically a container that keep track of player's input in orderly manner. This help make sure that the game feel responsive and never miss player's input.

  • Queuing Pitfall: The tricky part is that if the player press the attack button 5 times consecutively, we might think that they want 5 attacks, but actually player might just be spamming an attack input trying to just land 1 more hit. The worst part about an absolute queue is that player lose their control immediate after unconsciously pressing 5 attacks input causing the unresponsiveness.

  • Another problem with the normal (.Net) Queue is that after we called Dequeue(), unlike in language like C/C++ that we can manually de-allocate the memory, C# relies on the Garbage Collector to clean up the memory, so all these Enqueue()/Dequeue() going to be generating a lot garbage every frame at run-time and can quickly stall the game when the Garbage Collector hit.

  • Mitigation: The solution that I took is to use a fixed small-sized Circular Queue that maintain an array and head/tail index. By having a small-sized Circular Queue as small as 2-3, we only keep track of the next 2-3 input of the player, which feel pretty responsive (since each attack move is pretty fast itself). The most useful perk of using Circular Queue is that we don't generate garbage at run-time, we simply assigning the value of the array in the Circular Queue. I can also clear the queue pretty easily and cheap, letting me able to make Roll, JumpDash, and WallJump take precedent and cancel all the input.

Inventory System:

 
  • One of the interesting feature that I really want to have in my game is the character customization system, where the character in the game is able to equip different equipment (helmet, armor, etc) at runtime.

    • Equipping different equipment at run-time is more than just parenting some equipment mesh onto the right character’s joint, I need to take account of how each equipment going to deform when the character is doing different pose (animation).

    • This mean each equipment mesh need to be skin weighted properly to the character, which sound really over-scoped for me as a single semester solo game project.

  • After several days of research, I finally figured out a way to let me obtain the result I wanted.

    • First, I need to bind each mesh to the right character’s joint in Maya, then skin weight the mesh properly before exporting it as .FBX into Unity.

    • Luckily, the default skin weight of Maya work perfectly like I intended most of the time, so only a little tweak of skin weight value is required before I can move on.

Tweaking the skin weight with Maya's Component editor

Binding Mesh to Joint

  • After I import the mesh into Unity, the mesh will come with the joint hierarchy. Notice that unlike other static mesh, it will be using Skinnedmeshrenderer component instead of normal MeshRenderer to render it in the scene, and this is exactly what I wanted.

  • The special thing about the Skinnedmeshrenderer is that the Skinnedmeshrenderer will take the joint hierarchy’s transform as a reference to deform the mesh at run-time, so if I want to make the character equip some equipment mesh, I could simply just assign the bones reference of the equipment to the character’s joint hierarchy.

Exported equipment comes with joint hierarchy

Pickup some equipment in run-time

  • Pickup: While for equipment like helmet and armor we need to manage the bones reference for each equipment, a pickable item like weapon, potion, and bomb we can get away with just parenting them in the character’s arm joint hierarchy. Hence, we simply need Meshrenderer component for rendering each of them.

Parenting pickable to arm's joint transform work perfectly

AI System:

 
  • Finite State Machine Architecture: I decide to go with the FSM AI architecture simply because the architecture itself is very straight forward and not difficult to implement, yet it is sufficient to create a compelling AI for this project. The downside of the FSM is that it is poorly scale, but considering this project's content scope, its unlikely that we'll face such a problem.

  • States: Every state in a FSM is a class that derived from a state class below.

  • Implementing each AI state is as simple as creating a new class that inherit from State class and override OnEnterState(), OnUpdateState(), OnExitState() functions.

  • There are only 11 different states in the game including AttackState, GuardState, IdleState, InvokeActionState, MoveState, NestedState, PatrolState, RollState, SearchState, StrafeState, and WaitState.

  • With the FSM and these 11 simple states, I was able to create different AI behaviors ranging from a simple chaser/jumper AI to a smarter looking AI that can outsmart at a time and create the illusion of intelligent. Tweaking AI behaviors, their status, and equipment can easily help us create more enemy variation, and while variation is easy to acquire, the hard part is to create a variation that actually make player perceive it differently in a meaningful way.

Simple Chaser AI that chase player on sight

Simple Jumper AI that jump toward player on sight

  • By adding some smarter decision making to some enemy, I was able to intimidate some player and stop them from mashing button. To not make the enemy too op, I limit the overuse of dodging/guarding action by some pseudo random chance.

Smarter Looking AI that strafing slowly pretending to gauge distance

Smarter Looking AI that occasionally react to player's attack

©2017 by Rittikorn Tangtrongchit.