Welcome to the North Pole!
We made a game about snowmen.
You can build snowmen, crush snow and get a present.
<== Play it here | Rate it there ==>
Thanks everyone for the awesome games we've seen so far!

We made a game about snowmen.
You can build snowmen, crush snow and get a present.
<== Play it here | Rate it there ==>
Thanks everyone for the awesome games we've seen so far!

In this post I'll be describing the implementation of some of the platformer elements in Welcome to the North Pole.
Perhaps this can serve as inspiration for others or as a way to start exchanging tips. We made the game using Unity but these concepts apply elsewhere.
See also: * Part 2 - Building snowmen

Each snowball has 4 detectors that report contact with the ground or other characters. We use them to detect whether a snowball can jump and what shape a snowman has.
Detectors are configured as trigger and only react to contact with other characters or the environment:

Our layer setup, note that it avoids inter-collisions between static layers or between triggers
This is the simplified code for the collision detection:
csharp
private void OnTriggerStay2D(Collider2D other) {
// Layers is defined by hand.
if (other.gameObject.layer == (int) Layers.GROUND) {
GroundContact = true;
lastGroundContactTime = Time.time;
}
}
Contacts are reset in FixedUpdate(), which executes just before the physics loop (where OnTriggerStay2D is called). We moved CharacterGroundDetector last in the script execution order to make sure that the correct value can be read from other scripts' Update() or FixedUpdate() loops.
```csharp void FixedUpdate() { // Do not clear contact if the rigidbody is sleeping, // otherwise it won't be re-registered until the rigidbody wakes up. if (controller.rb.IsSleeping()) return;
// Allow some tolerance after the last contact before clearing the flag.
// This allows e.g. jumping slightly after leaving a platform.
if (Time.time > lastGroundContactTime + ContactTimeTolerance) {
GroundContact = false;
}
} ```
Detectors are children of an "Upright" GameObject that preserves its orientation even though the snowball rotates:
csharp
void FixedUpdate() {
transform.rotation = Quaternion.identity;
}
Two size 1 snowballs: before accumulating snow (left) and about to grow (right)
Main ideas: * The physical size of a snowball is fixed for each stage (small/medium/large). * The visual sprite grows slowly as you accumulate snow.
This let us balance the game for a fixed collider size while giving a visual feedback that a snowball changes size (coherent with the world).
We kept the ball diameter below the size of one tile to make sure a snowball can easily fit through a gap of the corresponding size (collider diameters are 0.8, 1.8 and 2.6, a tile is 1x1).
Because snowballs are made of snow, the default visual size is already slightly bigger than the collider because a slight overlap looks more natural than having snowballs touch the ground right along their edge.

We took the movement principles from this Sonic physics guide. Another helpful resource was this blog post by Yoann Pignole.
Main ideas: * Each snowball has a maximum speed and an acceleration factor (the bigger the snowball, the slower it is). * While a direction is held, we increase the snowball's velocity in that direction up to the maximum speed. * Velocity is not reduced if it's above the maximum speed. This lets you gather a lot of momentum on the ground and keep it while jumping, or as a small snowball and preserve it as you grow bigger (adds a nice flow and is actually helpful for one of the challenges). * The ball does have some amount of friction that we let the physics engine resolve. This is helpful to make the snowball rotate naturally as it rolls along the ground.
Translated into code, it looks something like this:
csharp
private void MoveSideways() {
var currentSpeed = Mathf.Abs(rb.velocity.x);
var currentDirection = Mathf.Sign(rb.velocity.x);
// Air speed is lower than ground speed to allow less control in the air than on ground,
// but momentum is preserved if you hold the direction.
var maxSpeed = Grounded ? stats.GroundSpeed : stats.AirSpeed;
var targetSpeed = currentSpeed;
if (currentDirection == Direction) {
// Accelerate up to maxSpeed, but do not decelerate while holding the key.
targetSpeed += Mathf.Min(stats.Acceleration * Time.deltaTime,
Mathf.Max(maxSpeed - targetSpeed, 0));
} else if (Direction == 0) {
// Decelerate up to 0 if no key is held.
targetSpeed -= stats.IdleDeceleration * Time.deltaTime;
targetSpeed = Mathf.Max(targetSpeed, 0);
} else {
// Decelerate faster until we flip direction if the opposite key is held.
targetSpeed -= stats.OppositeDeceleration * Time.deltaTime;
}
// Apply the velocity change through a force on the rigidbody.
rb.AddForce(Vector2.right * currentDirection * (targetSpeed - currentSpeed) * rb.mass,
ForceMode2D.Impulse);
}
Holding the jump key makes you jump higher:

