记得在红白机(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









































































































































Role.java










































































































































































启动类:Main.java

































































































































































运行效果如下图:

现在开始,我会在blog中逐步构建马里奥中的一关,有关心java pc游戏开发者敬请留意。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库