So, in my trying to create a weapon recoil system, I finally fully understand how the animation and coding system work together to create the full animations. I'm going to break it down here, and ask any talented artists out there to pick up the visual art side of it. If someone will, I'll do the coding side to get hi-res, 60fps animations into the game.
Currently, there is 5 frames, ONLY 5 FRAMES, to each animation @ 10 frames per second. So how does it work exactly to get the pre-created frame sprites into the engine, timed, and animated properly?
Current animations are 5 separate image files, each one a frame in the engine/game animation the player sees on screen. Think of each individual image file as a single frame being rendered. So, in default DF, there is a single frame rendered every 10 frames, for a total of 50 frames for the animation over the animation time frame the engine sets. The engine, via a basic attack speed integer value in FPSWeapon.cs, tells the display how fast those 50 frames, which only contain 5 actual animation images, should be rendered on the players display.
Let me provide the file breakdown schematic first, along with a nice real life metaphor of a car.
DFUnity Schematic
weaponBasics.cs(Static) <-get- weaponManager.cs -send-> FPSWeapon.cs -send-> (game rendering engine) -send-> (your display/eyeballs).
Car Metaphor Schematic
individual engine/car parts(static)<-get- Car Engine -send-> Dashboard -send-> (photon/information packets) -send-> (your eyeballs).
Creating The Animation:
- Step 1 - Create the Animation: Based on the above, all that is needed is a good graphics artist to use a modern image creation program to create a single 60 frame animation from the current weapon images, or even the hd ones on the forum, then convert that to 60 single files that can be loaded by the engine.
Some Advice: If you know what you're doing, you should be able to make a single animation file in a image editor and then convert it down to single files with predefined names using imaging software also. This should save you from having to hand create and name 60 separate image files.
- Step 2 - Adjust the animation Code: Go to the weaponbasic.cs script. In the script, go to newanimation[] Melee method, go to the line with StrikeUpAnimSpeed in it (this tells you this animation record is for this animation) then change num of frames to 60 and FramePerSecond to 1. Compile dfunity, and magic, you should have a 60 frame animation running at 1 frame per second. However, I need a guinea pig willing to test this with me to see if I'm correct or if I missed part of the code and engine.
- WeaponBasics.cs: Think of this as the basic parts to a car and its engine. These are static pieces, that can't be changed or removed, as they create what a car is and how it works. It contains your animation class(engine block), animation objects (cylinders), your total frames for the animation (fuel injectors), your animation's frames per second (Spark splugs), and any animation screen offsets for placing the animation (Timing Sensor). This file does the same thing for animations by creating and setting up the basic animation objects and their variables to create the animation engine. This is where we can easily create 60 Frame animations, or really any animation frame amount we want. This is also where we will ultimate add any new animations we want. It will require coding in new weapon states to trigger them, but the coding side looks pretty easy compared to creating all the animations, especially for an untalented artist like me.
Code: Select all
public static class WeaponBasics { #region Weapon Animations // Weapon animation speeds in frames-per-second public static int IdleAnimSpeed = 10; public static int StrikeDownAnimSpeed = 10; public static int StrikeDownLeftAnimSpeed = 10; public static int StrikeLeftAnimSpeed = 10; public static int StrikeRightAnimSpeed = 10; public static int StrikeDownRightAnimSpeed = 10; public static int StrikeUpAnimSpeed = 10; public static int WereStrikeAnimSpeed = 20; public static int BowAnimSpeed = 10; // Animations for melee - offset and aligment changes public static WeaponAnimation[] MeleeWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0.15f}, new WeaponAnimation() {Record = 1, NumFrames = 5, FramePerSecond = StrikeDownAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 2, NumFrames = 5, FramePerSecond = StrikeDownLeftAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 3, NumFrames = 5, FramePerSecond = StrikeLeftAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 4, NumFrames = 5, FramePerSecond = StrikeRightAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 5, NumFrames = 5, FramePerSecond = StrikeDownRightAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 6, NumFrames = 5, FramePerSecond = StrikeUpAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, }; // General animations for most weapons public static WeaponAnimation[] GeneralWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 1, NumFrames = 5, FramePerSecond = StrikeDownAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 2, NumFrames = 5, FramePerSecond = StrikeDownLeftAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 3, NumFrames = 5, FramePerSecond = StrikeLeftAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 4, NumFrames = 5, FramePerSecond = StrikeRightAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 5, NumFrames = 5, FramePerSecond = StrikeDownRightAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 6, NumFrames = 5, FramePerSecond = StrikeUpAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, }; // Animations for dagger - offset and alignment changes public static WeaponAnimation[] DaggerWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0.04f}, new WeaponAnimation() {Record = 1, NumFrames = 5, FramePerSecond = StrikeDownAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 2, NumFrames = 5, FramePerSecond = StrikeDownLeftAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 3, NumFrames = 5, FramePerSecond = StrikeLeftAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 4, NumFrames = 5, FramePerSecond = StrikeRightAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 5, NumFrames = 5, FramePerSecond = StrikeDownRightAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 6, NumFrames = 5, FramePerSecond = StrikeUpAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, }; // Animations for staff - offset changes public static WeaponAnimation[] StaffWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0.02f}, new WeaponAnimation() {Record = 1, NumFrames = 5, FramePerSecond = StrikeDownAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 2, NumFrames = 5, FramePerSecond = StrikeDownLeftAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 3, NumFrames = 5, FramePerSecond = StrikeLeftAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 4, NumFrames = 5, FramePerSecond = StrikeRightAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0f}, new WeaponAnimation() {Record = 5, NumFrames = 5, FramePerSecond = StrikeDownRightAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 6, NumFrames = 5, FramePerSecond = StrikeUpAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, }; // Animations for bow public static WeaponAnimation[] BowWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 7, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 7, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 7, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 7, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 7, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 0, NumFrames = 4, FramePerSecond = BowAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, }; // Animations for werecreature - alignment changes public static WeaponAnimation[] WerecreatureWeaponAnims = new WeaponAnimation[] { new WeaponAnimation() {Record = 0, NumFrames = 1, FramePerSecond = IdleAnimSpeed, Alignment = WeaponAlignment.Center, Offset = 0.02f}, new WeaponAnimation() {Record = 1, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0.2f}, new WeaponAnimation() {Record = 2, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 3, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 4, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Right, Offset = 0f}, new WeaponAnimation() {Record = 5, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0f}, new WeaponAnimation() {Record = 6, NumFrames = 5, FramePerSecond = WereStrikeAnimSpeed, Alignment = WeaponAlignment.Left, Offset = 0.2f}, }; #endregion #region Helpers public static WeaponAnimation[] GetWeaponAnims(WeaponTypes weaponType) { if (weaponType == WeaponTypes.Melee) return MeleeWeaponAnims; else if (weaponType == WeaponTypes.Dagger || weaponType == WeaponTypes.Dagger_Magic) return DaggerWeaponAnims; else if (weaponType == WeaponTypes.Staff || weaponType == WeaponTypes.Staff_Magic) return StaffWeaponAnims; else if (weaponType == WeaponTypes.Bow) return BowWeaponAnims; else if (weaponType == WeaponTypes.Werecreature) return WerecreatureWeaponAnims; else return GeneralWeaponAnims; } public static string GetWeaponFilename(WeaponTypes weaponType) { switch (weaponType) { case WeaponTypes.LongBlade: return "WEAPON04.CIF"; case WeaponTypes.LongBlade_Magic: return "WEAPO104.CIF"; case WeaponTypes.Staff: return "WEAPON01.CIF"; case WeaponTypes.Staff_Magic: return "WEAPO101.CIF"; case WeaponTypes.Dagger: return "WEAPON02.CIF"; case WeaponTypes.Dagger_Magic: return "WEAPO102.CIF"; case WeaponTypes.Mace: return "WEAPON05.CIF"; case WeaponTypes.Mace_Magic: return "WEAPO105.CIF"; case WeaponTypes.Flail: return "WEAPON06.CIF"; case WeaponTypes.Flail_Magic: return "WEAPO106.CIF"; case WeaponTypes.Warhammer: return "WEAPON07.CIF"; case WeaponTypes.Warhammer_Magic: return "WEAPO107.CIF"; case WeaponTypes.Battleaxe: return "WEAPON08.CIF"; case WeaponTypes.Battleaxe_Magic: return "WEAPO108.CIF"; case WeaponTypes.Bow: return "WEAPON09.CIF"; case WeaponTypes.Melee: return "WEAPON10.CIF"; case WeaponTypes.Werecreature: return "WEAPON11.CIF"; default: throw new Exception("Unknown weapon type."); } } public static string GetMagicAnimFilename(ElementTypes elementType) { switch (elementType) { case ElementTypes.Fire: return "FIRE00C6.CIF"; case ElementTypes.Cold: return "FRST00C6.CIF"; case ElementTypes.Poison: return "POIS00C6.CIF"; case ElementTypes.Shock: return "SHOK00C6.CIF"; case ElementTypes.Magic: return "MJIC00C6.CIF"; default: throw new Exception("Unsupported element type."); } } #endregion } }
- weaponManager.cs: Works as the bridge between the code and vars assigned in weaponBasics.cs and the screen weapon methods assigned in fpsWeapon.cs. It does this by, through numerous coding switches, assigning the weapon attack direction and pushing it into the weapon script file through a case switch AttackDirectionvalue in fpsWeapon.cs. Consider this the engine under the hood for the weapon being displayed. Without weaponManager, the df engine wouldn't know what weapon state/gear your weapon/car is in, how much throttle/attack speed your pushing, if your weapon/car engine has any materials/aftermarket modifications on it. and so on.
At the end of all the triggers, coding, and methods, all that is being done to start and animation is this single method. By this point, the script has figured out the attack direction for the animation (and all other related weapon needs, like attack speed, range, modifiers, ect.). So it tells the display weapon/cars dash what animations/dash lights to turn on and off using the assigned mouse direction.Here is the code the assigns the attack direction based off control type assigned and if a bow is equipped or not. Again, part of weaponManager.cs.Code: Select all
void ExecuteAttacks(MouseDirections direction, bool hitobject) { if(ScreenWeapon) { // Fire screen weapon animation ScreenWeapon.OnAttackDirection(direction); lastAttackHand = Hand.Right; } else { // No weapon set, no attacks possible lastAttackHand = Hand.None; } }
And finally, here is the code in weaponManager to compute the length of the attack animation in human time. It doesn't matter how many frames per second or frames your animation has, this is what actually decides how fast the attack plays in game. This will increase or decrease attacks, including animations, raycasts, and everything else.Code: Select all
var attackDirection = MouseDirections.None; if (!isAttacking) { if (bowEquipped) { // Ensure attack button was released before starting the next attack if (lastAttackHand == Hand.None) attackDirection = DaggerfallUnity.Settings.BowDrawback ? MouseDirections.Up : MouseDirections.Down; // Force attack without tracking a swing for Bow } else if (isClickAttack) { attackDirection = (MouseDirections)UnityEngine.Random.Range((int)MouseDirections.Left, (int)MouseDirections.DownRight + 1); isClickAttack = false; } else { attackDirection = TrackMouseAttack(); // Track swing direction for other weapons } }
Code: Select all
// Calculate damage int animTime = (int)(ScreenWeapon.GetAnimTime() * 1000); // Get animation time, converted to ms. int damage = FormulaHelper.CalculateAttackDamage(playerEntity, enemyEntity, entityMobileUnit.Summary.AnimStateRecord, animTime, strikingWeapon);
- FPSWeapon.cs: This is the front end/dash board of the weapon. You can think of this as the script that controls the displayed weapon itself and what it does/shows you. It takes the mouse direction that weaponManager sent to it, uses a case switch method to select the weapon state the onscreen weapon should initiate through the weapon state var, and then runs and manages the actual animations and frames themselves for the weapon based on the initiation weapon state. Again, think of this as the dash of a car, as it reads out, in a visually understandable and usable way, what your engine and the basic parts are doing.
Here is the main code that concerns activating the animations. Everything below it is the complicated coding that figures out the frame calculations and creates what you see on screen. Nothing most people should be messing with, and you do not need to touch it to create higher frame rate or more/differing 2d weapon animations.This is the method in FPSWeapon that actually renders the animation in non-binary time AKA: Human milliseconds. What this outputs, gets multiplied by 1000 in the weaponmaanager attack initiation, found above.Code: Select all
public void OnAttackDirection(WeaponManager.MouseDirections direction, bool hitobject) { // Get state based on attack direction WeaponStates state; switch (direction) { case WeaponManager.MouseDirections.Down: state = WeaponStates.StrikeDown; break; case WeaponManager.MouseDirections.DownLeft: state = WeaponStates.StrikeDownLeft; break; case WeaponManager.MouseDirections.Left: state = WeaponStates.StrikeLeft; break; case WeaponManager.MouseDirections.Right: state = WeaponStates.StrikeRight; break; case WeaponManager.MouseDirections.DownRight: state = WeaponStates.StrikeDownRight; break; case WeaponManager.MouseDirections.Up: state = WeaponStates.StrikeUp; break; default: return; } // Do not change if already playing attack animation, unless releasing an arrow (bow & state=up->down) if (!IsPlayingOneShot() || (WeaponType == WeaponTypes.Bow && weaponState == WeaponStates.StrikeUp && state == WeaponStates.StrikeDown)) ChangeWeaponState(state); } public void ChangeWeaponState(WeaponStates state) { weaponState = state; // Only reset frame to 0 for bows if idle state if (WeaponType != WeaponTypes.Bow || state == WeaponStates.Idle) currentFrame = animTicks = 0; UpdateWeapon(); } public bool IsAttacking() { return IsPlayingOneShot(); } public int GetHitFrame() { if (WeaponType == WeaponTypes.Bow) return 5; else return 2; } public int GetCurrentFrame() { return currentFrame; } public float GetAnimTime() { return animTicks * GetAnimTickTime(); }
*NOTE: I ADDED THE AttackSpeed VAR TO ALLOW FOR DYNAMIC WEAPON SPEEDS. IT IS NOT PART OF DEFAULT DF.*Code: Select all
private float GetAnimTickTime() { PlayerEntity player = GameManager.Instance.PlayerEntity; float speed = 0; if (WeaponType == WeaponTypes.Bow || player == null) return GameManager.classicUpdateInterval; else { speed = (3 * ((115 - player.Stats.LiveSpeed) + AttackSpeed)); return speed / 980; // Approximation of classic frame update } }