用Golang写一款小游戏

写一个用Golang运行的小游戏打发时间---贪吃蛇
为了实现可视化,我使用了Go的GUI库fyne

思路:

定义一个结构体Snake表示贪吃蛇,包含贪吃蛇的长度、方向、身体坐标等属性。
定义一个结构体Game表示游戏状态,包含贪吃蛇、食物、得分等属性。
实现游戏画面的显示,使用fyne库中的Canvas组件,绘制贪吃蛇和食物的图形。
实现键盘事件的监听,根据不同的按键来改变贪吃蛇的方向。
实现贪吃蛇的移动逻辑,每个时间间隔更新贪吃蛇的位置,判断是否吃到食物或碰到墙壁或自己的身体,更新得分并重新生成食物。
完成游戏结束的处理,当贪吃蛇碰到墙壁或自己的身体时,游戏结束,显示得分和重新开始按钮。

代码:

package main

import (
	"fmt"
	"image/color"
	"math/rand"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"
)

const (
	cellSize   = 20 // 每个格子的大小
	gridWidth  = 30 // 游戏区域的宽度(格子数)
	gridHeight = 20 // 游戏区域的高度(格子数)
)

var (
	directions = []fyne.Key{
		fyne.KeyUp,
		fyne.KeyDown,
		fyne.KeyLeft,
		fyne.KeyRight,
	}
)

// Snake表示贪吃蛇
type Snake struct {
	Body       []fyne.Position // 身体坐标
	Length     int             // 长度
	Direction  fyne.Key        // 方向
	lastMoveAt time.Time       // 上次移动时间
}

// NewSnake创建一个新的贪吃蛇
func NewSnake() Snake {
	head := fyne.NewPos(cellSize*gridWidth/2, cellSize*gridHeight/2)
	return Snake{
		Body:       []fyne.Position{head},
		Length:     1,
		Direction:  fyne.KeyRight,
		lastMoveAt: time.Now(),
	}
}

// Move移动贪吃蛇
func (s *Snake) Move() {
	if time.Since(s.lastMoveAt) < 100*time.Millisecond {
		return
	}

	head := s.Body[0]
	var newHead fyne.Position

	switch s.Direction {
	case fyne.KeyUp:
		newHead = fyne.NewPos(head.X, head.Y-cellSize)
	case fyne.KeyDown:
		newHead = fyne.NewPos(head.X, head.Y+cellSize)
	case fyne.KeyLeft:
		newHead = fyne.NewPos(head.X-cellSize, head.Y)
	case fyne.KeyRight:
		newHead = fyne.NewPos(head.X+cellSize, head.Y)
	}

	s.Body = append([]fyne.Position{newHead}, s.Body[:s.Length-1]...)
	s.lastMoveAt = time.Now()
}

// EatFood吃掉食物
func (s *Snake) EatFood() {
	s.Length++
	s.Body = append([]fyne.Position{s.Body[0]}, s.Body...)
}

// CollideWith自己的身体
func (s *Snake) CollideWithSelf() bool {
	head := s.Body[0]
	for _, b := range s.Body[1:] {
		if head == b {
			return true
		}
	}
	return false
}

// CollideWithWall撞到墙壁
func (s *Snake) CollideWithWall() bool {
	head := s.Body[0]
	return head.X < 0 || head.X >= cellSize*gridWidth || head.Y < 0 || head.Y >= cellSize*gridHeight
}

// Game表示游戏状态
type Game struct {
	Snake   Snake           // 贪吃蛇
	Food    fyne.Position  // 食物坐标
	Score   int             // 得分
	Over    bool            // 游戏是否结束
	WinSize fyne.Size      // 窗口大小
}

// NewGame创建一个新的游戏状态
func NewGame(winSize fyne.Size) *Game {
	return &Game{
		Snake:   NewSnake(),
		Food:    fyne.NewPos(rand.Intn(gridWidth)*cellSize, rand.Intn(gridHeight)*cellSize),
		WinSize: winSize,
	}
}

// Update更新游戏状态
func (g *Game) Update() {
	if g.Over {
		return
	}

	g.Snake.Move()

	if g.Snake.CollideWithSelf() || g.Snake.CollideWithWall() {
		g.Over = true
		return
	}

	if g.Snake.Body[0] == g.Food {
		g.Snake.EatFood()
		g.Food = fyne.NewPos(rand.Intn(gridWidth)*cellSize, rand.Intn(gridHeight)*cellSize)
		g.Score++
	}
}

