<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Java &#38; Game &#187; 游戏</title>
	<atom:link href="http://www.javagg.com/archives/category/%e6%b8%b8%e6%88%8f/feed" rel="self" type="application/rss+xml" />
	<link>http://www.javagg.com</link>
	<description>java&#38;j2me</description>
	<lastBuildDate>Sat, 02 Jul 2011 08:59:18 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>愤怒的小鸟电脑版下载</title>
		<link>http://www.javagg.com/archives/831</link>
		<comments>http://www.javagg.com/archives/831#comments</comments>
		<pubDate>Sat, 08 Jan 2011 10:43:12 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[我的日记]]></category>
		<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=831</guid>
		<description><![CDATA[www.javagg.com/AngryBirds.rar
]]></description>
			<content:encoded><![CDATA[<p>www.javagg.com/AngryBirds.rar</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/831/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>“钉子户”游戏</title>
		<link>http://www.javagg.com/archives/821</link>
		<comments>http://www.javagg.com/archives/821#comments</comments>
		<pubDate>Mon, 11 Oct 2010 15:55:24 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=821</guid>
		<description><![CDATA[搞笑的钉子户游戏
搞笑的钉子户游戏
搞笑的钉子户游戏
                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                 


搞笑的钉子户游戏
]]></description>
			<content:encoded><![CDATA[<p>搞笑的钉子户游戏<br />
搞笑的钉子户游戏<br />
搞笑的钉子户游戏<br />
                                                                                                                                                                                                                                                   <span id="more-821"></span></p>
<p>                                                                                                                                                                                                                                 <br />
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="600" height="450" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="data" value="http://flash.7k7k.com/fl_8/20100820/dingzihu.swf" /><param name="src" value="http://flash.7k7k.com/fl_8/20100820/dingzihu.swf" /><embed type="application/x-shockwave-flash" width="600" height="450" src="http://flash.7k7k.com/fl_8/20100820/dingzihu.swf" data="http://flash.7k7k.com/fl_8/20100820/dingzihu.swf"></embed></object></p>
<p><a href="http://flash.7k7k.com/fl_8/20100820/dingzihu.swf"><br />
搞笑的钉子户游戏</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/821/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>motionWelder动作编辑器封装使用</title>
		<link>http://www.javagg.com/archives/805</link>
		<comments>http://www.javagg.com/archives/805#comments</comments>
		<pubDate>Fri, 09 Jul 2010 08:12:11 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[j2me]]></category>
		<category><![CDATA[游戏]]></category>
		<category><![CDATA[动作编辑器]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=805</guid>
		<description><![CDATA[motionWelder是一位印度先生写的动作编辑器.曾经使用时向作者提出过一些BUG.人家都修改了.可谓是好心人啊.现在这软件也越来越强大了.
下面是我使用中的一些封装..
他有两种方式一种是MSimpleAnimationPlayer.另一种是implements MSprite.前一种是简单动画.后一种自己实现MSprite接口.可能扩展
比如MSimpleAnimationPlayer.我将成封装成Simple_Animation类
package pupaClan;
//简单动画类
import javax.microedition.lcdui.Graphics;
import mgo.common.Gamedata;
import com.studio.motionwelder.MSimpleAnimationPlayer;
import com.studio.motionwelder.MSpriteData;
import com.studio.motionwelder.MSpriteLoader;
public class Simple_Animation {
 public pupaClan_canvas canvas;
 public byte animation_Id;
 public final static byte loop_Once=-1;//一次
 public final static byte loop_While=0;//无限
 MSpriteData AnimationData=null;
    MSimpleAnimationPlayer  player=null;
    //构造方法
    public Simple_Animation(pupaClan_canvas canvas,String path,int animation_Id,int spriteX,int spriteY,boolean splitImageClips,int loopOffset){
  try {
   this.canvas=canvas;
   this.animation_Id=(byte) animation_Id;
   AnimationData=MSpriteLoader.loadMSprite(path,splitImageClips,ResourceLoader.getInstance());
   player = new MSimpleAnimationPlayer(AnimationData,spriteX,spriteY);
   player.setAnimation(this.animation_Id);
   player.setLoopOffset(loopOffset);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
    [...]]]></description>
			<content:encoded><![CDATA[<p>motionWelder是一位印度先生写的动作编辑器.曾经使用时向作者提出过一些BUG.人家都修改了.可谓是好心人啊.现在这软件也越来越强大了.</p>
<p>下面是我使用中的一些封装..</p>
<p>他有两种方式一种是MSimpleAnimationPlayer.另一种是implements MSprite.前一种是简单动画.后一种自己实现MSprite接口.可能扩展<span id="more-805"></span></p>
<p>比如MSimpleAnimationPlayer.我将成封装成Simple_Animation类</p>
<p>package pupaClan;<br />
//简单动画类</p>
<p>import javax.microedition.lcdui.Graphics;</p>
<p>import mgo.common.Gamedata;</p>
<p>import com.studio.motionwelder.MSimpleAnimationPlayer;<br />
import com.studio.motionwelder.MSpriteData;<br />
import com.studio.motionwelder.MSpriteLoader;</p>
<p>public class Simple_Animation {<br />
 public pupaClan_canvas canvas;<br />
 public byte animation_Id;<br />
 public final static byte loop_Once=-1;//一次<br />
 public final static byte loop_While=0;//无限<br />
 MSpriteData AnimationData=null;<br />
    MSimpleAnimationPlayer  player=null;<br />
    //构造方法<br />
    public Simple_Animation(pupaClan_canvas canvas,String path,int animation_Id,int spriteX,int spriteY,boolean splitImageClips,int loopOffset){<br />
  try {<br />
   this.canvas=canvas;<br />
   this.animation_Id=(byte) animation_Id;<br />
   AnimationData=MSpriteLoader.loadMSprite(path,splitImageClips,ResourceLoader.getInstance());<br />
   player = new MSimpleAnimationPlayer(AnimationData,spriteX,spriteY);<br />
   player.setAnimation(this.animation_Id);<br />
   player.setLoopOffset(loopOffset);<br />
  } catch (Exception e) {<br />
   e.printStackTrace();<br />
  }<br />
 }<br />
    public void run(){//逻辑<br />
     player.update();<br />
    }<br />
    public int getAnimation(){//得到当前动画<br />
    return player.getAnimation();<br />
   }<br />
   public int getCurrentFrame(){//得到当前动画桢<br />
    return player.getCurrentFrame();<br />
   }<br />
    public boolean end(){//是否结束<br />
     return (player.getCurrentFrame()==(player.getFrameCount()-1));<br />
    }<br />
    public void paint(Graphics g){//绘制<br />
  g.setClip(0, 0, Gamedata.width, Gamedata.height);<br />
  player.drawFrame(g);<br />
 }<br />
}<br />
简单的动画就可以用他来生成一个.比如</p>
<p>startup_Flash=new Simple_Animation(this,&#8221;/anu/startup/pupastartup.anu&#8221;,0, 88, 190,false,Simple_Animation.loop_Once);</p>
<p>第一个参数是把CANVAS画布.第二个是用motionwelder生成的anu文件.第三个表示动画中的第几个(因为我的动画有好几个).第四,五个是坐标.第六个表示此动画要不要旋转.第7个是只播放一次.</p>
<p>第二种动画是implements MSprite实现的.</p>
<p>下面是我RPG游戏中一个实例</p>
<p>package pupaClan;<br />
//战斗怪类<br />
import javax.microedition.lcdui.Graphics;<br />
import com.studio.motionwelder.MPlayer;<br />
import com.studio.motionwelder.MSprite;<br />
import com.studio.motionwelder.MSpriteAnimationPlayer;<br />
import com.studio.motionwelder.MSpriteData;<br />
import com.studio.motionwelder.MSpriteLoader;<br />
import mgo.common.Gamedata;<br />
import mgo.common.Obj;<br />
import mgo.common.Timer;<br />
import mgo.common.Tools;<br />
import mgo.common.MediaPlayer;<br />
public class Monster extends Obj implements MSprite{<br />
 public pupaClan_canvas canvas;<br />
 /***Direction***/<br />
 public static final byte DIR_RIGHT = 0;<br />
 public static final byte DIR_LEFT = 1;<br />
 /** Sprite */<br />
 public  MPlayer player;<br />
 private MSpriteData spriteData;<br />
 public int hp,max_hp;<br />
 public int attack;<br />
 public int recovery;<br />
 public int exp;<br />
 public int coin;<br />
 public int level;<br />
 public String name;<br />
    //密锋动画初始化X偏移量<br />
 public final static int Bee_Animation_OPP_X=18;<br />
    //密锋动画初始化Y最大偏移量<br />
// public final static int Bee_Animation_MAX_OPP_Y=39;<br />
    //蜘蛛动画初始化X偏移量<br />
 public final static int Spider_Animation_OPP_X=14;<br />
    //蜘蛛动画初始化Y最大偏移量<br />
// public final static int Spider_Animation_MAX_OPP_Y=25;<br />
    //兔子动画初始化X偏移量<br />
 public final static int Coney_Animation_OPP_X=12;<br />
    //兔子动画初始化Y最大偏移量<br />
// public final static int Coney_Animation_MAX_OPP_Y=24;<br />
    //野鹿动画初始化X偏移量<br />
 public final static int Deer_Animation_OPP_X=14;<br />
    //野鹿动画初始化Y最大偏移量<br />
// public final static int Deer_Animation_MAX_OPP_Y=38;<br />
    //毒蜘蛛动画初始化X偏移量<br />
 public final static int ToxinSpider_Animation_OPP_X=21;<br />
    //毒蜘蛛动画初始化Y最大偏移量<br />
// public final static int ToxinSpider_Animation_MAX_OPP_Y=49;<br />
    //灰鹿动画初始化X偏移量<br />
 public final static int BlackDeer_Animation_OPP_X=16;<br />
    //灰鹿动画初始化Y最大偏移量<br />
// public final static int BlackDeer_Animation_MAX_OPP_Y=51;<br />
    //蛇动画初始化X偏移量<br />
 public final static int Snake_Animation_OPP_X=16;<br />
    //蛇动画初始化Y最大偏移量<br />
// public final static int Snake_Animation_MAX_OPP_Y=28;<br />
    //野狼动画初始化X偏移量<br />
 public final static int Wolf_Animation_OPP_X=23;<br />
    //野狼动画初始化Y最大偏移量<br />
// public final static int Wolf_Animation_MAX_OPP_Y=28;<br />
    //鹰动画初始化X偏移量<br />
// public final static int Eagle_Animation_OPP_X=14;<br />
    //鹰动画初始化Y最大偏移量<br />
// public final static int Eagle_Animation_MAX_OPP_Y=40;<br />
    //食人花初始化X偏移量<br />
 public final static int Flower_Animation_OPP_X=17;<br />
    //食人花初始化Y最大偏移量<br />
// public final static int Flower_Animation_MAX_OPP_Y=40;<br />
    //穿山甲初始化X偏移量<br />
 public final static int Pangolin_Animation_OPP_X=29;<br />
    //穿山甲初始化Y最大偏移量<br />
// public final static int Pangolin_Animation_MAX_OPP_Y=23;<br />
    //树怪初始化X偏移量<br />
   //public final static int TreeMonster_Animation_OPP_X=19;<br />
    //树怪初始化Y最大偏移量<br />
// public final static int TreeMonster_Animation_MAX_OPP_Y=38;<br />
    //雕初始化X偏移量<br />
 public final static int Vulture_Animation_OPP_X=25;<br />
    //雕初始化Y最大偏移量<br />
// public final static int Vulture_Animation_MAX_OPP_Y=54;<br />
    //霸王花初始化X偏移量<br />
 public final static int OverloadFlower_Animation_OPP_X=22;<br />
    //霸王花初始化Y最大偏移量<br />
// public final static int OverloadFlower_Animation_MAX_OPP_Y=45;<br />
    //眼镜蛇王初始化X偏移量<br />
 public final static int Cobra_Animation_OPP_X=21;<br />
    //眼镜蛇王初始化Y最大偏移量<br />
// public final static int Cobra_Animation_MAX_OPP_Y=34;<br />
    //千年树妖初始化X偏移量<br />
 //public final static int KiloTreeMonster_Animation_OPP_X=19;<br />
    //千年树妖初始化Y最大偏移量<br />
// public final static int KiloTreeMonster_Animation_MAX_OPP_Y=51;<br />
    //血狼初始化X偏移量<br />
 public final static int BloodWolf_Animation_OPP_X=25;<br />
    //血狼初始化Y最大偏移量<br />
// public final static int BloodWolf_Animation_MAX_OPP_Y=33;<br />
 <br />
    //人熊初始化X偏移量<br />
 public final static int Bear_Animation_OPP_X=27;<br />
    //人熊初始化Y最大偏移量<br />
// public final static int Bear_Animation_MAX_OPP_Y=65;<br />
    //大狼初始化X偏移量<br />
 public final static int BigWolf_Animation_OPP_X=17;<br />
    //大狼初始化Y最大偏移量<br />
// public final static int BigWolf_Animation_MAX_OPP_Y=48;<br />
    //剑齿虎初始化X偏移量<br />
 public final static int Tiger_Animation_OPP_X=43;<br />
    //剑齿虎初始化Y最大偏移量<br />
// public final static int Tiger_Animation_MAX_OPP_Y=51;<br />
    /** Animations */<br />
 public final static byte MON_SLEEP=0;//站着不动<br />
 public final static byte MON_KILL1=1;//普通攻击<br />
 public final static byte MON_INJURED1=2;//被攻击1,黄色闪光（小）<br />
 public final static byte MON_DIE=3;//死亡<br />
 public final static byte MON_KILL2=4;//非普通攻击,BOSS用<br />
 public final static byte MON_KILL3=5;//非普通攻击,BOSS大狼用<br />
 <br />
    private int animation;<br />
    public boolean 怪物操作;<br />
    public Timer timer=new Timer();<br />
    public Monster(pupaClan_canvas canvas,byte type,int spriteX,int spriteY){<br />
  try{<br />
   this.canvas=canvas;<br />
   this.type=type;<br />
   String path = null;<br />
   int Animation_OPP_X = 0;<br />
   switch(this.type){<br />
       case 蜜蜂:<br />
        setLevel(Tools.getRand(1,3));<br />
        Animation_OPP_X=Bee_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/Bee.anu&#8221;;<br />
         name=&#8221;蜜蜂&#8221;;<br />
    break;<br />
       case 蜘蛛:<br />
        setLevel(Tools.getRand(1,3));<br />
        Animation_OPP_X=Spider_Animation_OPP_X;<br />
        path=&#8221;/anu/monster/Spider.anu&#8221;;<br />
        name=&#8221;蜘蛛&#8221;;<br />
       break;<br />
       case 兔子:<br />
        setLevel(Tools.getRand(3,7));<br />
        Animation_OPP_X=Coney_Animation_OPP_X;<br />
        path=&#8221;/anu/monster/Coney.anu&#8221;;<br />
        name=&#8221;兔子&#8221;;<br />
       break;<br />
       case 野鹿:<br />
        setLevel(Tools.getRand(5,10));<br />
        Animation_OPP_X=Deer_Animation_OPP_X;<br />
        path=&#8221;/anu/monster/Deer.anu&#8221;;<br />
        name=&#8221;野鹿&#8221;;<br />
       break;<br />
       case 灰兔:<br />
        setLevel(Tools.getRand(10,13));<br />
        Animation_OPP_X=Coney_Animation_OPP_X;<br />
        path=&#8221;/anu/monster/hui-Coney.anu&#8221;;<br />
        name=&#8221;灰兔&#8221;;<br />
       break;<br />
       case 毒蜘蛛:<br />
        setLevel(Tools.getRand(11,15));<br />
        Animation_OPP_X=ToxinSpider_Animation_OPP_X;<br />
        path=&#8221;/anu/monster/ToxinSpider.anu&#8221;;<br />
        name=&#8221;毒蜘蛛&#8221;;<br />
       break;<br />
       case 蜂王:<br />
        setLevel(Tools.getRand(15,20));<br />
        Animation_OPP_X=Bee_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/king-Bee.anu&#8221;;<br />
         name=&#8221;蜂王&#8221;;<br />
       break;<br />
       case 灰鹿:<br />
        setLevel(Tools.getRand(18,22));<br />
        Animation_OPP_X=BlackDeer_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/BlackDeer.anu&#8221;;<br />
         name=&#8221;灰鹿&#8221;;<br />
       break;<br />
       case 蛇:<br />
        setLevel(Tools.getRand(20,25));<br />
        Animation_OPP_X=Snake_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/Snake.anu&#8221;;<br />
         name=&#8221;蛇&#8221;;<br />
       break;<br />
       case 野狼:<br />
        setLevel(Tools.getRand(22,25));<br />
        Animation_OPP_X=Wolf_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/Wolf.anu&#8221;;<br />
         name=&#8221;野狼&#8221;;<br />
       break;<br />
       case 青狼:<br />
        setLevel(Tools.getRand(38,40));<br />
        Animation_OPP_X=Wolf_Animation_OPP_X;<br />
         path=&#8221;/anu/monster/Wolf1.anu&#8221;;<br />
         name=&#8221;青狼&#8221;;<br />
       break;<br />
       case 黑鹿:<br />
        setLevel(Tools.getRand(25,28));<br />
           Animation_OPP_X=BlackDeer_Animation_OPP_X;<br />
            path=&#8221;/anu/monster/HaDeer.anu&#8221;;<br />
            name=&#8221;黑鹿&#8221;;<br />
          break;<br />
       case 食人花:<br />
        setLevel(Tools.getRand(28,32));<br />
           Animation_OPP_X=Flower_Animation_OPP_X;<br />
            path=&#8221;/anu/monster/Flower.anu&#8221;;<br />
            name=&#8221;食人花&#8221;;<br />
          break;<br />
       case 穿山甲:<br />
        setLevel(Tools.getRand(30,35));<br />
           Animation_OPP_X=Pangolin_Animation_OPP_X;<br />
            path=&#8221;/anu/monster/Pangolin.anu&#8221;;<br />
            name=&#8221;穿山甲&#8221;;<br />
       break;<br />
       case 穿山甲王:<br />
        setLevel(Tools.getRand(43,46));<br />
           Animation_OPP_X=Pangolin_Animation_OPP_X;<br />
            path=&#8221;/anu/monster/Pangolin1.anu&#8221;;<br />
            name=&#8221;穿山甲王&#8221;;<br />
       break;<br />
       case 雕:<br />
        setLevel(Tools.getRand(35,40));<br />
           Animation_OPP_X=Vulture_Animation_OPP_X;<br />
            path=&#8221;/anu/monster/Vulture.anu&#8221;;<br />
            name=&#8221;雕&#8221;;<br />
       break;<br />
       case 霸王花:<br />
        setLevel(Tools.getRand(40,45));<br />
              Animation_OPP_X=OverloadFlower_Animation_OPP_X;<br />
               path=&#8221;/anu/monster/OverloadFlower.anu&#8221;;<br />
               name=&#8221;霸王花&#8221;;<br />
          break;<br />
       case 眼镜蛇王:<br />
        setLevel(Tools.getRand(42,47));<br />
              Animation_OPP_X=Cobra_Animation_OPP_X;<br />
               path=&#8221;/anu/monster/Cobra.anu&#8221;;<br />
               name=&#8221;眼镜蛇王&#8221;;<br />
          break;<br />
       case 血狼:<br />
        setLevel(Tools.getRand(45,50));<br />
                 Animation_OPP_X=BloodWolf_Animation_OPP_X;<br />
                  path=&#8221;/anu/monster/BloodWolf.anu&#8221;;<br />
                  name=&#8221;血狼&#8221;;<br />
             break;<br />
       case 人熊:<br />
        hp=max_hp=480;attack=95;recovery=50;exp=632;coin=500;<br />
                 Animation_OPP_X=Bear_Animation_OPP_X;<br />
                  path=&#8221;/anu/monster/Bear.anu&#8221;;<br />
                  name=&#8221;人熊&#8221;;<br />
             break;<br />
       case 大狼:<br />
        hp=max_hp=850;attack=208;recovery=108;exp=2571;coin=1280;<br />
                 Animation_OPP_X=BigWolf_Animation_OPP_X;<br />
                  path=&#8221;/anu/monster/BigWolf.anu&#8221;;<br />
                  name=&#8221;大狼&#8221;;<br />
             break;<br />
       case 剑齿虎:<br />
        hp=max_hp=1120;attack=309;recovery=160;exp=4720;coin=2000;<br />
                 Animation_OPP_X=Tiger_Animation_OPP_X;<br />
                  path=&#8221;/anu/monster/Tiger.anu&#8221;;<br />
                  name=&#8221;剑齿虎&#8221;;<br />
             break;<br />
   }<br />
   this.spriteX=Animation_OPP_X+spriteX;<br />
   this.spriteY=spriteY;<br />
   spriteData=MSpriteLoader.loadMSprite(path,false,ResourceLoader.getInstance());<br />
   player = new MSpriteAnimationPlayer(spriteData,this);<br />
   setAnimation(MON_SLEEP);<br />
   direction = DIR_RIGHT;<br />
  }catch (Exception e){<br />
   e.printStackTrace();<br />
  }<br />
 }<br />
 public void run(){<br />
  if(Obj.isMonsterBoss(this.type)){//BOSS<br />
   switch(this.type){<br />
       case Obj.人熊:<br />
       case Obj.剑齿虎:<br />
        switch (animation) {<br />
         case MON_SLEEP:<br />
          if(canvas.battle_hero.主角操作完成 &amp; timer.arrive_Time(canvas.battle_hero.延迟时间)){<br />
           if(hp&lt;(max_hp&gt;&gt;1)){//自身生命少于一半<br />
               setAnimation(Tools.getOnlyRand(MON_KILL1, MON_KILL2));<br />
              }else{<br />
               setAnimation(MON_KILL1);<br />
              }<br />
          }<br />
         break;<br />
            case MON_KILL1:<br />
             if(!timer.arrive_Time(canvas.battle_hero.延迟时间))return;<br />
             if(getCurrentFrame()==5)canvas.battle_hero.setAnimation(BattleHero.HERO_INJURED1);<br />
            break;<br />
            case MON_KILL2:<br />
             if(!timer.arrive_Time(canvas.battle_hero.延迟时间))return;<br />
             if(getCurrentFrame()==6)canvas.battle_hero.setAnimation(BattleHero.HERO_INJURED1);<br />
            break;<br />
        }<br />
    break;<br />
       case Obj.大狼:<br />
        switch (animation) {<br />
         case MON_SLEEP:<br />
          if(canvas.battle_hero.主角操作完成 &amp; timer.arrive_Time(canvas.battle_hero.延迟时间)){<br />
           if(hp&lt;(max_hp&gt;&gt;1)){//自身生命少于一半<br />
               setAnimation(Tools.getOnlyRand(MON_KILL1, MON_KILL2, MON_KILL3));<br />
              }else{<br />
               setAnimation(MON_KILL1);<br />
              }<br />
          }<br />
         break;<br />
            case MON_KILL1:<br />
             if(!timer.arrive_Time(canvas.battle_hero.延迟时间))return;<br />
             if(getCurrentFrame()==5)canvas.battle_hero.setAnimation(BattleHero.HERO_INJURED1);<br />
            break;<br />
            case MON_KILL2:<br />
            case MON_KILL3:<br />
             if(!timer.arrive_Time(canvas.battle_hero.延迟时间))return;<br />
             if(getCurrentFrame()==5)canvas.battle_hero.setAnimation(BattleHero.HERO_INJURED1);<br />
            break;<br />
        }<br />
    break;<br />
   }<br />
  }else{//小怪<br />
   switch (animation){<br />
       case MON_SLEEP:<br />
        if(canvas.battle_hero.主角操作完成 &amp; timer.arrive_Time(canvas.battle_hero.延迟时间))setAnimation(MON_KILL1);<br />
       break;<br />
          case MON_KILL1:<br />
           if(!timer.arrive_Time(canvas.battle_hero.延迟时间))return;<br />
           if(getCurrentFrame()==4)canvas.battle_hero.setAnimation(BattleHero.HERO_INJURED1);<br />
          break;<br />
      }<br />
  }<br />
  player.update();<br />
  }<br />
 public void endOfAnimation(){<br />
        switch (animation){<br />
              case MON_KILL1:<br />
              case MON_KILL2:<br />
              case MON_KILL3:<br />
               setAnimation(MON_SLEEP);<br />
               canvas.battle_hero.主角操作完成=false;<br />
           break;<br />
        case MON_INJURED1://普通攻击<br />
         check_Die(canvas.common_hero.attack+(canvas.返回主角身上装备(Equip.攻击装备)!=null?((Equip)canvas.返回主角身上装备(Equip.攻击装备)).getAttack():0)-recovery);<br />
        break;<br />
        case MON_DIE:<br />
         if(!canvas.battle_Complete){<br />
           canvas.mediaPlayer.playSound(&#8220;/sound/battlesuccess.mid&#8221;, MediaPlayer.play_Once);//播放音乐<br />
          checkTaskFromBattle();//更新任务<br />
          /****增加主角经验,金币,检测主角是否升级****/<br />
          canvas.common_hero.coin=canvas.common_hero.coin+coin;<br />
          canvas.common_hero.exp=canvas.common_hero.exp+exp;<br />
          canvas.hero_IsUpgrade=canvas.common_hero.checkLevel();<br />
          canvas.battle_Complete=true;<br />
          canvas.cursor_y=0;<br />
          canvas.backup_time=System.currentTimeMillis();<br />
          canvas.timer.init_Time();<br />
         }<br />
        break; <br />
  }<br />
  }<br />
 private void check_Die(int injure){//根据injure值检测怪物是否死亡<br />
  if(injure&lt;0)injure=0;<br />
        hp=hp-injure;<br />
        if(hp&lt;0)hp=0;<br />
        if(hp&gt;0){//怪没有死<br />
         if(Obj.isMonsterBoss(this.type)){//BOSS<br />
       switch(this.type){<br />
           case Obj.人熊:<br />
           case Obj.剑齿虎:<br />
            if(hp&lt;(max_hp&gt;&gt;1)){//自身生命少于一半<br />
             setAnimation(Tools.getOnlyRand(MON_KILL1, MON_KILL2));<br />
            }else{<br />
             setAnimation(MON_KILL1);<br />
            }<br />
            canvas.主角操作完毕并设置延迟时间(700l);<br />
        break;<br />
           case Obj.大狼:<br />
            if(hp&lt;(max_hp&gt;&gt;1)){//自身生命少于一半<br />
             setAnimation(Tools.getOnlyRand(MON_KILL1, MON_KILL2, MON_KILL3));<br />
            }else{<br />
             setAnimation(MON_KILL1);<br />
            }<br />
            canvas.主角操作完毕并设置延迟时间(700l);<br />
           break;<br />
       }<br />
      }else{//小怪<br />
       setAnimation(MON_KILL1);<br />
       canvas.主角操作完毕并设置延迟时间(700l);<br />
      }<br />
     }else setAnimation(MON_DIE);//怪死亡了<br />
    }<br />
 public void paint(Graphics g){//绘制<br />
  g.setClip(0, 0, Gamedata.width, Gamedata.height);<br />
  player.drawFrame(g);<br />
 }<br />
 public int getSpriteDrawX(){<br />
  return getSpriteX();<br />
 }<br />
 <br />
 public int getSpriteDrawY(){<br />
  return getSpriteY();<br />
 }<br />
    byte orientation;<br />
 public byte getSpriteOrientation(){<br />
  return (byte)direction;<br />
 }<br />
 public void updateSpritePosition(int incX,int incY){<br />
  spriteX+=incX;<br />
  spriteY+=incY;</p>
<p> }<br />
  public boolean end(){<br />
     return (player.getCurrentFrame()==(player.getFrameCount()-1));<br />
  }<br />
 public int getAnimation(){<br />
  return player.getAnimation();<br />
 }<br />
 public int getCurrentFrame(){<br />
  return player.getCurrentFrame();<br />
 }<br />
 public void setAnimation(int animation){<br />
   switch (animation) {<br />
      case MON_SLEEP:<br />
      player.setAnimation(MON_SLEEP);<br />
      player.setLoopOffset(0);<br />
   break;<br />
      default:<br />
       player.setAnimation(animation);<br />
      player.setLoopOffset(-1);<br />
      break;<br />
      }<br />
         this.animation = animation; <br />
 }<br />
  public void setLevel(int level){//设置怪物级别<br />
  this.level=level;<br />
  switch(this.level){<br />
      case 1:<br />
       hp=max_hp=20;attack=5;recovery=2;exp=Tools.getRand(8,10);coin=Tools.getRand(1,10);<br />
   break;<br />
      case 2:<br />
       hp=max_hp=23;attack=6;recovery=3;exp=Tools.getRand(8,10);coin=Tools.getRand(1,10);<br />
   break;<br />
      case 3:<br />
       hp=max_hp=26;attack=8;recovery=4;exp=Tools.getRand(8,10);coin=Tools.getRand(1,10);<br />
   break;<br />
      case 4:<br />
       hp=max_hp=30;attack=9;recovery=4;exp=Tools.getRand(8,10);coin=Tools.getRand(1,10);<br />
   break;<br />
      case 5:<br />
       hp=max_hp=34;attack=11;recovery=5;exp=Tools.getRand(8,10);coin=Tools.getRand(1,10);<br />
   break;<br />
      case 6:<br />
       hp=max_hp=39;attack=13;recovery=6;exp=Tools.getRand(34,40);coin=Tools.getRand(14,40);<br />
   break;<br />
      case 7:<br />
       hp=max_hp=44;attack=15;recovery=7;exp=Tools.getRand(34,40);coin=Tools.getRand(14,40);<br />
   break;<br />
      case 8:<br />
       hp=max_hp=50;attack=18;recovery=9;exp=Tools.getRand(34,40);coin=Tools.getRand(14,40);<br />
   break;<br />
      case 9:<br />
       hp=max_hp=56;attack=20;recovery=10;exp=Tools.getRand(34,40);coin=Tools.getRand(14,40);<br />
   break;<br />
      case 10:<br />
       hp=max_hp=63;attack=23;recovery=11;exp=Tools.getRand(34,40);coin=Tools.getRand(14,40);<br />
   break;<br />
      case 11:<br />
       hp=max_hp=70;attack=26;recovery=13;exp=Tools.getRand(94,110);coin=Tools.getRand(48,90);<br />
   break;<br />
      case 12:<br />
       hp=max_hp=78;attack=29;recovery=14;exp=Tools.getRand(94,110);coin=Tools.getRand(48,90);<br />
   break;<br />
      case 13:<br />
       hp=max_hp=86;attack=32;recovery=16;exp=Tools.getRand(94,110);coin=Tools.getRand(48,90);<br />
   break;<br />
      case 14:<br />
       hp=max_hp=95;attack=36;recovery=18;exp=Tools.getRand(94,110);coin=Tools.getRand(48,90);<br />
   break;<br />
      case 15:<br />
       hp=max_hp=104;attack=40;recovery=20;exp=Tools.getRand(94,110);coin=Tools.getRand(48,90);<br />
   break;<br />
      case 16:<br />
       hp=max_hp=113;attack=44;recovery=22;exp=Tools.getRand(171,200);coin=Tools.getRand(102,160);<br />
   break;<br />
      case 17:<br />
       hp=max_hp=123;attack=48;recovery=24;exp=Tools.getRand(171,200);coin=Tools.getRand(102,160);<br />
   break;<br />
      case 18:<br />
       hp=max_hp=134;attack=52;recovery=26;exp=Tools.getRand(171,200);coin=Tools.getRand(102,160);<br />
   break;<br />
      case 19:<br />
       hp=max_hp=144;attack=57;recovery=28;exp=Tools.getRand(171,200);coin=Tools.getRand(102,160);<br />
   break;<br />
      case 20:<br />
       hp=max_hp=156;attack=62;recovery=31;exp=Tools.getRand(171,200);coin=Tools.getRand(102,160);<br />
   break;<br />
  <br />
      case 21:<br />
       hp=max_hp=168;attack=67;recovery=33;exp=Tools.getRand(300,350);coin=Tools.getRand(176,250);<br />
   break;<br />
      case 22:<br />
       hp=max_hp=180;attack=72;recovery=36;exp=Tools.getRand(300,350);coin=Tools.getRand(176,250);<br />
   break;<br />
      case 23:<br />
       hp=max_hp=193;attack=77;recovery=38;exp=Tools.getRand(300,350);coin=Tools.getRand(176,250);<br />
   break;<br />
      case 24:<br />
       hp=max_hp=206;attack=83;recovery=41;exp=Tools.getRand(300,350);coin=Tools.getRand(176,250);<br />
   break;<br />
      case 25:<br />
       hp=max_hp=220;attack=89;recovery=44;exp=Tools.getRand(300,350);coin=Tools.getRand(176,250);<br />
   break;<br />
      case 26:<br />
       hp=max_hp=234;attack=95;recovery=47;exp=Tools.getRand(462,540);coin=Tools.getRand(270,360);<br />
   break;<br />
      case 27:<br />
       hp=max_hp=248;attack=101;recovery=50;exp=Tools.getRand(462,540);coin=Tools.getRand(270,360);<br />
   break;<br />
      case 28:<br />
       hp=max_hp=263;attack=107;recovery=53;exp=Tools.getRand(462,540);coin=Tools.getRand(270,360);<br />
   break;<br />
      case 29:<br />
       hp=max_hp=279;attack=114;recovery=57;exp=Tools.getRand(462,540);coin=Tools.getRand(270,360);<br />
   break;<br />
      case 30:<br />
       hp=max_hp=295;attack=120;recovery=60;exp=Tools.getRand(462,540);coin=Tools.getRand(270,360);<br />
   break;<br />
      case 31:<br />
       hp=max_hp=311;attack=127;recovery=63;exp=Tools.getRand(651,760);coin=Tools.getRand(384,490);<br />
   break;<br />
      case 32:<br />
       hp=max_hp=328;attack=135;recovery=67;exp=Tools.getRand(651,760);coin=Tools.getRand(384,490);<br />
   break;<br />
      case 33:<br />
       hp=max_hp=346;attack=142;recovery=71;exp=Tools.getRand(651,760);coin=Tools.getRand(384,490);<br />
   break;<br />
      case 34:<br />
       hp=max_hp=363;attack=149;recovery=74;exp=Tools.getRand(651,760);coin=Tools.getRand(384,490);<br />
   break;<br />
      case 35:<br />
       hp=max_hp=382;attack=157;recovery=78;exp=Tools.getRand(651,760);coin=Tools.getRand(384,490);<br />
   break;<br />
      case 36:<br />
       hp=max_hp=401;attack=165;recovery=82;exp=Tools.getRand(857,1000);coin=Tools.getRand(518,640);<br />
   break;<br />
      case 37:<br />
       hp=max_hp=420;attack=173;recovery=86;exp=Tools.getRand(857,1000);coin=Tools.getRand(518,640);<br />
   break;<br />
      case 38:<br />
       hp=max_hp=440;attack=182;recovery=91;exp=Tools.getRand(857,1000);coin=Tools.getRand(518,640);<br />
   break;<br />
      case 39:<br />
       hp=max_hp=460;attack=190;recovery=95;exp=Tools.getRand(857,1000);coin=Tools.getRand(518,640);<br />
   break;<br />
      case 40:<br />
       hp=max_hp=480;attack=199;recovery=99;exp=Tools.getRand(857,1000);coin=Tools.getRand(518,640);<br />
   break;<br />
      case 41:<br />
       hp=max_hp=501;attack=208;recovery=104;exp=Tools.getRand(1114,1300);coin=Tools.getRand(672,810);<br />
   break;<br />
      case 42:<br />
       hp=max_hp=523;attack=217;recovery=108;exp=Tools.getRand(1114,1300);coin=Tools.getRand(672,810);<br />
   break;<br />
      case 43:<br />
       hp=max_hp=545;attack=227;recovery=113;exp=Tools.getRand(11114,1300);coin=Tools.getRand(672,810);<br />
   break;<br />
      case 44:<br />
       hp=max_hp=567;attack=236;recovery=118;exp=Tools.getRand(11114,1300);coin=Tools.getRand(672,810);<br />
   break;<br />
      case 45:<br />
       hp=max_hp=590;attack=246;recovery=123;exp=Tools.getRand(11114,1300);coin=Tools.getRand(672,810);<br />
   break;<br />
      case 46:<br />
       hp=max_hp=614;attack=256;recovery=128;exp=Tools.getRand(1285,1500);coin=Tools.getRand(846,1000);<br />
   break;<br />
      case 47:<br />
       hp=max_hp=638;attack=266;recovery=133;exp=Tools.getRand(1285,1500);coin=Tools.getRand(846,1000);<br />
   break;<br />
      case 48:<br />
       hp=max_hp=662;attack=276;recovery=138;exp=Tools.getRand(1285,1500);coin=Tools.getRand(846,1000);<br />
   break;<br />
      case 49:<br />
       hp=max_hp=687;attack=287;recovery=143;exp=Tools.getRand(1285,1500);coin=Tools.getRand(846,1000);<br />
   break;<br />
      case 50:<br />
       hp=max_hp=712;attack=298;recovery=149;exp=Tools.getRand(1285,1500);coin=Tools.getRand(846,1000);<br />
   break;<br />
     }<br />
 }<br />
 public void checkTaskFromBattle(){//战斗胜利,更新任务中各个数量<br />
   for(int i=0;i&lt;canvas.taskVector.size();i++){<br />
    Task t=(Task) canvas.taskVector.elementAt(i);<br />
    if(t.task_Name.equals(Task.task_Name5)&amp;this.type==Obj.蜜蜂){<br />
     if(canvas.common_hero.杀死蜜蜂数量&lt;20)canvas.common_hero.杀死蜜蜂数量++;<br />
     break;<br />
    }<br />
    if(t.task_Name.equals(Task.task_Name6)&amp;this.type==Obj.毒蜘蛛){<br />
     if(canvas.common_hero.杀死毒蜘蛛数量&lt;30)canvas.common_hero.杀死毒蜘蛛数量++;<br />
     break;<br />
    }<br />
    if(t.task_Name.equals(Task.task_Name7)&amp;this.type==Obj.食人花){<br />
     if(canvas.common_hero.杀死食人花数量&lt;30)canvas.common_hero.杀死食人花数量++;<br />
     break;<br />
    }<br />
    if(t.task_Name.equals(Task.task_Name8)){<br />
     switch(this.type){<br />
         case Obj.霸王花:<br />
          if(canvas.common_hero.杀死霸王花数量&lt;10)canvas.common_hero.杀死霸王花数量++;<br />
      break;<br />
         case Obj.眼镜蛇王:<br />
          if(canvas.common_hero.杀死眼镜蛇王数量&lt;10)canvas.common_hero.杀死眼镜蛇王数量++;<br />
         break;<br />
         case Obj.穿山甲王:<br />
          if(canvas.common_hero.杀死穿山甲王数量&lt;10)canvas.common_hero.杀死穿山甲王数量++;<br />
         break;<br />
         case Obj.血狼:<br />
          if(canvas.common_hero.杀死血狼数量&lt;10)canvas.common_hero.杀死血狼数量++;<br />
         break;<br />
     }<br />
     break;<br />
    }<br />
   }<br />
 }<br />
}</p>
<p>此类是怪的行为类.</p>
<p>endOfAnimation()在一个动画结束时会调用.</p>
<p>updateSpritePosition()是更新坐标.检测</p>
<p>getSpriteOrientation()得到方向</p>
<p>setAnimation()设置新的动画</p>
<p>end()检查是否播放结束</p>
<p>getSpriteDrawX()得到绘制X.</p>
<p>getSpriteDrawY()得到绘制Y.</p>
<p>无论用哪种方式.都要自己实现ResourceLoader类</p>
<p>ResourceLoader类就是你的动画需要指定创建哪些图片资源</p>
<p>有loadImage和loadImageClip二种方法</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/805/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>手机游戏动作编辑器MotionWelder&#8211;编写程序</title>
		<link>http://www.javagg.com/archives/800</link>
		<comments>http://www.javagg.com/archives/800#comments</comments>
		<pubDate>Thu, 08 Jul 2010 06:49:01 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[j2me]]></category>
		<category><![CDATA[游戏]]></category>
		<category><![CDATA[动作编辑器]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=800</guid>
		<description><![CDATA[开发环境：Eclipse+EclipseMe （Eclipse3.4版本以上使用EclipseMe时在添加第三方库常常会出现classnotfound异常，可使用mtj代替EclipseME，或使用低一些的Eclipse版本）
1、新建J2ME工程，导入MotionWelder的J2ME库（在该程序下载包里J2ME 的lib目录里的motionwelder.jar）
       右键工程&#8211;&#62;Build Path &#8211;&#62;Add external archives&#8230; &#8212;&#62;选择motionwelder.jar打开，即可导入完毕。
2、把导出的动作数据文件拷到工程的资源目录，导入动作数据文件
/*
spriteName 动作数据文件
splitImageClips 内存加载图片的方式，如果为false则加载完整的图片（图片没有切片），true则加载切片的图片
imageloader  加载动作所需图片的类
*/
MSpriteData animationData = MSpriteLoader.loadMSprite(java.lang.String spriteName, boolean splitImageClips, MSpriteImageLoader imageloader)
3、加载动作图片
interface MSpriteImageLoader：
splitImageClips = false  - 加载完整的图片
           Image[]  loadImage(spriteName,imageId,orientationUsedInStudio);
splitImageClips = true &#8211; 加载图片切片
          Image[] loadImageClip(String spriteName,int imageId,int x,int y,int w,int h,int orientationUsedInStudio);
-spriteName
         &#8211; 动作数据文件的名字
-orientationUsedInStudio
        -使用了的翻转类型,
               MSprite.ORIENTATION_NONE                         &#8211; 无翻转
               MSprite.ORIENTATION_FLIP_H                      - 水平翻转
               MSprite.ORIENTATION_FLIP_V                      - 垂直翻转
               MSprite.ORIENTATION_FLIP_BOTH_H_V   &#8211; 水平和垂直翻转
- imageId
         &#8211; 图片id，即动作所使用到的图片
-x,y,w,h      
       -切片在图片中的坐标（x，y，宽，高）.
Image[3]
       Image[0] =无翻转图片
       Image[1] [...]]]></description>
			<content:encoded><![CDATA[<p>开发环境：Eclipse+EclipseMe （Eclipse3.4版本以上使用EclipseMe时在添加第三方库常常会出现classnotfound异常，可使用mtj代替EclipseME，或使用低一些的Eclipse版本）<br />
1、新建J2ME工程，导入MotionWelder的J2ME库（在该程序下载包里J2ME 的lib目录里的motionwelder.jar）<br />
       右键工程&#8211;&gt;Build Path &#8211;&gt;Add external archives&#8230; &#8212;&gt;选择motionwelder.jar打开，即可导入完毕。<br />
2、把导出的动作数据文件拷到工程的资源目录，导入动作数据文件<br />
/*<br />
spriteName 动作数据文件<br />
splitImageClips 内存加载图片的方式，如果为false则加载完整的图片（图片没有切片），true则加载切片的图片<br />
imageloader  加载动作所需图片的类<br />
*/<br />
MSpriteData animationData = MSpriteLoader.loadMSprite(java.lang.String spriteName, boolean splitImageClips, MSpriteImageLoader imageloader)<br />
3、加载动作图片<br />
interface MSpriteImageLoader：<br />
splitImageClips = false  - 加载完整的图片<br />
           Image[]  loadImage(spriteName,imageId,orientationUsedInStudio);<span id="more-800"></span></p>
<p>splitImageClips = true &#8211; 加载图片切片<br />
          Image[] loadImageClip(String spriteName,int imageId,int x,int y,int w,int h,int orientationUsedInStudio);</p>
<p>-spriteName<br />
         &#8211; 动作数据文件的名字<br />
-orientationUsedInStudio<br />
        -使用了的翻转类型,<br />
               MSprite.ORIENTATION_NONE                         &#8211; 无翻转<br />
               MSprite.ORIENTATION_FLIP_H                      - 水平翻转<br />
               MSprite.ORIENTATION_FLIP_V                      - 垂直翻转<br />
               MSprite.ORIENTATION_FLIP_BOTH_H_V   &#8211; 水平和垂直翻转<br />
- imageId<br />
         &#8211; 图片id，即动作所使用到的图片<br />
-x,y,w,h      <br />
       -切片在图片中的坐标（x，y，宽，高）.<br />
Image[3]<br />
       Image[0] =无翻转图片<br />
       Image[1] = 水平翻转图片      // 如果没有为null<br />
       Image[2] = 垂直翻转图片         // 如果没有为null<br />
4、播放动作<br />
/*动作列表*/<br />
public static int ANIM_STAND = 0;<br />
public static int ANIM_RUN = 1;</p>
<p>/* 创建动作播放器 */<br />
MSimpleAnimationPlayer player = new MSimpleAnimationPlayer(MSpriteData spriteData, int spriteX, int spriteY);<br />
或<br />
MSpriteAnimationPlayer player = new MSpriteAnimationPlayer(MSpriteData spriteData, MSprite sprite);<br />
两个的区别在于：正如名字一样MSpriteAnimationPlayer提供的一个简单的播放器，它在处理end of animation时是仅停止播放，不能更好地拓展。如果需要灵巧地控制各个动画的切换最好使用MSpriteAnimationPlayer。</p>
<p>/*设置当前的播放动作*/<br />
player.setAnimation(ANIM_STAND);<br />
player.setLoopOffset(-1);           // 播放一次..<br />
player.setLoopOffset(0);            // 从第一帧到帧尾循环播放<br />
player.setLoopOffset(2);           // 从第二帧到帧尾循环播放</p>
<p>/* 循环调用这里，更新和绘制动作*/<br />
player.update();<br />
player.drawFrame(g);</p>
<p>例子：使用第一节的数据文件，当我们按数字键5或者中键的时候施放这个向下攻击的动作。主要代码如下：<br />
主屏幕   GameScreen.java<br />
import javax.microedition.lcdui.Canvas;<br />
import javax.microedition.lcdui.Graphics;<br />
public class GameScreen extends Canvas implements Runnable {<br />
private GameSprite gameSprite;//游戏精灵<br />
private static final int KEY_FIRE=-5;//键盘中键<br />
public static boolean isHit = false;//是否发动攻击<br />
public GameScreen() {<br />
  gameSprite = new GameSprite();//实例化游戏精灵<br />
  Thread t = new Thread(this);//创建线程<br />
  t.start();//启动线程<br />
}<br />
protected void paint(Graphics g) {<br />
  g.setColor(0xffffffff);<br />
  g.fillRect(0, 0, getWidth(), getHeight());//清屏<br />
  gameSprite.paint(g);//绘制精灵<br />
}<br />
public void run() {<br />
  try {<br />
   long time;<br />
   while (true) {<br />
    time = System.currentTimeMillis();<br />
    //重绘<br />
    repaint();<br />
    serviceRepaints();<br />
    if (isHit) {//如果发动攻击<br />
     gameSprite.update();//更新精灵<br />
    }<br />
    long timeTakenForPainting = System.currentTimeMillis() &#8211; time;<br />
    if (timeTakenForPainting &lt; 100) {<br />
     Thread.sleep(100 &#8211; timeTakenForPainting);<br />
    }<br />
   }<br />
  } catch (Exception e) {<br />
   System.out.println(&#8220;Error &#8221; + e);<br />
  }<br />
}<br />
protected void keyPressed(int key) {<br />
  if (key == Canvas.KEY_NUM5 || key == KEY_FIRE) {<br />
   isHit = true;//按数字键5或中键攻击<br />
  }<br />
}<br />
protected void keyReleased(int key) {<br />
}<br />
}</p>
<p>游戏精灵  GameSprite.java<br />
import javax.microedition.lcdui.Graphics;<br />
import com.studio.motionwelder.MPlayer;<br />
import com.studio.motionwelder.MSprite;<br />
import com.studio.motionwelder.MSpriteAnimationPlayer;<br />
import com.studio.motionwelder.MSpriteData;<br />
import com.studio.motionwelder.MSpriteLoader;<br />
public class GameSprite implements MSprite {<br />
private MSpriteData spriteData;<br />
private MPlayer player;<br />
private int animation;<br />
private static final int DOWN_HIT = 0;<br />
int spriteX = 100;//精灵坐标<br />
int spriteY = 175;<br />
public GameSprite() {<br />
  try {<br />
   loadResource();//加载资源<br />
  } catch (Exception e) {<br />
   e.printStackTrace();<br />
  }<br />
  player = new MSpriteAnimationPlayer(spriteData, this);//创建播放器<br />
  this.setAnimation(DOWN_HIT);//设置当前动画ID<br />
}<br />
private void setAnimation(int animation) {//设置动画ID<br />
  switch (animation) {<br />
  case DOWN_HIT:<br />
   player.setAnimation(DOWN_HIT);<br />
   break;<br />
  }<br />
  this.animation = animation;<br />
}<br />
private void loadResource() throws Exception {<br />
  spriteData = MSpriteLoader.loadMSprite(&#8220;/SwordSprite.anu&#8221;, true,<br />
    ResourceLoader.getInstance());//加载数据文件<br />
}<br />
public void update() {<br />
  switch (animation) {<br />
  case DOWN_HIT:<br />
   player.setLoopOffset(-1);//设置仅播放一次<br />
   break;<br />
  }<br />
  player.update();//下一帧<br />
}<br />
public void endOfAnimation() {//处理动画播放完后的方法，可以添加切换到另外的动画等等<br />
  switch (animation) {<br />
  case DOWN_HIT:<br />
   player.setFrame(0);//如果播放完则设置帧为第0帧<br />
   break;<br />
  }<br />
  GameScreen.isHit = false;//攻击动作完成<br />
}<br />
public int getSpriteDrawX() {//设置精灵的绘制坐标X<br />
  return spriteX;<br />
}<br />
public int getSpriteDrawY() {//设置精灵的绘制坐标Y<br />
  return spriteY;<br />
}<br />
public byte getSpriteOrientation() {//设置精灵的方向<br />
  return 0;<br />
}<br />
public void updateSpritePosition(int arg0, int arg1) {<br />
  //更新精灵的位置，比如跑动是X及Y的坐标变化<br />
}<br />
public void paint(Graphics g) {<br />
  player.drawFrame(g);//绘制动作的帧<br />
}<br />
}</p>
<p>资源加载 ResourceLoader.java<br />
import java.io.IOException;<br />
import javax.microedition.lcdui.Image;<br />
import javax.microedition.lcdui.game.Sprite;<br />
import com.studio.motionwelder.MSprite;<br />
import com.studio.motionwelder.MSpriteImageLoader;<br />
public class ResourceLoader implements MSpriteImageLoader {<br />
private static ResourceLoader resLoader;// 单例<br />
private ResourceLoader() {<br />
}<br />
public static ResourceLoader getInstance() {// 获取ResourceLoader的实例<br />
  if (resLoader == null)<br />
   resLoader = new ResourceLoader();<br />
  return resLoader;<br />
}<br />
public Image[] loadImage(String spriteName, int imageId,<br />
   int orientationUsedInStudio) {// 整图形式加载图片<br />
  return null;<br />
}<br />
public Image[] loadImageClip(String spriteName, int imageId, int x, int y,<br />
   int w, int h, int orientationUsedInStudio) {// 切片形式加载图片<br />
  boolean doYouNeedHFlippedSpriteInYourgame = false;<br />
  boolean doYouNeedVFlippedSpriteInYourgame = false; // 是否有旋转操作<br />
  Image baseImage = null;<br />
  if (imageId == 0) {<br />
   baseImage = loadImage(&#8220;/hero.png&#8221;);<br />
   doYouNeedHFlippedSpriteInYourgame = false;<br />
   doYouNeedVFlippedSpriteInYourgame = false;<br />
  }<br />
  Image img[] = new Image[3];<br />
  img[0] = Image.createImage(baseImage, x, y, w, h, Sprite.TRANS_NONE); // 保存无旋转的切片<br />
  /** 如果使用了Nokia的图像操作就不要这样用，关于Nokia的操作作者在库代码里把它注释掉了 */<br />
  if (orientationUsedInStudio == MSprite.ORIENTATION_FLIP_H<br />
    || orientationUsedInStudio == MSprite.ORIENTATION_FLIP_BOTH_H_V<br />
    || doYouNeedHFlippedSpriteInYourgame)<br />
   img[1] = Image.createImage(baseImage, x, y, w, h,<br />
     Sprite.TRANS_MIRROR); // 保存水平旋转的切片<br />
  /** 如果使用了Nokia的图像操作就不要这样用，关于Nokia的操作作者在库代码里把它注释掉了 */<br />
  if (orientationUsedInStudio == MSprite.ORIENTATION_FLIP_V<br />
    || orientationUsedInStudio == MSprite.ORIENTATION_FLIP_BOTH_H_V<br />
    || doYouNeedVFlippedSpriteInYourgame)<br />
   img[2] = Image.createImage(baseImage, x, y, w, h,<br />
     Sprite.TRANS_MIRROR_ROT180); // 保存垂直旋转的切片<br />
  return img;<br />
}<br />
private Image loadImage(String imagePath) {<br />
  try {<br />
   Image img = Image.createImage(imagePath);<br />
   return img;<br />
  } catch (IOException e) {<br />
   e.printStackTrace();<br />
  }<br />
  return null;<br />
}<br />
}</p>
<p><a href="http://www.javagg.com/wp-content/uploads/2010/07/http_imgload.gif"><img class="alignnone size-full wp-image-801" title="http_imgload" src="http://www.javagg.com/wp-content/uploads/2010/07/http_imgload.gif" alt="" width="160" height="191" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/800/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>中国一共有多少神仙!今天让大家开开眼！</title>
		<link>http://www.javagg.com/archives/781</link>
		<comments>http://www.javagg.com/archives/781#comments</comments>
		<pubDate>Sun, 16 May 2010 05:11:46 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">http://www.javagg.com/archives/781</guid>
		<description><![CDATA[重要神仙表
> 盘古氏－又称元始天王，一名，浮黎元始天尊。
>
> 三清：
> 元始天尊
> 灵宝天尊 又名太上道君
> 道德天尊 又名太上老君(西游记里也称为太上道祖)
>
> 六御
> 中央玉皇大帝 妻：王母娘娘，又称为 西王母
> 北方北极中天紫微大帝
> 南方南极长生大帝,又名玉清真王，为元始天王九子。
> 东方东极青华大帝太乙救苦天尊
> 西方太极天皇大帝 (手下：八大元帅，五极战神(天空战神，大地战神，人中战神,
> 北极战神和南极战神))
> 大地之母：承天效法后土皇地祗
>
> 五方五老：
> 南方南极观音
> 东方崇恩圣帝
> 三岛十洲仙翁东华大帝君（即东王公,名&#8221;金蝉氏&#8221;,号木公）
> 北方北极玄灵斗姆元君（佛教中二十诸天的摩利支天）
> 中央黄极黄角大仙
>
>
> 中央天宫仙位表
> 千里眼 &#124; 顺风耳 &#124; 金童 &#124; 玉女 &#124; 雷公 &#124; 电母（金光圣母） &#124; 风伯 &#124; 雨师 &#124; 游奕灵官 &#124; 翊圣真君 &#124; 大力鬼王 &#124; 七仙女 &#124; 太白金星 &#124; 赤脚大仙 &#124; 广寒仙子（姮娥仙子）嫦娥 [...]]]></description>
			<content:encoded><![CDATA[<p>重要神仙表<br />
> 盘古氏－又称元始天王，一名，浮黎元始天尊。<br />
><br />
> 三清：<br />
> 元始天尊<br />
> 灵宝天尊 又名太上道君<br />
> 道德天尊 又名太上老君(西游记里也称为太上道祖)<br />
><br />
> 六御<br />
> 中央玉皇大帝 妻：王母娘娘，又称为 西王母<br />
> 北方北极中天紫微大帝<br />
> 南方南极长生大帝,又名玉清真王，为元始天王九子。<br />
> 东方东极青华大帝太乙救苦天尊<br />
> 西方太极天皇大帝 (手下：八大元帅，五极战神(天空战神，大地战神，人中战神,<br />
> 北极战神和南极战神))<br />
> 大地之母：承天效法后土皇地祗<br />
><br />
> 五方五老：<br />
> 南方南极观音<br />
> 东方崇恩圣帝<br />
> 三岛十洲仙翁东华大帝君（即东王公,名&#8221;金蝉氏&#8221;,号木公）<br />
> 北方北极玄灵斗姆元君（佛教中二十诸天的摩利支天）<br />
> 中央黄极黄角大仙<br />
><br />
><br />
> 中央天宫仙位表<br />
> 千里眼 | 顺风耳 | 金童 | 玉女 | 雷公 | 电母（金光圣母） | 风伯 | 雨师 | 游奕灵官 | 翊圣真君 | 大力鬼王 | 七仙女 | 太白金星 | 赤脚大仙 | 广寒仙子（姮娥仙子）嫦娥 | 玉兔 | 玉蟾 | 吴刚 | 天蓬元帅 | 天佑元帅 | 九天玄女 | 十二金钗 | 九曜星 | 日游神 | 夜游神 | 太阴星君 | 太阳星君 | 武德星君 | 佑圣真君 | 托塔天王李靖 | 金吒 | 木吒（行者惠岸） | 三坛海会大神哪吒 | 巨灵神 | 月老 | 左辅右弼 | 二郎神杨戬 | 太乙雷声应化天尊王善王灵官 | 萨真人 | 紫阳真人(张伯端) | 文昌帝君 | 天聋 | 地哑<br />
><br />
> 三官大帝： 天官 | 地官 | 水官<br />
> 四大天王： 增长天王、持国天王、多闻天王与广目天王<br />
> 四值功曹： 值年神李丙 | 值月神黄承乙 | 值日神周登 | 值时神刘洪<br />
> 四大天师： 张道陵、许逊（字敬之，号许旌阳）、邱弘济、葛洪<br />
> 四方神: 青龙孟章神君、白虎监兵神君、朱雀陵光神君、玄武执明神君。 <span id="more-781"></span></p>
<p>> 四渎龙神: 黄河 | 长江 | 淮河 | 济水河神<br />
><br />
> 马赵温关四大元帅：<br />
> 马元帅 又名马天君，又称华光天王、华光大帝<br />
> 赵元帅 即武财神赵公明，又名赵玄坛<br />
> 温元帅 温琼，东岳大帝部将<br />
> 关元帅 关羽。<br />
><br />
> 五方谒谛：金光揭谛、银头揭谛、波罗揭谛、波罗僧揭谛、摩诃揭谛<br />
><br />
> 五炁真君：东方岁星木德真君 | 南方荧惑火德真君 | 西方太白金德真君 | 北方辰星水德真君 | 中央镇星土德真君<br />
> 五岳 :东岳泰山天齐仁圣大帝　南岳衡山司天昭圣大帝　中岳嵩山中天崇圣大帝 北岳恒山安天玄圣大帝　西岳华山金天愿圣大帝 (五岳帝君：东岳帝君，名金虹氏，东华帝君弟。其它四岳帝君为东华帝君的四个儿子。) 及 碧霞元君<br />
><br />
> 五斗星君： 东斗星君 | 西斗星君 | 中斗星君 | 南斗星君 | 北斗星君<br />
><br />
> 六丁六甲： 六丁为阴神玉女 | 丁卯神司马卿 | 丁已神崔巨卿 | 丁未神石叔通 | 丁酉神臧文公 丁亥神张文通 | 丁丑神赵子玉 | 六甲为阳神玉男 | 甲子神王文卿 | 甲戌神展子江 | 甲申神扈文长 | 甲午神卫玉卿 | 甲辰神孟非卿 | 甲寅神明文章<br />
><br />
> 南斗六星君<br />
> 第一天府宫：司命星君<br />
> 第二天相宫：司禄星君<br />
> 第三天梁宫：延寿星君<br />
> 第四天同宫：益算星君<br />
> 第五天枢宫：度厄星君<br />
> 第六天机宫：上生星君<br />
><br />
> 北斗七星君：(《狮驼国》中的北天七皇)<br />
> 北斗第一阳明贪狼星君<br />
> 北斗第二阴精巨门星君<br />
> 北斗第三真人禄存星君<br />
> 北斗第四玄冥文曲星君<br />
> 北斗第五丹元廉贞星君<br />
> 北斗第六北极武曲星君<br />
> 北斗第七天关破军星君<br />
> (《狮驼国》中的北斗七星君为北斗星君的另一个称号：天枢、天璇、天玑、天权、玉衡、开阳、摇光。&#8221;天枢、天璇、天玑、天权&#8221;合起来又称为&#8221;斗魁&#8221;或&#8221;璇&#8221;，后三星组成斗柄，称&#8221;杓&#8221;<br />
><br />
> 八仙： 铁拐李、汉钟离、吕洞宾、何仙姑、蓝采和、韩湘子、曹国舅、张果老 </p>
<p>> 增长天王手下八将：庞刘荀毕、邓辛张陶，其全名为刘俊、荀雷吉、庞煜、毕宗远；邓伯温、辛汉臣、张元伯、陶元信（四目）<br />
> 九曜星 金星 | 木星 | 水星 | 火星 | 土星 | 罗睺(蚀星) | 计都星 | 紫炁星 | 月孛星<br />
><br />
> 十二元辰 子丑寅卯等<br />
> 二十八星宿 :亢金龙、女土蝠、房日兔、心月狐、尾火虎、箕水豹、斗木獬、 牛金牛、氐土貉、虚日鼠、危月燕、室火猪、壁水獝、奎木狼、 娄金狗、胃土彘、昴日鸡、毕月乌、觜火猴、参水猿、井木犴、 鬼金羊、柳土獐、星日马、张月鹿、翼火蛇、轸水蚓。<br />
><br />
> 三十六天将<br />
> 蒋光 | 钟英 | 金游 | 殷郊 | 庞煜 | 刘吉 | 关羽 | 马胜 | 温琼 | 王善 | 康应 | 朱彦 | 吕魁 | 方角 | 耿通 | 邓伯温 | 辛汉臣 | 张元伯 | 陶元信 | 荀雷吉 | 毕宗远 | 赵公明 | 吴明远 | 李青天 | 梅天顺 | 熊光显 | 石远信 | 孔雷结 | 陈元远 | 林大华 | 周青远 | 纪雷刚 | 崔志旭 | 江飞捷 | 贺天祥 | 高克 (三十六天将的版本是最多，以上仅供参考)<br />
><br />
> 地上天仙表<br />
> 姜子牙（亦为东华帝君，估计是木公> 的接班人）<br />
> 蓬莱三仙：<br />
> 福禄寿三星，福神天官大帝，另一说是西汉杨成，又一说中是唐阳城;财神赵公明、（一说比干范蠡为文财神）；寿星南极仙翁，女寿星：麻姑<br />
> 真武大帝，又名九天降魔祖师、玄武元帅。<br />
> 龟蛇二将(又名太玄水精黑灵尊神、太玄火精赤灵尊神)<br />
> 小张太子与五大神龙<br />
> 黎山****、镇元子<br />
> 龙王:东海龙王敖广 | 南海龙王敖钦 | 西海龙王敖闰 | 北海龙王敖顺 | 井海王<br />
><br />
> 神霄派诸神<br />
> 紫微北极大帝<br />
> 玉清真王（南极长生大帝）－－元始天王第九子<br />
> 神霄八帝（多为道教虚构），玉清真王与神霄八帝合起来又称为神霄九宸大帝<br />
> 东极青华大帝、九天应元雷声普化天尊（黄帝）、九天雷祖大帝等。<br />
> （太乙天帝、六天洞渊大帝、六波天主帝君、可韩真君、采访真君） </p>
<p>> 九司三省与北极四圣<br />
><br />
> 九司：玉府判府真君、玉府左右待中、玉府左右仆谢、天雷上相、玉枢使相、<br />
> 斗枢上相、上清司命玉府右卿、五雷院使君、雷霆都司元命真君<br />
><br />
> 三省：雷霆泰省、雷霆玄省、雷霆都省<br />
> 北极四圣： 天蓬元帅（猪八戒） 手下天罡大圣、九天杀童大将（北斗第八星，又称天杀大神）、<br />
><br />
> 雷使者等。<br />
> 天佑（猷）元帅<br />
> 翊圣元帅<br />
> 玄武元帅 真武大帝<br />
><br />
> 另有：五方雷王、五方雷霆大帝<br />
><br />
> 阴曹地府<br />
> 北阴酆都大帝<br />
> 五方鬼帝：<br />
> 东方鬼帝蔡郁垒、神荼，治\&#8221;桃止山-:special:1:- 鬼门关<br />
> 西方鬼帝赵文和，王真人，治-:special:1:-嶓冢山-:special:1:-<br />
> 北方鬼帝张衡、杨云，治罗酆山；<br />
> 南方鬼帝杜子仁，治罗浮山；<br />
> 中央鬼帝周乞、稽康，治-:special:1:-抱犊山-:special:1:-<br />
><br />
> 罗酆六天以下为宫名，六天为守宫神)<br />
> 纣绝阴天宫、泰煞谅事宗天宫、明晨耐犯武城天宫、<br />
> 恬昭罪气天宫、宗灵七非天宫、敢司连宛屡天宫<br />
> 地藏菩萨<br />
> 十殿阎王:秦广王、楚江王、宋帝王、仵官王、阎罗王、<br />
> 平等王、泰山王、都市王、卞城王、转轮王<br />
> 其这将、臣：<br />
> 　　首席判官崔府君、钟魁、黑白无常、牛头马面、孟婆神、<br />
><br />
><br />
> 上古神话诸神<br />
><br />
> 混沌天神－－较正式的说法，盘古为开天辟地之始神，但仍有部分传说中，混沌早于盘古<br />
> 而生。<br />
><br />
> 创世神<br />
> 天吴、毕方、据比、竖亥、烛阴、女娲<br />
> 上古四方天帝与辅神：<br />
> 太阳神炎帝与火神祝融共同治理天南一万二千里的地方<br />
> 少昊与水神共工建立天西一万二千里的地方<br />
> 颛顼与海神禺强(又名冬神玄冥)治理天北一万二千里的地方<br />
> 青帝伏羲与九河神女华胥氏及属神句芒治理天东一万二千里的地方<br />
><br />
> 黄帝时代的诸神<br />
> 陆吾、英招、离珠、金甲神(此神应是其它神的一种化身说，根据他我虚拟了狮驼国中的金甲雷神) </p>
<p>> 蚩尤、风伯雨师、赤松子、力牧、神皇、风后、应龙、魃、夸父、大力神夸娥氏、大庭氏、五龙氏<br />
> 炎帝，又称为神农氏<br />
> 炎帝的女儿<br />
> 女娃(后化身精卫鸟)<br />
> 瑶姬，在《狮驼国》中又名婉华仙子。<br />
> 还有一个小女儿，其名不可考，(拙著《狮驼国》中为炎天圣母)<br />
> 少昊母为皇娥、长子春神句芒、次子秋神蓐收<br />
><br />
> 颛顼的后代<br />
> 四子：虐鬼、魍魉、送穷鬼、梼杌<br />
> 后代：老童、太子长琴、黎、重、彭祖(孙)<br />
> 帝俊<br />
> 天上妻子：羲和、常羲<br />
> 人间妻子(省略)<br />
> 女丑、羿<br />
> 鲧 妻:女喜。<br />
> 尧 又名：放勋， 妻女皇；<br />
> 舜 姓姚，名重华，妻娥皇，女英；<br />
> 禹，父鲧，妻&#8221;女娇&#8221;,又名涂山氏，系九尾白狐精<br />
><br />
> 三皇：<br />
> 指天地人三皇，分别是伏羲、神农与女娲。<br />
> 五帝：<br />
> 通常指 黄帝 | 颛顼 | 帝喾 | 尧 | 舜<br />
><br />
> 其它－－后天著名仙真表<br />
> 房中之祖－－彭祖 | 纵横始祖－－鬼谷子 | 文始真人－－尹喜 | 南华真人－－庄子 | 求仙使者－－徐福 | 茅山仙祖－－三茅真君 | 万古丹王－－魏伯阳 | 太极真人－－刘安 | 诙谐岁星－－东方朔 | 太平教主－－于吉 | 役使鬼神－－费长房 | 竹林狂士－－嵇康 | 水府仙伯－－郭璞 | 净明教主－－许逊 | 蓬莱（都）水监－－陶弘景 | 天师－－寇谦之<br />
> 情仙－－裴航 | 扶摇子－－陈抟 | 显化真人－－张三丰 | 王重阳与全真七子（长春子丘处机、玉阳子王处一、广宁子郝大通、 清净散人孙不二、长生子刘处玄、长真子谭处端、丹阳子马钰）<br />
><br />
> 其它－－民间神灵不完全列表<br />
> 天妃娘娘 | 城隍 | 土地神 | 门神 秦叔宝、尉迟敬德 | 床神（又分床公床母，前者又称-:special:1:-九天监生明素真君-:special:1:-，后者又称-:special:1:-九天卫房圣母天君-:special:1:-）| 喜神 | 厕神紫姑 | 石敢当 | 小儿神项橐 | 朱天大帝崇帧 | 茶神陆羽 | 花神 | 染织二圣梅、葛 | 酒神杜康 | 土工祖师神鲁班 | 纺织神黄道婆 | 蚕神马头娘（山海经载为西陵氏，嫘祖） | 狱神皋陶 | 梨园神唐明皇　|　马神 | 青蛙神白玉蟾 ｜ 驱蝗神刘猛（取猛将军之意） | 蛇王施相公（施全） | 痘神张帅 | 农神后稷 | </p>
<p>> 瘟神：又称五鬼或五方力士，人间又有称五瘟，其中春瘟张元伯、夏瘟刘元达、秋瘟赵公明、冬瘟钟士贵、总管中瘟史文业。 | 窑神太上老君 | 贼神时迁 | 穷神 | **神管仲 | 武穆> 王岳飞 | 周公、桃花女 | 欢喜神和合二仙寒山、拾得<br />
> 纯本书虚构主要仙魔表<br />
> 太古猿君 | 魔佛老人 | 千面天妖 | 地心古龙 | 圣手仙王 | 圣手文王 | 天罗王（道教中为三清的一种化身） |乾坤大仙 | 颠倒老祖 | 穹天老祖 | 先天老祖 | 无极老祖 | 无为老祖 | 霹雳老祖 | 藤祖 | 幻仙子 | 清弥天诸神 | 阴阳法王 |毒龙山千毒沼沼底－－－蟒神（有双翼）| 南海深处一千里以下的－－－海皇(章鱼怪)；<br />
> 兽帝（九蛇头加龟背）(九婴与相柳借天地交合之气所造怪物)<br />
> 霸王（长白山天池的箭恐龙） | 天子梼杌（颛顼之子，住在北方玄冰宫）｜ 不坏林王狻猊(住在南方热带密林中，身坚胜铁，刀枪不入）<br />
> 平天大圣牛魔王 | 覆海大圣蛟魔王 | 移山大圣狮驼王 | 驱神大圣野象王 | 浑天大圣鹏魔王 | 通风大圣弥猴王 | 齐天大圣美猴王<br />
><br />
><br />
> 西天灵山仙佛表<br />
><br />
> 三世佛：南无过去、现在、未来 注：通常三世佛分横三世佛与竖三世佛。<br />
> 竖三世佛：<br />
><br />
> 过去佛的燃灯上古佛，加上现在世的释迦佛（原名：悉达多），以及未来<br />
> 世的弥勒佛<br />
><br />
> 横三世佛：<br />
> 中间是释迦牟尼佛，右有文殊菩萨，左立普贤菩萨；<br />
> 右边是西方极乐世界的阿弥陀佛，两旁是观世音菩萨和大势至菩萨；<br />
> 左边为东方净琉璃世界的药师佛，两旁日光菩萨和月光菩萨。<br />
> (因都有如来在，所以本文中就不增加三世佛这个名词.西游原著里，提<br />
> 及了南无过去现在未来佛，本书中称之为三世佛。)<br />
><br />
> 四大金刚：<br />
> 五台山秘魔岩神通广大泼法金刚<br />
> 峨眉山淸凉洞法力无量胜至金刚<br />
> 须弥山摩耳崖毗卢沙门大力金刚 </p>
<p>> 昆仑山金雫岭不坏尊王永住金刚<br />
><br />
> 五方佛:<br />
> 东方不动（身）佛；南方宝生佛；中央毗卢遮那佛；西方阿弥陀佛；北方不空成就佛。<br />
><br />
> 八菩萨：<br />
> 观音菩萨、普贤菩萨、文殊菩萨、地藏王菩萨、灵吉菩萨、大势至菩萨、日光菩萨、月光菩萨<br />
><br />
> 十大弟子：<br />
> 　　舍利弗智慧第一 | 目犍连神通第一 | 阿难陀多闻第一 | 优波离持戒第一<br />
> 　　阿那律天眼第一 | 大迦叶头陀第一 | 富楼那说法第一 | 迦旃延论议第一<br />
> 　　罗睺罗密行第一 | 须菩提解空第一<br />
><br />
> 十八罗汉：<br />
> 托塔罗汉 | 探手罗汉 | 过江罗汉 | 芭蕉罗汉 | 静座罗汉 | 骑象罗汉 | 看门罗汉 | 降龙罗汉 | 举钵罗汉 | 布袋罗汉 | 长眉罗汉 | 开心罗汉 | 喜庆罗汉 | 挖耳罗汉 | 笑狮罗汉 | 伏虎罗汉 | 沉思罗汉 | 骑鹿罗汉 |<br />
> 十八伽蓝<br />
> 美音 | 梵音 | 天鼓 | 叹妙 | 叹美 | 摩妙 | 雷音 | 师子 | 妙叹<br />
> 梵响 | 人音 | 佛奴 | 颂德 | 广目 | 妙眼 | 彻听 | 彻视 | 遍视<br />
><br />
> 二十诸天：<br />
> 日天（又名日宫天子）| 大梵天 | 多闻天 | 金刚密迹 | 鬼子母神；<br />
> 月天 (又名月宫天子) | 帝释天 | 持国天 | 大自在天 | 摩利支天；<br />
><br />
> (大)辩才天 | (大)功德天 | 增长天 | 散脂大将 | 婆竭龙王；<br />
> 韦驮天(战神塞犍陀) | 坚牢地神 | 广目天 | 菩提树神 | 阎摩罗王。<br />
><br />
> 其它：<br />
> 金顶大仙、阿傩、伽叶。<br />
><br />
> 婆罗门教诸神<br />
><br />
> 佛祖摩诃婆罗佛与婆罗三世佛（此四佛全部为虚构、以下人名为与如来有关的仙、人）<br />
> 频婆娑、阿罗蓝、郁陀、提婆达多<br />
><br />
> 主要大神：大梵天、湿婆楼陀罗、雪山女神杜尔迦、群主<br />
> 婆罗八部（婆罗门天龙八部）－（婆罗八部为虚构，以下诸神为印度创世诸神）<br />
><br />
> 　　水神伐楼那 | 土神陀湿多 | 风神伐由 | 日神苏里耶 | 天帝因陀罗 </p>
<p>> 道神普善 | 保护神毗湿奴 | 阿修罗：底提耶与檀那婆 | 火神婆由</p>
<p>   为了你的电脑安全，请只打开来源可靠的网址。打开网址　取消  此链接转自手机浏览器，可能无法打开。打开网址　取消</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/781/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>宠物部分技能关系类比</title>
		<link>http://www.javagg.com/archives/767</link>
		<comments>http://www.javagg.com/archives/767#comments</comments>
		<pubDate>Sun, 09 May 2010 03:40:18 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>
		<category><![CDATA[宠物]]></category>
		<category><![CDATA[技能]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=767</guid>
		<description><![CDATA[1.
高级重生与高级毒药技能：
高级重生技能：死亡时有30%几率复活，并恢复全部气血。
高级毒药技能：击中目标时，有一定几率使目标中毒，毒效果每回合减少目标10%气血，5%法力，持续3回合，且自身免疫毒。
如果带高级重生技能的宠物中毒后死亡，
可知，高级重生技能宠物中毒死亡后，如果高级重生技能没有产生效果，则宠物会倒在地上永远不起。
此效果并非是宠物携带高重生和高鬼魅后的效果。特别注意！之前我的观点是错误的
2.
高级驱鬼与高级鬼魅技能存在于不同的宠物内：
高级驱鬼技能：攻击鬼魅类宠物时，伤害增加100%，并可以将鬼魅类宠物打出场外。
鬼魅、高级鬼魅、超级鬼魅一旦打入宠物技能中，该宠物就属鬼魅类宠物。驱鬼、高级驱鬼、超级驱鬼并非是对鬼魂种族的宠物有效果。
3.
高级破防与超级活力：
高级破防技能：物理命中时有25%概率降低目标物理防御5回合。
超级活力技能：免疫一切异常状态。
高级破防类技能对拥有超级活力技能的宠物无效。
4.
高级破防与活力：
活力：每回合结束，自动解除异常状态
而今天我做黑龙任务时发现
可知，活力对于高级破防类技能没有效果。
5.
高级破防与高级鬼魅技能：
可知，高级鬼魅对高级破防类技能没有效果。
6.
超级活力与超级吸血技能：
超级吸血技能：物理攻击时，吸取对方所掉气血的30%血气，部分技能免疫吸血。
可知，超级活力也无视超级吸血类技能。
7.
超级吸血与高级鬼魅技能：
]]></description>
			<content:encoded><![CDATA[<p>1.</p>
<p>高级重生与高级毒药技能：</p>
<p>高级重生技能：死亡时有30%几率复活，并恢复全部气血。</p>
<p>高级毒药技能：击中目标时，有一定几率使目标中毒，毒效果每回合减少目标10%气血，5%法力，持续3回合，且自身免疫毒。</p>
<p>如果带高级重生技能的宠物中毒后死亡，<span id="more-767"></span></p>
<p>可知，高级重生技能宠物中毒死亡后，如果高级重生技能没有产生效果，则宠物会倒在地上永远不起。</p>
<p>此效果并非是宠物携带高重生和高鬼魅后的效果。特别注意！之前我的观点是错误的</p>
<p>2.</p>
<p>高级驱鬼与高级鬼魅技能存在于不同的宠物内：</p>
<p>高级驱鬼技能：攻击鬼魅类宠物时，伤害增加100%，并可以将鬼魅类宠物打出场外。</p>
<p>鬼魅、高级鬼魅、超级鬼魅一旦打入宠物技能中，该宠物就属鬼魅类宠物。驱鬼、高级驱鬼、超级驱鬼并非是对鬼魂种族的宠物有效果。</p>
<p>3.</p>
<p>高级破防与超级活力：</p>
<p>高级破防技能：物理命中时有25%概率降低目标物理防御5回合。</p>
<p>超级活力技能：免疫一切异常状态。</p>
<p>高级破防类技能对拥有超级活力技能的宠物无效。</p>
<p>4.</p>
<p>高级破防与活力：</p>
<p>活力：每回合结束，自动解除异常状态</p>
<p>而今天我做黑龙任务时发现</p>
<p>可知，活力对于高级破防类技能没有效果。</p>
<p>5.</p>
<p>高级破防与高级鬼魅技能：</p>
<p>可知，高级鬼魅对高级破防类技能没有效果。</p>
<p>6.</p>
<p>超级活力与超级吸血技能：</p>
<p>超级吸血技能：物理攻击时，吸取对方所掉气血的30%血气，部分技能免疫吸血。</p>
<p>可知，超级活力也无视超级吸血类技能。</p>
<p>7.</p>
<p>超级吸血与高级鬼魅技能：</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/767/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>一个免费的精灵编辑器</title>
		<link>http://www.javagg.com/archives/569</link>
		<comments>http://www.javagg.com/archives/569#comments</comments>
		<pubDate>Tue, 02 Mar 2010 08:59:04 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">eric_DIARY_105507582</guid>
		<description><![CDATA[http://www.motionwelder.com/
]]></description>
			<content:encoded><![CDATA[<p>http://www.motionwelder.com/</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/569/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>J2ME游戏开发之免费地图编辑器(2D&amp;2.5D)</title>
		<link>http://www.javagg.com/archives/570</link>
		<comments>http://www.javagg.com/archives/570#comments</comments>
		<pubDate>Tue, 02 Mar 2010 08:38:53 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">eric_DIARY_105506371</guid>
		<description><![CDATA[如果你是个人在自学J2ME，那么手头上有一款好的MapEditor是至关重要的，下面向大家推荐两款~放心，都是开源的
2D地图编辑器：
当之无愧的就是TileStudio了，功能强大得没话说，而且作者还一直在更新，用Delphi编写

下载地址：
http://tilestudio.sourceforge.net
还有一个纯Java的开源编辑器， Tiled，可以下载源码编译运行。
http://mapeditor.org/
2.5D地图编辑器：
Daimonin是一款开源的MMORPG游戏，它也自带了一款MapEditor,且自带的图片资源N多,用Java写的
美中不足的是它生成的地图文件并不是数组形式的，所以要想使之适合自己的手机游戏开发
只有修改一下生成地图部分的源代码了~
下载地址:
www.daimonin.net

]]></description>
			<content:encoded><![CDATA[<p>如果你是个人在自学J2ME，那么手头上有一款好的MapEditor是至关重要的，下面向大家推荐两款~放心，都是开源的</p>
<p>2D地图编辑器：</p>
<p>当之无愧的就是TileStudio了，功能强大得没话说，而且作者还一直在更新，用Delphi编写</p>
<p><span id="more-570"></span></p>
<p>下载地址：</p>
<p><a href="http://tilestudio.sourceforge.net">http://tilestudio.sourceforge.net</a></p>
<p>还有一个纯Java的开源编辑器， Tiled，可以下载源码编译运行。</p>
<p><a href="http://mapeditor.org/">http://mapeditor.org/</a></p>
<p>2.5D地图编辑器：</p>
<p>Daimonin是一款开源的MMORPG游戏，它也自带了一款MapEditor,且自带的图片资源N多,用Java写的</p>
<p>美中不足的是它生成的地图文件并不是数组形式的，所以要想使之适合自己的手机游戏开发</p>
<p>只有修改一下生成地图部分的源代码了~</p>
<p>下载地址:</p>
<p><a href="http://www.daimonin.net">www.daimonin.net</a></p>
<p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/570/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>经济体系设计策略</title>
		<link>http://www.javagg.com/archives/594</link>
		<comments>http://www.javagg.com/archives/594#comments</comments>
		<pubDate>Fri, 29 Jan 2010 06:27:13 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">eric_DIARY_102733671</guid>
		<description><![CDATA[目录1. 游戏货币 11.1. 游戏币产出 1怪物掉落金钱： 1任务奖励金钱： 1把道具买入商店获得金钱： 11.2. 游戏币主要回收方式 1从商店里购买道具： 1从系统中购买某些权限： 1修理： 11.3. 游戏货币体系要点 22. 游戏物品 22.1. 物品产出 22.2. 物品消亡 22.3. 如何让物质充沛起来 23. 人民币代币 23.1. 人民币代币产出 23.2. 人民币代币消费 33.3. 人民币代币消费的要点 34. 物货交流 34.1. 物货流动的动力源 34.2. 物品如何消费掉 34.2.1. 使用掉： 34.2.2. 时间淘汰： 34.2.3. 相对贬值： 34.2.4. 战斗系统对消费的影响 44.3. 物货之间的缓冲 44.3.1. 缓冲物的特点： 44.3.2. 简易策略 44.4. 物货交流中的外因 44.4.1. 现实经济地位差异对游戏影响 44.4.2. [...]]]></description>
			<content:encoded><![CDATA[<p>目录<br />1. 游戏货币 1<br />1.1. 游戏币产出 1<br />怪物掉落金钱： 1<br />任务奖励金钱： 1<br />把道具买入商店获得金钱： 1<br />1.2. 游戏币主要回收方式 1<br />从商店里购买道具： 1<br />从系统中购买某些权限： 1<br />修理： 1<br />1.3. 游戏货币体系要点 2<br />2. 游戏物品 2<br />2.1. 物品产出 2<br />2.2. 物品消亡 2<br />2.3. 如何让物质充沛起来 2<br />3. 人民币代币 2<br />3.1. 人民币代币产出 2<br />3.2. 人民币代币消费 3<br />3.3. 人民币代币消费的要点 3<br />4. 物货交流 3<br />4.1. 物货流动的动力源 3<br />4.2. 物品如何消费掉 3<br />4.2.1. 使用掉： 3<br />4.2.2. 时间淘汰： 3<br />4.2.3. 相对贬值： 3<br />4.2.4. 战斗系统对消费的影响 4<br />4.3. 物货之间的缓冲 4<br />4.3.1. 缓冲物的特点： 4<br />4.3.2. 简易策略 4<br />4.4. 物货交流中的外因 4<br />4.4.1. 现实经济地位差异对游戏影响 4<br />4.4.2. 应对策略 4<br />4.4.3. 有钱出钱有力出力的意义 4<br />4.4.4. 免费游戏的应对策略 5<span id="more-594"></span></p>
<p>&nbsp;</p>
<p>1. 游戏货币<br />1.1. 游戏币产出<br />怪物掉落金钱：<br />一般游戏都有此途径，但是也有游戏完全不掉，比如《大话西游2》，这不是大话设计失败，而是大话成功亮点之处，详细见 物货交流中的外因。<br />任务奖励金钱：<br />游戏中金钱获得的重要途径，大话西游95%以上的金钱产出是通过任务产出的。一般的游戏没有这么高的比例，但是达到30%以上的也不少。<br />把道具买入商店获得金钱：<br />大部分游戏也会在怪物掉落中掉落一些装备或者道具类型的杂物，这些杂物一般都没有使用价值，产出后很快就卖入商店。这些掉落的杂物其实等同于怪物掉落，除此之外，就是装备道具被淘汰后卖入商店这一次要途径。<br />1.2. 游戏币主要回收方式<br />从商店里购买道具：<br />卖得最多的道具通常是药水，这是一种简单有力的做法。<br />从系统中购买某些权限：<br />这种消费一般来说有限，也不够灵活，通常单项需要花费比较大的游戏币<br />修理：<br />修理收费有极好的缓冲性，当玩家使用低品质的装备的时候修理费很低，而是用高品质装备的时候修理费就会比较高。这样不同层次的玩家都能接受。<br />1.3. 游戏货币体系要点<br />货币必须保证一定的贬值率，这是一个基础经济学原理，参照真实世界的经济发展模式，不详细说明。<br />无论哪种游戏模式，把握好货币产出和货币回收方式就可以了。<br />2. 游戏物品<br />2.1. 物品产出<br />副职业系统生产技能产出：重要或者主要产出途径。<br />掉落产出（掉落产出的物品使用价值低于其卖店价值的直接视为游戏币）一般为主要途径<br />任务奖励：一般只提供中到中上层次的道具<br />活动奖励：提供的道具层次一般比较高，但是绝对数量很少。</p>
<p>2.2. 物品消亡<br />卖店：买入商店转化成游戏币<br />转化：转化或者被作为材料合成为其他的物品，<br />销毁：被系统或者玩家销毁掉，或者在合成中由于失败几率而消亡<br />使用掉：类如药品使用<br />2.3. 如何让物质充沛起来<br />首先，物质横向上有所分化，装备体系中 各种不同的装备位置、各种不同的强化方式、不同的品质，就是对物质体系的横向分化，<br />其次，物质在纵向上也要有所分化， 野外物质加工成办成品材料，半成品材料再加工成成品，这个过程就是物质体系的纵向分化。<br />最后，分化的目的都是丰富游戏的物品种类，分化的主要手段是游戏的道具系统、武器系统、副职业系统，也可以统称为战略系统。他们都是向战斗系统输送资源，并且通过战斗系统来最终表现其价值的。<br />3. 人民币代币<br />3.1. 人民币代币产出<br />点卡充值作为唯一途径<br />3.2. 人民币代币消费<br />免费游戏在游戏中购买道具、权限，收费游戏通常消费游戏时间。<br />3.3. 人民币代币消费的要点<br />促进人民币消费是免费游戏的重要手段，但是一味地拓展收费项不是明智之举。物货交流的外因中将会详细说明原因，<br />真正的促进消费人民币的手段，1是丰富物质，也就是物质充沛的横向发展和纵向发展，说到最后就是游戏的战略系统。2是物品需要定时定量的消费掉，而消费掉的这个动力，就是游戏战斗系统。一个游戏的战斗系统越是吸引人，这个游戏消费道具的能力就会非常惊人，最终消费人民币的能力也非常惊人。除此之外的其他手段都不能治本。3是让富人为穷人买单，在下面的物货交流的外因中详细说明。<br />4. 物货交流<br />4.1. 物货流动的动力源<br />物品的流动是动力源，货币只是为了满足物品流动的需要。而物品流动本身的动力，是物品的消费和淘汰，这一点和现实世界的经济体系不同，现实世界中最大的矛盾是物品不够丰富，游戏中的&ldquo;物&rdquo;只要分化适当，丰富的问题不大，而瓶颈是物品如何消费掉。物品合情合理、有节奏地消费掉、淘汰掉，是游戏世界物货交流的首要问题。<br />4.2. 物品如何消费掉<br />4.2.1. 使用掉：<br />可以使用的物品且在使用过程被消费掉，以药水、卷轴等一次性或者有限次性物品为代表<br />4.2.2. 时间淘汰：<br />随着时间淘汰掉的物品，以装备类作为典型代表。<br />4.2.3. 相对贬值：<br />不会被淘汰，但是大多数会因为游戏后继版本的开发，渐渐贬值的价值，通常它们不以道具形态出现。比如经验值。<br />4.2.4. 战斗系统对消费的影响<br />以上只是消费掉的途径，最终消费的源头是游戏的战斗系统，所有的道具或者资源都要这个系统中发挥作用。<br />4.3. 物货之间的缓冲<br />4.3.1. 缓冲物的特点：<br />当物品匮乏的时候，他们能转化成物品。当金钱匮乏的时候他们能转化成金钱。<br />4.3.2. 简易策略<br />游戏中满足这一身份的道具，就是处于游戏中间品质的装备或者道具。游戏中保证该类道具处于绝对优势的数量，即可保证物货之间的缓冲。<br />4.4. 物货交流中的外因<br />4.4.1. 现实经济地位差异对游戏影响<br />对游戏中物货影响最主要的外因，就是玩家在真实世界中的经济地位。在真实世界中的地位不同，迟早会影响到他们在游戏中的经济地位。这个外因顺而用之，游戏可以蓬勃发展，逆而用之，只能让游戏越来越死板。<br />4.4.2. 应对策略<br />首先，从外部看，流入游戏的外部资源，一是人民币，二是玩家的时间和精力。原本最理想的情况是：玩家既花费精力和时间也花费人民币。但是联系到现在真实世界的贫富差距，这种设计太过一厢情愿，所以正确的做法是有钱的出钱，有力的出力。<br />4.4.3. 有钱出钱有力出力的意义<br />其次，有钱出钱，有力出力下一个目标，是不同经济类型的玩家在游戏中进行精力时间和rmb之间的交换，最终达到大家在 有钱有力的情况下能得到的一定程度游戏享受。在这个环节有一个重要的问题。&ldquo;力&rdquo;的产物和&ldquo;钱&rdquo;的产物需要一个交换。而交换的前提是，大家互相有需求，力和钱之间互相不能取代，而且互相之间各自产物有极大的需求。有了需求，才有交换。目前游戏中大部分的游戏在这方面都有很大的设计问题，往往&ldquo;钱&rdquo;的产物全面压倒&ldquo;力&rdquo;的产物，从局部看来仿佛刺激了有钱人的消费，其实是目光浅短的做法，在这种模式下，有钱玩家只对自身的消费买单，有&ldquo;力&rdquo;玩家也只能享受到低档道具的消费，因为有钱人根本没有把用点卡购买物来和游戏币购买物交换的动力。正确的做法以《大话西游》《梦幻西游》举例，其经济体系分成两个极点，一个是点卡能换取的游戏时间资源，一个是游戏币能换取的游戏道具资源，形成了一个大范围的对流，在这个系统中，有钱人需要给出力人的时间消费买单，而出力人需要为有钱人的道具消费买单。在这个经济环境中，有钱人有自己掌握的资源，又有自己没有掌握但急需的资源，没钱有时间的人也有自己掌握的资源，又有自己没有掌握但有急需的资源。中间还有方面这两种资源进行交换的点卡寄售系统。<br />4.4.4. 免费游戏的应对策略<br />免费游戏和收费游戏细节略有不同，但是大体上的经济模式是一致的，通过给有钱人制造对游戏币、给没钱人制造对人民币代币的需求，把有钱和有力的玩家整合到一起去。而不是有钱人一个圈，没钱人是另一个圈，仅仅赚取少数有钱人的钱。最终目的是，让没钱人拿自己自己花费时间和精力的产物去换游戏代币，然后通过游戏代币来消费，让有钱人拿钱给自己和没钱人一起买单。<br />这个理论打个比方比较好理解，穷人在饭店里吃馒头，还吃不饱。而富人吃山珍海味，但是胃口不佳，吃不多。现在让穷人给富人打打工或者拿自己辛苦来东西和富人换，来换去更好更多的食物，而富人帮穷人去买单。从最终效果来看，穷人有事情做了，富人也买了更多的单。最终大赚的是饭店。&nbsp;&nbsp;在整个这个过程中，最重要的环节是给富人一个理由：穷人那边有什么东西值得我来拿人民币换的？现在的有些游戏中人民币代币全面压倒游戏币的做法，就是典型的反例。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/594/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[转贴] 游戏服务器架构一</title>
		<link>http://www.javagg.com/archives/307</link>
		<comments>http://www.javagg.com/archives/307#comments</comments>
		<pubDate>Fri, 09 Jan 2009 07:24:48 +0000</pubDate>
		<dc:creator>纯净水</dc:creator>
				<category><![CDATA[游戏]]></category>

		<guid isPermaLink="false">http://www.javagg.com/?p=307</guid>
		<description><![CDATA[
来自：http://www.libing.net.cn/read.php/1724.htm
这里讨论的游戏服务器架构大概是目前国内乃至世界上的网游通用的一种架构了：
http://bbs.gameres.com/showthread.asp?threadid=93775
作者：qinglan

有段时间没有研究技术了，这次正好看到了新版的mangos，较之以前我看的版本有了比较大的完善，于是再次浏览了下他的代码，也借此机会整理下我在游戏服务器开发方面的一些心得，与大家探讨。
另外由于为避免与公司引起一些不必要的纠纷，我所描述的全都是通过google能够找到的资料，所以也可以认为我下面的内容都是网上所找资料的整理合 集。在平时的开发中我也搜索过相关的中文网页，很少有讲游戏服务器相关技术的，大家的讨论主要还是集中在3D相关技术，所以也希望我将开始的这几篇文章能 够起到抛砖引玉的作用，潜水的兄弟们也都上来透透气。
要描述一项技术或是一个行业，一般都会从其最古老的历史开始说起，我本也想按着 这个套路走，无奈本人乃一八零后小辈，没有经历过那些苦涩的却令人羡慕的单机游戏开发，也没有响当当的拿的出手的优秀作品，所以也就只能就我所了解的一些 技术做些简单的描述。一来算是敦促自己对知识做个梳理，二来与大家探讨的过程也能够找到我之前学习的不足和理解上的错误，最后呢，有可能的话也跟业内的同 行们混个脸熟，哪天要是想换个工作了也好有个人帮忙介绍下。最后的理由有些俗了。
关于游戏开发，正如云风在其blog上所说，游戏项 目始终只是个小工程，另外开发时间还是个很重要的问题，所以软件工程的思想及方法在大部分的游戏公司中并不怎么受欢迎。当然这也只是从我个人一些肤浅的了 解所得，可能不够充分。从游戏开发的程序团队的人员构成上也可看出来，基本只能算作是小开发团队。有些工作室性质的开发团队，那就更简单了。
我所了解的早些的开发团队，其成员间没有什么严格的分工，大家凭兴趣自由选择一些模块来负责，完成了再去负责另一模块，有其他同事的工作需要接手或协助 的也会立即转入。所以游戏开发人员基本都是多面手，从网络到数据库，从游戏逻辑到图形图象，每一项都有所了解，并能实际应用。或者说都具有非常强的学习能 力，在接手一项新的任务后能在很短的时间内对该领域的技术迅速掌握并消化，而且还能现炒现卖。当然，这也与早期2D游戏的技术要求相对比较简单，游戏逻辑 也没有现在这般复杂有关。而更重要的可能是，都是被逼出来的吧！:)
好了，闲话少说，下一篇，也就是第一篇了，主题为，服务器结构探讨。
服务器结构探讨 &#8212; 最简单的结构
所谓服务器结构，也就是如何将服务器各部分合理地安排，以实现最初的功能需求。所以，结构本无所谓正确与错误；当然，优秀的结构更有助于系统的搭建，对系统的可扩展性及可维护性也有更大的帮助。
好的结构不是一蹴而就的，而且每个设计者心中的那把尺都不相同，所以这个优秀结构的定义也就没有定论。在这里，我们不打算对现有游戏结构做评价，而是试着从头开始搭建一个我们需要的MMOG结构。
对于一个最简单的游戏服务器来说，它只需要能够接受来自客户端的连接请求，然后处理客户端在游戏世界中的移动及交互，也即游戏逻辑处理即可。如果我们把这两项功能集成到一个服务进程中，则最终的结构很简单：
client &#8212;&#8211; server
嗯，太简单了点，这样也敢叫服务器结构？好吧，现在我们来往里面稍稍加点东西，让它看起来更像是服务器结构一些。
一般来说，我们在接入游戏服务器的时候都会要提供一个帐号和密码，验证通过后才能进入。关于为什么要提供用户名和密码才能进入的问题我们这里不打算做过 多讨论，云风曾对此也提出过类似的疑问，并给出了只用一个标识串就能进入的设想，有兴趣的可以去看看他们的讨论。但不管是采用何种方式进入，照目前看来我 们的服务器起码得提供一个帐号验证的功能。
我们把观察点先集中在一个大区内。在大多数情况下，一个大区内都会有多组游戏服，也就是多 个游戏世界可供选择。简单点来实现，我们完全可以抛弃这个大区的概念，认为一个大区也就是放在同一个机房的多台服务器组，各服务器组间没有什么关系。这 样，我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样：
loginServer   gameServer
&#124;           /
&#124;         /
client
该结构下的玩家操作流程为，先选择大区，再选择大区下的某台服务器，即某个游戏世界，点击进入后开始帐号验证过程，验证成功则进入了该游戏世界。但是，如果玩家想要切换游戏世界，他只能先退出当前游戏世界，然后进入新的游戏世界重新进行帐号验证。
早期的游戏大都采用的是这种结构，有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号，但整体结构还是未做改变。
该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单，就是将玩家提交的帐号和密码送到数据库进行验证，和生成会话密钥发送给游 戏服和客户端，操作完成后连接就会立即断开，而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较 空闲的，但是在某些时候，由于请求比较密集，比如开新服的时候，登录服的负载又会比较大，甚至会处理不过来。
另外在实际的游戏运营中，有些游戏世界很火爆，而有些游戏世界却非常冷清，甚至没有多少人玩的情况也是很常见的。所以，我们能否更合理地配置登录服资源，使得整个大区内的登录服可以共享就成了下一步改进的目标。
服务器结构探讨 &#8212; 登录服的负载均衡
回想一下我们在玩wow时的操作流程：运行wow.exe进入游戏后，首先就会要求我们输入用户名和密码进行验证，验证成功后才会出来游戏世界列表，之后是排队进入游戏世界，开始游戏&#8230;
可以看到跟前面的描述有个很明显的不同，那就是要先验证帐号再选择游戏世界。这种结构也就使得登录服不是固定配备给个游戏世界，而是全区共有的。
我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述过的那样，登录服在大多数情况下都是比较空闲的，也许我们的一个拥有20个游戏世 界的大区仅仅使用10台或更少的登录服即可满足需求。而当在开新区的时候，或许要配备40台登录服才能应付那如潮水般涌入的玩家登录请求。所以，登录服在 设计上应该能满足这种动态增删的需求，我们可以在任何时候为大区增加或减少登录服的部署。
当然，在这里也不会存在要求添加太多登录服的情况。还是拿开新区的情况来说，即使新增加登录服满足了玩家登录的请求，游戏世界服的承载能力依然有限，玩家一样只能在排队系统中等待，或者是进入到游戏世界中导致大家都卡。
另外，当我们在增加或移除登录服的时候不应该需要对游戏世界服有所改动，也不会要求重启世界服，当然也不应该要求客户端有什么更新或者修改，一切都是在背后自动完成。
最后，有关数据持久化的问题也在这里考虑一下。一般来说，使用现有的商业数据库系统比自己手工技术先进要明智得多。我们需要持久化的数据有玩家的帐号及密码，玩家创建的角色相关信息，另外还有一些游戏世界全局共有数据也需要持久化。
好了，需求已经提出来了，现在来考虑如何将其实现。
对于负载均衡来说，已有了成熟的解决方案。一般最常用，也最简单部署的应该是基于DNS的负载均衡系统了，其通过在DNS中为一个域名配置多个IP地址 来实现。最新的DNS服务已实现了根据服务器系统状态来实现的动态负载均衡，也就是实现了真正意义上的负载均衡，这样也就有效地解决了当某台登录服当机 后，DNS服务器不能立即做出反应的问题。当然，如果找不到这样的解决方案，自己从头打造一个也并不难。而且，通过DNS来实现的负载均衡已经包含了所做 的修改对登录服及客户端的透明。
而对于数据库的应用，在这种结构下，登录服及游戏世界服都会需要连接数据库。从数据库服务器的部署上来说，可以将帐号和角色数据都放在一个中心数据库中，也可分为两个不同的库分别来处理，基到从物理上分到两台不同的服务器上去也行。
但是对于不同的游戏世界来说，其角色及游戏内数据都是互相独立的，所以一般情况下也就为每个游戏世界单独配备一台数据库服务器，以减轻数据库的压力。所 以，整体的服务器结构应该是一个大区有一台帐号数据库服务器，所有的登录服都连接到这里。而每个游戏世界都有自己的游戏数据库服务器，只允许本游戏世界内 的服务器连接。
最后，我们的服务器结构就像这样：
大区服务器
/   　 &#124;       \
/       &#124;       　\
登录服1   登录服2   世界服1   世界服2
\     　   &#124;       　 &#124;     　 &#124;
\   　   &#124;       　 &#124;         &#124;
帐号数据库         DBS     DBS
这里既然讨论到了大区及帐号数据库，所以顺带也说一下关于激活大区的概念。wow中一共有八个大区，我们想要进入某个大区游戏之前，必须到官网上激活这个区，这是为什么呢？
一般来说，在各个大区帐号数据库之上还有一个总的帐号数据库，我们可以称它为中心数据库。比如我们在官网上注册了一个帐号，这时帐号数据是只保存在中心 数据库上的。而当我们要到一区去创建角色开始游戏的时候，在一区的帐号数据库中并没有我们的帐号数据，所以，我们必须先到官网上做一次激活操作。这个激活 [...]]]></description>
			<content:encoded><![CDATA[<div id="blog_text">
<p>来自：<a href="http://www.libing.net.cn/read.php/1724.htm">http://www.libing.net.cn/read.php/1724.htm</a></p>
<p>这里讨论的游戏服务器架构大概是目前国内乃至世界上的网游通用的一种架构了：</p>
<p>http://bbs.gameres.com/showthread.asp?threadid=93775</p>
<p>作者：qinglan<br />
<a name="entrymore"></a><span id="more-307"></span></p>
<p>有段时间没有研究技术了，这次正好看到了新版的mangos，较之以前我看的版本有了比较大的完善，于是再次浏览了下他的代码，也借此机会整理下我在游戏服务器开发方面的一些心得，与大家探讨。<br />
另外由于为避免与公司引起一些不必要的纠纷，我所描述的全都是通过google能够找到的资料，所以也可以认为我下面的内容都是网上所找资料的整理合 集。在平时的开发中我也搜索过相关的中文网页，很少有讲游戏服务器相关技术的，大家的讨论主要还是集中在3D相关技术，所以也希望我将开始的这几篇文章能 够起到抛砖引玉的作用，潜水的兄弟们也都上来透透气。</p>
<p>要描述一项技术或是一个行业，一般都会从其最古老的历史开始说起，我本也想按着 这个套路走，无奈本人乃一八零后小辈，没有经历过那些苦涩的却令人羡慕的单机游戏开发，也没有响当当的拿的出手的优秀作品，所以也就只能就我所了解的一些 技术做些简单的描述。一来算是敦促自己对知识做个梳理，二来与大家探讨的过程也能够找到我之前学习的不足和理解上的错误，最后呢，有可能的话也跟业内的同 行们混个脸熟，哪天要是想换个工作了也好有个人帮忙介绍下。最后的理由有些俗了。</p>
<p>关于游戏开发，正如云风在其blog上所说，游戏项 目始终只是个小工程，另外开发时间还是个很重要的问题，所以软件工程的思想及方法在大部分的游戏公司中并不怎么受欢迎。当然这也只是从我个人一些肤浅的了 解所得，可能不够充分。从游戏开发的程序团队的人员构成上也可看出来，基本只能算作是小开发团队。有些工作室性质的开发团队，那就更简单了。</p>
<p>我所了解的早些的开发团队，其成员间没有什么严格的分工，大家凭兴趣自由选择一些模块来负责，完成了再去负责另一模块，有其他同事的工作需要接手或协助 的也会立即转入。所以游戏开发人员基本都是多面手，从网络到数据库，从游戏逻辑到图形图象，每一项都有所了解，并能实际应用。或者说都具有非常强的学习能 力，在接手一项新的任务后能在很短的时间内对该领域的技术迅速掌握并消化，而且还能现炒现卖。当然，这也与早期2D游戏的技术要求相对比较简单，游戏逻辑 也没有现在这般复杂有关。而更重要的可能是，都是被逼出来的吧！:)</p>
<p>好了，闲话少说，下一篇，也就是第一篇了，主题为，服务器结构探讨。</p>
<p>服务器结构探讨 &#8212; 最简单的结构</p>
<p>所谓服务器结构，也就是如何将服务器各部分合理地安排，以实现最初的功能需求。所以，结构本无所谓正确与错误；当然，优秀的结构更有助于系统的搭建，对系统的可扩展性及可维护性也有更大的帮助。</p>
<p>好的结构不是一蹴而就的，而且每个设计者心中的那把尺都不相同，所以这个优秀结构的定义也就没有定论。在这里，我们不打算对现有游戏结构做评价，而是试着从头开始搭建一个我们需要的MMOG结构。</p>
<p>对于一个最简单的游戏服务器来说，它只需要能够接受来自客户端的连接请求，然后处理客户端在游戏世界中的移动及交互，也即游戏逻辑处理即可。如果我们把这两项功能集成到一个服务进程中，则最终的结构很简单：</p>
<p>client &#8212;&#8211; server</p>
<p>嗯，太简单了点，这样也敢叫服务器结构？好吧，现在我们来往里面稍稍加点东西，让它看起来更像是服务器结构一些。</p>
<p>一般来说，我们在接入游戏服务器的时候都会要提供一个帐号和密码，验证通过后才能进入。关于为什么要提供用户名和密码才能进入的问题我们这里不打算做过 多讨论，云风曾对此也提出过类似的疑问，并给出了只用一个标识串就能进入的设想，有兴趣的可以去看看他们的讨论。但不管是采用何种方式进入，照目前看来我 们的服务器起码得提供一个帐号验证的功能。</p>
<p>我们把观察点先集中在一个大区内。在大多数情况下，一个大区内都会有多组游戏服，也就是多 个游戏世界可供选择。简单点来实现，我们完全可以抛弃这个大区的概念，认为一个大区也就是放在同一个机房的多台服务器组，各服务器组间没有什么关系。这 样，我们可为每组服务器单独配备一台登录服。最后的结构图应该像这样：</p>
<p>loginServer   gameServer<br />
|           /<br />
|         /<br />
client</p>
<p>该结构下的玩家操作流程为，先选择大区，再选择大区下的某台服务器，即某个游戏世界，点击进入后开始帐号验证过程，验证成功则进入了该游戏世界。但是，如果玩家想要切换游戏世界，他只能先退出当前游戏世界，然后进入新的游戏世界重新进行帐号验证。</p>
<p>早期的游戏大都采用的是这种结构，有些游戏在实现时采用了一些技术手段使得在切换游戏服时不需要再次验证帐号，但整体结构还是未做改变。</p>
<p>该结构存在一个服务器资源配置的问题。因为登录服处理的逻辑相对来说比较简单，就是将玩家提交的帐号和密码送到数据库进行验证，和生成会话密钥发送给游 戏服和客户端，操作完成后连接就会立即断开，而且玩家在以后的游戏过程中不会再与登录服打任何交道。这样处理短连接的过程使得系统在大多数情况下都是比较 空闲的，但是在某些时候，由于请求比较密集，比如开新服的时候，登录服的负载又会比较大，甚至会处理不过来。</p>
<p>另外在实际的游戏运营中，有些游戏世界很火爆，而有些游戏世界却非常冷清，甚至没有多少人玩的情况也是很常见的。所以，我们能否更合理地配置登录服资源，使得整个大区内的登录服可以共享就成了下一步改进的目标。</p>
<p>服务器结构探讨 &#8212; 登录服的负载均衡</p>
<p>回想一下我们在玩wow时的操作流程：运行wow.exe进入游戏后，首先就会要求我们输入用户名和密码进行验证，验证成功后才会出来游戏世界列表，之后是排队进入游戏世界，开始游戏&#8230;</p>
<p>可以看到跟前面的描述有个很明显的不同，那就是要先验证帐号再选择游戏世界。这种结构也就使得登录服不是固定配备给个游戏世界，而是全区共有的。</p>
<p>我们可以试着从实际需求的角度来考虑一下这个问题。正如我们之前所描述过的那样，登录服在大多数情况下都是比较空闲的，也许我们的一个拥有20个游戏世 界的大区仅仅使用10台或更少的登录服即可满足需求。而当在开新区的时候，或许要配备40台登录服才能应付那如潮水般涌入的玩家登录请求。所以，登录服在 设计上应该能满足这种动态增删的需求，我们可以在任何时候为大区增加或减少登录服的部署。</p>
<p>当然，在这里也不会存在要求添加太多登录服的情况。还是拿开新区的情况来说，即使新增加登录服满足了玩家登录的请求，游戏世界服的承载能力依然有限，玩家一样只能在排队系统中等待，或者是进入到游戏世界中导致大家都卡。</p>
<p>另外，当我们在增加或移除登录服的时候不应该需要对游戏世界服有所改动，也不会要求重启世界服，当然也不应该要求客户端有什么更新或者修改，一切都是在背后自动完成。</p>
<p>最后，有关数据持久化的问题也在这里考虑一下。一般来说，使用现有的商业数据库系统比自己手工技术先进要明智得多。我们需要持久化的数据有玩家的帐号及密码，玩家创建的角色相关信息，另外还有一些游戏世界全局共有数据也需要持久化。</p>
<p>好了，需求已经提出来了，现在来考虑如何将其实现。</p>
<p>对于负载均衡来说，已有了成熟的解决方案。一般最常用，也最简单部署的应该是基于DNS的负载均衡系统了，其通过在DNS中为一个域名配置多个IP地址 来实现。最新的DNS服务已实现了根据服务器系统状态来实现的动态负载均衡，也就是实现了真正意义上的负载均衡，这样也就有效地解决了当某台登录服当机 后，DNS服务器不能立即做出反应的问题。当然，如果找不到这样的解决方案，自己从头打造一个也并不难。而且，通过DNS来实现的负载均衡已经包含了所做 的修改对登录服及客户端的透明。</p>
<p>而对于数据库的应用，在这种结构下，登录服及游戏世界服都会需要连接数据库。从数据库服务器的部署上来说，可以将帐号和角色数据都放在一个中心数据库中，也可分为两个不同的库分别来处理，基到从物理上分到两台不同的服务器上去也行。</p>
<p>但是对于不同的游戏世界来说，其角色及游戏内数据都是互相独立的，所以一般情况下也就为每个游戏世界单独配备一台数据库服务器，以减轻数据库的压力。所 以，整体的服务器结构应该是一个大区有一台帐号数据库服务器，所有的登录服都连接到这里。而每个游戏世界都有自己的游戏数据库服务器，只允许本游戏世界内 的服务器连接。</p>
<p>最后，我们的服务器结构就像这样：</p>
<p>大区服务器<br />
/   　 |       \<br />
/       |       　\<br />
登录服1   登录服2   世界服1   世界服2<br />
\     　   |       　 |     　 |<br />
\   　   |       　 |         |<br />
帐号数据库         DBS     DBS</p>
<p>这里既然讨论到了大区及帐号数据库，所以顺带也说一下关于激活大区的概念。wow中一共有八个大区，我们想要进入某个大区游戏之前，必须到官网上激活这个区，这是为什么呢？</p>
<p>一般来说，在各个大区帐号数据库之上还有一个总的帐号数据库，我们可以称它为中心数据库。比如我们在官网上注册了一个帐号，这时帐号数据是只保存在中心 数据库上的。而当我们要到一区去创建角色开始游戏的时候，在一区的帐号数据库中并没有我们的帐号数据，所以，我们必须先到官网上做一次激活操作。这个激活 的过程也就是从中心库上把我们的帐号数据拷贝到所要到的大区帐号数据库中。</p>
<p>服务器结构探讨 &#8212; 简单的世界服实现</p>
<p>讨论了这么久我们一直都还没有进入游戏世界服务器内部，现在就让我们来窥探一下里面的结构吧。</p>
<p>对于现在大多数MMORPG来说，游戏服务器要处理的基本逻辑有移动、聊天、技能、物品、任务和生物等，另外还有地图管理与消息广播来对其他高级功能做支撑。如纵队、好友、公会、战场和副本等，这些都是通过基本逻辑功能组合或扩展而成。</p>
<p>在所有这些基础逻辑中，与我们要讨论的服务器结构关系最紧密的当属地图管理方式。决定了地图的管理方式也就决定了我们的服务器结构，我们仍然先从最简单的实现方式开始说起。</p>
<p>回想一下我们曾战斗过无数个夜晚的暗黑破坏神，整个暗黑的世界被分为了若干个独立的小地图，当我们在地图间穿越时，一般都要经过一个叫做传送门的装置。 世界中有些地图间虽然在地理上是直接相连的，但我们发现其游戏内部的逻辑却是完全隔离的。可以这样认为，一块地图就是一个独立的数据处理单元。</p>
<p>既然如此，我们就把每块地图都当作是一台独立的服务器，他提供了在这块地图上游戏时的所有逻辑功能，至于内部结构如何划分我们暂不理会，先把他当作一个黑盒子吧。</p>
<p>当两个人合作做一件事时，我们可以以对等的关系相互协商着来做，而且一般也都不会有什么问题。当人数增加到三个时，我们对等的合作关系可能会有些复杂， 因为我们每个人都同时要与另两个人合作协商。正如俗语所说的那样，三个和尚可能会碰到没水喝的情况。当人数继续增加，情况就变得不那么简单了，我们得需要 一个管理者来对我们的工作进行分工、协调。游戏的地图服务器之间也是这么回事。</p>
<p>一般来说，我们的游戏世界不可能会只有一块或者两块小地图，那顺理成章的，也就需要一个地图管理者。先称它为游戏世界的中心服务器吧，毕竟是管理者嘛，大家都以它为中心。</p>
<p>中心服务器主要维护一张地图ID到地图服务器地址的映射表。当我们要进入某张地图时，会从中心服上取得该地图的IP和port告诉客户端，客户端主动去 连接，这样进入他想要去的游戏地图。在整个游戏过程中，客户端始终只会与一台地图服务器保持连接，当要切换地图的时候，在获取到新地图的地址后，会先与当 前地图断开连接，再进入新的地图，这样保证玩家数据在服务器上只有一份。</p>
<p>我们来看看结构图是怎样的：</p>
<p>中心服务器<br />
/       \         \<br />
/         \         \<br />
登录服     地图1     地图2   地图n<br />
\         |         /       /<br />
\       |         /       /<br />
客户端</p>
<p>很简单，不是吗。但是简单并不表示功能上会有什么损失，简单也更不能表示游戏不能赚钱。早期不少游戏也确实采用的就是这种简单结构。</p>
<p>服务器结构探讨 &#8212; 继续世界服</p>
<p>都已经看出来了，这种每切换一次地图就要重新连接服务器的方式实在是不够优雅，而且在实际游戏运营中也发现，地图切换导致的卡号，复制装备等问题非常多，这里完全就是一个事故多发地段，如何避免这种频繁的连接操作呢？</p>
<p>最直接的方法就是把那个图倒转过来就行了。客户端只需要连接到中心服上，所有到地图服务器的数据都由中心服来转发。很完美的解决方案，不是吗？</p>
<p>这种结构在实际的部署中也遇到了一些挑战。对于一般的MMORPG服务器来说，单台服务器的承载量平均在2000左右，如果你的服务器很不幸地只能带 1000人，没关系，不少游戏都是如此；如果你的服务器上跑了3000多玩家依然比较流畅，那你可以自豪地告诉你的策划，多设计些大量消耗服务器资源的玩 法吧，比如大型国战、公会战争等。</p>
<p>2000人，似乎我们的策划朋友们不大愿意接受这个数字。我们将地图服务器分开来原来也是想将负载分开，以多带些客户端，现在要所有的连接都从中心服上转发，那连接数又遇到单台服务器的可最大承载量的瓶颈了。</p>
<p>这里有必要再解释下这个数字。我知道，有人一定会说，才带2000人，那是你水平不行，我随便写个TCP服务器都可带个五六千连接。问题恰恰在于你是随 便写的，而MMORPG的服务器是复杂设计的。如果一个演示socket API用的echo服务器就能满足MMOG服务器的需求，那写服务器该是件多么惬意的事啊。</p>
<p>但我们所遇到的事实是，服务器收到一个移 动包后，要向周围所有人广播，而不是echo服务器那样简单的回应；服务器在收到一个连接断开通知时要向很多人通知玩家退出事件，并将该玩家的资料写入数 据库，而不是echo服务器那样什么都不需要做；服务器在收到一个物品使用请求包后要做一系列的逻辑判断以检查玩家有没有作弊；服务器上还启动着很多定时 器用来更新游戏世界的各种状态&#8230;&#8230;</p>
<p>其实这么一比较，我们也看出资源消耗的所在了：服务器上大量的复杂的逻辑处理。再回过头来看看我们想要实现的结构，我们既想要有一个唯一的入口，使得客户端不用频繁改变连接，又希望这个唯一入口的负载不会太大，以致于接受不了多少连接。</p>
<p>仔细看一看这个需求，我们想要的仅仅只是一台管理连接的服务器，并不打算让他承担太多的游戏逻辑。既然如此，那五六千个连接也还有满足我们的要求。至少 在现在来说，一个游戏世界内，也就是一组服务器内同时有五六千个在线的玩家还是件让人很兴奋的事。事实上，在大多数游戏的大部分时间里，这个数字也是很让 人眼红的。</p>
<p>什么？你说梦幻、魔兽还有史先生的那个什么征途远不止这么点人了！噢，我说的是大多数，是大多数，不包括那些明星。你知道大陆现在有多少游戏在运营吗？或许你又该说，我们不该在一开始就把自己的目标定的太低！好吧，我们还是先不谈这个。</p>
<p>继续我们的结构讨论。一般来说，我们把这台负责连接管理的服务器称为网关服务器，因为内部的数据都要通过这个网关才能出去，不过从这台服务器提供的功能来看，称其为反向代理服务器可能更合适。我们也不在这个名字上纠缠了，就按大家通用的叫法，还是称他为网关服务器吧。</p>
<p>网关之后的结构我们依然可以采用之前描述的方案，只是，似乎并没有必要为每一个地图都开一个独立的监听端口了。我们可以试着对地图进行一些划分，由一个 Master Server来管理一些更小的Zone Server，玩家通过网关连接到Master Server上，而实际与地图有关的逻辑是分派给更小的Zone Server去处理。</p>
<p>最后的结构看起来大概是这样的：</p>
<p>Zone Server         Zone Server<br />
\             /<br />
\           /<br />
Master Server           Master Server<br />
/       \                   /<br />
/         \                 /<br />
Gateway Server         \               /<br />
|         \         \             /<br />
|         \         \           /<br />
|               Center Server<br />
|<br />
|<br />
Client</p>
<p>服务器结构探讨 &#8212; 最终的结构</p>
<p>如果我们就此打住，可能马上就会有人要嗤之以鼻了，就这点古董级的技术也敢出来现。好吧，我们还是把之前留下的问题拿出来解决掉吧。</p>
<p>一般来说，当某一部分能力达不到我们的要求时，最简单的解决方法就是在此多投入一点资源。既然想要更多的连接数，那就再加一台网关服务器吧。新增加了网 关服后需要在大区服上做相应的支持，或者再简单点，有一台主要的网关服，当其负载较高时，主动将新到达的连接重定向到其他网关服上。</p>
<p>而对于游戏服来说，有一台还是多台网关服是没有什么区别的。每个代表客户端玩家的对象内部都保留一个代表其连接的对象，消息广播时要求每个玩家对象使用自 己的连接对象发送数据即可，至于连接是在什么地方，那是完全透明的。当然，这只是一种简单的实现，也是普通使用的一种方案，如果后期想对消息广播做一些优 化的话，那可能才需要多考虑一下。</p>
<p>既然说到了优化，我们也稍稍考虑一下现在结构下可能采用的优化方案。</p>
<p>首先是当前的Zone Server要做的事情太多了，以至于他都处理不了多少连接。这其中最消耗系统资源的当属生物的AI处理了，尤其是那些复杂的寻路算法，所以我们可以考虑把这部分AI逻辑独立出来，由一台单独的AI服务器来承担。</p>
<p>然后，我们可以试着把一些与地图数据无关的公共逻辑放到Master Server上去实现，这样Zone Server上只保留了与地图数据紧密相关的逻辑，如生物管理，玩家移动和状态更新等。</p>
<p>还有聊天处理逻辑，这部分与游戏逻辑没有任何关联，我们也完全可以将其独立出来，放到一台单独的聊天服务器上去实现。</p>
<p>最后是数据库了，为了减轻数据库的压力，提高数据请求的响应速度，我们可以在数据库之前建立一个数据库缓存服务器，将一些常用数据缓存在此，服务器与数据库的通信都要通过这台服务器进行代理。缓存的数据会定时的写入到后台数据库中。</p>
<p>好了，做完这些优化我们的服务器结构大体也就定的差不多了，暂且也不再继续深入，更细化的内容等到各个部分实现的时候再探讨。</p>
<p>好比我们去看一场晚会，舞台上演员们按着预定的节目单有序地上演着，但这就是整场晚会的全部吗？显然不止，在幕后还有太多太多的人在忙碌着，甚至在晚会前和晚会后都有。我们的游戏服务器也如此。</p>
<p>在之前描述的部分就如同舞台上的演员，是我们能直接看到的，幕后的工作人员我们也来认识一下。</p>
<p>现实中有警察来维护秩序，游戏中也如此，这就是我们常说的GM。GM可以采用跟普通玩家一样的拉入方式来进入游戏，当然权限会比普通玩家高一些，也可以提供一台GM服务器专门用来处理GM命令，这样可以有更高的安全性，GM服一般接在中心服务器上。</p>
<p>在以时间收费的游戏中，我们还需要一台计费的服务器，这台服务器一般接在网关服务器上，注册玩家登录和退出事件以记录玩家的游戏时间。</p>
<p>任何为用户提供服务的地方都会有日志记录，游戏服务器当然也不例外。从记录玩家登录的时间，地址，机器信息到游戏过程中的每一项操作都可以作为日志记录下来，以备查错及数据挖掘用。至于搜集玩家机器资料所涉及到的法律问题不是我们该考虑的。</p>
<p>差不多就这么多了吧，接下来我们会按照这个大致的结构来详细讨论各部分的实现。</p>
<p>服务器结构探讨 &#8212; 一点杂谈</p>
<p>再强调一下，服务器结构本无所谓好坏，只有是否适合自己。我们在前面探讨了一些在现在的游戏中见到过的结构，并尽我所知地分析了各自存在的一些问题和可以做的一些改进，希望其中没有谬误，如果能给大家也带来些启发那自然更好。</p>
<p>突然发现自己一旦罗嗦起来还真是没完没了。接下来先说说我在开发中遇到过的一些困惑和一基础问题探讨吧，这些问题可能有人与我一样，也曾遇到过，或者正在被困扰中，而所要探讨的这些基础问题向来也是争论比较多的，我们也不评价其中的好与坏，只做简单的描述。</p>
<p>首先是服务器操作系统，linux与windows之争随处可见，其实在大多数情况下这不是我们所能决定的，似乎各大公司也基本都有了自己的传统，如网易的freebsd，腾讯的linux等。如果真有权利去选择的话，选自己最熟悉的吧。</p>
<p>决定了OS也就基本上确定了网络IO模型，windows上的IOCP和linux下的epool，或者直接使用现有的网络框架，如ACE和asio等，其他还有些商业的网络库在国内的使用好像没有见到，不符合中国国情嘛。:)</p>
<p>然后是网络协议的选择，以前的选择大多倾向于UDP，为了可靠传输一般自己都会在上面实现一层封装，而现在更普通的是直接采用本身就很可靠的TCP，或 者TCP与UDP的混用。早期选择UDP的主要原因还是带宽限制，现在宽带普通的情况下TCP比UDP多出来的一点点开销与开发的便利性相比已经不算什么 了。当然，如果已有了成熟的可靠UDP库，那也可以继续使用着。</p>
<p>还有消息包格式的定义，这个曾在云风的blog上展开过激烈的争论。消息包格式定义包括三段，包长、消息码和包体，争论的焦点在于应该是消息码在前还是包长在前，我们也把这个当作是信仰问题吧，有兴趣的去云风的blog上看看，论论。</p>
<p>另外早期有些游戏的包格式定义是以特殊字符作分隔的，这样一个好处是其中某个包出现错误后我们的游戏还能继续。但实际上，我觉得这是完全没有必要的，真 要出现这样的错误，直接断开这个客户端的连接可能更安全。而且，以特殊字符做分隔的消息包定义还加大了一点点网络数据量。</p>
<p>最后是一个 纯技术问题，有关socket连接数的最大限制。开始学习网络编程的时候我犯过这样的错误，以为port的定义为unsigned short，所以想当然的认为服务器的最大连接数为65535，这会是一个硬性的限制。而实际上，一个socket描述符在windows上的定义是 unsigned int，因此要有限制那也是四十多亿，放心好了。</p>
<p>在服务器上port是监听用的，想象这样一种情况，web server在80端口上监听，当一个连接到来时，系统会为这个连接分配一个socket句柄，同时与其在80端口上进行通讯；当另一个连接到来时，服务 器仍然在80端口与之通信，只是分配的socket句柄不一样。这个socket句柄才是描述每个连接的唯一标识。按windows网络编程第二版上的说 法，这个上限值配置影响。</p>
<p>好了，废话说完了，下一篇，我们开始进入登录服的设计吧。</p>
<p>登录服的设计 &#8212; 功能需求</p>
<p>正如我们在前面曾讨论过的，登录服要实现的功能相当简单，就是帐号验证。为了便于描述，我们暂不引入那些讨论过的优化手段，先以最简单的方式实现，另外也将基本以mangos的代码作为参考来进行描述。</p>
<p>想象一下帐号验证的实现方法，最容易的那就是把用户输入的明文用帐号和密码直接发给登录服，服务器根据帐号从数据库中取出密码，与用户输入的密码相比较。</p>
<p>这个方法存在的安全隐患实在太大，明文的密码传输太容易被截获了。那我们试着在传输之前先加一下密，为了服务器能进行密码比较，我们应该采用一个可逆的 加密算法，在服务器端把这个加密后的字串还原为原始的明文密码，然后与数据库密码进行比较。既然是一个可逆的过程，那外挂制作者总有办法知道我们的加密过 程，所以，这个方法仍不够安全。</p>
<p>哦，如果我们只是希望密码不可能被还原出来，那还不容易吗，使用一个不可逆的散列算法就行了。用户在 登录时发送给服务器的是明文的帐号和经散列后的不可逆密码串，服务器取出密码后也用同样的算法进行散列后再进行比较。比如，我们就用使用最广泛的md5算 法吧。噢，不要管那个王小云的什么论文，如果我真有那么好的运气，早中500w了，还用在这考虑该死的服务器设计吗？</p>
<p>似乎是一个很完美的方案，外挂制作者再也偷不到我们的密码了。慢着，外挂偷密码的目的是什么？是为了能用我们的帐号进游戏！如果我们总是用一种固定的算法来对密码做散列，那外挂只需要记住这个散列后的字串就行了，用这个做密码就可以成功登录。</p>
<p>嗯，这个问题好解决，我们不要用固定的算法进行散列就是了。只是，问题在于服务器与客户端采用的散列算法得出的字串必须是相同的，或者是可验证其是否匹 配的。很幸运的是，伟大的数学字们早就为我们准备好了很多优秀的这类算法，而且经理论和实践都证明他们也确实是足够安全的。</p>
<p>这其中之 一是一个叫做SRP的算法，全称叫做Secure Remote Password，即安全远程密码。wow使用的是第6版，也就是SRP6算法。有关其中的数学证明，如果有人能向我解释清楚，并能让我真正弄明白的话， 我将非常感激。不过其代码实现步骤倒是并不复杂，mangos中的代码也还算清晰，我们也不再赘述。</p>
<p>登录服除了帐号验证外还得提供另 一项功能，就是在玩家的帐号验证成功后返回给他一个服务器列表让他去选择。这个列表的状态要定时刷新，可能有新的游戏世界开放了，也可能有些游戏世界非常 不幸地停止运转了，这些状态的变化都要尽可能及时地让玩家知道。不管发生了什么事，用户都有权利知道，特别是对于付过费的用户来说，我们不该藏着掖着，不 是吗？</p>
<p>这个游戏世界列表的功能将由大区服来提供，具体的结构我们在之前也描述过，这里暂不做讨论。登录服将从大区服上获取到的游戏世界列表发给已验证通过的客户端即可。好了，登录服要实现的功能就这些，很简单，是吧。</p>
<p>确实是太简单了，不过简单的结构正好更适合我们来看一看游戏服务器内部的模块结构，以及一些服务器共有组件的实现方法。这就留作下一篇吧。</p>
<p>服务器公共组件实现 &#8212; mangos的游戏主循环</p>
<p>当阅读一项工程的源码时，我们大概会选择从main函数开始，而当开始一项新的工程时，第一个写下的函数大多也是main。那我们就先来看看，游戏服务器代码实现中，main函数都做了些什么。</p>
<p>由于我在读技术文章时最不喜看到的就是大段大段的代码，特别是那些直接Ctrl+C再Ctrl+V后未做任何修改的代码，用句时髦的话说，一点技术含量 都没有！所以在我们今后所要讨论的内容中，尽量会避免出现直接的代码，在有些地方确实需要代码来表述时，也将会选择使用伪码。</p>
<p>先从mangos的登录服代码开始。mangos的登录服是一个单线程的结构，虽然在数据库连接中可以开启一个独立的线程，但这个线程也只是对无返回结果的执行类SQL做缓冲，而对需要有返回结果的查询类SQL还是在主逻辑线程中阻塞调用的。</p>
<p>登录服中唯一的这一个线程，也就是主循环线程对监听的socket做select操作，为每个连接进来的客户端读取其上的数据并立即进行处理，直到服务器收到SIGABRT或SIGBREAK信号时结束。</p>
<p>所以，mangos登录服主循环的逻辑，也包括后面游戏服的逻辑，主循环的关键代码其实是在SocketHandler中，也就是那个Select函数 中。检查所有的连接，对新到来的连接调用OnAccept方法，有数据到来的连接则调用OnRead方法，然后socket处理器自己定义对接收到的数据 如何处理。</p>
<p>很简单的结构，也比较容易理解。</p>
<p>只是，在对性能要求比较高的服务器上，select一般不会 是最好的选择。如果我们使用windows平台，那IOCP将是首选；如果是linux，epool将是不二选择。我们也不打算讨论基于IOCP或是基于 epool的服务器实现，如果仅仅只是要实现服务器功能，很简单的几个API调用即可，而且网上已有很多好的教程；如果是要做一个成熟的网络服务器产品， 不是我几篇简单的技术介绍文章所能达到。</p>
<p>另外，在服务器实现上，网络IO与逻辑处理一般会放在不同的线程中，以免耗时较长的IO过程阻塞住了需要立即反应的游戏逻辑。</p>
<p>数据库的处理也类似，会使用异步的方式，也是避免耗时的查询过程将游戏服务器主循环阻塞住。想象一下，因某个玩家上线而发起的一次数据库查询操作导致服务器内所有在线玩家都卡住不动将是多么恐怖的一件事！</p>
<p>另外还有一些如事件、脚本、消息队列、状态机、日志和异常处理等公共组件，我们也会在接下来的时间里进行探讨。</p>
<p>服务器公共组件实现 &#8212; 继续来说主循环</p>
<p>前面我们只简单了解了下mangos登录服的程序结构，也发现了一些不足之处，现在我们就来看看如何提供一个更好的方案。</p>
<p>正如我们曾讨论过的，为了游戏主逻辑循环的流畅运行，所有比较耗时的IO操作都会分享到单独的线程中去做，如网络IO，数据库IO和日志IO等。当然，也有把这些分享到单独的进程中去做的。</p>
<p>另外对于大多数服务器程序来说，在运行时都是作为精灵进程或服务进程的，所以我们并不需要服务器能够处理控制台用户输入，我们所要处理的数据来源都来自网络。</p>
<p>这样，主逻辑循环所要做的就是不停要取消息包来处理，当然这些消息包不仅有来自客户端的玩家操作数据包，也有来自GM服务器的管理命令，还包括来自数据库查询线程的返回结果消息包。这个循环将一直持续，直到收到一个通知服务器关闭的消息包。</p>
<p>主逻辑循环的结构还是很简单的，复杂的部分都在如何处理这些消息包的逻辑上。我们可以用一段简单的伪码来描述这个循环过程：</p>
<p>while (Message* msg = getMessage())<br />
{<br />
if (msg为服务器关闭消息)<br />
break;<br />
处理msg消息;<br />
}</p>
<p>这里就有一个问题需要探讨了，在getMessage()的时候，我们应该去哪里取消息？前面我们考虑过，至少会有三个消息来源，而我们还讨论过，这些消息源的IO操作都是在独立的线程中进行的，我们这里的主线程不应该直接去那几处消息源进行阻塞式的IO操作。</p>
<p>很简单，让那些独立的IO线程在接收完数据后自己送过来就是了。好比是，我这里提供了一个仓库，有很多的供货商，他们有货要给我的时候只需要交到仓库， 然后我再到仓库去取就是了，这个仓库也就是消息队列。消息队列是一个普通的队列实现，当然必须要提供多线程互斥访问的安全性支持，其基本的接口定义大概类 似这样：</p>
<p>IMessageQueue<br />
{<br />
void putMessage(Message*);<br />
Message* getMessage();<br />
}</p>
<p>网络IO，数据库IO线程把整理好的消息包都加入到主逻辑循环线程的这个消息队列中便返回。有关消息队列的实现和线程间消息的传递在ACE中有比较完全的代码实现及描述，还有一些使用示例，是个很好的参考。</p>
<p>这样的话，我们的主循环就很清晰了，从主线程的消息队列中取消息，处理消息，再取下一条消息&#8230;&#8230;</p>
<p>服务器公共组件实现 &#8212; 消息队列</p>
<p>既然说到了消息队列，那我们继续来稍微多聊一点吧。</p>
<p>我们所能想到的最简单的消息队列可能就是使用stl的list来实现了，即消息队列内部维护一个list和一个互斥锁，putMessage时将 message加入到队列尾，getMessage时从队列头取一个message返回，同时在getMessage和putMessage之前都要求先 获取锁资源。</p>
<p>实现虽然简单，但功能是绝对满足需求的，只是性能上可能稍稍有些不尽如人意。其最大的问题在频繁的锁竞争上。</p>
<p>对于如何减少锁竞争次数的优化方案，Ghost Cheng提出了一种。提供一个队列容器，里面有多个队列，每个队列都可固定存放一定数量的消息。网络IO线程要给逻辑线程投递消息时，会从队列容器中取 一个空队列来使用，直到将该队列填满后再放回容器中换另一个空队列。而逻辑线程取消息时是从队列容器中取一个有消息的队列来读取，处理完后清空队列再放回 到容器中。</p>
<p>这样便使得只有在对队列容器进行操作时才需要加锁，而IO线程和逻辑线程在操作自己当前使用的队列时都不需要加锁，所以锁竞争的机会大大减少了。</p>
<p>这里为每个队列设了个最大消息数，看来好像是打算只有当IO线程写满队列时才会将其放回到容器中换另一个队列。那这样有时也会出现IO线程未写满一个队 列，而逻辑线程又没有数据可处理的情况，特别是当数据量很少时可能会很容易出现。Ghost Cheng在他的描述中没有讲到如何解决这种问题，但我们可以先来看看另一个方案。</p>
<p>这个方案与上一个方案基本类似，只是不再提供队列 容器，因为在这个方案中只使用了两个队列，arthur在他的一封邮件中描述了这个方案的实现及部分代码。两个队列，一个给逻辑线程读，一个给IO线程用 来写，当逻辑线程读完队列后会将自己的队列与IO线程的队列相调换。所以，这种方案下加锁的次数会比较多一些，IO线程每次写队列时都要加锁，逻辑线程在 调换队列时也需要加锁，但逻辑线程在读队列时是不需要加锁的。</p>
<p>虽然看起来锁的调用次数是比前一种方案要多很多，但实际上大部分锁调用都是不会引起阻塞的，只有在逻辑线程调换队列的那一瞬间可能会使得某个线程阻塞一下。另外对于锁调用过程本身来说，其开销是完全可以忽略的，我们所不能忍受的仅仅是因为锁调用而引起的阻塞而已。</p>
<p>两种方案都是很优秀的优化方案，但也都是有其适用范围的。Ghost Cheng的方案因为提供了多个队列，可以使得多个IO线程可以总工程师的，互不干扰的使用自己的队列，只是还有一个遗留问题我们还不了解其解决方法。 arthur的方案很好的解决了上一个方案遗留的问题，但因为只有一个写队列，所以当想要提供多个IO线程时，线程间互斥地写入数据可能会增大竞争的机 会，当然，如果只有一个IO线程那将是非常完美的。</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.javagg.com/archives/307/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

