坦克大战
后面开始学习怎么使用java制造一个坦克大战游戏
但是不是直接开始做,而是随着这个游戏程序的制造,一边学习新知识融入到游戏中。包括多线程,反射,IO流…
Java坐标体系
在几乎所有的坐标中都有一个x轴和y轴,根据x,y的不同数据可以从x轴和y轴出发一条线,而这两条线就会形成一个交叉点。
x轴和y轴的出发点交叉点,叫做原点。如图所示
像素
计算机在屏幕上显示的内容都是由像素组成的,例如1080*1920,表示每行有1920个像素,每列有1080个像素。所以整个屏幕有2073600个像素,所以像素是密度单位,不是长度单位。
例如:你在1080的显示屏上使用x,y轴画了两条线,再使用2k的显示屏打开时,你会发现缩小了。
这里就是因为屏幕能显示的像素变多了,所以整体看着就觉得缩小了
Java绘图
使用Java画一个圆
在现实中绘画,需要{ 画框,画板,笔},在java中也有对应的类
- 定义一个自己的面板类,继承Java的面板类JPanel相当于画板。
JPanel有个方法paint,可以使用画笔Graphics类对象的方法进行绘制 - 定义一个自己的框架类,也就是显示的窗口,继承Java的框架类JFrame
在框架类的无参构造器中 把画板添加到框架里,且设置框架的尺寸和什么时候关闭程序
JFrame有add方法可以将画板添加到框架里,setDefaultCloseOperation方法觉得啥时候关闭框架,结束程序
还可以设置框架的尺寸,
public class test {
public static void main(String[] args) {
//11.实例化一个框架(框架的无参构造器,已经创建了面板和画笔了)
new CircleFrame();
}
}
//1.先定义一个自己的面板类(画板),继承Java的面板类,画画就是在画板上画
class MyPanel extends JPanel{
//2.重写JPanel的paint方法(画笔方法),这里编写你要用画笔干啥,Graphics g可以理解为画笔
@Override
public void paint(Graphics g) {
super.paint(g);//调用父类的方法,完成初始化
System.out.println("paint方法运行");
//3.画一个圆
g.drawOval(20,20,100,100);//画圆的方法,x,y定义圆的起始位置,后面定义圆的宽和高
}
}
//4.定义一个展示面板的框架(窗口,可以理解为画框),继承Java的JFrame框架类
class CircleFrame extends JFrame{
//5.定义一个面板变量
MyPanel myPanel = null;
public CircleFrame(){//无参构造器
//6.初始化面板,实例化面板
myPanel = new MyPanel();
//7.将面板放到框架(窗口)里面
this.add(myPanel);
//8.设置框架(窗口)的尺寸
this.setSize(500,500);
//9.让窗口显示出来
this.setVisible(true);
//10.当点击了窗口的关闭键后退出程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Graphics类的常用方法
- 画直线-drawLine(int x,int,y,int x2,int y2)
- 画矩形边框-drawRect(int x,int y,int width,int height)
- 画椭圆-drawOval(int x,int y,int width,int height)
- 填充矩形fillRect((int x,int y,int width,int height)
- 填充椭圆fillOval(int x,int y,int width,int height)
- 画图片-drawImage(Image img,int,int y…)
- 画字符串drawString(String str,int x,int y)
- 设置画笔的字体setFont(Font font)
- 设置画笔的颜色 setColor(Color c )
使用演示:
public class test {
public static void main(String[] args) {
new CircleFrame();
}
}
class MyPanel extends JPanel{
@Override
public void paint(Graphics g) {
super.paint(g);
System.out.println("paint方法运行");
// 1. 画直线-drawLine(int x,int,y,int x2,int y2)
g.drawLine(10,10,50,50);
// 2. 画矩形边框-drawRect(int x,int y,int width,int height)
g.drawRect(20,20,70,70);
// 3. 画椭圆-drawOval(int x,int y,int width,int height)
g.drawOval(20,20,100,100);
// 4. 填充矩形fillRect((int x,int y,int width,int height)
//需要先设置一下画笔的颜色
g.setColor(Color.BLACK);
g.fillRect(70,70,100,90);
// 5. 填充椭圆fillOval(int x,int y,int width,int height)
//如果不改其他颜色,就不用再设置一下了,要换其他颜色,再重新调用setColor
g.fillOval(30,30,60,60);
// 6. 画图片-drawImage(Image img,int,int y...)
//需要先获取一下图片,需要提前把图片放在根目录,不然编译器找不到
Image image = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/4.jpg"));
g.drawImage(image,70,70,860,360,this);
// 7. 画字符串drawString(String str,int x,int y)
//可以先设置一个颜色和设置一下字体
g.setColor(Color.PINK);
g.setFont(new Font("微软雅黑",Font.BOLD,30));//字体,加粗,字体大小
g.drawString("Java",90,110);
// 8. 设置画笔的字体setFont(Font font)
// 9. 设置画笔的颜色 setColor(Color c )
}
}
class CircleFrame extends JFrame{
MyPanel myPanel = null;
public CircleFrame(){
myPanel = new MyPanel();
this.add(myPanel);
this.setSize(500,500);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
绘制坦克大战的背景区域
思路:
按照前面的学习
定义画板类和画框类,然后直接在画板上新建一个和框架(窗口)大小一样的矩形(需要填充)即可
public class Tank {
int x ;//坦克的横坐标
int y ;//坦克的纵坐标
}
public class MyPanel extends Panel {
@Override
public void paint(Graphics g){
super.paint(g);
g.fillRect(0,0,1000,950);
}
}
public class myWindow extends JFrame {
MyPanel m = null;
public myWindow(){
m = new MyPanel();
this.add(m);
this.setSize(1000,950);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new myWindow();
}
}
绘制坦克
可以坦克有两个特性
不同朝向
不同阵营
所以可以将绘制坦克封装到方法里,根据不同参数绘制不同的坦克
public class MyPanel extends Panel {
Tank tank;
@Override
public void paint(Graphics g) {
super.paint(g);
System.out.println("调用paint");
g.fillRect(0,0,500,600);
drawTank(20,460,g,0,0);
drawTank(90,350,g,0,1);
}
/**
@param x 坦克的起始横坐标
@param y 坦克的起始纵坐标
@param g 画笔
@param direct 根据方向绘制不同朝向的坦克
@param type 根据0或者1 绘制不同颜色的坦克
*/
public void drawTank(int x,int y,Graphics g,int direct,int type){
switch (type){
case 0:
g.setColor(Color.PINK);//自己的坦克就粉色
break;
case 1:
g.setColor(Color.RED);//敌人的坦克就红色
break;
}
switch (direct){
case 0://0表示绘制方向朝上的坦克
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+22,y-5,x+22,y+25);
break;
default://其他情况暂不考虑
break;
}
}
}
键盘监听
坦克大战肯定是要根据键盘的操作,坦克做出不同的动作
为了实现这个目标,需要了解一个接口
KeyListener接口,可以对键盘做出监听,获取到键盘的操作
KeyListener接口有三个方法
//字符输出时,该方法触发
public void keyTyped(KeyEvent e) {}
//有按键按下时,该方法触发
public void keyPressed(KeyEvent e) {}
//有按键松开时,该方法触发
public void keyReleased(KeyEvent e) { }
我们可以使用 e.getCode,获取到键盘对应的ASCII码
然后再根据ASCII码是对应的动作时 调用repaint方法,触发paint方法重绘画板
但是要在框架类 使用this.addKeyListener();方法把画板传进去
java事件处理机制
java事件处理是采取“委派事件模型”,当事件发生时,产生事件的对象,会把此“信息”,传递给“事件的监听者”处理,所谓“信息”实际上就是java.awt.event事件类库里某个类所创建的对象,把它称之为“事件的对象”
委派事件模型
什么是委派事件模型?
举例说明:
我通过微信向同学说“借我300块”,同学收到后,就发了红包给我
那么 事件源就是我要借钱,事件/对象,就是一条微信,同学就是事件监听者。他给我发红包就是对事件进行处理
在上面已经演示了键盘监听类 KeyEvent
java中还有其他的事件监听类
事件类 | 说明 |
---|---|
ActionEvent | 通常在按下按钮,或双击一个列表项或选中某个菜单时发生) |
AdjustmentEvent | 当操作一个滚动条时发生。 |
ComponentEvent: | 当一个组件隐藏,移动,改变大小时发送。 |
ContainerEvent | 当一个组件从容器中加入或者删除时发生。 |
FocusEvent | 当一个组件获得或是失去焦点时发生。 |
ItemEvent | 当一个复选框或是列表项被选中时,当一个选择框或选择菜 |
KeyEvent | 当从键盘的按键被按下,松开时发生。 |
MouseEvent | 当鼠标被拖动,移动,点击,按下…2 |
TextEvento | 当文本区和文本域的文本发生改变时发生。 |
WindowEvent | 当一个窗口激活,关闭,失效,恢复,最小化. |
事件监听器接口
- 当事件源产生一个事件,可以传送给事件监听者处理
- 事件监听者,实际上就是一个类,这个类实现了某个事件监听器接口
- 事件监听者接口可以有多种,不同的事件监听者接口可以监听不同事件,一个类可以实现多个监听接口
- 这些接口通常在java.awt.event包和javax.swing.event包中定义,
让坦克动起来
要让坦克动起来,得先画出不同朝向的坦克
首先前面已经画出了朝上的,那么横坐标和纵坐标其实不用动,将宽和高调整即可
朝上的:
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+22,y-5,x+22,y+25);
朝右的:
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x+58,y+25);
朝下的:
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+12,25,25);
g.drawLine(x+22,y+55,x+22,y+25);
朝左的:
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x-8,y+25);
不同的方向都制造了好后,由于前面绘制坦克的是由drawTank方法的direct来决定的
所以我们在Tank类把他变成一个属性,方便后面监听事件的更改
public class MyPanel extends Panel implements KeyListener {
int speek = 1;
Tank tank;
public MyPanel(){
tank = new Tank(20,460,0);
}
@Override
public void paint(Graphics g) {
super.paint(g);
System.out.println("调用paint");
g.fillRect(0,0,500,600);
drawTank(tank.x,tank.y,g,tank.direct,0);
}
/**
@param x 坦克的起始横坐标
@param y 坦克的起始纵坐标
@param g 画笔
@param direct 根据方向绘制不同朝向的坦克
@param type 根据0或者1 绘制不同颜色的坦克
*/
public void drawTank(int x,int y,Graphics g,int direct,int type){
switch (type){
case 0:
g.setColor(Color.PINK);//自己的坦克就粉色
break;
case 1:
g.setColor(Color.RED);//敌人的坦克就红色
break;
}
switch (direct){//0上,1右,2下,3左
case 0://0表示绘制方向朝上的坦克
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+22,y-5,x+22,y+25);
break;
case 1://1表示绘制方向朝右的坦克
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x+58,y+25);
break;
case 2://2表示绘制方向朝下的坦克
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+12,25,25);
g.drawLine(x+22,y+55,x+22,y+25);
break;
case 3://3表示绘制方向朝左的坦克
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x-8,y+25);
break;
default://其他情况暂不考虑
break;
}
}
@Override //字符输出时,该方法触发
public void keyTyped(KeyEvent e) {
}
@Override//有按键按下时,该方法触发
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 87){//前进
moveUp();
tank.direct = 0;
}else if(e.getKeyCode() == 83){
moveDown();
tank.direct = 2;
}else if(e.getKeyCode() == 65){
moveLeft();
tank.direct = 3;
}
else if(e.getKeyCode() == 68){
moveRight();
tank.direct = 1;
}else if(e.getKeyCode() == 32){
System.out.println("氮气加速~");
speek +=5;
}else{
System.out.println(e.getKeyCode());
}
repaint();
}
@Override//有按键松开时,该方法触发
public void keyReleased(KeyEvent e) {
}
public void moveUp(){
tank.y-=speek;
}
public void moveLeft(){
tank.x-=speek;
}
public void moveRight(){
tank.x+=speek;
}
public void moveDown(){
tank.y+=speek;
}
}
画出敌人坦克
因为敌人坦克后面肯定是有自己的特性的所以新建一个EnemyTanks类
敌人坦克肯定不止一个,所以需要集合
且后面会运用到多线程,所以使用Vector集合来存储敌人坦克,提高安全性
然后遍历集合绘制敌人坦克
public class MyPanel extends Panel implements KeyListener {
int speek = 1;
Tank tank;
Vector<EnemyTanks> enemyTanks = new Vector<>();
public MyPanel(){
tank = new Tank(20,460,0);
for (int i = 0; i < 3; i++) {
EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
enemyTanks.add(enemyTank);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
System.out.println("调用paint");
g.fillRect(0,0,500,600);
//画我方坦克
drawTank(tank.x,tank.y,g,tank.direct,0);
for (int i = 0; i < enemyTanks.size(); i++) {
EnemyTanks e = enemyTanks.get(i);
//画敌方坦克
drawTank(e.x,e.y,g,e.direct,1);
}
}
/**
@param x 坦克的起始横坐标
@param y 坦克的起始纵坐标
@param g 画笔
@param direct 根据方向绘制不同朝向的坦克
@param type 根据0或者1 绘制不同颜色的坦克
*/
public void drawTank(int x,int y,Graphics g,int direct,int type){
switch (type){
case 0:
g.setColor(Color.PINK);//自己的坦克就粉色
break;
case 1:
g.setColor(Color.RED);//敌人的坦克就红色
break;
}
switch (direct){//0上,1右,2下,3左
case 0://0表示绘制方向朝上的坦克
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+22,y-5,x+22,y+25);
break;
case 1://1表示绘制方向朝右的坦克
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x+58,y+25);
break;
case 2://2表示绘制方向朝下的坦克
g.fill3DRect(x,y,10,50,false);
g.fill3DRect(x+10,y+5,25,40,false);
g.fill3DRect(x+35,y,10,50,false);
g.fillOval(x+10,y+12,25,25);
g.drawLine(x+22,y+55,x+22,y+25);
break;
case 3://3表示绘制方向朝左的坦克
g.fill3DRect(x,y+7,50,10,false);
g.fill3DRect(x+5,y+14,40,25,false);
g.fill3DRect(x,y+37,50,10,false);
g.fillOval(x+10,y+15,25,25);
g.drawLine(x+25,y+25,x-8,y+25);
break;
default://其他情况暂不考虑
break;
}
}
@Override //字符输出时,该方法触发
public void keyTyped(KeyEvent e) {
}
@Override//有按键按下时,该方法触发
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 87){//前进
moveUp();
tank.direct = 0;
}else if(e.getKeyCode() == 83){
moveDown();
tank.direct = 2;
}else if(e.getKeyCode() == 65){
moveLeft();
tank.direct = 3;
}
else if(e.getKeyCode() == 68){
moveRight();
tank.direct = 1;
}else if(e.getKeyCode() == 32){
System.out.println("氮气加速~");
speek +=5;
}else{
System.out.println(e.getKeyCode());
}
repaint();
}
@Override//有按键松开时,该方法触发
public void keyReleased(KeyEvent e) {
}
public void moveUp(){
tank.y-=speek;
}
public void moveLeft(){
tank.x-=speek;
}
public void moveRight(){
tank.x+=speek;
}
public void moveDown(){
tank.y+=speek;
}
}
1.0完成
至此坦克大战1.0完成,后面学习了多线程再制造2.0版本,让敌人坦克也动起来。