Build a Snake Game in SwiftUI Using GeometryReader | by Mark Lucking | Feb, 2022

See how to leverage Preferences protocol

Mark Locking
Photo by Riho Kroll on Unsplash

Although I am reasonably sure Confucius, who was around in 450BC, wasn’t a programmer, some advice he had about learning things couldn’t have been more true when it comes to coding.

Tell Me and I Will Forget;
Show Me and I May Remember;
Involve Me and I Will Understand.

So join me in this article to figure out how to build a simple snake-like game. Code in which I will touch on the preferences protocol in SwiftUI, the GeometryReaderetc.

As you might have guessed, I didn’t design a game to run left to right — but I needed to roll to the side to show you. So turn your screen to the right to see it correctly.

You’re looking at a for each loop of shapes, connected with a for each loop of lines through preferences protocol. I use a delay to move the different segments when I drag the head because it looks better. The shapes passing left to right start out as circles that you need to catch. Fail to do so by the time they get to mid-screen and they change into stars. Stars you don’t want to catch. When you catch a circle, the snake grows by one segment. Catch a star by accident or completely miss a circle [which becomes a star], and the snake shrinks by a segment. The end game is when the snake has no more segments to lose.

Last week I published an article on another game I just built that you find here. It, too, relied on circles and lines; But I made it quite differently. To construct the graph like structure within it, I predefined all the coordinates. But in this case, I needed to be able to build it dynamically, which is why I turned to the SwiftUI preferences protocol.

The animated GIF shows a POC with all the essential elements in place — I got a snake, passing things to catch or not, and some scoring going on, where to next. Upmost in my mind is the phrase less is more.

  • Most games have some stages or challenges — need to do.
  • Most games have some levels at which point you can join — need to do.
  • Most games have a league table of sorts — need to do.

Bon, at this point, I have “berries”, if you will, that fall at a constant rate. If the snake misses them mid-screen, then he needs to avoid them. What are some easy wins? Here are some thoughts on how I could make the game more challenging without too much more code.

  • I could introduce “berries” that he needs to avoid altogether?
  • I could introduce “berries” that fall at different rates, some faster, some slower?
  • I could introduce “berries” that change their skin at some random point on their journey?
  • I could make some “berries” more valuable than others and have some scoring mechanism.
  • I could make the “berries” fall at different speeds?
  • On the snake side of things, changing the segments’ shape would be an excellent way to control the difficulty level; the larger the snakehead, the easier it to capture “berries”?
  • I could slow the slithering of the snake as it gets bigger.
  • I could make his body a point of vulnerability currently. Nothing happens beyond the head.
  • Going back to the value of the berries, I could make the snake grow faster or slower.

Ok, what does that look like this — . The format of the snake has changed a tad but is more or less the same — except for the animation of the tail that gets slower as it gets longer. The berries now come in four flavors; the darker ones you want to catch, the lighter you don’t. They are all moving at different speeds now too.

The same rules apply: catch a berry the tail gets longer, drop one and gets shorter. Catch the wrong berry the tail gets shorter. It’s a far faster game than POCI.

Bon — enough text and pictures — how did I do it. As I said, the key is in implementing the SwiftUI preferences protocol that looks like this.

I created a struct in which I store an index value and CGRect. I named that struct to save in the preferences protocol template and then made a third struct to use it.
Beyond that, I make the PreferenceViewSetter struct as the background of the snake segments. So with this snippet of code.

RoundedRectangle(cornerRadius: 8)
.stroke(Color.black, lineWidth: 2)
.frame(width: 24, height: 24)
.background(PreferenceViewSetter(idx: idx))

Code I call within a loop that says how many segments I need. It’s a loop the size of which I change as the game progresses. I got this code in place outside the loop, drawing the segments.

.onPreferenceChange(TextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIdx] = p.rect
}

It saves the CGRects as each segment is drawn within to an external array that I reference reference with this struct again within the same run as the drawing of the segments.

Again called within a loop, this routine draws a line between each snake segment. Putting it all together, it looks like this. SnakeView here draws the segments updates preferences that update the reacts array — an array that is past back to the drawing of the lines.

Implementing the berry drops is far more straightforward and simply uses a timer to trigger an array of berry data to act upon.

All of which brings me to the end of this short piece. Download the entire code base from bitbucket on this link to get more involved, as Confucius suggested you should. Use it as a basis to build your own snake game!

Leave a Comment