- How to install GO.
- How to install fyne.
- Drawing Snake and Fruit on Fyne Canvas.
- Adding a timer to move the snake.
- Using Keys to Control direction.
** 1 How to install Go**
The GO can be downloaded and installed from the official Golang website https://go.dev/doc/install
In this tutorial i will be using a windows environment. I f you are on Mac or Linux please follow the tutorial given on the official golang docs
2 How to install Fyne
2.1. For those on Windows environments download and install the GCC compiler. If you are running on a different OS you need to follow this guide Fyne Getting Started.
2.2 Using command Line create directory myapp
mkdir myapp && cd myapp
2.3. In myapp directory Run the following command and replace MODULE_NAME with your preferred module name (this should be called in a new folder specific for your application).
go mod init MODULE_NAME
2.4. You now need to download the Fyne module. This will be done using the following command:
go get fyne.io/fyne/v2
2.5. To finish your module’s set up, you now need to tidy the module file to correctly reference Fyne as a dependency.
go mod tidy
3 Drawing Snake and Fruit on Fyne Canvas
3.1. Firstly, we will create a setup function that will build the game screen. We will call
this function setupGame and create list that we we populate.
3.2 By iterating through a loop of 10 elements (i from 0 to 9) and make a new Rectangle for each position. The elements are all created 10 x 20 in size and placed one above the other using the Move function. We
will add them all to the segment slice.
3.3 The return from this method is a container with no layout so that we can later use a manual layout for the visual elements:
func setupGame() *fyne.Container {
for i := 0; i < lenght; i++ {
seg := snakePart{9, float32(5 + i)}
snakeParts = append(snakeParts, seg)
r := canvas.NewRectangle(&color.RGBA{G: 0x66, A: 0xff})
r.Resize(fyne.NewSize(10, 20))
r.Move(fyne.NewPos(150, float32(500+i*20)))
segments = append(segments, r)
}
head = canvas.NewRectangle(&color.RGBA{G: 0x66, A: 0xff})
head.Resize(fyne.NewSize(10, 20))
head.Move(fyne.NewPos(snakeParts[0].x*50, snakeParts[0].y*50))
fruit = canvas.NewCircle(&color.RGBA{R: 0x66, A: 0xff})
fruit.Resize(fyne.NewSize(10, 10))
fruitPos= fyne.NewPos(snakeParts[0].x*20, snakeParts[0].y*20)
fruit.Move(fruitPos)
segments = append(segments, head)
segments = append(segments, fruit)
c:= fyne.NewContainerWithoutLayout(segments...)
return c
}
3.4. With the graphical setup code, we can wrap this in the usual application load code, this time passing the result of setupGame() to the SetContent function. As this game will not have dynamic sizing, we will call SetFixedSize(true) so that the window cannot be resized
func main() {
a := app.New()
w := a.NewWindow("Snake")
w.Resize(fyne.NewSize(800, 800))
w.SetFixedSize(false)
game = setupGame()
w.SetContent(game)
w.Canvas().SetOnTypedKey(keyTyped)
go runGame()
w.ShowAndRun()
}
4. Adding a timer to move the snake
The next step is to add some motion to the game. We will start with a simple timer that
repositions the snake on screen:
4.1. To help manage the game state, we will define a new type to store the x, y value of
each snake segment, named snakePart. We then make a slice that contains all of
the elements, and this is what we will update as the snake moves around the screen.
type snakePart struct {
x, y float32
}
var (
snakeParts []snakePart
game *fyne.Container
head *canvas.Rectangle
fruit *canvas.Circle
move = moveDown
segments []fyne.CanvasObject
lenght int = 20
fruitPos fyne.Position
)
4.2. We create a new function that will update the rectangles that we created earlier based on updated snake section information. To make sure that the game refreshes each time we move the snake, we need to move the rectangles and call ** Refresh()**.
func refreshGame() {
for i, seg := range snakeParts {
rect := game.Objects[i]
rect.Move(fyne.NewPos(seg.x*10, seg.y*20))
}
game.Refresh()
}
4.3. To run the main game loop, we need one more function that will use a timer of 250 milliseconds to move the snake. We call this function runGame.
4.4. To move the snake forward , we copy the position of each element from that of the element that is one segment further forward, working from the tail to the head.
5. Using keys to control direction
5.1. To start with, we define a new type (moveType) that will be used to describe the
next direction in which to move. The move variable is then defined to hold the next move direction:
type moveType int
const (
moveUp moveType = iota
moveDown
moveLeft
moveRight
)
var move = moveUp
5.2. Create a new keyTyped function as follows that will perform the keyboard
mapping:
func keyTyped(e *fyne.KeyEvent) {
switch e.Name {
case fyne.KeyUp:
move = moveUp
case fyne.KeyDown:
move = moveDown
case fyne.KeyLeft:
move = moveLeft
case fyne.KeyRight:
move = moveRight
}
}
5.3. For the key events to be triggered we use the SetOnKeyTyped() function on the window's canvas.
w.Canvas().SetOnTypedKey(keyTyped)
5.4. Lastly, the code moves the head to a new position, in this case further up the screen (by using snakeParts[0].y-1). Refer to the following function:
5.5. We set up our animations inside the for loop in the runGame function, before the
timer pause
func runGame() {
nextPart := snakePart{snakeParts[0].x, snakeParts[0].y - 1}
for {
oldPos := fyne.NewPos(snakeParts[0].x*10, snakeParts[0].y*10)
newPos := fyne.NewPos(nextPart.x*10, nextPart.y*10)
canvas.NewPositionAnimation(oldPos, newPos, time.Millisecond*250, func(p fyne.Position) {
head.Move(p)
canvas.Refresh(head)
}).Start()
end := len(snakeParts) - 1
canvas.NewPositionAnimation(fyne.NewPos(snakeParts[end].x*10, snakeParts[end].y*10),
fyne.NewPos(snakeParts[end-1].x*10, snakeParts[end-1].y*10), time.Millisecond*250,
func(p fyne.Position) {
tail := game.Objects[end]
tail.Move(p)
canvas.Refresh(tail)
}).Start()
time.Sleep(time.Millisecond * 250)
for i := len(snakeParts) - 1; i >= 1; i-- {
snakeParts[i] = snakeParts[i-1]
}
snakeParts[0] = nextPart //SetUp the new head position
refreshGame()
}
}
5.6. After the refresh line, we need to update the movement calculations to set up nextPart ready for the next movement:
switch move {
case moveUp:
nextPart = snakePart{nextPart.x,
nextPart.y - 1}
case moveDown:
nextPart = snakePart{nextPart.x,
nextPart.y + 1}
case moveLeft:
nextPart = snakePart{nextPart.x - 1,
nextPart.y}
case moveRight:
nextPart = snakePart{nextPart.x + 1,
nextPart.y}
}
Top comments (1)
Interesting, thanks for sharing!