最近跟着b站视频手敲了一遍贪吃蛇小游戏的代码,虽然简单但是写完运行出来还是挺有成就感的,而且里面还涉及到有个jframe重绘闪动的问题,对遇到该问题的朋友或许有帮助。
游戏界面截图:
代码目录结构
代码简单讲解:
1、GameWin.java :继承JFrame,用于布局游戏界面,作为游戏启动的入口
ps:刚做完的时候界面一直频繁闪动,查了很多方法都没有处理好,最后才注意到在重写paint方法时,没有注释掉super.paint(g)
当然只注释掉这个还是不够的,还需要增加一个参数(Image offScreenImage = null;)来将界面上的小元素整合到一张图片上,实现双缓冲防止闪屏。
具体实现逻辑在paint方法中:注意了,后续的g都要替换成gImage,也就是咱们新生成的图片缓存
代码如下
package com.example.snake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Author: wangwang
* Date: 2022/11/1
*/
public class GameWin extends JFrame {
//游戏状态 0:未开始 1:游戏中 2:暂停 3:失败 4:通关 5:失败后重新开始 6:下一关
public static int state = 0;
public int score = 0;
int winWidth = 800;
int winHeight = 600;
HeadObj headObj = new HeadObj(GameUtils.rightImg,60,570,this);
public List<BodyObj> bodyObjList = new ArrayList<>();
public FoodObj foodObj = new FoodObj().getFood();
Image offScreenImage = null;
public void launch(){
this.setVisible(true);
this.setSize(winWidth,winHeight);
this.setLocationRelativeTo(null);
this.setTitle("snake");
bodyObjList.add(new BodyObj(GameUtils.bodyImg,30,570,this));
bodyObjList.add(new BodyObj(GameUtils.bodyImg,0,570,this));
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_SPACE){
switch (state){
case 0:
case 2:
state = 1;
break;
case 1:
state = 2;
repaint();
break;
case 3:
state = 5;
break;
case 4:
state = 6;
break;
default:
break;
}
}
}
});
while (true){
if(state == 1){
repaint();
}
if(state == 5){
state = 0;
resetGame();
}
if(state == 6 && GameUtils.level != 3){
state = 1;
GameUtils.level++;
resetGame();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void paint(Graphics g) {
if(offScreenImage == null){
offScreenImage = this.createImage(winWidth,winHeight);
}
Graphics gImage = offScreenImage.getGraphics();
gImage.setColor(Color.gray);
gImage.fillRect(0,0,winWidth,winHeight);
gImage.setColor(Color.black);
for (int i = 0; i <= 20; i++) {
gImage.drawLine(0,i * 30,600,i * 30);
gImage.drawLine(i * 30,0,i * 30,600);
}
for (int i = bodyObjList.size() - 1 ; i >= 0; i--){
bodyObjList.get(i).paintSelf(gImage);
}
headObj.paintSelf(gImage);
foodObj.paintSelf(gImage);
GameUtils.drawWord(gImage,"第"+GameUtils.level+"关",Color.orange,40,650,260);
GameUtils.drawWord(gImage,score+"分",Color.blue,50,650,330);
gImage.setColor(Color.GRAY);
prompt(gImage);
g.drawImage(offScreenImage,0,0,null);
}
void prompt(Graphics g){
if(state == 0){
g.fillRect(120,240,400,70);
GameUtils.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
}
if(state == 2){
g.fillRect(120,240,400,70);
GameUtils.drawWord(g,"按下空格继续游戏",Color.yellow,35,150,290);
}
if(state == 3){
g.fillRect(120,240,400,70);
GameUtils.drawWord(g,"游戏失败!按下空格重新进入游戏",Color.red,35,150,290);
}
if(state == 4){
g.fillRect(120,240,400,70);
if(GameUtils.level == 3){
GameUtils.drawWord(g,"恭喜通关!",Color.GREEN,35,150,290);
}else {
GameUtils.drawWord(g,"恭喜!按下空格进入下一关",Color.GREEN,35,150,290);
}
}
}
//游戏重置
void resetGame(){
//关闭当前窗口
this.dispose();
//开启新窗口
String[] args = {};
main(args);
}
public static void main(String[] args) {
GameWin gameWin = new GameWin();
gameWin.launch();
}
}
2、GameObj.java:游戏中所有物体的父类,例如蛇头,蛇身体、食物,其中对应HeadObj.java、BodyObj.java、FoodObj.java,这三个类都继承GameObj。
详细代码如下:
package com.example.snake;
import java.awt.*;
/**
* Author: wangwang
* Date: 2022/11/1
*/
public class GameObj {
Image img;
int x;
int y;
int width = 30;
int height = 30;
GameWin frame;
public Image getImg() {
return img;
}
public void setImg(Image img) {
this.img = img;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public GameWin getFrame() {
return frame;
}
public void setFrame(GameWin frame) {
this.frame = frame;
}
public GameObj() {
}
public GameObj(Image img, int x, int y, GameWin frame) {
this.img = img;
this.x = x;
this.y = y;
this.frame = frame;
}
public GameObj(Image img, int x, int y, int width, int height, GameWin frame) {
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.frame = frame;
}
public void paintSelf(Graphics g){
g.drawImage(img,x,y,width,height,null);
}
}
package com.example.snake;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;
/**
* Author: wangwang
* Date: 2022/11/1
*/
public class HeadObj extends GameObj{
//方向
private String direction = "right";
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public HeadObj(Image img, int x, int y, GameWin frame) {
super(img, x, y, frame);
this.frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
changeDirection(e);
}
});
}
public void changeDirection(KeyEvent e){
switch (e.getKeyCode()){
case KeyEvent.VK_A:
if(!"right".equals(direction)){
direction = "left";
img = GameUtils.leftImg;
}
break;
case KeyEvent.VK_D:
if(!"left".equals(direction)){
direction = "right";
img = GameUtils.rightImg;
}
break;
case KeyEvent.VK_W:
if(!"down".equals(direction)){
direction = "up";
img = GameUtils.upImg;
}
break;
case KeyEvent.VK_S:
if(!"up".equals(direction)){
direction = "down";
img = GameUtils.downImg;
}
break;
default:
break;
}
}
public void move(){
List<BodyObj> bodyObjList = this.frame.bodyObjList;
for(int i = bodyObjList.size() - 1; i >= 1; i--){
bodyObjList.get(i).x = bodyObjList.get(i-1).x;
bodyObjList.get(i).y = bodyObjList.get(i-1).y;
if(this.x == bodyObjList.get(i).x && this.y == bodyObjList.get(i).y){
//失败
GameWin.state = 3;
}
}
bodyObjList.get(0).x = this.x;
bodyObjList.get(0).y = this.y;
switch (direction){
case "up":
y -= height;
break;
case "down":
y +=height;
break;
case "left":
x -= width;
break;
case "right":
x += width;
break;
default:
break;
}
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
FoodObj foodObj = this.frame.foodObj;
Integer newX = null;
Integer newY = null;
if(this.x == foodObj.x && this.y == foodObj.y){
this.frame.foodObj = foodObj.getFood();
BodyObj lastBody = this.frame.bodyObjList.get(this.frame.bodyObjList.size() -1);
newX = lastBody.x;
newY = lastBody.y;
this.frame.score++;
}
if(this.frame.score >= 3){
GameWin.state = 4;
}
move();
if(newX != null && newY != null){
this.frame.bodyObjList.add(new BodyObj(GameUtils.bodyImg,newX,newY,this.frame));
}
//越界处理
if(x < 0){
x = 570;
}else if(x >570){
x = 0;
}
if(y < 30){
y = 570;
}else if(y > 570){
y = 30;
}
}
}
package com.example.snake;
import java.awt.*;
/**
* Author: wangwang
* Date: 2022/11/1
*/
public class BodyObj extends GameObj{
public BodyObj(Image img, int x, int y, GameWin frame) {
super(img, x, y, frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
package com.example.snake;
import java.awt.*;
import java.util.Random;
/**
* Author: wangwang
* Date: 2022/11/2
*/
public class FoodObj extends GameObj{
Random r = new Random();
public FoodObj() {
super();
}
public FoodObj(Image img, int x, int y, GameWin frame) {
super(img, x, y, frame);
}
public FoodObj getFood(){
return new FoodObj(GameUtils.foodImg,r.nextInt(20)*30,(r.nextInt(19)+1)*30,this.frame);
}
@Override
public void paintSelf(Graphics g) {
super.paintSelf(g);
}
}
3、GameUtils.java是工具类,主要用来获取界面内物品的图片,生成界面内文字
package com.example.snake;
import java.awt.*;
/**
* Author: wangwang
* Date: 2022/11/1
*/
public class GameUtils {
public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/up.png");
public static Image downImg = Toolkit.getDefaultToolkit().getImage("img/down.png");
public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/left.png");
public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/right.png");
public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");
public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");
public static int level = 1;
public static void drawWord(Graphics g, String str, Color color, int fontSize, int x, int y){
g.setColor(color);
g.setFont(new Font("仿宋",Font.BOLD,fontSize));
g.drawString(str,x,y);
}
}
4、界面静态资源存放在img下,大家也可以去我的资源列表下载完整代码,也可以自己找图片代替。