学习过java基础后,我们可以试着锻炼锻炼自己独立编程的能力。从构思到逻辑的实现,来体会java是如何编写一个贪吃蛇小游戏。
分析一个游戏应该有的需求,逻辑。贪吃蛇无非就是等待键盘操作,根据操作更改画面。其实不管是什么游戏都少不了这两个步骤,所以我们可以把这两个操作抽象出来定义一个规则,这就是经常听说的游戏引擎。
一个java实现的简易游戏引擎
文章用到该游戏引擎。来完成一个简单的贪吃蛇游戏。
JavaSE GUI
游戏中的元素:蛇,苹果,地图。
接下来需要考虑怎么把这些元素画出来?毫无疑问需要用到java中的gui技术。
问题一:我们可以把蛇看成一个有多个节点组成的链表。这样就可以通过的加和减少链表中的节点数来控制蛇的长度动态的变化。
问题二:我们可以把地图画成一个网格,这样就可以通过x、y轴坐标来表示蛇的各个节点和苹果节点的位置。
问题三:有了坐标系就可以使用随机数来生成随机坐标。
解决了这三个问题,其余的画面更新,读取操作指令就叫给游戏引擎。让我来看看具体的代码。
节点:Node
import java.awt.*;
public class Node {
int x; //节点的x坐标
int y; //节点的y坐标
public Node(int x,int y){
this.x = x;
this.y = y;
}
/**
* 在地图中画出节点
* @param g2d
*/
public void draw(Graphics2D g2d){
g2d.fillRect(x*Config.GRID_SIZE,y*Config.GRID_SIZE,
Config.GRID_SIZE,Config.GRID_SIZE);
}
}
地图表格:MapGrid(注:表格的行列是自定义数值的,通过Config配置类中静态常量来控制,方便修改,这里也可以使用配置文件来设置也可以)
import java.awt.*;
public class MapGrid {
/**
* 画出表格 自定义行列
* @param g2d
*/
public void draw(Graphics2D g2d){
for (int i = 0; i <Config.ROW ; i++) {
g2d.drawLine(0,Config.GRID_SIZE*i,Config.SCREEN_WIDTH,Config.GRID_SIZE*i);
}
for (int i = 0; i < Config.COLOUM; i++) {
g2d.drawLine(Config.GRID_SIZE*i,0,Config.GRID_SIZE*i,Config.SCREEN_HEIGHT);
}
}
}
Config配置:
public class Config {
public static final int SCREEN_WIDTH = 500;
public static final int SCREEN_HEIGHT = 500;
public static final int GRID_SIZE = 10; //一个格子占多少像素
public static final int COLOUM = SCREEN_WIDTH/GRID_SIZE;
public static final int ROW = SCREEN_HEIGHT/GRID_SIZE;
public static final int DRICT_RIGHT = 1;
public static final int DRICT_LEFT = 2;
public static final int DRICT_UP = 3;
public static final int DRICT_DOWN = 4;
}
蛇:Snake
import java.awt.*;
import java.util.LinkedList;
public class Snake {
LinkedList<Node> list = new LinkedList<>();
int headDriction = 1; //代表移动的方向(1向右、2向左、3向上、4向下)
public Snake(){
list.add(new Node(6,4));
list.addLast(new Node(5,4));
list.addLast(new Node(4,4));
list.addLast(new Node(3,4));
}
/**
* 右移
* @param isHaveApple 是否苹果的存在
*/
public void moveRight(boolean isHaveApple){
if(headDriction == Config.DRICT_LEFT){
System.out.println("不能反方向移动");
return;
}
headDriction = Config.DRICT_RIGHT;
Node newHead = new Node(list.getFirst().x+1,list.getFirst().y);
list.addFirst(newHead);
if (!isHaveApple){
list.removeLast();
}
}
/**
* 左移
* @param isHaveApple
*/
public void moveLeft(boolean isHaveApple){
if(headDriction == Config.DRICT_RIGHT){
System.out.println("不能反方向移动");
return;
}
headDriction = Config.DRICT_LEFT;
Node newHead = new Node(list.getFirst().x-1,list.getFirst().y);
list.addFirst(newHead);
if (!isHaveApple){
list.removeLast();
}
}
/**
* 上移
* @param isHaveApple
*/
public void moveUp(boolean isHaveApple){
if(headDriction == Config.DRICT_DOWN){
System.out.println("不能反方向移动");
return;
}
headDriction = Config.DRICT_UP;
Node newHead = new Node(list.getFirst().x,list.getFirst().y-1);
list.addFirst(newHead);
if (!isHaveApple){
list.removeLast();
}
}
/**
* 下移
* @param isHaveApple
*/
public void moveDown(boolean isHaveApple){
if(headDriction == Config.DRICT_UP){
System.out.println("不能反方向移动");
return;
}
headDriction = Config.DRICT_DOWN;
Node newHead = new Node(list.getFirst().x,list.getFirst().y+1);
list.addFirst(newHead);
if (!isHaveApple){
list.removeLast();
}
}
/**
* 判断蛇头是否与苹果相遇
* @param apple 被检查的苹果
* @return 相遇返回ture 否则返回false
*/
public boolean isHaveApple(Apple apple){
if((list.getFirst().x== apple.getX())&&(list.getFirst().y== apple.getY())){
apple.getNextApple();
return true;
}
return false;
}
/**
* 画出链表即蛇
* @param g2d
*/
public void draw(Graphics2D g2d){
for (Node node:list){
node.draw(g2d);
}
}
}
苹果:Apple
import java.awt.*;
import java.util.Random;
public class Apple {
Random random = new Random();
Node apple = null;
public Apple(){
apple = new Node(random.nextInt(Config.ROW),random.nextInt(Config.COLOUM));
}
public int getX(){
return apple.x;
}
public int getY(){
return apple.y;
}
public void draw(Graphics2D g2d){
g2d.setColor(Color.RED);
apple.draw(g2d);
g2d.setColor(Color.black);
}
/**
* 产生一个新苹果
*/
public void getNextApple(){
apple = new Node(random.nextInt(Config.ROW),random.nextInt(Config.COLOUM));
}
}
贪吃蛇游戏引擎:SnakeGameEngine (实现游戏引擎GameEngine抽象类,重写方法)
import java.awt.*;
import java.awt.event.KeyEvent;
public class SnakeGameEngine extends GameEngine {
int keycode;
boolean ishaveapple;
MapGrid grid = new MapGrid();
Snake snake = new Snake();
Apple apple = new Apple();
/**
* 读取、更新 操作
*/
@Override
public void updateLogic() {
keycode = Game.getCurrentKeyCode();
ishaveapple = snake.isHaveApple(apple);
if (keycode == KeyEvent.VK_RIGHT){
snake.moveRight(ishaveapple);
}
else if (keycode == KeyEvent.VK_LEFT){
snake.moveLeft(ishaveapple);
}
else if (keycode == KeyEvent.VK_UP){
snake.moveUp(ishaveapple);
}
else if (keycode == KeyEvent.VK_DOWN){
snake.moveDown(ishaveapple);
}
else {
return;
}
}
/**
* 更新画面
* @param g2d
*/
@Override
public void renderUI(Graphics2D g2d) {
grid.draw(g2d);
snake.draw(g2d);
apple.draw(g2d);
}
}
主函数:SnakeGame (Game为游戏公共类也属于游戏引擎中的一部分,本质是一个画板,用来初始化画板)
public class SnakeGame {
public static void main(String[] args) {
Game.init("贪吃蛇", Config.SCREEN_WIDTH, Config.SCREEN_HEIGHT,new SnakeGameEngine());
}
}
游戏引擎代码:
GameEngine:
import java.awt.*;
/**
* 游戏引擎抽象模板类
*
提供两个抽象方法
*
updateLogic
*
renderUI
*/
public abstract class GameEngine {
/**
* 逻辑更新
*/
public abstract void updateLogic();
/**
* 画面更新
*/
public abstract void renderUI(Graphics2D g2d);
}
Game:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
* 游戏公共类
*/
public class Game extends JPanel {
static GameEngine myGameEngine;
static int keycode;
/**
* 初始化方法,展现游戏界面
* @param title 窗体标题
* @param width 窗体宽度
* @param height 窗体高度
* @param engine 游戏引擎
*/
public static void init(String title,int width,int height,GameEngine engine){
myGameEngine = engine;
JFrame frame =new JFrame(title);
Game game = new Game();
game.setPreferredSize(new Dimension(width,height));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.pack();
//匿名内部类创建键盘监听器
frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
keycode = e.getKeyCode();
}
@Override
public void keyReleased(KeyEvent e) {
keycode = -1;
}
});
frame.setVisible(true);
while (true){
engine.updateLogic();
game.repaint(); //自动重绘
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 返回所按的按键码
* @return int类型的按键码
*/
public static int getCurrentKeyCode(){
return keycode;
}
/**
* 游戏结束,结束JVM进程
* @param message 退出时所显示的文字,例:游戏失败.
*/
public static void GameOver(String message){
JOptionPane.showMessageDialog(null,message);
System.exit(0);
}
@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
myGameEngine.renderUI(g2d);
}
}
关于本文用到的游戏引擎和公共类在下面的文章给出了具体分析。
【架构】Java实现游戏引擎
与其临渊羡鱼,不如退而结网。——《史记汉书董仲舒传》