記得在紅白機(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:ceponline@yahoo.com.cn
* @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:ceponline@yahoo.com.cn
* @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.image.Bitmap;

/**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
*
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @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游戲開發者敬請留意。
許多年過去了,滄海桑田,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:ceponline@yahoo.com.cn
* @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:ceponline@yahoo.com.cn
* @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.image.Bitmap;
/**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:
* </p>
* <p>
* Copyright: Copyright (c) 2008
* </p>
* <p>
* Company: LoonFramework
* </p>
*
* @author chenpeng
* @email:ceponline@yahoo.com.cn
* @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游戲開發者敬請留意。


浙公網安備 33010602011771號