一、代码逻辑
1、初始化界面:MainScene类
2、画面绘制:因为主要是绘制蛇,所以放在Snack类中
3、蛇控制:使用ArrayList存放结点位置信息,通过增删控制蛇的移动,应该可以用LinkedList优化。
4、实现功能:
- 移动:穿越边界时从对面边界出现
- 果实拾取,蛇身加长
- 碰撞
- 游戏暂停、结束后重开
- 分数显示
二、代码
package com.SnackGame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class MainScene {
private Container contentPane;
public static void main(String[] args) {
PublicData.newApple();
new MainScene().init();
}
//初始化操作
private void init(){
//创建窗口
JFrame frame = new JFrame();
frame.setBounds(100,100,PublicData.width,PublicData.height);
frame.setResizable(false);
this.contentPane = frame.getContentPane();
this.contentPane.setBackground(new Color(42, 42, 42));
//创建蛇
Snack snack = new Snack();
snack.init();
frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
snack.pressKey(e.getKeyCode());
}
});
frame.add(snack);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//每50ms根据状态刷新一次游戏,游戏结束时终止线程
new Thread(() -> {
while(PublicData.GameState != GameConst.GAME_OVER){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(PublicData.GameState == GameConst.GAME_RUN){
snack.move();
}
else{
snack.repaint();
}
}
snack.repaint();
}).start();
}
}
package com.SnackGame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
public class Snack extends JLabel {
//初始化信息
private int dirt = GameConst.DIRT_RIGHT;
private int snackState = GameConst.SNACK_NORMAL;
private int bodyLength = 3;
private int nodeLength = 10;
private ArrayList<Point> bodyPositions = new ArrayList<>();
void init(){
for (int i = 0; i < 3; i++) {
bodyPositions.add(new Point(PublicData.width / 3 - i * nodeLength,90));
}
repaint();
}
//移动逻辑
public void move(){
addHead();
if(snackState == GameConst.SNACK_EAT){
bodyLength++;
snackState = GameConst.SNACK_NORMAL;
PublicData.newApple();
}
else{
bodyPositions.remove(bodyLength);
}
repaint();
}
//由当前方向判定新头位置,并判断新头是否与果实或身体发生碰撞
private void addHead(){
int offsetX = 0;
int offsetY = 0;
switch (dirt){
case GameConst.DIRT_UP:
offsetY = -1;
break;
case GameConst.DIRT_DOWN:
offsetY = 1;
break;
case GameConst.DIRT_LEFT:
offsetX = -1;
break;
default:
offsetX = 1;
}
Point oldHead = bodyPositions.get(0);
int newX = oldHead.x + offsetX * nodeLength;
int newY = oldHead.y + offsetY * nodeLength;
if(newX < 0 || newX > PublicData.width){
newX = Math.abs(Math.abs(newX) - PublicData.width);
}
if(newY < 0 || newY > PublicData.height){
newY = Math.abs(Math.abs(newY) - PublicData.height);
}
if(checkHit(newX, newY, PublicData.appleX, PublicData.appleY)){
snackState = GameConst.SNACK_EAT;
}
for(Point p: bodyPositions){
if(checkHit(newX, newY, p.x, p.y)){
snackState = GameConst.SNACK_DEAD;
PublicData.GameState = GameConst.GAME_OVER;
break;
}
}
bodyPositions.add(0,new Point(newX,newY));
}
//判断两个结点是否重合
private boolean checkHit(int x1, int y1, int x2, int y2){
return (Math.abs(x1 - x2) < nodeLength && Math.abs(y1 - y2) < nodeLength);
}
//键盘监听到方向键或空格键时触发操作
void pressKey(int keyCode){
if(keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W){
if(this.dirt != GameConst.DIRT_DOWN){
this.dirt = GameConst.DIRT_UP;
}
}
else if(keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S){
if(this.dirt != GameConst.DIRT_UP){
this.dirt = GameConst.DIRT_DOWN;
}
}
else if(keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A){
if(this.dirt != GameConst.DIRT_RIGHT){
this.dirt = GameConst.DIRT_LEFT;
}
}
else if(keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D){
if(this.dirt != GameConst.DIRT_LEFT){
this.dirt = GameConst.DIRT_RIGHT;
}
}
else if(keyCode == KeyEvent.VK_SPACE){
if(PublicData.GameState == GameConst.GAME_RUN){
PublicData.GameState = GameConst.GAME_PAUSE;
}
else if(PublicData.GameState == GameConst.GAME_PAUSE){
PublicData.GameState = GameConst.GAME_RUN;
}
else if(PublicData.GameState == GameConst.GAME_OVER){
PublicData.GameState = GameConst.GAME_RUN;
init();
}
}
}
//画面绘制
@Override
public void paint(Graphics g) {
//绘制果实
g.setColor(new Color(250, 244, 190));
g.fillOval(PublicData.appleX - PublicData.appleSize/2, PublicData.appleY - PublicData.appleSize/2, PublicData.appleSize, PublicData.appleSize);
//绘制蛇
int headX = 0;
int headY = 0;
boolean isHead = true;
for(Point p: bodyPositions){
if(isHead){
headX = p.x;
headY = p.y;
paintHead(headX,headY,g);
isHead = false;
g.setColor(new Color(108, 243, 112));
}
else{
g.fillRect(p.x - nodeLength / 2,p.y - nodeLength / 2, nodeLength, nodeLength);
}
}
//结束时再次绘制蛇头避免与蛇身发生碰撞导致蛇头被蛇身覆盖
paintHead(headX,headY,g);
//绘制得分
g.setColor(Color.white);
g.setFont(new Font("微软雅黑",Font.BOLD,20));
g.drawString("得分:" + (bodyLength - 3), 30,30);
//绘制暂停或结束时屏幕提示信息
if(PublicData.GameState != GameConst.GAME_RUN){
g.setFont(new Font("微软雅黑",Font.BOLD,40));
String s = PublicData.GameState == GameConst.GAME_OVER ? "按下【空格】开始游戏": "按下【空格】继续或暂停游戏";
g.drawString(s, (PublicData.width-s.length()*40)/2,PublicData.height/2);
}
}
//将蛇头绘制到(x,y)处
private void paintHead(int x, int y, Graphics g){
g.setColor(new Color(255, 91, 91));
g.fillOval(x - nodeLength / 2,y - nodeLength / 2, nodeLength, nodeLength);
g.setColor(new Color(108, 243, 112));
}
}
package com.SnackGame;
public class PublicData {
//全局变量:游戏状态、窗口宽高
public static int GameState = GameConst.GAME_OVER;
public static int width = 600;
public static int height = 400;
//果实尺寸、位置
public static int appleSize = 10;
public static int appleX;
public static int appleY;
//生成新的果实位置
public static void newApple(){
appleX = ((int)(Math.random() * PublicData.width) * 8 + PublicData.width)/10;
appleY = ((int)(Math.random() * PublicData.height) * 8 + PublicData.height)/10;
}
}
package com.SnackGame;
public class GameConst {
public static final int GAME_OVER = 0;
public static final int GAME_PAUSE = 1;
public static final int GAME_RUN = 2;
public static final int DIRT_UP = 0;
public static final int DIRT_LEFT = 1;
public static final int DIRT_RIGHT = 2;
public static final int DIRT_DOWN = 3;
public static final int SNACK_NORMAL = 0;
public static final int SNACK_EAT = 1;
public static final int SNACK_DEAD = 2;
}