Jump to content
  • Advertisement

tatar1nro

Member
  • Content Count

    5
  • Joined

  • Last visited

Community Reputation

5 Neutral

About tatar1nro

  • Rank
    Newbie

Personal Information

  • Interests
    Programming

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. My mistake, perepostil in the article.
  2. Hello, everyone. We are a small indie game studio called Drunken Monday. We’ve recently made a game in Unity3d where you run around an arena, rotate around yourself with the huge axe and try to hit other players. Good smash - good kill. We used ragdoll animation based on physics to make the deaths look more realistic. And everything was good in the beginning... However, when the number of the characters and calculations started growing, the game began working slowly and lagging on older phones. Disabling all physics calculation gave us 50-60 fps and absolute smoothness, but we didn't want to give up the cool ragdoll deaths of the characters. One of the solutions was to force the animators to build a pack of death animations. But we had a great idea to record a few ragdoll deaths directly in Unity and then just show the desired animation? Deaths will turn out to be diverse, there will no need to occupy animators, and the most important - everything will be fast, beautiful and realistic. What we get: Implementation The animation clip in the Unity is presented by the AnimationClip class, which contains an array of AnimationCurve. AnimationCurve defines the curve for the changes of one particular property of a particular object, for example, the localPosition.x. Values change in the timeline are described by a number of Keyframe structures. The idea is simple: for the each property of the each character object we create an AnimationCurve and store the values of this property on the curve for the each frame. The generated AnimationClip is exported through AssetDatabase.CreateAsset at the end. Let's create the class AnimationRecorderItem to track the each character object. All properties of the monitored object will be described through the dictionary, where the keys are the name of the properties and the values are the animation curves. Properties = new Dictionary<string, AnimationCurve> (); Properties.Add ( "localPosition.x", new AnimationCurve () ); Properties.Add ( "localPosition.y", new AnimationCurve () ); Properties.Add ( "localPosition.z", new AnimationCurve () ); Properties.Add ( "localRotation.x", new AnimationCurve () ); Properties.Add ( "localRotation.y", new AnimationCurve () ); Properties.Add ( "localRotation.z", new AnimationCurve () ); Properties.Add ( "localRotation.w", new AnimationCurve () ); Properties.Add ( "localScale.x", new AnimationCurve () ); Properties.Add ( "localScale.y", new AnimationCurve () ); Properties.Add ( "localScale.z", new AnimationCurve () ); For all of the object properties in each frame will be set their current values: Properties["localPosition.x"].AddKey (new Keyframe (time, _animObj.localPosition.x, 0.0f, 0.0f)); Properties["localPosition.y"].AddKey (new Keyframe (time, _animObj.localPosition.y, 0.0f, 0.0f)); Properties["localPosition.z"].AddKey (new Keyframe (time, _animObj.localPosition.z, 0.0f, 0.0f)); Properties["localRotation.x"].AddKey (new Keyframe (time, _animObj.localRotation.x, 0.0f, 0.0f)); Properties["localRotation.y"].AddKey (new Keyframe (time, _animObj.localRotation.y, 0.0f, 0.0f)); Properties["localRotation.z"].AddKey (new Keyframe (time, _animObj.localRotation.z, 0.0f, 0.0f)); Properties["localRotation.w"].AddKey (new Keyframe (time, _animObj.localRotation.w, 0.0f, 0.0f)); Properties["localScale.x"].AddKey (new Keyframe (time, _animObj.localScale.x, 0.0f, 0.0f)); Properties["localScale.y"].AddKey (new Keyframe (time, _animObj.localScale.y, 0.0f, 0.0f)); Properties["localScale.z"].AddKey (new Keyframe (time, _animObj.localScale.z, 0.0f, 0.0f)); But if you record all values for the each frame for the each property of the each object, the output file of the animation will turn out to be too large. Let's introduce the conditions for limiting the minimum changes in comparison with the previous frame. If the object has moved, increased and turned just a little bit, we will not record these changes. Completed class: AnimationRecorderItem.cs Also, we have to create a manager class AnimationRecorder. This script should be executed through the all children of the animated object and create an instance of AnimationRecorder for each of them. And also immediately generate and remember relativePath under which it will be saved in AnimationClip. According to the documentation, relativePath is generated as follows: The code will look like: private List<AnimationRecorderItem> _recorders; void Start () { Configurate (); } void Configurate () { _recorders = new List<AnimationRecorderItem> (); var allTransforms = gameObject.GetComponentsInChildren< Transform > (); for ( int i = 0; i < allTransforms.Length; ++i ) { string path = CreateRelativePathForObject ( transform, allTransforms [ i ] ); _recorders.Add( new AnimationRecorderItem ( path, allTransforms [ i ] ) ); } } private string CreateRelativePathForObject ( Transform root, Transform target ) { if ( target == root ) { return string.Empty; } string name = target.name; Transform bufferTransform = target; while ( bufferTransform.parent != root ) { name = string.Format ( "{0}/{1}", bufferTransform.parent.name, name ); bufferTransform = bufferTransform.parent; } return name; } To calculate current animation time and record the properties values for the each frame: private float _recordingTimer; private bool _recording = false; void Update () { if ( _recording ) { for ( int i = 0; i < _recorders.Count; ++i ) { _recorders [ i ].AddFrame ( _recordingTimer ); } _recordingTimer += Time.deltaTime; } } But the Update function is called quite often and recording the animation every frame is pretty redundant, so we limit the record. 30 fps should be enough for everyone. We will start recording by tapping on Spacebar. private const float CAPTURING_INTERVAL = 1.0f / 30.0f; private float _lastCapturedTime; private float _recordingTimer; private bool _recording = false; void Update () { if ( Input.GetKeyDown ( KeyCode.Space ) && !_recording ) { StartRecording (); return; } if ( _recording ) { if (_recordingTimer==0.0f||_recordingTimer-_lastCapturedTime>=CAPTURING_INTERVAL) { for ( int i = 0; i < _recorders.Count; ++i ) { _recorders [ i ].AddFrame ( _recordingTimer ); } _lastCapturedTime = _recordingTimer; } _recordingTimer += Time.deltaTime; } } public void StartRecording () { Debug.Log ( "AnimationRecorder recording started" ); _recording = true; } Let’s implement an animation export. We will create the AnimationClip instance and fill it with the collected values. private void ExportAnimationClip () { AnimationClip clip = new AnimationClip (); for ( int i = 0; i < _recorders.Count; ++i ) { Dictionary<string,AnimationCurve> propertiles = _recorders [ i ].Properties; for ( int j = 0; j < propertiles.Count; ++j ) { string name = _recorders [ i ].PropertyName; string propery = propertiles.ElementAt ( j ).Key; var curve = propertiles.ElementAt ( j ).Value; clip.SetCurve ( name, typeof(Transform), propery, curve ); } } clip.EnsureQuaternionContinuity (); string path = "Assets/" + gameObject.name + ".anim"; AssetDatabase.CreateAsset ( clip, path ); Debug.Log ( "AnimationRecorder saved to = " + path ); } Completed class AnimationRecorder.cs Finally, we will create the AnimationRecorderRagdollHelper helper class, which function will stop the Animator on the animated object, turn on all collisions, give the object acceleration and start recording our animation. The end of the animation recording will be completed by ourselves. To avoid some artefacts due to scene loading and the initialization of various objects, the script will start working with the specified delay. Completed class AnimationRecorderRagdollHelper.cs That's all, we add AnimationRecorderRagdollHelper on our character, set the impact force, then start the scene - and watch how the character cheerfully flies around the scene. When the cold corpse freezes on the ground - press Spacebar. The script will export our animation to the root of the project. https://gfycat.com/RecklessFickleBarracuda We recorded 4-5 animations for each character in this way and choose between them randomly when the character dies. P. S. Or not quite randomly. Our game is multiplayer, physics is calculated on the server and the vector of impact comes to us. So we select the necessary animation based on the vector which comes to us from the server, simply looking for the closest vector which the animation was recorded. Links: Project on GitHub A video of the game on YouTube with some deaths Slash Arena: Online ( Facebook ) Slash Arena: Online ( Steam )
  3. How we optimized Ragdoll animation of death in Unity Or how easily to turn Ragdoll into AnimationClip. Hello everyone. We are a small indie game studio called Drunken Monday. We’ve made recently a game on Unity3d where you run on the arena, rotate around yourself with the huge axe and try to hit other players. Good smash - good kill. To make the death looks more realistic we used usual ragdoll animation based on physics. And everything was good… in the beginning. When the number of the characters and calculations started growing, the game began working slowly and lagging on the old phones. Disabling all physics calculation gave us 50-60 fps and absolute smoothness. But we didn't want to refuse the cool ragdoll deaths of the characters. One of the solutions was to force the animators to build a pack of death animations. But we’ve got a great idea to record a few ragdoll deaths directly in Unity and then just show the desired animation? Deaths will turn out to be diverse, there will no need to occupy animators, and the most important - everything will be fast, beautiful and realistic. What we get: Implementation The animation clip in the Unity is presented by the AnimationClip class, which contains array of AnimationCurve. AnimationCurve defines the curve for the changes of one particular property of a particular object, for example, the localPosition.x. Values change in the timeline are described by a number of Keyframe structures. The idea is simple: for the each property of the each character object we create an AnimationCurve and store the values of this property on the curve for the each frame. The generated AnimationClip is exported through AssetDatabase.CreateAsset at the end. Let's create the class AnimationRecorderItem to track the each character object. All properties of the monitored object will be described through the dictionary, where the keys are the name of the properties and the values are the animation curves. Properties = new Dictionary<string, AnimationCurve> (); Properties.Add ( "localPosition.x", new AnimationCurve () ); Properties.Add ( "localPosition.y", new AnimationCurve () ); Properties.Add ( "localPosition.z", new AnimationCurve () ); Properties.Add ( "localRotation.x", new AnimationCurve () ); Properties.Add ( "localRotation.y", new AnimationCurve () ); Properties.Add ( "localRotation.z", new AnimationCurve () ); Properties.Add ( "localRotation.w", new AnimationCurve () ); Properties.Add ( "localScale.x", new AnimationCurve () ); Properties.Add ( "localScale.y", new AnimationCurve () ); Properties.Add ( "localScale.z", new AnimationCurve () ); For the all object properties in the each frame will be set theirs current values: Properties["localPosition.x"].AddKey (new Keyframe (time, _animObj.localPosition.x, 0.0f, 0.0f)); Properties["localPosition.y"].AddKey (new Keyframe (time, _animObj.localPosition.y, 0.0f, 0.0f)); Properties["localPosition.z"].AddKey (new Keyframe (time, _animObj.localPosition.z, 0.0f, 0.0f)); Properties["localRotation.x"].AddKey (new Keyframe (time, _animObj.localRotation.x, 0.0f, 0.0f)); Properties["localRotation.y"].AddKey (new Keyframe (time, _animObj.localRotation.y, 0.0f, 0.0f)); Properties["localRotation.z"].AddKey (new Keyframe (time, _animObj.localRotation.z, 0.0f, 0.0f)); Properties["localRotation.w"].AddKey (new Keyframe (time, _animObj.localRotation.w, 0.0f, 0.0f)); Properties["localScale.x"].AddKey (new Keyframe (time, _animObj.localScale.x, 0.0f, 0.0f)); Properties["localScale.y"].AddKey (new Keyframe (time, _animObj.localScale.y, 0.0f, 0.0f)); Properties["localScale.z"].AddKey (new Keyframe (time, _animObj.localScale.z, 0.0f, 0.0f)); But if you record all values for the each frame for the each property of the each object, the output file of the animation will turn out to be too large. Lets introduce the conditions for limiting the minimum changes in comparison with the previous frame. If the object has moved, increased and turned just a little bit, we will not record these changes. Completed class: AnimationRecorderItem.cs Also we have to create a manager class AnimationRecorder. This script should be executed through the all children of the animated object and create an instance of AnimationRecorder for each of them. And also immediately generate and remember relativePath under which it will be saved in AnimationClip. According to the documentation, relativePath is generated as follows: The code will look like: private List<AnimationRecorderItem> _recorders; void Start () { Configurate (); } void Configurate () { _recorders = new List<AnimationRecorderItem> (); var allTransforms = gameObject.GetComponentsInChildren< Transform > (); for ( int i = 0; i < allTransforms.Length; ++i ) { string path = CreateRelativePathForObject ( transform, allTransforms [ i ] ); _recorders.Add( new AnimationRecorderItem ( path, allTransforms [ i ] ) ); } } private string CreateRelativePathForObject ( Transform root, Transform target ) { if ( target == root ) { return string.Empty; } string name = target.name; Transform bufferTransform = target; while ( bufferTransform.parent != root ) { name = string.Format ( "{0}/{1}", bufferTransform.parent.name, name ); bufferTransform = bufferTransform.parent; } return name; } To calculate current animation time and record the properties values for the each frame: private float _recordingTimer; private bool _recording = false; void Update () { if ( _recording ) { for ( int i = 0; i < _recorders.Count; ++i ) { _recorders [ i ].AddFrame ( _recordingTimer ); } _recordingTimer += Time.deltaTime; } } But the Update function is called quite often and recording the animation every frame is pretty redundant, so we limit the record. 30 fps should be enough for everyone. We will start recording by tapping on Spacebar. private const float CAPTURING_INTERVAL = 1.0f / 30.0f; private float _lastCapturedTime; private float _recordingTimer; private bool _recording = false; void Update () { if ( Input.GetKeyDown ( KeyCode.Space ) && !_recording ) { StartRecording (); return; } if ( _recording ) { if (_recordingTimer==0.0f||_recordingTimer-_lastCapturedTime>=CAPTURING_INTERVAL) { for ( int i = 0; i < _recorders.Count; ++i ) { _recorders [ i ].AddFrame ( _recordingTimer ); } _lastCapturedTime = _recordingTimer; } _recordingTimer += Time.deltaTime; } } public void StartRecording () { Debug.Log ( "AnimationRecorder recording started" ); _recording = true; } Let’s implement an animation export. We will create the AnimationClip instance and fill it with the collected values. private void ExportAnimationClip () { AnimationClip clip = new AnimationClip (); for ( int i = 0; i < _recorders.Count; ++i ) { Dictionary<string,AnimationCurve> propertiles = _recorders [ i ].Properties; for ( int j = 0; j < propertiles.Count; ++j ) { string name = _recorders [ i ].PropertyName; string propery = propertiles.ElementAt ( j ).Key; var curve = propertiles.ElementAt ( j ).Value; clip.SetCurve ( name, typeof(Transform), propery, curve ); } } clip.EnsureQuaternionContinuity (); string path = "Assets/" + gameObject.name + ".anim"; AssetDatabase.CreateAsset ( clip, path ); Debug.Log ( "AnimationRecorder saved to = " + path ); } Completed class AnimationRecorder.cs Finally, we will create the AnimationRecorderRagdollHelper helper class, which function will stop the Animator on the animated object, turn on all collisions, give the object acceleration and start recording our animation. The end of the animation recording will be completed by ourselves. To avoid some artifacts due to scene loading and the initialization of various objects, script will start working with the specified delay. Completed class <b>AnimationRecorderRagdollHelper.cs That's all, we add AnimationRecorderRagdollHelper on our character, set the impact force, then start the scene - and watch how the character cheerfully fly around the scene. When the cold corpse freezes on the ground - press Spacebar. The script will exports our animation to the root of the project. https://gfycat.com/RecklessFickleBarracuda We recorded 4-5 animations for each character in this way and switch on them randomly at the character death. P. S. Or not quite randomly. Our game is multiplayer, physics is calculated on the server and the vector of impact comes to us. So we select the necessary animation based on vector which comes to us from the server, simply looking for the closest vector which the animation was recorded. Links: Project on a GitHub A video of the game on YouTube with some deaths Slash Arena: Online ( Facebook ) Slash Arena: Online ( Steam )
  4. Hello! We are small independent game studio from St.Peterburg, Russia. We are only two people team and over the previous year we have developed a cross-platform multiplayer game - Slash Arena: Online. We have done a small video footage with different development stages of our game. Here is how the game evolved from a simple prototype to an almost done game. We hope it will be interesting. You can play on: Facebook, it passed greenlight and coming on Steam, GooglePlay and AppStore.
  5. Hi, we are small game develop studio called Drunken Monday. We are only two people and during the last year we developed a cross-platform multiplayer game: Slash Arena. And we're almost done. We are glad to present you our game: Massively multiplayer online battles with swords and axes.Simple arcade action! Dodge the attack and choose a perfect time to strike.Upgrade your weapon, slash enemies, collect resources and reach the top! Battle Modes: ★ Deathmatch — Player vs All mode for 30 players. Score the highest damage and survive to win. ★ Arena 1vs1 — ranking duel for hardcore players. Your skills mean more than your high-level weapon. Features: ★ Rapid battles. Play 5 minutes or 5 hours. It’s all up to you! ★ Swing your hammer and make 'em fly! Damage is calculated according to physical laws. Timing and distance matter! ★ Two types of attack — enough to make your enemy suffer from a painful combo! Master your skills. ★ Separate ratings for each Battle Mode. Monitor your progress. ★ Monthly rewards for the best players. Earn a pile of resources and unique character portraits. ★ Daily tasks. See if you can cope with them! >:] ★ Three characters with unique weapons and fighting styles. More characters are coming soon! ★ More than 30 upgrade levels for each character’s weapon and armor. Start with a simple leather jacket and get to the legendary royal armor! ★ Character’s appearance changes each 3 levels. Everyone will see how cool you are! Game available on: Facebook, it passed greenlight and coming on Steam, soft-launched on GooglePlay and AppStore in Russia ( If you contact us we will send you .apk or testflight invitation ). Also take a look at Slash Arena: Online and Drunken Monday web sites. We will be glad to hear your opinion!
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!