第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;
}
}
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)食物类
//程序的主体框架
//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);
}
}
运行之后如下