{"author_link":"\/users\/fre","author_name":"fre","author_uid":"fre","comments":[],"epoch":1544923706,"event":"LD43","format":"md","ldjam_node_id":137459,"likes":16,"metadata":{"p_key":"127169","p_author":"fre","p_authorkey":"1132742","p_urlkey":"343289","p_title":"How we made Welcome to the North Pole (Part 2 - Building snowmen)","p_cat":"LDJam ","p_event":"LD43","p_time":"1544923706","p_likes":"16","p_comments":"0","p_status":"WAYBACK","us_key":"1132742","us_name":"fre","us_username":"fre","event_start":"1543536000","event_key":"71","event_name":"LD 43"},"node":{"_collation":{"body_sanitizer":"TextUtils::SanitizeHTML via existing importer","event":"LD43","removed_author":false},"_superparent":120415,"_trust":3,"author":132742,"body":"This is part 2 of a series of blog posts describing how we made [Welcome to the North Pole](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole).\n\nIf you've made a similar blog post, [paste the link here](https:\/\/goo.gl\/forms\/xBXN7IdD5lgzo5AE3) so we can create an index of tips and tricks.\n\nSee also:\n* [Part 1 - Platformer mechanics](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics).\n\n## Accumulating snow\n\nSnow accumulates as you move on the ground.\n\n![WTTNP_Grow.gif](\/\/\/raw\/686\/02\/z\/1fb05.gif)\n\nThe ground detector detects whether the snowball is touching the ground (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics)).\n\nAs long as we haven't reached maximum size, we grow by a fraction of the `GrowthDistance` defined for that snowball size. Along with other stats, `GrowthDistance` is stored in a `ScriptableObject` instance for each size of snowball:\n\n![WTTNP_ScriptableObjects.PNG](\/\/\/raw\/686\/02\/z\/1fb06.png)\n\nThis is what the code looks like:\n\n```csharp\n  void Grow() {\n    var position = transform.position;\n    var delta = (position - lastPosition).magnitude;\n    lastPosition = position;\n\n    if (controller.Grounded) {\n      SizeProgress += delta \/ stats.GrowthDistance;\n      SizeProgress = Mathf.Clamp01(SizeProgress);\n      if (SizeProgress == 1) {\n        if (statsTemplates.Count > SizeIndex + 1) {\n          SizeProgress = 0;\n          SizeIndex++;\n        } else {\n          Die();\n        }\n      }\n    }\n    stats = statsTemplates[SizeIndex];\n  }\n```\n\n## Stacks\n\nUsing a snowball's four detectors (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole)), we compute the top, bottom, left and right stacks of connected snowballs as seen from that snowball. Each stack reports its total weight.\n\nWe use stack information for the following:\n* Crushing snowballs under too much weight (see below).\n* Controlling multi-tiered snowmen (see below).\n* Jumping over other active snowballs (only the last snowball in a horizontal stack jumps).\n\n![WTTNP_Stacks_annotated.png](\/\/\/raw\/686\/02\/z\/1fb02.png)\n_Snowball and stack weight, as seen by the center snowball_\n\nA small trick to avoid code redundancy was to use C# lambda expressions:\n\n```csharp\n    delegate CharacterGroundDetector GetNextDetector(CharacterController current);\n\n    [System.Serializable]\n    public struct StackResult {\n        public int Depth;\n        public int Size;\n        public CharacterController End;\n    }\n\n    private void UpdateStacks() {\n        BottomStack = LookupStackEnd(c => c.groundDetector);\n        TopStack = LookupStackEnd(c => c.ceilingDetector);\n        ForwardStack = LookupStackEnd(\n            c => Direction > 0 ? c.rightDetector : Direction < 0 ? c.leftDetector : null);\n        BackStack = LookupStackEnd(\n            c => Direction > 0 ? c.leftDetector : Direction < 0 ? c.rightDetector : null);\n    }\n\n    private StackResult LookupStackEnd(GetNextDetector detectorLookup) {\n        var result = new StackResult();\n        result.Depth = 0;\n        result.Size = 0;\n        result.End = this;\n        \/\/ Limit the max depth to prevent loops in rare cases where detectors point to each other.\n        while (detectorLookup(result.End) && result.Depth < 20) {\n            var next = detectorLookup(result.End).CharacterInContact;\n            if (!next || next == result.End) return result;\n            result.Depth++;\n            result.Size += next.Size;\n            result.End = next;\n        }\n        return result;\n    }\n```\n\n\n## Crush weight\n\nIf a snowball lands on top of another, the bottom snowball becomes inert. If the weight of its top stack is too high, the bottom snowball gets crushed.\n\n![WTTNP_Crush.gif](\/\/\/raw\/686\/02\/z\/1fb04.gif)\n\nOther than looking cool, this helps manage the number of snowballs in existence and prevents clogging up doorways and tunnels. We slightly increase the linear friction of inert snowballs to keep them from rolling around too much (deceleration for active snowballs is handled by the movement code).\n\nThe crush code looks like this:\n\n\n```csharp\n  void FixedUpdate() {\n    if (TopStack.Depth > 0) {\n      if (TopStack.Size > Size * WeightLimitFactor) {\n        Explode();\n      } else {\n        Die();\n      }\n    }\n  }\n```\n\n\n## Snowman magnet\n\nTo keep snowmen in balance, we apply an elastic force to the top snowball towards its ideal position on top of the bottom snowball. To help you climb, this force is also applied as you move against another snowball:\n\n![WTTNP_Magnet.gif](\/\/\/raw\/686\/02\/z\/1fb07.gif)\n\nThe force is applied by enabling a `TargetJoint2D` on the top snowball and adjusting its target position each frame:\n\n![WTTNP_TargetJoint2D.PNG](\/\/\/raw\/686\/02\/z\/1fb08.png)\n\nThis is what the update code looks like:\n\n```csharp\n    private void UpdateLink() {\n        var other = groundDetector.CharacterInContact;\n        if (!other && forwardDetector != null) {\n            other = forwardDetector.CharacterInContact;\n        }\n        if (other && !Jumping && !input.JumpHold && !input.Down) {\n            PullJoint.target = other.transform.position + Vector3.up * (other.Scale + Scale) * 0.5f;\n            PullJoint.maxForce = status.stats.LinkForce;\n            PullJoint.enabled = true;\n        } else {\n            PullJoint.enabled = false;\n        }\n    }\n```\n\n## Snowman control\n\nA snowman is controlled by its head:\n\n![WTTNP_SnowmanControl.gif](\/\/\/raw\/686\/02\/z\/1fb0a.gif)\n\nInstead of applying the sideways movement to the head (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics)), we apply it on the bottom-most snowball.\n\nWe add a slight multiplier to account for the added friction between snowballs:\n\n```csharp\n    private void MoveStack() {\n        var target = Jumping ? this : BottomStack.Target;\n        var powerMultiplier = 1f;\n        if (target != this) {\n            powerMultiplier = target.stats.StackControlMultiplier;\n        }\n        MoveSideways(target, powerMultiplier);\n    }\n```\n\n## End of Part 2\n\nThanks for reading!\n\nSee also:\n* [Part 1 - Platformer mechanics](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics).\n\nIn the next parts we plan to describe:\n* Gameplay polish (camera logic, smoothing)\n* Art style and implementation\n* Music and sound effect design\n\nFeel free to post other suggestions in this thread.\n\nIf you've made a similar blog post, [paste the link here](https:\/\/goo.gl\/forms\/xBXN7IdD5lgzo5AE3) so we can create an index of tips and tricks.\n\n@fre and @catie-jo","comments":5,"comments-timestamp":"2018-12-16T16:26:51Z","created":"2018-12-15T21:44:09Z","files":[],"files-timestamp":0,"id":137459,"love":16,"love-timestamp":"2018-12-21T06:03:06Z","meta":[],"modified":"2018-12-21T06:03:06Z","name":"How we made Welcome to the North Pole (Part 2 - Building snowmen)","node-timestamp":"2018-12-16T16:25:09Z","parent":132745,"parents":[1,5,9,120415,132745],"path":"\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-2-building-snowmen","published":"2018-12-16T01:28:26Z","scope":"public","slug":"how-we-made-welcome-to-the-north-pole-part-2-building-snowmen","subsubtype":"","subtype":"","type":"post","version":414076},"node_metadata":{"n_key":"137459","n_urlkey":"343289","n_parent":"132745","n_path":"\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-2-building-snowmen","n_slug":"how-we-made-welcome-to-the-north","n_type":"post","n_subtype":"","n_subsubtype":"","n_author":"132742","n_created":"1544910249","n_modified":"1545372186","n_version":"414076","n_status":"WAYBACK"},"source_url":"https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-2-building-snowmen","text":"This is part 2 of a series of blog posts describing how we made [Welcome to the North Pole](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole).\n\nIf you've made a similar blog post, [paste the link here](https:\/\/goo.gl\/forms\/xBXN7IdD5lgzo5AE3) so we can create an index of tips and tricks.\n\nSee also:\n* [Part 1 - Platformer mechanics](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics).\n\n## Accumulating snow\n\nSnow accumulates as you move on the ground.\n\n![WTTNP_Grow.gif](\/\/\/raw\/686\/02\/z\/1fb05.gif)\n\nThe ground detector detects whether the snowball is touching the ground (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics)).\n\nAs long as we haven't reached maximum size, we grow by a fraction of the `GrowthDistance` defined for that snowball size. Along with other stats, `GrowthDistance` is stored in a `ScriptableObject` instance for each size of snowball:\n\n![WTTNP_ScriptableObjects.PNG](\/\/\/raw\/686\/02\/z\/1fb06.png)\n\nThis is what the code looks like:\n\n```csharp\n  void Grow() {\n    var position = transform.position;\n    var delta = (position - lastPosition).magnitude;\n    lastPosition = position;\n\n    if (controller.Grounded) {\n      SizeProgress += delta \/ stats.GrowthDistance;\n      SizeProgress = Mathf.Clamp01(SizeProgress);\n      if (SizeProgress == 1) {\n        if (statsTemplates.Count > SizeIndex + 1) {\n          SizeProgress = 0;\n          SizeIndex++;\n        } else {\n          Die();\n        }\n      }\n    }\n    stats = statsTemplates[SizeIndex];\n  }\n```\n\n## Stacks\n\nUsing a snowball's four detectors (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole)), we compute the top, bottom, left and right stacks of connected snowballs as seen from that snowball. Each stack reports its total weight.\n\nWe use stack information for the following:\n* Crushing snowballs under too much weight (see below).\n* Controlling multi-tiered snowmen (see below).\n* Jumping over other active snowballs (only the last snowball in a horizontal stack jumps).\n\n![WTTNP_Stacks_annotated.png](\/\/\/raw\/686\/02\/z\/1fb02.png)\n_Snowball and stack weight, as seen by the center snowball_\n\nA small trick to avoid code redundancy was to use C# lambda expressions:\n\n```csharp\n    delegate CharacterGroundDetector GetNextDetector(CharacterController current);\n\n    [System.Serializable]\n    public struct StackResult {\n        public int Depth;\n        public int Size;\n        public CharacterController End;\n    }\n\n    private void UpdateStacks() {\n        BottomStack = LookupStackEnd(c => c.groundDetector);\n        TopStack = LookupStackEnd(c => c.ceilingDetector);\n        ForwardStack = LookupStackEnd(\n            c => Direction > 0 ? c.rightDetector : Direction < 0 ? c.leftDetector : null);\n        BackStack = LookupStackEnd(\n            c => Direction > 0 ? c.leftDetector : Direction < 0 ? c.rightDetector : null);\n    }\n\n    private StackResult LookupStackEnd(GetNextDetector detectorLookup) {\n        var result = new StackResult();\n        result.Depth = 0;\n        result.Size = 0;\n        result.End = this;\n        \/\/ Limit the max depth to prevent loops in rare cases where detectors point to each other.\n        while (detectorLookup(result.End) && result.Depth < 20) {\n            var next = detectorLookup(result.End).CharacterInContact;\n            if (!next || next == result.End) return result;\n            result.Depth++;\n            result.Size += next.Size;\n            result.End = next;\n        }\n        return result;\n    }\n```\n\n\n## Crush weight\n\nIf a snowball lands on top of another, the bottom snowball becomes inert. If the weight of its top stack is too high, the bottom snowball gets crushed.\n\n![WTTNP_Crush.gif](\/\/\/raw\/686\/02\/z\/1fb04.gif)\n\nOther than looking cool, this helps manage the number of snowballs in existence and prevents clogging up doorways and tunnels. We slightly increase the linear friction of inert snowballs to keep them from rolling around too much (deceleration for active snowballs is handled by the movement code).\n\nThe crush code looks like this:\n\n\n```csharp\n  void FixedUpdate() {\n    if (TopStack.Depth > 0) {\n      if (TopStack.Size > Size * WeightLimitFactor) {\n        Explode();\n      } else {\n        Die();\n      }\n    }\n  }\n```\n\n\n## Snowman magnet\n\nTo keep snowmen in balance, we apply an elastic force to the top snowball towards its ideal position on top of the bottom snowball. To help you climb, this force is also applied as you move against another snowball:\n\n![WTTNP_Magnet.gif](\/\/\/raw\/686\/02\/z\/1fb07.gif)\n\nThe force is applied by enabling a `TargetJoint2D` on the top snowball and adjusting its target position each frame:\n\n![WTTNP_TargetJoint2D.PNG](\/\/\/raw\/686\/02\/z\/1fb08.png)\n\nThis is what the update code looks like:\n\n```csharp\n    private void UpdateLink() {\n        var other = groundDetector.CharacterInContact;\n        if (!other && forwardDetector != null) {\n            other = forwardDetector.CharacterInContact;\n        }\n        if (other && !Jumping && !input.JumpHold && !input.Down) {\n            PullJoint.target = other.transform.position + Vector3.up * (other.Scale + Scale) * 0.5f;\n            PullJoint.maxForce = status.stats.LinkForce;\n            PullJoint.enabled = true;\n        } else {\n            PullJoint.enabled = false;\n        }\n    }\n```\n\n## Snowman control\n\nA snowman is controlled by its head:\n\n![WTTNP_SnowmanControl.gif](\/\/\/raw\/686\/02\/z\/1fb0a.gif)\n\nInstead of applying the sideways movement to the head (see [Part 1](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics)), we apply it on the bottom-most snowball.\n\nWe add a slight multiplier to account for the added friction between snowballs:\n\n```csharp\n    private void MoveStack() {\n        var target = Jumping ? this : BottomStack.Target;\n        var powerMultiplier = 1f;\n        if (target != this) {\n            powerMultiplier = target.stats.StackControlMultiplier;\n        }\n        MoveSideways(target, powerMultiplier);\n    }\n```\n\n## End of Part 2\n\nThanks for reading!\n\nSee also:\n* [Part 1 - Platformer mechanics](https:\/\/ldjam.com\/events\/ludum-dare\/43\/welcome-to-the-north-pole\/how-we-made-welcome-to-the-north-pole-part-1-platformer-mechanics).\n\nIn the next parts we plan to describe:\n* Gameplay polish (camera logic, smoothing)\n* Art style and implementation\n* Music and sound effect design\n\nFeel free to post other suggestions in this thread.\n\nIf you've made a similar blog post, [paste the link here](https:\/\/goo.gl\/forms\/xBXN7IdD5lgzo5AE3) so we can create an index of tips and tricks.\n\n@fre and @catie-jo","title":"How we made Welcome to the North Pole (Part 2 - Building snowmen)","wayback_source":[]}