When I was visiting a museum with the family, my daughter saw this little plush penguin which was kind of sad sitting there alone all day long.
She saved him from this terrible fate (in exchange for a small amount of my money), and we took him home with us. He told us that his name was Pingui. Pingui became her favorite toy (at least for a while).
One day, my daughter saw that I was playing a game on my phone. After consulting with Pingui, she asked me if I could make a game where the main character was Pingui himself. She offered to draw him, and I could make the game. Pingui was delighted with the idea, and I could not say no to my daughter.
These were the drawings she made:
The second drawing is from above, because we thought to make a scroller game, and Pingui will be seen from above sliding on ice.
Now, these are a bit hard to use directly for a game, so we first needed to make a digital version of Pingui.
That was a bit of a challenge, but with the help of my daughter, who made very opinionated comments on my work, we managed to create this digital version of Pingui:
After we had a digital version of Pingui, we could start making the game. So, after learning a bit of Adobe Illustrator, I had just to learn the Swift programming language. Then the Apple SpriteKit framework. And physics simulation and collisions. And a lot of other small things.
Anyway, after a few weekends, we had a working game.
It is a very simple scroller where you help Pingui to grab the fish that is falling over from the net of some fishers.
Of course, things are not that simple. There are also some pesky ice cubes that can hit Pingui. He must be very attentive to avoid them. And the tasty fish fall much faster as the game progress.
I grabbed some free assets from Flaticon for the sprites:
The fish drops from the fish net and has a struggle like animation while on ice. This is what the Fish sprite implementation looks like:
class Fish: SKSpriteNode {
var grabHandled = false
init(num: Int) {
let num = num < 1 ? 1 : 5 < num ? 5 : num
let texture = SKTexture(imageNamed: "fish_\(num)")
super.init(texture: texture, color: SKColor.white, size: texture.size())
let physicsBody = SKPhysicsBody(texture: texture, size: size)
physicsBody.categoryBitMask = ContactCategory.fish
physicsBody.contactTestBitMask = ContactCategory.pinguiFace
physicsBody.collisionBitMask =
ContactCategory.fish | ContactCategory.iceCube
physicsBody.affectedByGravity = false
self.physicsBody = physicsBody
}
func drop() {
alpha = 0.1
let struggleHalfDuration = CGFloat.random(in: 0.15...0.3)
run(
SKAction.group([
SKAction.fadeAlpha(to: 1, duration: 0.4),
SKAction.sequence([
SKAction.scale(to: 1.5, duration: 0.2).easeIn(),
SKAction.scale(to: 1, duration: 0.1).easeIn(),
]),
SKAction.sequence([
SKAction.rotate(
byAngle: -.pi / 16,
duration: struggleHalfDuration / 2,
),
SKAction.repeatForever(SKAction.sequence([
SKAction.rotate(
byAngle: .pi / 8,
duration: struggleHalfDuration,
).easeOut(),
SKAction.rotate(
byAngle: -.pi / 8,
duration: struggleHalfDuration,
).easeOut(),
]))
])
])
)
}
}
Pingui is guided by touching the screen. When he is near a fish, he grabs it. The grab animation was my daughter’s idea, Pingui quickly reaches in front when a fish is near, and looks quite nice.
The ice background is moving continuously under Pingui. Well, Pingui thinks he is moving, but anyway.
I simulated the continuous scroll by using two identical sprites that move in tandem, so it covers two screen heights.
When the scroll for a full screen height finishes, it starts again.
You think there is an infinite ice sheet moving, but you can never tell that is just two blocks.
I’ve read somewhere that games are all about smoke and mirrors, and it seems it is true!
This is how it looks like in the implementation:
class IceBackground: SKNode {
public let bounds: CGRect
private let background1: SKSpriteNode
private let background2: SKSpriteNode
init(bounds: CGRect) {
self.bounds = bounds
background1 = SKSpriteNode(imageNamed: "ice_background")
background1.zPosition = 2
background2 = SKSpriteNode(imageNamed: "ice_background")
background2.zPosition = 1
background2.position = CGPoint(x: 0, y: background2.size.height - 2)
super.init()
addChild(background1)
addChild(background2)
}
func move() {
background1.run(
SKAction.moveBy(
x: 0,
y: -background1.size.height + 2,
duration: fullMoveDuration,
)
)
background2.run(
SKAction.moveBy(
x: 0,
y: -background2.size.height + 2,
duration: fullMoveDuration,
)
) {
self.background1.position = CGPoint(x: 0, y: 0)
self.background2.position =
CGPoint(x: 0, y: self.background2.size.height - 2)
self.move()
}
}
// ...
}
A nice trick I’ve first seen in a starship game is to slow the game almost to a halt when you rise your finder from the screen. This is a very natural “pause” mode without actually explicitly pausing the game or requiring to rapidly tap a pause button on the screen. I liked it a lot, so I’ve implemented it in Pingui game as well.
This is how it looks like in the implementation:
private func changeStatePlayingToSlowed() {
guard let boat, let pingui else { return }
pingui.stop()
boat.isDroppingEnabled = false
slow()
gameState = .slowed
}
private func changeStateSlowedToPlaying() {
guard let boat else { return }
boat.isDroppingEnabled = true
fast()
gameState = .playing
}
private func fast() {
guard let iceBackground, let boat else { return }
iceBackground.run(SKAction.speed(to: 1, duration: 0.5))
boat.run(SKAction.speed(to: 1, duration: 0.5))
}
private func slow() {
guard let iceBackground, let boat else { return }
let slowSpeed: CGFloat = iceBackground.fullMoveDuration / 100
iceBackground.run(SKAction.speed(to: slowSpeed, duration: 0.5))
boat.run(SKAction.speed(to: slowSpeed, duration: 0.5))
}
The level of difficulty increases after some time and the ice starts to move faster. The scope is to help Pingui grab as many fish as he can, but it becomes harder to evade the pesky ice cubes. I didn’t want to interrupt the flow of the game when the level changes, so I decided to just “write” the level number on the ice itself, using a semi-transparent band. It looks good, is not intrusive, and it is another nice touch.
Here are a couple of screenshots from the final game:
I didn’t get to actually publish the game on the Apple App Store because it requires an annual fee and couldn’t convince myself to do it for this alone. We just played the game installed directly from the laptop on my phone. The overall experience to get to implement this simple game was really fun, I learned a lot, and my daughter and Pingui already have some ideas for the next ones.