水平方向と垂直方向の座標値を記録します。
package cn.xeblog.snake.model; import java.util.Objects; /** * 坐标 * * @author anlingyi * @date 2022/8/2 3:35 PM */ public class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Point point = (Point) o; return x == point.x && y == point.y; } @Override public int hashCode() { return Objects.hash(x, y); } @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } }移動方向 Direction.java
上、下、左、右の 4 つの移動方向の列挙を提供します。
package cn.xeblog.snake.model; /** * @author anlingyi * @date 2022/8/2 5:25 PM */ public enum Direction { UP, DOWN, LEFT, RIGHT }SNAKESnake.java
ヘビの体の座標情報を保存し、ヘビの体の動きを提供し、ヘビの尾の座標を削除し、ヘビの頭、ヘビの尾の座標、ヘビの体の長さを取得します。など。
ヘビの移動の実装原理について説明します:
ゲームが開始されると、移動方向は固定され、ヘビは常にこの方向に移動します。方向キーで蛇を動かします。実際に移動するということは、蛇の体の座標を移動することを意味します。たとえば、蛇の体の長さ (蛇の頭を除く) は 6 です。移動するときは、蛇の体の座標を で移動するだけです。位置 5 の座標を位置 6 の座標に、位置 4 の蛇の体の長さを位置 5 の座標に移動するには、簡単に言うと、前の座標を後ろの座標に変更することです。ヘビの体の動き ヘビの頭の動きは別途計算する必要がある 上下方向に動く場合は y の動き 座標の加減算では、上向きの動きは高さの 1 つを引く必要がある一方、下方向に移動するには、ヘビの体の 1 つの高さを追加する必要があります。同様のことが、左右の動きにも当てはまります。package cn.xeblog.snake.model;
import java.util.List;
/**
* 蛇
*
* @author anlingyi
* @date 2022/8/2 3:32 PM
*/
public class Snake {
public static int width = 10;
public static int height = 10;
/**
* 蛇身坐标列表
*/
public List<Point> body;
public Snake(List<Point> body) {
this.body = body;
}
/**
* 添加蛇身坐标
*
* @param x
* @param y
*/
public void add(int x, int y) {
this.body.add(new Point(x * width, y * height));
}
/**
* 移除蛇尾坐标
*/
public void removeLast() {
int size = size();
if (size == 0) {
return;
}
this.body.remove(size - 1);
}
/**
* 获取蛇头坐标
*
* @return
*/
public Point getHead() {
if (size() > 0) {
return this.body.get(0);
}
return null;
}
/**
* 获取蛇尾坐标
*
* @return
*/
public Point getTail() {
int size = size();
if (size > 0) {
return this.body.get(size - 1);
}
return null;
}
/**
* 蛇身长度
*
* @return
*/
public int size() {
return this.body.size();
}
/**
* 蛇移动
*
* @param direction 移动方向
*/
public void move(Direction direction) {
if (size() == 0) {
return;
}
for (int i = this.size() - 1; i > 0; i--) {
// 从蛇尾开始向前移动
Point point = this.body.get(i);
Point nextPoint = this.body.get(i - 1);
point.x = nextPoint.x;
point.y = nextPoint.y;
}
// 蛇头移动
Point head = getHead();
switch (direction) {
case UP:
head.y -= height;
break;
case DOWN:
head.y += height;
break;
case LEFT:
head.x -= width;
break;
case RIGHT:
head.x += width;
break;
}
}
}
「ピル」の座標と型情報を格納します。
package cn.xeblog.snake.model; /** * 药丸 * * @author anlingyi * @date 2022/8/2 4:49 PM */ public class Pill { public static int width = 10; public static int height = 10; /** * 坐标 */ public Point point; /** * 药丸类型 */ public PillType pillType; public enum PillType { /** * 红色药丸 */ RED(5), /** * 蓝色药丸 */ BLUE(2), /** * 绿色药丸 */ GREEN(1), ; /** * 分数 */ public int score; PillType(int score) { this.score = score; } } public Pill(int x, int y, PillType pillType) { this.point = new Point(x * width, y * height); this.pillType = pillType; } }
ゲーム インターフェイス
ゲームの開始を検知すると、ヘビと「錠剤」の位置情報を初期化し、タイマーをスタートさせてヘビが移動できるようにゲーム画面を時々再描画します。
ヘビに噛まれたり、壁にぶつかったりしたことが検出されると、ゲームのステータスが「ゲーム失敗」となり、ゲーム終了画面が表示され、タイマーが停止します。スネーク本体が 0 の場合、ゲームは終了します。ゲームのステータスは「ゲーム勝利」とマークされます。
package cn.xeblog.snake.ui; import cn.xeblog.snake.model.Direction; import cn.xeblog.snake.model.Pill; import cn.xeblog.snake.model.Point; import cn.xeblog.snake.model.Snake; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.Collections; import java.util.Random; /** * @author anlingyi * @date 2022/8/2 3:51 PM */ public class SnakeGameUI extends JPanel implements ActionListener { /** * 宽度 */ private int width; /** * 高度 */ private int height; /** * 蛇 */ private Snake snake; /** * 药丸 */ private Pill pill; /** * 移动方向 */ private Direction direction; /** * 停止游戏标记 */ private boolean stop; /** * 游戏状态 0.初始化 1.游戏胜利 2.游戏失败 */ private int state = -1; /** * 定时器 */ private Timer timer; /** * 移动速度 */ private int speed = 100; /** * 分数 */ private int score; /** * 特殊药丸列表 */ private ArrayList<Pill.PillType> specialPill; public SnakeGameUI(int width, int height) { this.width = width; this.height = height; this.timer = new Timer(speed, this); this.stop = true; initPanel(); } /** * 初始化 */ private void init() { this.score = 0; this.state = 0; this.stop = true; this.timer.setDelay(speed); initSnake(); initPill(); generatePill(); repaint(); } /** * 初始化游戏面板 */ private void initPanel() { this.setPreferredSize(new Dimension(this.width, this.height)); this.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (stop && e.getKeyCode() != KeyEvent.VK_SPACE) { return; } switch (e.getKeyCode()) { case KeyEvent.VK_UP: if (direction == Direction.DOWN) { break; } direction = Direction.UP; break; case KeyEvent.VK_DOWN: if (direction == Direction.UP) { break; } direction = Direction.DOWN; break; case KeyEvent.VK_LEFT: if (direction == Direction.RIGHT) { break; } direction = Direction.LEFT; break; case KeyEvent.VK_RIGHT: if (direction == Direction.LEFT) { break; } direction = Direction.RIGHT; break; case KeyEvent.VK_SPACE: if (state != 0) { init(); } stop = !stop; if (!stop) { timer.start(); } break; } } }); } /** * 初始化蛇 */ private void initSnake() { this.direction = Direction.LEFT; int maxX = this.width / Snake.width; int maxY = this.height / Snake.height; this.snake = new Snake(new ArrayList<>()); this.snake.add(maxX - 2, 3); this.snake.add(maxX - 1, 3); this.snake.add(maxX - 1, 2); this.snake.add(maxX - 1, 1); for (int i = maxX - 1; i > 0; i--) { this.snake.add(i, 1); } for (int i = 1; i < maxY - 1; i++) { this.snake.add(1, i); } for (int i = 1; i < maxX - 1; i++) { this.snake.add(i, maxY - 2); } } /** * 初始化药丸 */ private void initPill() { this.specialPill = new ArrayList<>(); for (int i = 0; i < 5; i++) { this.specialPill.add(Pill.PillType.RED); } for (int i = 0; i < 10; i++) { this.specialPill.add(Pill.PillType.BLUE); } Collections.shuffle(specialPill); } /** * 生成药丸 */ private void generatePill() { // 是否获取特殊药丸 boolean getSpecialPill = new Random().nextInt(6) == 3; Pill.PillType pillType; if (getSpecialPill && this.specialPill.size() > 0) { // 生成特殊药丸 int index = new Random().nextInt(this.specialPill.size()); pillType = this.specialPill.get(index); this.specialPill.remove(index); } else { // 生成绿色药丸 pillType = Pill.PillType.GREEN; } // 随机坐标 int x = new Random().nextInt(this.width / Pill.width - 1); int y = new Random().nextInt(this.height / Pill.height - 1); this.pill = new Pill(x, y, pillType); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(new Color(66, 66, 66)); g2.fillRect(0, 0, this.width, this.height); if (this.snake != null) { // 画蛇 g2.setColor(new Color(255, 255, 255)); for (int i = this.snake.size() - 1; i >= 0; i--) { Point point = this.snake.body.get(i); if (i == 0) { // 蛇头 g2.setColor(new Color(255, 92, 92)); } else { g2.setColor(new Color(215, 173, 173)); } g2.fillRect(point.x, point.y, Snake.width, Snake.height); } } if (this.pill != null) { // 画药丸 Color pillColor; switch (this.pill.pillType) { case RED: pillColor = new Color(255, 41, 41); break; case BLUE: pillColor = new Color(20, 250, 243); break; default: pillColor = new Color(97, 255, 113); break; } g2.setColor(pillColor); g2.fillOval(pill.point.x, pill.point.y, Pill.width, Pill.height); } if (state > 0) { // 显示游戏结果 String tips = "游戏失败!"; if (state == 1) { tips = "游戏胜利!"; } g2.setFont(new Font("", Font.BOLD, 20)); g2.setColor(new Color(208, 74, 74)); g2.drawString(tips, this.width / 3, this.height / 3); g2.setFont(new Font("", Font.PLAIN, 18)); g2.setColor(Color.WHITE); g2.drawString("得分:" + this.score, this.width / 2, this.height / 3 + 50); } if (stop) { g2.setFont(new Font("", Font.PLAIN, 18)); g2.setColor(Color.WHITE); g2.drawString("按空格键开始/暂停游戏!", this.width / 4, this.height - 50); } } @Override public void actionPerformed(ActionEvent e) { // 是否吃药 boolean isAte = false; if (!this.stop) { // 移动蛇 this.snake.move(this.direction); Point head = this.snake.getHead(); if (head.equals(this.pill.point)) { // 吃药了 isAte = true; // 药丸分数 int getScore = this.pill.pillType.score; // 累计分数 this.score += getScore; for (int i = 0; i < getScore; i++) { // 移除蛇尾 this.snake.removeLast(); if (this.snake.size() == 0) { // 游戏胜利 this.state = 1; this.stop = true; break; } } pill = null; if (this.score % 10 == 0) { int curSpeed = this.timer.getDelay(); if (curSpeed > 30) { // 加速 this.timer.setDelay(curSpeed - 10); } } } if (state == 0) { // 判断蛇有没有咬到自己或是撞墙 int maxWidth = this.width - this.snake.width; int maxHeight = this.height - this.snake.height; boolean isHitWall = head.x > maxWidth || head.x < 0 || head.y > maxHeight || head.y < 0; boolean isBiting = false; for (int i = this.snake.size() - 1; i > 0; i--) { if (head.equals(this.snake.body.get(i))) { isBiting = true; break; } } if (isHitWall || isBiting) { // 游戏失败 this.state = 2; this.stop = true; } } } if (this.stop) { this.timer.stop(); } else if (isAte) { // 重新生成药丸 generatePill(); } repaint(); } }
スタートアップクラス
以上がJava で小規模なゲームを作成するためのチュートリアル: スネーク ゲームはありませんの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。