Game development with Go and the Fyne framework
Programming Snapshot – Game Development
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.
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].
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
physics.go
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
plot.go
01 package main 02 import ( 03 "gonum.org/v1/plot" 04 "gonum.org/v1/plot/plotter" 05 "gonum.org/v1/plot/plotutil" 06 "gonum.org/v1/plot/vg" 07 "math" 08 ) 09 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 } 29 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 }
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.
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!
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
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.
-
Juno Tab 3 Launches with Ubuntu 24.04
Anyone looking for a full-blown Linux tablet need look no further. Juno has released the Tab 3.
-
New KDE Slimbook Plasma Available for Preorder
Powered by an AMD Ryzen CPU, the latest KDE Slimbook laptop is powerful enough for local AI tasks.
-
Rhino Linux Announces Latest "Quick Update"
If you prefer your Linux distribution to be of the rolling type, Rhino Linux delivers a beautiful and reliable experience.
-
Plasma Desktop Will Soon Ask for Donations
The next iteration of Plasma has reached the soft feature freeze for the 6.2 version and includes a feature that could be divisive.
-
Linux Market Share Hits New High
For the first time, the Linux market share has reached a new high for desktops, and the trend looks like it will continue.
-
LibreOffice 24.8 Delivers New Features
LibreOffice is often considered the de facto standard office suite for the Linux operating system.
-
Deepin 23 Offers Wayland Support and New AI Tool
Deepin has been considered one of the most beautiful desktop operating systems for a long time and the arrival of version 23 has bolstered that reputation.