Game development with Go and the Fyne framework

Programming Snapshot – Game Development

Article from Issue 255/2022

We all know that the Fyne framework for Go can be used to create GUIs for the desktop, but you can also write games with it. Mike Schilli takes on a classic from the soccer field.

The European soccer championship a year ago was quite a flop for Germany, with what used to be a World Cup-winning squad, but one scene from the Czech Republic's match against Scotland still sticks in my mind. The Scots goalkeeper had run far out of the goal, which Czech player Patrik Schick noticed while hovering at the halfway line. Schick quickly fired the ball into the out-of-bounds goalkeeper's goal with an eye-catching arcing shot. Since then, I've been trying to replicate this feat in my position as striker for the amateur team "Beer Fit" in San Francisco, though without any success so far. This is what prompted me to turn this into a video game written in Go for my Programming Snapshot column.

The underlying physics for the chip shot [1] in soccer is known as "projectile motion," and it's described in any good undergrad physics book. I happen to know this exactly because during my electrical engineering studies I sweated my way through many an exam in the murderous "Technical Mechanics" course. And even many, many years later, holding a totally yellowed degree certificate in my trembling hands, I only needed a short refresher to derive the formulas for the ball position as a function of the starting point, the angle and the velocity of the launch, and the elapsed time.

The trajectory of the soccer ball sailing over the head of the hapless goalkeeper in a high arc into the net behind is by no means the only application of these mechanical principles (Figure 1). The same long-established formula also calculates the trajectories of ballistic projectiles, from cannonballs to short-range missiles.

Figure 1: Classic chip shot: The ball flies over the keeper's head and drops into the goal.

First Approximation

To keep the equations (Figure 2) simple for the shot's trajectory in X/Y coordinates as a function of elapsed time, the in-game implementation only takes into account the gravity that brings the ball back to earth on its arc trajectory, in addition to the launch angle and the initial velocity with which the attacker kicks the ball into the air. It neglects the air drag on the ball in the atmosphere. This could be incorporated with different flow models, but then any prevailing headwind or tailwind would also need to be considered, along with atmospheric conditions such as fog or drizzle. Therefore, the program simply assumes that the ball is flying in a vacuum – after all, it's all about the concept and not 100 percent accuracy [2].

Figure 2: Equation to calculate X/Y coordinates on the throwing parabola, as a function of the elapsed time. © Wikipedia

Listing 1 [3] molds the math into Go code and puts it into the chipShot() function, which expects the launch speed, the angle in radiant format, and the elapsed time in seconds as input parameters. It returns the position of the ball on the arc path at the given time as X and Y coordinates. Because the formula for the Y-coordinate on the trajectory is happy to return negative values, but the Earth's surface does not allow a soccer ball to go underground, line 10 sets the height value to zero as soon as the flight parabola assumes negative values.

Listing 1


01 package main
02 import (
03   "math"
04 )
05 func chipShot(v float64, a float64, t float64) (float64, float64) {
06   const g = 9.81
07   x := v * t * math.Cos(a)
08   y := v*t*math.Sin(a) - g/2*t*t
09   if y < 0 {
10     y = 0
11   }
12   return x, y
13 }

For testing purposes, Listing 2 uses the Go standard plotter package plot to draw the ball's flight path, with various initial parameters in an X/Y coordinate system, and generates a PNG file. Figure 3 shows the ball's trajectory after launch with a velocity of 10 meters per second (m/s) and at an angle of attack of 45 degrees. If the striker applies a little more force to the kick, and the ball takes off at 15m/s at the same angle, it correspondingly flies higher into the air and also covers a greater distance before coming back to earth. The launch angle is defined by lines 16 through 19 in Listing 2, respectively, in radians rather than degrees, just like the sine and cosine functions from the math package in Go expect it to. Because 180 degrees corresponds to the value of pi, the function only has to calculate the corresponding fractions. This means that 45 degrees becomes a quarter of pi and 30 degrees becomes a sixth of pi.

Listing 2


01 package main
02 import (
03   ""
04   ""
05   ""
06   ""
07   "math"
08 )
10 func main() {
11   p := plot.New()
12   p.Title.Text = "Projectile Motion"
13   p.X.Label.Text = "X"
14   p.Y.Label.Text = "Y"
15   err := plotutil.AddLinePoints(p,
16     "v=10/a=45", shoot(10, math.Pi/4),
17     "v=15/a=45", shoot(15, math.Pi/4),
18     "v=10/a=60", shoot(10, math.Pi/3),
19     "v=10/a=30", shoot(10, math.Pi/6),
20   )
21   if err != nil {
22     panic(err)
23   }
24   err = p.Save(8*vg.Inch, 8*vg.Inch, "curve.png")
25   if err != nil {
26     panic(err)
27   }
28 }
30 func shoot(v float64, a float64) plotter.XYs {
31   n := 20
32   pts := make(plotter.XYs, n)
33   t := 0.0
34   for i := range pts {
35     pts[i].X, pts[i].Y = chipShot(v, a, t)
36     t += 0.25
37   }
38   return pts
39 }
Figure 3: The ball's trajectory generated by the code in Listing 2.

