Java游戏程序设计教程 第4章 游戏的运行机制

第4章 游戏的运行机制

4.1游戏中的物理运动

4.1.1模拟匀速直线运动

package com.view.test;
import java.awt.*;
public class Test{
	public Test(){
	Frame f=new Frame("my app");//顶层容器
	MyPanel mp=new MyPanel();//绘图容器
	Thread t=new Thread(mp);
	t.start();
	f.setLocation(300,200);
	f.setSize(300,300);
	f.add(mp);
	f.setVisible(true);
	}
	public static void main(String args[]){
		new Test();
	}
}

class MyPanel extends Panel implements Runnable{
	private int x;
	private int y;
	private int dx;
	private int dy;
	
	public MyPanel(){
		x=50;
		y=50;
		dx=10;
		dy=10;
	}
	public void paint(Graphics g){ //重载paint方法
		g.setColor(Color.green);
		g.fillOval(x, y, 20, 20);
	}
	
	public void gameUpdate(){
		x=x+dx;
		y=y+dx;
	}
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			gameUpdate();
			repaint();
		}
	}
}

4.1.2 匀加速直线运动

class MyPanel extends Panel implements Runnable{
	private int x;
	private int y;
	private int dx;
	private int dy;
	private int dcx;
	private int dcy;
	
	public MyPanel(){
		x=50;
		y=50;
		dx=10;
		dy=10;
		dcx=1;
		dcy=2;
	}
	public void paint(Graphics g){ //重载paint方法
		g.setColor(Color.green);
		g.fillOval(x, y, 20, 20);
	}
	
	public void gameUpdate(){
		dx=dx+dcx;
		x=x+dx;
		dy=dy+dcy;
		y=y+dy;
	}

4.2 碰撞检测

//俄罗斯方块,判断两个物体的坐标范围是否存在交集
//边界检测,两个举证左上角坐标分别为(x1,y1)(x2,y2),
长度分别为w1和w2,长度分别为h1,h2  长宽不知谁大谁小
x1-x2<w2 && x2-x1<w1 &&
y1-y2<h2 && y2-y1<h1
//贪吃蛇,当两者的中心距离小于常数L
//中心检测
((x1+w1/2)-(x2+w2)/2))*((x1+w1/2)-(x2+w2/2))+
((y1+h1/2)-(y2+h2)/2))*((y1+h1/2)-(y2+h2/2))<L*L
//在窗口四周弹跳的小球
class MyPanel extends Panel implements Runnable{
	private int x;
	private int y;
	private int dx;
	private int dy;
	private int diameter;
	private int width;
	private int height;
	
	public MyPanel(){
		x=4;
		y=60;
		dx=20;
		dy=20;
		diameter=5;
		width=300;//这里有一个问题,为什么我用width=this.getWidth()得到的结果是零
		height=300;
	}
	public void paint(Graphics g){ //重载paint方法
		g.setColor(Color.green);
		while(true){
			for(int i=0;i<10000000;i++){}
			g.fillOval(x, y, 20, 20);
			gameUpdate();
		}
		
	}
	
	public void gameUpdate(){
		x+=dx;
		y+=dy;
		System.out.println(width);
		if((x<0)||(x>width-diameter)){
			dx=-dx;
		}
		if((y<0)||(y>height-diameter)){
			dy=-dy;
		}
	}

Java游戏程序设计教程 第4章 游戏的运行机制

4.3 传递控制命令

//小球本身会动,你按下键盘控制移动方向
package com.view.test;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Test{
	public Test(){
	Frame f=new Frame("my app");//顶层容器
	MyPanel mp=new MyPanel();//绘图容器
	Thread t=new Thread(mp);
    t.start();
	f.setLocation(300,200);
	f.setSize(300,300);
	f.add(mp);
	f.setVisible(true);
	}
	public static void main(String args[]){
		new Test();
	}
}

class MyPanel extends Panel implements Runnable,KeyListener{
	private int x;
	private int y;
	private int dx;
	private int dy;
	private int diameter;
	private int direction;
	private static final int SOUTH=0;
	private static final int NORTH=1;
	private static final int EAST=2;
	private static final int WEST=3;
	
	public MyPanel(){
		x=50;
		y=50;
		dx=10;
		dy=10;
		diameter=5;
		addKeyListener(this);//注册键盘监听器
	}
	public void paint(Graphics g){ //重载paint方法
		g.setColor(Color.green);
		g.fillOval(x, y, 20, 20);
	}
	