// Draw绘制游戏画面
func (g *Game) Draw() fyne.CanvasObject {
	snakeColor := color.RGBA{0, 255, 0, 255}
	foodColor := color.RGBA{255, 0, 0, 255}

	objects := []fyne.CanvasObject{}

	// 绘制贪吃蛇
	for _, b := range g.Snake.Body {
		objects = append(objects, canvas.NewRectangle(snakeColor).SetMinSize(fyne.NewSize(cellSize, cellSize)).SetPosition(b))
	}

	// 绘制食物
	objects = append(objects, canvas.NewCircle(foodColor, cellSize/2).SetPosition(g.Food).SetMinSize(fyne.NewSize(cellSize, cellSize)))

	// 绘制得分
	scoreLabel := widget.NewLabel(fmt.Sprintf("Score: %d", g.Score))
	scoreLabel.Move(fyne.NewPos(g.WinSize.Width-scoreLabel.MinSize().Width-10, 10))
	objects = append(objects, scoreLabel)

	return container.NewWithoutLayout(objects...)
}

// SetDirection设置贪吃蛇的方向
func (g *Game) SetDirection(direction fyne.Key) {
	if direction == fyne.KeyReturn && g.Over {
		*g = *NewGame(g.WinSize)
		return
	}
	if contains(directions, direction) {
		g.Snake.Direction = direction
	}
}

// contains检查是否包含指定元素
func contains(keys []fyne.Key, key fyne.Key) bool {
	for _, k := range keys {
		if k == key {
			return true
		}
	}
	return false
}

在这个游戏状态中,我们定义了贪吃蛇和食物的坐标、得分、游戏是否结束以及窗口大小。我们还定义了NewGame函数,用于创建一个新的游戏状态,并在其中创建一个新的贪吃蛇和一个随机的食物。我们还定义了Update函数和Draw函数,用于绘制游戏界面,并将贪吃蛇、食物和得分标签添加到画布对象中。最后,我们还定义了SetDirection函数,用于设置贪吃蛇的移动方向,并在游戏结束后通过按下回车键来重新开始游戏

现在我们需要在main函数中创建一个游戏对象,并将其传递给fyne框架,让它处理游戏的渲染和事件

func main() {
	// 创建一个新的游戏对象
	game := NewGame(fyne.NewSize(gridWidth*cellSize, gridHeight*cellSize))

	// 创建一个窗口
	app := app.New()
	window := app.NewWindow("Snake Game")
	window.Resize(game.WinSize)

	// 设置事件处理程序
	window.SetContent(canvas.NewRasterWithPixels(func(w, h int) []byte {
		img := image.NewRGBA(image.Rect(0, 0, w, h))
		draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)

		// 绘制游戏画面
		canvasObj := game.Draw()
		canvasObj.Paint(img)

		return img.Pix
	}))

	// 监听按键事件
	window.Canvas().SetOnKeyDown(func(event *fyne.KeyEvent) {
		game.SetDirection(event.Name)
	})

	// 定期更新游戏状态
	go func() {
		for {
			time.Sleep(time.Second / fps)
			game.Update()
			window.Canvas().Refresh(game.Draw)
		}
	}()

	// 显示窗口
	window.ShowAndRun()
}

在这个main函数中,我们首先创建一个新的游戏对象,并将窗口的大小设置为网格的大小乘以每个单元格的大小。然后我们创建一个fyne的窗口对象,并将其内容设置为一个用于绘制游戏界面的函数。这个函数将在每个帧中被调用,并使用game.Draw函数返回的画布对象来渲染游戏界面。

我们还设置了一个按键事件处理程序,它将接收用户按下的键,并使用game.SetDirection函数来更新贪吃蛇的移动方向。最后,我们使用一个无限循环来定期更新游戏状态并重新绘制游戏界面,然后调用window.ShowAndRun()函数来显示窗口并开始运行游戏。

这就是一个简单的贪吃蛇游戏的实现。由于篇幅限制,这里只是提供了一个基本的实现,并未对游戏进行优化或添加更多的功能。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李 忘 忧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值