This is implemented as a combination of forces:
* JumpImpulse is applied right as you press the jump key.
* JumpHoldForce is applied over time, while the jump key is held, up to a maximum duration (0.35s).
In code, it looks something like this:
```csharp private void HandleJump() { // Trigger jump if (Grounded && input.Jump && !Jumping) { Jumping = true; jumpEndTime = Time.time + stats.MaxJumpDuration; // Cancel vertical velocity and jump up. // Vertical velocity must be canceled because the snowball may still be falling // when the ground detector triggers. rb.AddForce(Vector2.up * (stats.JumpImpulse - rb.velocity.y) * rb.mass, ForceMode2D.Impulse); }
if (Time.time > jumpEndTime) {
Jumping = false;
}
if (Jumping && input.JumpHold) {
rb.AddForce(Vector2.up * stats.JumpHoldForce * rb.mass, ForceMode2D.Force);
} else {
// Stop jump hold force when input is released.
Jumping = false;
}
}
```
We later added a small tweak: moving against a wall while holding jump gives you a small additional boost (ClimbHoldForce) to counteract the added friction and because it feels satisfying:

This allows you to clear 4-high walls without building a snowman :)
Thanks for reading!
See also: * Part 2 - Building snowmen
In the next parts we plan to describe: * Art style and implementation * Music and sound effect design
Feel free to post other suggestions in this thread.
@fre and @catie-jo
This is part 2 of a series of blog posts describing how we made Welcome to the North Pole.
If you've made a similar blog post, paste the link here so we can create an index of tips and tricks.
See also: * Part 1 - Platformer mechanics.
Snow accumulates as you move on the ground.

The ground detector detects whether the snowball is touching the ground (see Part 1).
As 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:

This is what the code looks like:
```csharp void Grow() { var position = transform.position; var delta = (position - lastPosition).magnitude; lastPosition = position;
if (controller.Grounded) {
SizeProgress += delta / stats.GrowthDistance;
SizeProgress = Mathf.Clamp01(SizeProgress);
if (SizeProgress == 1) {
if (statsTemplates.Count > SizeIndex + 1) {
SizeProgress = 0;
SizeIndex++;
} else {
Die();
}
}
}
stats = statsTemplates[SizeIndex];
} ```
Using a snowball's four detectors (see Part 1), we compute the top, bottom, left and right stacks of connected snowballs as seen from that snowball. Each stack reports its total weight.
We use stack information for the following: * Crushing snowballs under too much weight (see below). * Controlling multi-tiered snowmen (see below). * Jumping over other active snowballs (only the last snowball in a horizontal stack jumps).
Snowball and stack weight, as seen by the center snowball
A small trick to avoid code redundancy was to use C# lambda expressions:
```csharp delegate CharacterGroundDetector GetNextDetector(CharacterController current);
[System.Serializable]
public struct StackResult {
public int Depth;
public int Size;
public CharacterController End;
}
private void UpdateStacks() {
BottomStack = LookupStackEnd(c => c.groundDetector);
TopStack = LookupStackEnd(c => c.ceilingDetector);
ForwardStack = LookupStackEnd(
c => Direction > 0 ? c.rightDetector : Direction < 0 ? c.leftDetector : null);
BackStack = LookupStackEnd(
c => Direction > 0 ? c.leftDetector : Direction < 0 ? c.rightDetector : null);
}
private StackResult LookupStackEnd(GetNextDetector detectorLookup) {
var result = new StackResult();
result.Depth = 0;
result.Size = 0;
result.End = this;
// Limit the max depth to prevent loops in rare cases where detectors point to each other.
while (detectorLookup(result.End) && result.Depth < 20) {
var next = detectorLookup(result.End).CharacterInContact;
if (!next || next == result.End) return result;
result.Depth++;
result.Size += next.Size;
result.End = next;
}
return result;
}
```
If 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.

Other 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).
The crush code looks like this:
csharp
void FixedUpdate() {
if (TopStack.Depth > 0) {
if (TopStack.Size > Size * WeightLimitFactor) {
Explode();
} else {
Die();
}
}
}
To 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:

The force is applied by enabling a TargetJoint2D on the top snowball and adjusting its target position each frame:

This is what the update code looks like:
csharp
private void UpdateLink() {
var other = groundDetector.CharacterInContact;
if (!other && forwardDetector != null) {
other = forwardDetector.CharacterInContact;
}
if (other && !Jumping && !input.JumpHold && !input.Down) {
PullJoint.target = other.transform.position + Vector3.up * (other.Scale + Scale) * 0.5f;
PullJoint.maxForce = status.stats.LinkForce;
PullJoint.enabled = true;
} else {
PullJoint.enabled = false;
}
}
A snowman is controlled by its head:

Instead of applying the sideways movement to the head (see Part 1), we apply it on the bottom-most snowball.
We add a slight multiplier to account for the added friction between snowballs:
csharp
private void MoveStack() {
var target = Jumping ? this : BottomStack.Target;
var powerMultiplier = 1f;
if (target != this) {
powerMultiplier = target.stats.StackControlMultiplier;
}
MoveSideways(target, powerMultiplier);
}
Thanks for reading!
See also: * Part 1 - Platformer mechanics.
In the next parts we plan to describe: * Gameplay polish (camera logic, smoothing) * Art style and implementation * Music and sound effect design
Feel free to post other suggestions in this thread.
If you've made a similar blog post, paste the link here so we can create an index of tips and tricks.
@fre and @catie-jo
My girlfriend and I made a game!

Imagine the love child of a “Choose your own Adventure” book :blue_book: and a game of Scrabble :a:
Armed with only a handful of letter tiles, take a bold adventurer :dancer: through the journey of her lifetime!