	public void gameUpdate(){
		switch(direction){
		case SOUTH:
			y=y+dy;
			break;
		case NORTH:
			y=y-dy;
			break;
		case EAST:
			x=x+dx;
			break;
		case WEST:
			x=x-dx;
			break;
		}
	}
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			gameUpdate();
			repaint();
		}
	}
	@Override
	public void keyTyped(KeyEvent e) {
		// TODO 自动生成的方法存根
		
	}
	@Override
	public void keyPressed(KeyEvent e) {
		// TODO 自动生成的方法存根
		System.out.println("KEYPREss");
		int keycode=e.getKeyCode();//获取键盘按下信息
		switch(keycode){
		case KeyEvent.VK_DOWN:
			direction=SOUTH;
			break;
		case KeyEvent.VK_UP:
			direction=NORTH;
			break;
		case KeyEvent.VK_LEFT:
			direction=WEST;
			break;
		case KeyEvent.VK_RIGHT:
			direction=EAST;
			break;
		}
		
	}
	@Override
	public void keyReleased(KeyEvent e) {
		// TODO 自动生成的方法存根
		
	}
}

4.4 游戏设计案例一:贪吃蛇游戏

4.4.1游戏整体设计

(1)主体框架类GameFrame
(2)游戏面板类GamePanel
(3)贪吃蛇类Snake
(4)食物类
Java游戏程序设计教程 第4章 游戏的运行机制

//程序的主体框架
//GamePanel类的程序框架
class GamePanel extends Panel implements Runnable,KeyListener{
	... ...
	private Snake sk;//建立贪吃蛇对象
	private Food fd;//建立失误对象
	public GamePanel(){
		//实例化一个贪吃蛇对象,并传递一个GamePanel对象引用
		sk=new Snake(this);
		//实例化一个食物对象,并传递一个GamePanel对象和一个Snake引用
		fd=new Food(this,sk);
	}
	... ...
	public void gameUpdate(){
		sk.update();//更新贪吃蛇的位置坐标
		bk.update();//更新食物的位置坐标
	}
	public void gameRender(){
		Image im=new creatImage(width,height);
		Graphics dbg=im.getGraphics();
		sk.draw(dbg);//在后备缓冲区绘制贪吃蛇的图像
		fd.draw(dbg,fd_location);//在后备缓冲区绘制食物的图形;
	}
	public void gamePaint(){
		Graphics g=this.getGraphics();
		g.drawImage(im, 0,0,null);//将后备缓冲区的内容在屏幕上显示出来
	}
	... ...
}
//Snake类的主体框架
public class Snake {
	......
	GamePanel gameP;
    public Snake(GamePanel gp){
    ......
    	gameP=gp;
    }
    public void update(){
    	......
    }
    public void draw(Graphics g){
    	......
    }
}
//Food类的程序框架
public class Food {
	... ...
	private GamePanel gameP;
	private Snake snk;
	public Food(GamePanel gp,Snake sk){
	... ...
		gameP=gp;//通过构造方法的参数来获取GamePanel对象的引用
		snk=sk;//通过构造方法的参数来获取Snake对象的引用
	}
	public void update(){//更新食物坐标的相关代码
		
	}
	public void draw(Graphics g){//绘制食物图形的相关代码
		
	}
}

源码书中没有给全,自己补充GamaPanel是自己补充的
刚打开会报错,之后游戏正常运行,不会使用后备缓冲手法,Image im =new createImage()没有办法使用。游戏比较关键的实现是使用了循环数组解决蛇身移动的问题,涉及到引用传递对象

源码如下:

package com.view.test;
import java.awt.*;
import java.awt.event.KeyListener;
public class Test{
	public Test(){
	Frame f=new Frame("my app");//顶层容器
	GamePanel gp=new GamePanel();//绘图容器
	Thread t=new Thread(gp);
    t.start();
	f.setLocation(300,200);
	f.setSize(300,300);
	f.add(gp);
	f.setVisible(true);
	}
	public static void main(String args[]){
		new Test();
	}
}

package com.view.test;
import java.awt.*;
import java.awt.event.*;

class GamePanel extends Panel implements Runnable,KeyListener{
	private int x;
	private int y;
	private int dx;
	private int dy;
	private int diameter;
	private int direction;
	int width;;
	int height;
	int fd_location;
	static final int SOUTH=0;
    static final int NORTH=1;
    static final int EAST=2;
    static final int WEST=3;
	
	private Snake sk;//建立贪吃蛇对象
	private Food fd;//建立失误对象
	
	public GamePanel(){
		addKeyListener(this);//注册键盘监听器
		width=300;
		height=300;
		//实例化一个贪吃蛇对象,并传递一个GamePanel对象引用
		sk=new Snake(this);
		//实例化一个食物对象,并传递一个GamePanel对象和一个Snake引用
		fd=new Food(this,sk);
	}
	public void paint(Graphics g){ //重载paint方法
	//	g.setColor(Color.green);
	//	g.fillOval(x, y, 20, 20);
		sk.draw(g);
		fd.draw(g);
	}
	
