项目:思想 | 思维
1.1.1 窗体
展示游戏的主界面
Frame -> 窗体 【位置、大小、背景色、标题】
package com.example.snake;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 绘制窗体
* Frame -> 窗体 【位置、大小、背景色、标题】
* 设置窗体可见
* 关闭窗口的事件监听
* 窗口是不能重新设置大小的
*/
public class SnakeFrame01 extends Frame {
//构造器中初始化窗体
public SnakeFrame01(){
this.setLocation(200,100);
this.setSize(600,500);
this.setBackground(Color.magenta);
this.setTitle("贪吃蛇V1.0");
this.setResizable(false);
//设置窗体显示
this.setVisible(true);
//注册窗口监听
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
//退出虚拟机
System.exit(0);
}
});
}
public static void main(String[] args) {
new SnakeFrame01();
}
}
1.1.2 蛇
蛇的主体
- paint->该方法是Frame中提供的,每一次窗体显示|更新的时候自动调用
- 蛇的主体绘制
封装一个节点类(Node)存储坐标,绘制自己本身
Node.java
package com.example.snake;
import java.awt.*;
/**
* 节点类: 保存坐标 x,y
*/
public class Node {
private int x;
private int y;
public Node(){
}
public Node(int x,int y){
this.x = x;
this.y = y;
}
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;
}
/**
* 绘制节点
* @param g
*/
public void drawNode(Graphics g,int i){
if (0==i){
g.fillOval(this.x,this.y,20-1,20-1);
} else {
g.fillRect(this.x,this.y,20-1,20-1);
}
}
}
窗体中,维护蛇的主体。Node[]
package com.example.snake;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 绘制窗体
* Frame -> 窗体 【位置、大小、背景色、标题】
* 设置窗体可见
* 关闭窗口的事件监听
* 窗口是不能重新设置大小的
*/
public class SnakeFrame02 extends Frame {
//蛇的主体部分
private Node[] body={
new Node(140,100),
new Node(120,100),
new Node(100,100)
};
//构造器中初始化窗体
public SnakeFrame02(){
this.setLocation(200,100);
this.setSize(600,500);
this.setBackground(Color.magenta);
this.setTitle("贪吃蛇V1.0");
this.setResizable(false);
//设置窗体显示
this.setVisible(true);
//注册窗口监听
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
//退出虚拟机
System.exit(0);
}
});
}
/**
* 窗体显示 | 更新 自动调用
* 绘制图形、线条、文本
* @param g 画笔
*/
@Override
public void paint(Graphics g) {
g.setColor(Color.orange);
//绘制蛇的主体部分
for (int i=0; i<body.length; i++){
body[i].drawNode(g,i);
}
}
public static void main(String[] args) {
new SnakeFrame02();
}
}
移动
一直重绘窗体repaint()—>paint()
/**
* 手动一直重绘窗体
*/
public void start(){
while (isRunning){
super.repaint(); //手动让窗体重绘
try {
Thread.sleep(500); //暂停事件
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
改变坐标
/**
* 通过修改坐标达到移动的效果
*/
public void remove(){
//向右移动
//每一次只需要给一个新的头节点,去掉尾节点即可
Node[] temp = new Node[body.length];
//去掉尾部
System.arraycopy(body,0,temp,1,body.length-1);
temp[0] = new Node(body[0].getX()+20,body[0].getY());
this.body = temp;
}
通过键盘控制方向
添加键盘监听事件
//注册键盘监听
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int dir = -1;
//上 1,下 3,右 0,左 2
switch (e.getKeyCode()){
case KeyEvent.VK_UP: //向上走
dir = 1;
break;
case KeyEvent.VK_DOWN: //向下走
dir = 3;
break;
case KeyEvent.VK_LEFT: //向左走
dir = 2;
break;
case KeyEvent.VK_RIGHT: //向右走
dir = 0;
break;
}
//根据合法性 修改方向 (不允许在同一条直线上修改)
if (direction%2 != dir%2){
direction = dir;
}
}
根据按键修改方向
/**
* 根据方向来确定一个新的头节点
* @return
*/
public Node getNewHead(){
Node newNode = body[0];
switch (this.direction){
//上 1,下 3,右 0,左 2
case 0: //右
newNode = new Node(newNode.getX()+20,newNode.getY());
break;
case 2: //左
newNode = new Node(newNode.getX()-20,newNode.getY());
break;
case 1: //上
newNode = new Node(newNode.getX(),newNode.getY()-20);
break;
case 3: //下
newNode = new Node(newNode.getX(),newNode.getY()+20);
break;
}
return newNode;
}
1.1.3 食物
食物的主体 Node
产生食物 —>随机产生[不要超过边界;满足窗体中一个隐形的格子]
/**
* 随机产生食物
*/
public void productFood(){
food = new Node(randomNum(0,600/20-1)*20, randomNum(2,500/20-1)*20);
}
1.1.4 蛇吃食物
何时能吃到食物
/**
* 是否吃到食物:下一步的头节点的位置刚好和食物的位置相等
* @return
*/
public boolean isEat(){
Node newNode = getNewHead();
if (newNode.equals(food)){
return true;
}
return false;
}
食物重新产生
蛇的主体会增加
/**
* 吃到食物了,增加蛇的身体
* 将食物变成蛇的头部
* 食物重新产生
* 食物消失,重生成
*/
public void addBody(){
Node[] temp = new Node[body.length+1];
temp[0] = food;
System.arraycopy(body,0,temp,1,body.length);
body = temp;
food = null;
productFood();
}
1.1.5 游戏结束
碰到边界
碰到自己的身体
/**
* 游戏是否结束
* 1.
* 2.
* @return true 游戏结束 false 未结束
*/
public void isOver(){
Node head = body[0];
//边界
if (head.getX()<=0 || head.getX()>=600-20 || head.getY()<=20 || head.getY()>=500-20){
isRunning = false;
}
//身体
for (int i=1; i<body.length; i++){
if (head.equals(body[i])){
isRunning = false;
}
}
}
优化代码和最终实现
package com.example.snake;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* 绘制窗体
* Frame -> 窗体 【位置、大小、背景色、标题】
* 设置窗体可见
* 关闭窗口的事件监听
* 窗口是不能重新设置大小的
*
* 2.绘制蛇的身体
*
* 3.动起来
* 1)一直重绘窗体
* 2)修改坐标
*
* 4.手动控制方向
* 1)、添加键盘监听事件
*
* 2)、根据按键修改方向
*
* 5.食物
* 主体
* 位置
*
* 6.蛇吃食物
* 何时吃到食物
* 产生食物
* 蛇的身体加长
* 7.游戏结束
* 头部碰到身体
* 头部碰到边界
*/
public class SnakeFrame08 extends Frame {
private static final int WINDOW_X = 200;
private static final int WINDOW_Y = 100;
private static final int WINDOW_W = 600;
private static final int WINDOW_H = 500;
//上 1,下 3,右 0,左 2
private static final int up = 1;
private static final int down = 3;
private static final int right = 0;
private static final int left = 2;
//程序还在继续吗
private boolean isRunning = true;
//上 1,下 3,右 0,左 2
private int direction = 0;
//食物
private Node food = new Node(randomNum(0,WINDOW_W/20-1)*20, randomNum(2,WINDOW_H/20-1)*20);
//蛇的主体部分
private Node[] body={
new Node(140,100),
new Node(120,100),
new Node(100,100)
};
//构造器中初始化窗体
public SnakeFrame08(){
this.setLocation(WINDOW_X,WINDOW_Y);
this.setSize(WINDOW_W,WINDOW_H);
this.setBackground(Color.magenta);
this.setTitle("贪吃蛇V1.0");
this.setResizable(false);
//设置窗体显示
this.setVisible(true);
//注册窗口监听
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
//退出虚拟机
System.exit(0);
}
});
//注册键盘监听
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int dir = -1;
//上 1,下 3,右 0,左 2
switch (e.getKeyCode()){
case KeyEvent.VK_UP: //向上走
dir = up;
break;
case KeyEvent.VK_DOWN: //向下走
dir = down;
break;
case KeyEvent.VK_LEFT: //向左走
dir = left;
break;
case KeyEvent.VK_RIGHT: //向右走
dir = right;
break;
}
//根据合法性 修改方向 (不允许在同一条直线上修改)
if (direction%2 != dir%2){
direction = dir;
}
}
});
start();
}
/**
* 窗体显示 | 更新 自动调用
* 绘制图形、线条、文本
* @param g 画笔
*/
@Override
public void paint(Graphics g) {
g.setColor(Color.orange);
//绘制蛇的主体部分
for (int i=0; i<body.length; i++){
body[i].drawNode(g,i);
}
g.setColor(Color.CYAN);
food.drawNode(g,-1);
if (isRunning){
if (isEat()){
//System.out.println("吃到食物了");
addBody();
} else {
//修改坐标
remove();
}
isOver();
}else {
//System.out.println("game over");
g.setColor(Color.green);
g.setFont(new Font(null,Font.ITALIC,100));
g.drawString("game over",WINDOW_W/2-220,WINDOW_H/2);
}
}
/**
* 游戏是否结束
* 1.
* 2.
* @return true 游戏结束 false 未结束
*/
public void isOver(){
Node head = body[0];
//边界
if (head.getX()<=0 || head.getX()>=WINDOW_W-20 || head.getY()<=20 || head.getY()>=WINDOW_H-20){
isRunning = false;
}
//身体
for (int i=1; i<body.length; i++){
if (head.equals(body[i])){
isRunning = false;
}
}
}
/**
* 吃到食物了,增加蛇的身体
* 将食物变成蛇的头部
* 食物重新产生
* 食物消失,重生成
*/
public void addBody(){
Node[] temp = new Node[body.length+1];
temp[0] = food;
System.arraycopy(body,0,temp,1,body.length);
body = temp;
food = null;
productFood();
}
/**
* 是否吃到食物:下一步的头节点的位置刚好和食物的位置相等
* @return
*/
public boolean isEat(){
Node newNode = getNewHead();
if (newNode.equals(food)){
return true;
}
return false;
}
/**
* 通过修改坐标达到移动的效果
*/
public void remove(){
//向右移动
//每一次只需要给一个新的头节点,去掉尾节点即可
Node[] temp = new Node[body.length];
temp[0] = getNewHead();
//去掉尾部
System.arraycopy(body,0,temp,1,body.length-1);
this.body = temp;
}
/**
* 随机产生食物
*/
public void productFood(){
food = new Node(randomNum(0,WINDOW_W/20-1)*20, randomNum(2,WINDOW_H/20-1)*20);
}
/**
* 根据方向来确定一个新的头节点
* @return
*/
public Node getNewHead(){
Node newNode = body[0];
switch (this.direction){
//上 1,下 3,右 0,左 2
case 0: //右
newNode = new Node(newNode.getX()+20,newNode.getY());
break;
case 2: //左
newNode = new Node(newNode.getX()-20,newNode.getY());
break;
case 1: //上
newNode = new Node(newNode.getX(),newNode.getY()-20);
break;
case 3: //下
newNode = new Node(newNode.getX(),newNode.getY()+20);
break;
}
return newNode;
}
/**
* 手动一直重绘窗体
*/
public void start(){
while (isRunning){
super.repaint(); //手动让窗体重绘
try {
Thread.sleep(500); //暂停事件
} catch (InterruptedException e) {
e.printStackTrace();
}
}
super.repaint();
}
/**
* 生成指定区间的随机整数
* @param min
* @param max
* @return
*/
public static int randomNum(int min, int max){
return (int)(Math.random()*(max-min+1) + min);
}
public static void main(String[] args) {
new SnakeFrame08();
}
}