Java游戏中延迟下载资源及调用示例

(源码依旧在Jar中)
 
 
老实说,延迟下载游戏资源及调用只是一种辅助手段,与游戏开发本身关系并不大,实质也无非只是文件下载及文件读取的混用。但考虑到上周有网友问及此 类问题,笔者觉得与其回邮件单独解释,倒不如写篇博文看起来更具体清晰,还能令大家帮助笔者斧正刊误,故成此文,仅供参考。  

一般来讲,我们之所以会需要通过下载方式加载游戏资源,无非是出于如下几种目的:

 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

* [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

* 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:[email]ceponline@yahoo.com.cn[/email]
* @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

* [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

* 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:[email]ceponline@yahoo.com.cn[/email]
* @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

* [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

* 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:[email]ceponline@yahoo.com.cn[/email]
* @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行走示例,画面如下图:
bf
(源码依旧在Jar中)
 


本文转自 cping 51CTO博客,原文链接:http://blog.51cto.com/cping1982/154277
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值