	public void gameUpdate(){
		switch(direction){
		case SOUTH:
			y=y+dy;
			break;
		case NORTH:
			y=y-dy;
			break;
		case EAST:
			x=x+dx;
			break;
		case WEST:
			x=x-dx;
			break;
		}
		
		sk.update();//更新贪吃蛇的位置坐标
		fd.update();//更新食物的位置坐标
	}

//	public void gameRender(){
//		Image im=new createImage(width,height);
//		Graphics dbg=im.getGraphics();
//		sk.draw(dbg);//在后备缓冲区绘制贪吃蛇的图像
//		fd.draw(dbg,fd_location);//在后备缓冲区绘制食物的图形;
//	}
//	
//	public void gamePaint(){
//		Graphics g=this.getGraphics();
//		g.drawImage(im, 0,0,null);//将后备缓冲区的内容在屏幕上显示出来
//	}
	@Override
public void run() {
		// TODO 自动生成的方法存根
		while(true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			gameUpdate();
			repaint();
		}
	}
	@Override
	public void keyTyped(KeyEvent e) {
		// TODO 自动生成的方法存根	
	}
	@Override
	public void keyPressed(KeyEvent e) {
		// TODO 自动生成的方法存根
		System.out.println("KEYPREss");
		int keycode=e.getKeyCode();//获取键盘按下信息
		switch(keycode){
		case KeyEvent.VK_DOWN:
			direction=SOUTH;
			break;
		case KeyEvent.VK_UP:
			direction=NORTH;
			break;
		case KeyEvent.VK_LEFT:
			direction=WEST;
			break;
		case KeyEvent.VK_RIGHT:
			direction=EAST;
			break;
		}
		
	}
	@Override
	public void keyReleased(KeyEvent e) {
		// TODO 自动生成的方法存根
		
	}
	public int getDirection() {
		return direction;
	}
	public void setDirection(int direction) {
		this.direction = direction;
	}
	
}

package com.view.test;
import java.awt.*;
public class Snake {
	GamePanel gameP;
	private Point[] body;//定义点类型数组,保存蛇蛇各个小球坐标
	public static final int MAXLENGTH=50;//蛇身最大长度
	private int head;//指示蛇头位置
	private int tail;//指示蛇尾位置
	public int length;//蛇身长度
	private int speed;//运行速度
	public int x;//蛇头小球的横坐标
	public int y;//蛇头小球的纵坐标
	public int diameter;//蛇身小球的直径
    public Snake(GamePanel gp){
    	gameP=gp;
    	body=new Point[MAXLENGTH];
    	head=-1;
    	tail=-1;
    	length=1;
    	speed=10;
    	x=50;
    	y=50;
    	diameter=10;
    }
    public void update(){
    	int direction=gameP.getDirection();//获取玩家的按键信息
    	switch(direction){
    	case GamePanel.SOUTH:
    		y+=speed;
    		break;
    	case GamePanel.NORTH:
    		y-=speed;
    		break;
    	case GamePanel.EAST:
    		x+=speed;
    		break;
    	case GamePanel.WEST:
    		x-=speed;
    		break;
    	}
    	head=(head+1) % body.length;//更新蛇头指针位置;
    	tail=(head+body.length-length+1) % body.length;//更新蛇尾指针位置
    	body[head]=new Point(x,y);
    }
    public void draw(Graphics g){
    	g.setColor(Color.blue);
    	if(length>1){
    		int i=tail;
    		while(i!=head){//循环绘制蛇身各个小球
    			g.fillOval(body[i].x, body[i].y, diameter, diameter);
    			i=(i+1) % body.length;
    		}
    	}
    	g.setColor(Color.red);//蛇头设置为红色
    	g.fillOval(body[head].x,body[head].y,diameter,diameter);
    }
}

package com.view.test;
import java.util.Random;
import java.awt.*;
public class Food {
	private GamePanel gameP;
	private Snake snk;
	public Point location;//食物的坐标
	public Point size;//食物方块的尺寸
	private Random rand;//随机类的对象
	public Food(GamePanel gp,Snake sk){
		gameP=gp;//通过构造方法的参数来获取GamePanel对象的引用
		snk=sk;//通过构造方法的参数来获取Snake对象的引用
		rand=new Random();
		//随机地出现在屏幕上某个位置
		location=new Point(Math.abs(rand.nextInt() % gameP.width),Math.abs(rand.nextInt() % gameP.height));
		size=new Point(sk.diameter,sk.diameter);
	}
	public void update(){//更新食物坐标的相关代码
		//碰撞检测,判定贪吃蛇是否迟到食物
		if((snk.x-location.x)*(snk.x-location.x)
				+(snk.y-location.y)*(snk.y-location.y)
				<snk.diameter*snk.diameter){
			location=new Point(Math.abs(rand.nextInt() % gameP.width),
					          Math.abs(rand.nextInt() % gameP.height));
			if(snk.length<Snake.MAXLENGTH){
				snk.length++;
			}	
		}
	}
	public void draw(Graphics g){//绘制食物图形的相关代码
		g.setColor(Color.black);
		g.fillRect(location.x, location.y, size.x, size.y);
	}
}

运行之后如下
Java游戏程序设计教程 第4章 游戏的运行机制
Java游戏程序设计教程 第4章 游戏的运行机制