Kotlin 贪食蛇游戏教程展示了如何使用 Swing 在 Kotlin 中创建蛇游戏。
源代码和图像可从作者的 Github Kotlin-Snake-Game 存储库中获得。
贪食蛇
贪食蛇是较旧的经典视频游戏。 它最初是在 70 年代后期创建的。 后来它被带到 PC 上。 在这个游戏中,玩家控制蛇。 游戏的目的是尽可能多地吃苹果。 当蛇吃了一个苹果时,它的身体就长了。 蛇必须避开墙壁和自己的身体。 该游戏有时称为 Nibbles 。
Swing
Swing 是 Java 编程语言的主要 GUI 工具包。 它是 JFC(Java 基础类)的一部分,JFC 是用于为 Java 程序提供图形用户界面的 API。
Kotlin 贪食蛇游戏
蛇的每个关节的大小为 10 像素。 蛇由光标键控制。 最初,蛇具有三个关节。 如果游戏结束,则在面板中间显示"Game Over"消息。
Board.kt
package com.zetcodeimport java.awt.*import java.awt.event.ActionEventimport java.awt.event.ActionListenerimport java.awt.event.KeyAdapterimport java.awt.event.KeyEventimport javax.swing.ImageIconimport javax.swing.JPanelimport javax.swing.Timerclass Board : JPanel(), ActionListener {private val boardWidth = 300private val boardHeight = 300private val dotSize = 10private val allDots = 900private val randPos = 29private val delay = 140private val x = IntArray(allDots)private val y = IntArray(allDots)private var nOfDots: Int = 0private var appleX: Int = 0private var appleY: Int = 0private var leftDirection = falseprivate var rightDirection = trueprivate var upDirection = falseprivate var downDirection = falseprivate var inGame = trueprivate var timer: Timer? = nullprivate var ball: Image? = nullprivate var apple: Image? = nullprivate var head: Image? = nullinit {addKeyListener(TAdapter())background = Color.blackisFocusable = truepreferredSize = Dimension(boardWidth, boardHeight)loadImages()initGame()}private fun loadImages() {val iid = ImageIcon("src/main/resources/dot.png")ball = iid.imageval iia = ImageIcon("src/main/resources/apple.png")apple = iia.imageval iih = ImageIcon("src/main/resources/head.png")head = iih.image}private fun initGame() {nOfDots = 3for (z in 0 until nOfDots) {x[z] = 50 - z * 10y[z] = 50}locateApple()timer = Timer(delay, this)timer!!.start()}public override fun paintComponent(g: Graphics) {super.paintComponent(g)doDrawing(g)}private fun doDrawing(g: Graphics) {if (inGame) {g.drawImage(apple, appleX, appleY, this)for (z in 0 until nOfDots) {if (z == 0) {g.drawImage(head, x[z], y[z], this)} else {g.drawImage(ball, x[z], y[z], this)}}Toolkit.getDefaultToolkit().sync()} else {gameOver(g)}}private fun gameOver(g: Graphics) {val msg = "Game Over"val small = Font("Helvetica", Font.BOLD, 14)val fontMetrics = getFontMetrics(small)val rh = RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)rh[RenderingHints.KEY_RENDERING] = RenderingHints.VALUE_RENDER_QUALITY(g as Graphics2D).setRenderingHints(rh)g.color = Color.whiteg.font = smallg.drawString(msg, (boardWidth - fontMetrics.stringWidth(msg)) / 2,boardHeight / 2)}private fun checkApple() {if (x[0] == appleX && y[0] == appleY) {nOfDots++locateApple()}}private fun move() {for (z in nOfDots downTo 1) {x[z] = x[z - 1]y[z] = y[z - 1]}if (leftDirection) {x[0] -= dotSize}if (rightDirection) {x[0] += dotSize}if (upDirection) {y[0] -= dotSize}if (downDirection) {y[0] += dotSize}}private fun checkCollision() {for (z in nOfDots downTo 1) {if (z > 4 && x[0] == x[z] && y[0] == y[z]) {inGame = false}}if (y[0] >= boardHeight) {inGame = false}if (y[0] < 0) {inGame = false}if (x[0] >= boardWidth) {inGame = false}if (x[0] < 0) {inGame = false}if (!inGame) {timer!!.stop()}}private fun locateApple() {var r = (Math.random() * randPos).toInt()appleX = r * dotSizer = (Math.random() * randPos).toInt()appleY = r * dotSize}override fun actionPerformed(e: ActionEvent) {if (inGame) {checkApple()checkCollision()move()}repaint()}private inner class TAdapter : KeyAdapter() {override fun keyPressed(e: KeyEvent?) {val key = e!!.keyCodeif (key == KeyEvent.VK_LEFT && !rightDirection) {leftDirection = trueupDirection = falsedownDirection = false}if (key == KeyEvent.VK_RIGHT && !leftDirection) {rightDirection = trueupDirection = falsedownDirection = false}if (key == KeyEvent.VK_UP && !downDirection) {upDirection = truerightDirection = falseleftDirection = false}if (key == KeyEvent.VK_DOWN && !upDirection) {downDirection = truerightDirection = falseleftDirection = false}}}}
首先,我们将定义游戏中使用的属性。
private val boardWidth = 300private val boardHeight = 300private val dotSize = 10private val allDots = 900private val randPos = 29private val delay = 140
boardWidth和boardHeight属性确定电路板的大小。 dotSize是苹果的大小和蛇的点。 allDots属性定义了板上可能的最大点数(900 = (300 * 300) / (10 * 10))。 randPos属性用于计算苹果的随机位置。 delay属性确定游戏的速度。
private val x = IntArray(allDots)private val y = IntArray(allDots)
这两个数组存储蛇的所有关节的x和y坐标。
private fun loadImages() {val iid = ImageIcon("src/main/resources/dot.png")ball = iid.imageval iia = ImageIcon("src/main/resources/apple.png")apple = iia.imageval iih = ImageIcon("src/main/resources/head.png")head = iih.image}
在loadImages()方法中,我们获得了游戏的图像。 ImageIcon类用于显示 PNG 图像。
private fun initGame() {nOfDots = 3for (z in 0 until nOfDots) {x[z] = 50 - z * 10y[z] = 50}locateApple()timer = Timer(delay, this)timer!!.start()}
在initGame()方法中,我们创建蛇,在板上随机放置一个苹果,然后启动计时器。
private fun doDrawing(g: Graphics) {if (inGame) {g.drawImage(apple, appleX, appleY, this)for (z in 0 until nOfDots) {if (z == 0) {g.drawImage(head, x[z], y[z], this)} else {g.drawImage(ball, x[z], y[z], this)}}Toolkit.getDefaultToolkit().sync()} else {gameOver(g)}}
在doDrawing()方法中,我们绘制了苹果和蛇对象。 如果游戏结束,我们将根据消息绘制游戏。
private fun gameOver(g: Graphics) {val msg = "Game Over"val small = Font("Helvetica", Font.BOLD, 14)val fontMetrics = getFontMetrics(small)val rh = RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)rh[RenderingHints.KEY_RENDERING] = RenderingHints.VALUE_RENDER_QUALITY(g as Graphics2D).setRenderingHints(rh)g.color = Color.whiteg.font = smallg.drawString(msg, (boardWidth - fontMetrics.stringWidth(msg)) / 2,boardHeight / 2)}
gameOver()方法在窗口中间绘制"Game Over"消息。 我们使用渲染提示来平滑地绘制消息。 我们使用字体指标来获取消息的大小。
private fun checkApple() {if (x[0] == appleX && y[0] == appleY) {nOfDots++locateApple()}}
如果苹果与头部碰撞,我们会增加蛇的关节数。 我们称locateApple()方法为随机放置一个新的Apple对象。
在move()方法中,我们有游戏的关键算法。 要了解它,请看一下蛇是如何运动的。 我们控制蛇的头。 我们可以使用光标键更改其方向。 其余关节在链上向上移动一个位置。 第二关节移动到第一个关节的位置,第三关节移动到第二个关节的位置,依此类推。
for (z in nOfDots downTo 1) {x[z] = x[z - 1]y[z] = y[z - 1]}
该代码将关节向上移动。
if (leftDirection) {x[0] -= dotSize}
这条线将头向左移动。
在checkCollision()方法中,我们确定蛇是否击中了自己或撞墙之一。
for (z in nOfDots downTo 1) {if (z > 4 && x[0] == x[z] && y[0] == y[z]) {inGame = false}}
如果蛇用头撞到其关节之一,则游戏结束。
if (y[0] >= boardHeight) {inGame = false}
如果蛇击中了棋盘的底部,则游戏结束。
Snake.kt
package com.zetcodeimport java.awt.EventQueueimport javax.swing.JFrameclass Snake : JFrame() {init {initUI()}private fun initUI() {add(Board())title = "Snake"isResizable = falsepack()setLocationRelativeTo(null)defaultCloseOperation = JFrame.EXIT_ON_CLOSE}companion object {@JvmStaticfun main(args: Array<String>) {EventQueue.invokeLater {val ex = Snake()ex.isVisible = true}}}}
这是主要的类。
isResizable = falsepack()
isResizable属性会影响某些平台上JFrame容器的插入。 因此,在pack()方法之前调用它很重要。 否则,蛇的头部与右边界和底边界的碰撞可能无法正常进行。

图:贪食蛇
这是 Kotlin 和 Swing 中的贪食蛇游戏。 您可能也对相关教程感兴趣: Kotlin Swing 教程, Kotlin 读取文件教程和 Kotlin 写入文件教程。
