Java游戏中延迟下载资源及调用示例
2009-04-29 19:16
507 查看
(源码依旧在Jar中)
源码下载地址:http://code.google.com/p/loon-simple/downloads/list
老实说,延迟下载游戏资源及调用只是一种辅助手段,与游戏开发本身关系并不大,实质也无非只是文件下载及文件读取的混用。但考虑到上周有网友问及此类问题,笔者觉得与其回邮件单独解释,倒不如写篇博文看起来更具体清晰,还能令大家帮助笔者斧正刊误,故成此文,仅供参考。
一般来讲,我们之所以会需要通过下载方式加载游戏资源,无非是出于如下几种目的:
1、精简游戏体积:
假设我做了个100MB的游戏,却非想把它宣传成仅有1MB的精巧程序,这时我该怎么办呢?
去欺骗用户,用大量复杂的技术名词忽悠他们说1MB和100MB等值吗?——用户不是傻子,至少不都是傻子,无论你的话术多么巧妙,也很难让绝大部分人都相信1MB和100MB是一样的。但大话已然出口,始终要想办法解决。
其实呢,在现有技术体系下,要搞定他们好简单的,只需将游戏初始界面混合下载器打包成1MB的文件发布,再“骗”他们下载执行,而后——就让他们慢慢等待系统加载剩下那99MB吧!毕竟没人说过这个游戏不需要额外的网络资源同步嘛……
2、网游资源的延迟加载需要:
目前的网络游戏——特别是网页游戏,为了尽可能的减少不必要的资源损耗,提高运行效率,大多数时候并不会一口气将所有资源都加载到游戏中,而是“大而化之,分而治之”,将游戏资源构建成一个个小小的资源包,仅仅在需要时,才或同步、或异步的加载到游戏中。这也正是我们在很多网游中所见到的,当角色过屏、读取新地图或遭遇新怪物时,画面会出现稍候字样或者部分马赛克乃至停顿的原因。
故此,通过网络适时地去加载需要的资源,几乎已成为网游开发中必不可少的技巧之一。
3、融入特殊的加密解密机制:
我们都知道,但凡是人所做出的程序,就没有人所不能破解掉的。但是——却很可能发生一个人做出来的程序,另一个人数年之内无法破解的现象。而当数年之后,另一个人破解出来时,这段程序却早已过气,白送都没人要了。
因此,当你极端的不想自己游戏被反向工程——尤其是想保护Java这种极好反编译的代码时,通过网络下载的另一种意义便显现出来了。你可以将下载的jar或class乃至其它种种保存到一个不同于执行目录的“隐秘”场所,并且无论密钥也好,特殊结构也罢,总之变着方的将资源加密混淆,就算混淆到连你自己都不知道这是什么东西也无所谓——能解释成字节码就好,最大限度的增加反向难度。而当你执行完毕,再一删了事——下次还可以再下嘛。这样做的话,虽然不能彻底杜绝代码被他人盗用,但,至少也可让反向我代码那哥们累掉层皮(^^)。
4、本地程序及资源合法性验证:
在大多数网络游戏中,为了保证用户不做出一些诸如使用外挂的“犯规”行为,是会对系统环境乃至封包数据进行合法性验证的,而一旦发现“非法”的东西存在,则会令“违法”玩家吊线或者干脆封号以示惩罚。
但这些验证,主要都只针对程序“外部”,即当“犯规”对象“不是我的游戏时”才能发挥功效,但万一“犯规”者“来自游戏本身时”或者“验证程序认为来自游戏本身时”,则变得无能为力,这也是为什么大多数网游都“内挂”泛滥的缘故。
幸运的是,Java程序由于其“天资所限”,是很难在虚拟机外部被攻破利用的,如果用Java***网游,原则上大可不必担心“内挂”问题——但,这也有个大前提,那就是在“内挂”运行于虚拟机之外时才行的通。
而如果“内挂”运行在虚拟机之内呢? 如果我的“内挂”是一小段插入原始游戏中的代码呢? 要知道,动态加载class,动态修改字节码,早就不算什么事情了。
这时,就需要校验Java程序的合法性。
本来要验证这种事情是比较耗费时间的,但如果我们善于利用每次下载资源(比较大的,比如过图或者游戏更新),如果不单单“下载”,更同步“上行”,利用空档同服务器校对本地Java程序的合法性及完整性,便能很大程度上避免这种无意义的校验时间浪费。这时有缺少的文件便添加,有多余的——也就是出现不该存在的Jar或class乃至原始字节码修改,便借机强行“咔”掉它,免得它“为祸人间”。
5、系统升级:
通常来说,大多数的程序是不可能一个版本用到底的,游戏也不例外,无论是功能的增加或者Bug修正都离不开系统的更新。为了避免每次升级都令用户面临重装的苦恼,通过网络下载更新资源并自动更新系统也就变得非常必要。
如何实现:
下面我给出一个简单的资源延迟下载Java实现示例,分别加载我以前博文中出现的两个示例程序,并运行其中之一。(PS:由于本例加载的资源为Jar,所以进行了动态类加载操作,当我们下载其它资源类型,比如仅包含图片的zip压缩文件时,下载的步骤还是一致,只不过要将操作换成读取压缩文件及加载压缩文件资源而已。)
DownloadTool.java(这是一个简单的下载工具类,内置有下载状态显示及下载条绘制)
DownloadCanvas.java(下载条及背景显示用画布)
Main.java(主类,用以启动此下载示例)
程序执行效果如下图所示:
下载完毕后后将自动加载并执行下载的Java2.5D行走示例,画面如下图:
http://p.blog.csdn.net/images/p_blog_csdn_net/cping1982/EntryImages/20090425/java_25d_20090424_01.jpg" border="0" >
(源码依旧在Jar中)
源码下载地址:http://code.google.com/p/loon-simple/downloads/list
源码下载地址:http://code.google.com/p/loon-simple/downloads/list
老实说,延迟下载游戏资源及调用只是一种辅助手段,与游戏开发本身关系并不大,实质也无非只是文件下载及文件读取的混用。但考虑到上周有网友问及此类问题,笔者觉得与其回邮件单独解释,倒不如写篇博文看起来更具体清晰,还能令大家帮助笔者斧正刊误,故成此文,仅供参考。
一般来讲,我们之所以会需要通过下载方式加载游戏资源,无非是出于如下几种目的:
1、精简游戏体积:
假设我做了个100MB的游戏,却非想把它宣传成仅有1MB的精巧程序,这时我该怎么办呢?
去欺骗用户,用大量复杂的技术名词忽悠他们说1MB和100MB等值吗?——用户不是傻子,至少不都是傻子,无论你的话术多么巧妙,也很难让绝大部分人都相信1MB和100MB是一样的。但大话已然出口,始终要想办法解决。
其实呢,在现有技术体系下,要搞定他们好简单的,只需将游戏初始界面混合下载器打包成1MB的文件发布,再“骗”他们下载执行,而后——就让他们慢慢等待系统加载剩下那99MB吧!毕竟没人说过这个游戏不需要额外的网络资源同步嘛……
2、网游资源的延迟加载需要:
目前的网络游戏——特别是网页游戏,为了尽可能的减少不必要的资源损耗,提高运行效率,大多数时候并不会一口气将所有资源都加载到游戏中,而是“大而化之,分而治之”,将游戏资源构建成一个个小小的资源包,仅仅在需要时,才或同步、或异步的加载到游戏中。这也正是我们在很多网游中所见到的,当角色过屏、读取新地图或遭遇新怪物时,画面会出现稍候字样或者部分马赛克乃至停顿的原因。
故此,通过网络适时地去加载需要的资源,几乎已成为网游开发中必不可少的技巧之一。
3、融入特殊的加密解密机制:
我们都知道,但凡是人所做出的程序,就没有人所不能破解掉的。但是——却很可能发生一个人做出来的程序,另一个人数年之内无法破解的现象。而当数年之后,另一个人破解出来时,这段程序却早已过气,白送都没人要了。
因此,当你极端的不想自己游戏被反向工程——尤其是想保护Java这种极好反编译的代码时,通过网络下载的另一种意义便显现出来了。你可以将下载的jar或class乃至其它种种保存到一个不同于执行目录的“隐秘”场所,并且无论密钥也好,特殊结构也罢,总之变着方的将资源加密混淆,就算混淆到连你自己都不知道这是什么东西也无所谓——能解释成字节码就好,最大限度的增加反向难度。而当你执行完毕,再一删了事——下次还可以再下嘛。这样做的话,虽然不能彻底杜绝代码被他人盗用,但,至少也可让反向我代码那哥们累掉层皮(^^)。
4、本地程序及资源合法性验证:
在大多数网络游戏中,为了保证用户不做出一些诸如使用外挂的“犯规”行为,是会对系统环境乃至封包数据进行合法性验证的,而一旦发现“非法”的东西存在,则会令“违法”玩家吊线或者干脆封号以示惩罚。
但这些验证,主要都只针对程序“外部”,即当“犯规”对象“不是我的游戏时”才能发挥功效,但万一“犯规”者“来自游戏本身时”或者“验证程序认为来自游戏本身时”,则变得无能为力,这也是为什么大多数网游都“内挂”泛滥的缘故。
幸运的是,Java程序由于其“天资所限”,是很难在虚拟机外部被攻破利用的,如果用Java***网游,原则上大可不必担心“内挂”问题——但,这也有个大前提,那就是在“内挂”运行于虚拟机之外时才行的通。
而如果“内挂”运行在虚拟机之内呢? 如果我的“内挂”是一小段插入原始游戏中的代码呢? 要知道,动态加载class,动态修改字节码,早就不算什么事情了。
这时,就需要校验Java程序的合法性。
本来要验证这种事情是比较耗费时间的,但如果我们善于利用每次下载资源(比较大的,比如过图或者游戏更新),如果不单单“下载”,更同步“上行”,利用空档同服务器校对本地Java程序的合法性及完整性,便能很大程度上避免这种无意义的校验时间浪费。这时有缺少的文件便添加,有多余的——也就是出现不该存在的Jar或class乃至原始字节码修改,便借机强行“咔”掉它,免得它“为祸人间”。
5、系统升级:
通常来说,大多数的程序是不可能一个版本用到底的,游戏也不例外,无论是功能的增加或者Bug修正都离不开系统的更新。为了避免每次升级都令用户面临重装的苦恼,通过网络下载更新资源并自动更新系统也就变得非常必要。
如何实现:
下面我给出一个简单的资源延迟下载Java实现示例,分别加载我以前博文中出现的两个示例程序,并运行其中之一。(PS:由于本例加载的资源为Jar,所以进行了动态类加载操作,当我们下载其它资源类型,比如仅包含图片的zip压缩文件时,下载的步骤还是一致,只不过要将操作换成读取压缩文件及加载压缩文件资源而已。)
DownloadTool.java(这是一个简单的下载工具类,内置有下载状态显示及下载条绘制)
package org.loon.game.simple.download; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class DownloadTool implements Runnable { final static private Font font = new Font("黑体", 0, 14); private Rectangle rectangle; final static private Image barImage = GraphicsUtils .loadImage("image/bar.png"); final static private int MAX_BUFFER_SIZE = 2048; final static private int DOWNLOADING = 0; final static private int PAUSED = 1; final static private int COMPLETE = 2; final static private int CANCELLED = 3; final static private int ERROR = 4; private Image backgroundBarImage; private Image progressBarImage; private Image dialogBarImage; private URL url; private int size; private int downloaded; private int contentLength; private int status; private DownloadListen listen; private String downloadName; /** * 创建进度条提示框 * * @param object * @param w * @param h * @param filtrate * @return */ private static Image createDialog(Image object, int w, int h, boolean filtrate) { Image barImage = null; if (filtrate) { barImage = GraphicsUtils.drawClipImage(object, 249, 30, 1, 57); barImage = GraphicsUtils.transBlackColor(barImage); } else { barImage = GraphicsUtils.drawClipImage(object, 249, 27, 0, 0); } Image imageLeft = GraphicsUtils.drawClipImage(barImage, 8, 27, 0, 0); Image imageRight = GraphicsUtils.drawClipImage(barImage, 8, 27, 241, 0); Image ImageCenter = GraphicsUtils .drawClipImage(barImage, 233, 27, 8, 0); ImageCenter = GraphicsUtils.getResize(ImageCenter, w, h); Graphics cg = ImageCenter.getGraphics(); cg.drawImage(imageLeft, 0, 0, null); cg.drawImage(imageRight, w - 8, 0, null); cg.dispose(); return ImageCenter; } /** * 构造函数,加载指定url * * @param url */ public DownloadTool(String url) { this.size = -1; this.downloaded = 0; this.status = DOWNLOADING; try { this.url = new URL(url); } catch (MalformedURLException e) { throw new RuntimeException(e); } } public DownloadTool(URL url) { this.url = url; this.size = -1; this.downloaded = 0; this.status = DOWNLOADING; } /** * 设定进度条所在方位 * * @param x * @param y * @param w * @param h */ public void setRectangle(int x, int y, int w, int h) { setRectangle(new Rectangle(x, y, w, h)); } /** * 设定进度条所在方位 * * @param rectangle */ public void setRectangle(Rectangle rectangle) { this.backgroundBarImage = GraphicsUtils.drawClipImage(barImage, 249, 27, 0, 0); this.backgroundBarImage = createDialog(backgroundBarImage, rectangle.width - 21, rectangle.height - 2, false); // this.progressBarImage = GraphicsUtils.drawClipImage(barImage, 27, 27, // 28, 28); // this.progressBarImage = GraphicsUtils.drawClipImage(barImage, 27, 27, // 56, 28); this.progressBarImage = GraphicsUtils.drawClipImage(barImage, 27, 27, 0, 28); this.dialogBarImage = createDialog(barImage, rectangle.width, rectangle.height, true); this.rectangle = rectangle; } /** * 返回当前url地址 * * @return */ public String getUrl() { return url.toString(); } /** * 返回当前下载文件总长度 * * @return */ public int getSize() { return size; } /** * 返回当前下载已完成长度 * * @return */ public int getLevel() { return downloaded; } /** * 返回当前进度 * * @return */ public int getProgress() { return (int) ((double) downloaded / (double) size * 100); } public int getStatus() { return status; } public void pause() { status = PAUSED; } public void resume() { status = DOWNLOADING; download(listen); } public void cancel() { status = CANCELLED; } private void error() { status = ERROR; } public int getContentLength() { return contentLength; } public void download(DownloadListen listen) { this.listen = listen; Thread thread = new Thread(this); thread.start(); } private String getFileName(URL url) { String fileName = url.getFile(); return fileName.substring(fileName.lastIndexOf('/') + 1); } public String getFileName() { return getFileName(url); } public boolean isExists() { return new File(getFileName()).exists(); } public String getDownloadName() { return downloadName; } public void setDownloadName(String downloadName) { this.downloadName = downloadName; } /** * 进行文件下载并显示进度 */ public void run() { if (!isExists()) { RandomAccessFile file = null; InputStream stream = null; try { HttpURLConnection connection = (HttpURLConnection) url .openConnection(); connection.setRequestProperty("Range", "bytes=" + downloaded + "-"); connection.connect(); if (connection.getResponseCode() / 100 != 2) { error(); } contentLength = connection.getContentLength(); if (contentLength < 1) { error(); } if (size == -1) { size = contentLength; } file = new RandomAccessFile(getFileName(url), "rw"); file.seek(downloaded); stream = connection.getInputStream(); while (status == DOWNLOADING) { byte buffer[]; if (size - downloaded > MAX_BUFFER_SIZE) { buffer = new byte[MAX_BUFFER_SIZE]; } else { buffer = new byte[size - downloaded]; } int read = stream.read(buffer); if (read == -1) { break; } file.write(buffer, 0, read); downloaded += read; listen.updateScreen(); } if (status == DOWNLOADING) { status = COMPLETE; listen.call(); } } catch (Exception e) { error(); } finally { if (file != null) { try { file.close(); file = null; } catch (Exception e) { } } if (stream != null) { try { stream.close(); stream = null; } catch (Exception e) { } } } } else { status = COMPLETE; listen.call(); } } /** * 绘制下载进度 * * @param g */ public synchronized void draw(Graphics g) { int progress = getProgress(); double ratio = (double) ((double) getLevel() / (double) getSize()); int offset = (int) (rectangle.width * ratio); g.drawImage(backgroundBarImage, rectangle.x + 19, rectangle.y, null); g.drawImage(progressBarImage, rectangle.x + 1, rectangle.y, offset + 20, rectangle.height - 2, null); g.drawImage(dialogBarImage, rectangle.x, rectangle.y, null); g.setFont(font); String mes = (getDownloadName() + ",已完成进度 : " + progress + " %") .intern(); FontMetrics fm = g.getFontMetrics(); int w = fm.stringWidth(mes); int h = fm.getHeight() + 2; g.setColor(Color.white); GraphicsUtils.setRenderingHints(g); g.drawString(mes, (rectangle.x + rectangle.width) / 2 - w / 2, rectangle.y + h); } }
DownloadCanvas.java(下载条及背景显示用画布)
package org.loon.game.simple.download; import java.awt.Canvas; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.image.BufferStrategy; import java.util.ArrayList; import java.util.List; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class DownloadCanvas extends Canvas implements DownloadListen { /** * */ private static final long serialVersionUID = 1L; private DownloadTool progress; private Graphics canvasGraphics = null; private BufferStrategy bufferStrategy; private Image backgroundImage = GraphicsUtils .loadImage("image/background.jpg"); private boolean initFlag; private Window window; final static List downloadList = new ArrayList(2); /** * 预定下载的文件 */ static { DownloadTool download1 = new DownloadTool( "http://loon-simple.googlecode.com/files/Java25DSimple.jar"); download1.setDownloadName("下载Java2.5D八法行走示例中"); DownloadTool download2 = new DownloadTool( "http://greenvm.googlecode.com/files/LocalOS_src.rar"); download2.setDownloadName("下载Java外挂入门示例中"); downloadList.add(download1); downloadList.add(download2); } public void call() { if (downloadList.size() == 0) { window.setVisible(false); window.dispose(); JarLoaderUtils.callJarMain("Java25DSimple.jar"); } else { progress = (DownloadTool) downloadList.remove(0); progress.setRectangle(rectangle); progress.download(this); } } final Rectangle rectangle; public void createBufferGraphics() { createBufferStrategy(2); bufferStrategy = getBufferStrategy(); } public DownloadCanvas(Window window, int width, int height) { int pw = 600; int ph = 27; this.rectangle = new Rectangle(width / 2 - pw / 2, height / 2 - ph / 2, pw, ph); this.window = window; this.call(); } public synchronized void updateScreen() { canvasGraphics = bufferStrategy.getDrawGraphics(); if (!initFlag) { canvasGraphics.drawImage(backgroundImage, 0, 0, null); initFlag = true; } else { progress.draw(canvasGraphics); } bufferStrategy.show(); canvasGraphics.dispose(); Toolkit.getDefaultToolkit().sync(); Thread.yield(); } }
Main.java(主类,用以启动此下载示例)
package org.loon.game.simple.download; import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; /** * Copyright 2008 - 2009 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loonframework * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class Main extends Frame { /** * */ private static final long serialVersionUID = 1L; public Main(String titleName, int width, int height) { this.setTitle(titleName); this.setBackground(Color.black); this.setPreferredSize(new Dimension(width + 5, height + 25)); this.requestFocus(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); DownloadCanvas progress = new DownloadCanvas(this,width,height); this.add(progress); this.pack(); progress.createBufferGraphics(); this.setResizable(false); this.setLocationRelativeTo(null); this.setIgnoreRepaint(true); this.setVisible(true); } public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new Main("下载游戏数据", 640, 480); } }); } }
程序执行效果如下图所示:
下载完毕后后将自动加载并执行下载的Java2.5D行走示例,画面如下图:
http://p.blog.csdn.net/images/p_blog_csdn_net/cping1982/EntryImages/20090425/java_25d_20090424_01.jpg" border="0" >
(源码依旧在Jar中)
源码下载地址:http://code.google.com/p/loon-simple/downloads/list
相关文章推荐
- Java游戏中延迟下载资源及调用示例 推荐
- Java游戏中延迟下载资源及调用示例
- Java游戏中延迟下载资源及调用示例
- JAVA中properties资源调用简单示例
- java、javascript实现附件下载示例
- Eclipse、MinGW、JNI编写C++生成dll, Java端调用的完整示例(附java.lang.UnsatisfiedLinkError解决方法)
- MyGame--java语言编写的打飞机游戏(附源码下载)
- Java/JSP中调用SQL Server存储过程完整示例
- Java/JSP中调用SQL Server存储过程完整示例
- Java 下载 HTTP 资源保存到本地
- JAVA下调用Native API的利器——JNative(附调用win32 api--ShellExecuteEx()示例)
- 游戏中C++调用Java
- java调用百度定位api服务获取地理位置示例
- 中科院分词(ICTCLAS)Java调用接口下载
- 用java程序下载远程资源文件
- eoLinker-API_Shop_短信服务接口-调用示例代码,支持PHP、Python、Java等语言
- 调用阿里云API 的demo示例(java/python)
- JAVA小白启蒙篇:第一个SSM框架搭建示例(附源码下载)
- C++ DirectX 游戏开发初级视频教程 17 资源下载链接
- java调用webservice示例(转)