记得在红白机(FC)年代,还刚刚上小学的我对马里奥、冒险岛、洛克人、魂斗罗等游戏几乎可说是痴迷,每天放学回家就是想去游戏,就是要通关,就是想和关底论个胜负高低。
许多年过去了,沧海桑田,FC曾经的荣耀早已不再,只留下我们对曾经少年时的点点记忆。即使当时在FC上看上去多么复杂,多么高不可攀的游戏,在当今,即使最普通的程序员都可以轻易实现。
本着向经典学习、向经典致敬的心情,我也准备用Java在PC机再现当年马里奥的风采。
下面我在代码中所演示的,是一个简单的ACT游戏动作及地图构成原型。
Map.java
package org.test.mario;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
/** *//**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:地图绘制及描述用类
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
*
* @author chenpeng
* @email:[email]ceponline@yahoo.com.cn[/email]
* @version 0.1
*/
public class Map ...{
// 在以前的blog文章中我介绍过,游戏开发中通常以数组描述地图
// 此处1描绘为一个障碍物,0描绘为一个可通行空间
final static private int[][] map = ...{
...{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
...{ 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } };
// 地面瓦片的宽度
final static private int TILE_SIZE = 32;
// 行
final static private int ROW = 15;
// 列
final static private int COL = 20;
/** *//**
* 构造函数
*
*/
public Map() ...{
}
public void draw(Graphics g) ...{
g.setColor(Color.ORANGE);
for (int i = 0; i < ROW; i++) ...{
for (int j = 0; j < COL; j++) ...{
switch (map[i][j]) ...{
case 1:
g.fillRect(tilesToPixels(j), tilesToPixels(i), TILE_SIZE,
TILE_SIZE);
break;
}
}
}
}
/** *//**
* 换算角色与地板的撞击,并返回Point用以描述新的x,y
*
* @param player
* @param newX
* @param newY
* @return
*/
public Point getTileHit(Role player, double newX, double newY) ...{
// 取最小的整数但不能小于自身,用以换算坐标
newX = Math.ceil(newX);
newY = Math.ceil(newY);
double fromX = Math.min(player.getX(), newX);
double fromY = Math.min(player.getY(), newY);
double toX = Math.max(player.getX(), newX);
double toY = Math.max(player.getY(), newY);
int fromTileX = pixelsToTiles(fromX);
int fromTileY = pixelsToTiles(fromY);
int toTileX = pixelsToTiles(toX + Role.WIDTH - 1);
int toTileY = pixelsToTiles(toY + Role.HEIGHT - 1);
// 返回Point,用以描述x,y坐标点
for (int x = fromTileX; x <= toTileX; x++) ...{
for (int y = fromTileY; y <= toTileY; y++) ...{
if (x < 0 || x >= COL) ...{
return new Point(x, y);
}
if (y < 0 || y >= ROW) ...{
return new Point(x, y);
}
if (map[y][x] == 1) ...{
return new Point(x, y);
}
}
}
return null;
}
/** *//**
* 将Tiles转为Pixels
*
* @param pixels
* @return
*/
public static int pixelsToTiles(double pixels) ...{
return (int) Math.floor(pixels / TILE_SIZE);
}
/** *//**
* 将Pixels转为Tiles
*
* @param pixels
* @return
*/
public static int tilesToPixels(int tiles) ...{
return tiles * TILE_SIZE;
}
}
Role.java
package org.test.mario;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
/** *//**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:角色描述及绘制用类
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
*
* @author chenpeng
* @email:[email]ceponline@yahoo.com.cn[/email]
* @version 0.1
*/
public class Role ...{
// 坐标的x,y
private double _x;
private double _y;
// 显示的x,_y
private double _vx;
private double _vy;
// 是否在平地
private boolean isFlat;
// 自定义的地图描述类
private Map _map;
// 角色宽
final static public int WIDTH = 32;
// 角色高
final static public int HEIGHT = 32;
// 移动速度
final static public int SPEED = 6;
// 跳越速度
final static public int JUMP_SPEED = 25;
/** *//**
* 构造函数,注入初始的角色x,y及map
*
* @param _x
* @param _y
* @param _map
*/
public Role(double x, double _y, Map _map) ...{
this._x = x;
this._y = _y;
this._map = _map;
_vx = 0;
_vy = 0;
isFlat = false;
}
/** *//**
* 停止动作
*
*/
public void stop() ...{
_vx = 0;
}
/** *//**
* 向左
*
*/
public void left() ...{
_vx = -SPEED;
}
/** *//**
* 向右
*
*/
public void right() ...{
_vx = SPEED;
}
/** *//**
* 跳越动作
*
*/
public void jump() ...{
// 当角色立于平地时
if (isFlat) ...{
_vy = -JUMP_SPEED;
isFlat = false;
}
}
/** *//**
* 变更位置
*
*/
public void update() ...{
// 加入偏差值
_vy += 1.0;
// 获得新的newX
double newX = _x + _vx;
// 获得地板x,_y
Point tile = _map.getTileHit(this, newX, _y);
// 不存在时则默认为newX
if (tile == null) ...{
_x = newX;
} else ...{
if (_vx > 0) ...{
_x = Map.tilesToPixels(tile.x) - WIDTH;
} else if (_vx < 0) ...{
_x = Map.tilesToPixels(tile.x + 1);
}
_vx = 0;
}
double newY = _y + _vy;
tile = _map.getTileHit(this, _x, newY);
if (tile == null) ...{
_y = newY;
isFlat = false;
} else ...{
if (_vy > 0) ...{
_y = Map.tilesToPixels(tile.y) - HEIGHT;
_vy = 0;
isFlat = true;
} else if (_vy < 0) ...{
_y = Map.tilesToPixels(tile.y + 1);
_vy = 0;
}
}
}
/** *//**
* 将角色绘制于指定Graphics上
*
* @param g
*/
public void draw(Graphics g) ...{
// 目前以一个红色方块代替
g.setColor(Color.RED);
g.fillRect((int) _x, (int) _y, WIDTH, HEIGHT);
}
public double getX() ...{
return _x;
}
public double getY() ...{
return _y;
}
}
启动类:Main.java
package org.test.mario;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Panel;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import org.loon.framework.game.p_w_picpath.Bitmap;
/** *//**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
*
* @author chenpeng
* @email:[email]ceponline@yahoo.com.cn[/email]
* @version 0.1
*/
public class Main extends Panel implements Runnable, KeyListener ...{
/** *//**
*
*/
private static final long serialVersionUID = 1L;
public static final int _WIDTH = 640;
public static final int _HEIGHT = 480;
private Map _map;
private Role _role;
private Thread _sleep;
private Image _screen = null;
private Graphics _graphics = null;
// 方向控制,由于是自然落体所以没有down
private boolean LEFT;
private boolean RIGHT;
private boolean UP;
public Main() ...{
setSize(_WIDTH, _HEIGHT);
setFocusable(true);
_screen = new Bitmap(_WIDTH, _HEIGHT).getImage();
_graphics = _screen.getGraphics();
_map = new Map();
_role = new Role(100, 32, _map);
// 监听窗体
addKeyListener(this);
// 启动线程
_sleep = new Thread(this);
_sleep.start();
}
/** *//**
* 运行
*/
public void run() ...{
while (true) ...{
//改变方向
if (LEFT) ...{
_role.left();
} else if (RIGHT) ...{
_role.right();
} else ...{
_role.stop();
}
if (UP) ...{
_role.jump();
}
_role.update();
repaint();
try ...{
Thread.sleep(20);
} catch (InterruptedException e) ...{
e.printStackTrace();
}
}
}
public void update(Graphics g) ...{
paint(g);
}
public void paint(Graphics g) ...{
_graphics.setColor(Color.BLACK);
_graphics.fillRect(0, 0, _WIDTH, _HEIGHT);
_map.draw(_graphics);
_role.draw(_graphics);
g.drawImage(_screen, 0, 0, null);
}
public void keyPressed(KeyEvent e) ...{
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) ...{
LEFT = true;
}
if (key == KeyEvent.VK_RIGHT) ...{
RIGHT = true;
}
if (key == KeyEvent.VK_UP) ...{
UP = true;
}
}
public void keyReleased(KeyEvent e) ...{
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) ...{
LEFT = false;
}
if (key == KeyEvent.VK_RIGHT) ...{
RIGHT = false;
}
if (key == KeyEvent.VK_UP) ...{
UP = false;
}
}
public void keyTyped(KeyEvent e) ...{
}
public static void main(String[] args) ...{
Frame frame = new Frame();
frame.setTitle("Java来做马里奥(1)—让精灵舞动");
frame.setSize(_WIDTH, _HEIGHT);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.add(new Main());
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() ...{
public void windowClosing(WindowEvent e) ...{
System.exit(0);
}
});
}
}
运行效果如下图:
现在开始,我会在blog中逐步构建马里奥中的一关,有关心java pc游戏开发者敬请留意。