For each graph, the shoot() function implemented in Listing 2 starting at line 30 defines 20 time points at 0.25-second intervals. It uses chipShot() from Listing 1 to calculate the X/Y coordinates of the current ball position and stores the measurement points in a plotter.XYs type array named pts. The function passes this array back to the main program at the end of the for loop. The main program then uses the AddLinePoints() function to pass the data to the plotter. It draws four of these datasets into the same chart as curves into the coordinate system, complete with a legend, until Save() in line 24 saves the graphic as an eight-by-eight-inch PNG file.

Gamify It

The remaining listings in this issue turn the physics of ballistic trajectory into a desktop game named Chipshot. In Figure 4, the game is in full swing and the user has set a ball launch velocity of 15m/s with the top slider and a launch angle of 45 degrees with the bottom slider. The goalkeeper is symbolized by the salmon-colored rectangle bottom center and the soccer goal by the green rectangle further to the right.

Figure 4: In this simulation, the ball flies over the keeper's head into the goal.

With parameters set by the sliders, the user presses the Shoot button in the upper left corner with the mouse, and the ball flies just over the goalkeeper and rolls into the goal with its last ounce of energy. But this doesn't always work. For example, Figure 5 shows an attempt where the ball flies over the keeper but then dies on the way to the goal because it doesn't have enough kinetic energy and just rolls to a stop due to friction after hitting the ground. Finally, in Figure 6, the ball comes down too soon, and the goalkeeper catches it. Game over!

Figure 5: Not kicked hard enough: The ball dies on its way to the goal.
Figure 6: Too short: The keeper fields the ball. Game over!

Simple video games of this kind first made their way into amusement arcades in the 1980s. They were rolled out in giant wooden boxes with built-in screens. Users fed small change into the coin slots for the fun of playing the built-in game for a few minutes, using a joystick and fire button. Younger readers are rubbing their eyes in disbelief and raise the question if these poor people didn't have PlayStation consoles at home! The strategies behind arcade games and information on creating the software that powered them are described in Classic Game Design by Franz Lanzinger [4], a pioneer of this technology.

According to Lanzinger, he once discovered an arcade game with the then-popular Crystal Castles [5] video game in an amusement park in Santa Cruz, Calif. With the help of the secret combination of the two fire buttons known to him, he found out that the visitors to the amusement park had put no less than 100,000 quarters in the machine during its lifetime. Extrapolated to the number of 5,000 machines produced at the time, this resulted in (quite optimistically estimated) total revenues for the game of $100 million.

From Frame to Frame

What all of these 2D video games have in common is that the computer calculates and displays the more or less smoothly displayed movements several times per second in what are known as frames. Video games are usually based on ready-made engines that provide the display functionality. They grant the application access to the game events by triggering a callback function for each frame, in which the game programmer then pushes the game characters forward or checks whether they have collided with any obstacles in the game.

The human brain playfully keeps track of a complex situation like this onscreen so that we immediately notice whether the ball in the video game field is heading for the goalkeeper or the goal. A software program, on the other hand, is dependent on repeatedly testing in each game frame whether the ball has actually already hit one of the monitored objects. The program can do this amazingly quickly, and that's why it looks like it has pattern recognition capabilities similar to those that humans have – but of course, this process is based on an illusion.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Treasure Hunt

    A geolocation guessing game based on the popular Wordle evaluates a player's guesses based on the distance from and direction to the target location. Mike Schilli turns this concept into a desktop game in Go using the photos from his private collection.

  • Team Spirit

    Instead of the coach determining the team lineup, an algorithm selects the players based on their strengths for Mike Schilli's amateur soccer team.

  • Straight to the Point

    With the Fyne framework, Go offers an easy-to-use graphical interface for all popular platforms. As a sample application, Mike uses an algorithm to draw arrows onto images.

  • Gaming

    Try your luck with Rocket League, Fear Equation, and Master of Orion.

  • GUI Apps with Fyne

    The Fyne toolkit offers a simple way to build native apps that work across multiple platforms. We show you how to build a to-do list app to demonstrate Fyne's power.

comments powered by Disqus