分类目录归档:工作

《烙印》9月20日开放压测

[url=http://www.9961.cn/download/index.jsp]客户端下载[/url]
[url=http://www.9961.cn/generate/news_80.html]获取封测账号[/url]

2010年9月20日中午12点正式开放登录,压测开始。压测时间从9月20日中午12:00时至9月21日晚上24:00时共36个小时。
2010年9月25日开始封测。

普通版游戏截图(精美版20日揭晓):
[img]http://www.9961.cn/upload/photos/jt-05.jpg[/img][img]http://www.9961.cn/upload/photos/jt-02.jpg[/img]
[img]http://www.9961.cn/upload/photos/jt-06.jpg[/img][img]http://www.9961.cn/upload/photos/jt-09.jpg[/img]

《烙印-巨龙的咆哮》

之前由于需要保密,现在终于可以在这里介绍我们的第一款产品《烙印》了。
我以后会在第一时间把最新的情况和资料放上来。
下面是各大网站对我们的介绍。

腾讯:[url=http://games.qq.com/a/20100426/000446.htm]http://games.qq.com/a/20100426/000446.htm[/url]
手游天下:[url=http://www.155.cn/news/18990.html]http://www.155.cn/news/18990.html[/url]
猫扑:
《烙印》媲美PC网游不再是传说[url=http://tech.mop.com/net/2010/0428/1835309722.shtml]http://tech.mop.com/net/2010/0428/1835309722.shtml[/url]
九九乐游开创高端手机网游先河[url=http://tech.mop.com/net/2010/0428/1836309723.shtml]http://tech.mop.com/net/2010/0428/1836309723.shtml[/url]
TOM:
手机网游挑战PC网游[url=http://post.news.tom.com/DA0001B0197.html]http://post.news.tom.com/DA0001B0197.html[/url]
《烙印》媲美PC网游不再是传说[url=http://post.news.tom.com/DA0001B0198.html]http://post.news.tom.com/DA0001B0198.html[/url]
msn网游首页:
九九乐游开创高端手机网游先河 [url=http://msn.yesky.com/gamedm/a/11237022.shtml]http://msn.yesky.com/gamedm/a/11237022.shtml[/url]
《烙印》媲美PC网游不再是传说 [url=http://msn.yesky.com/gamedm/a/11237007.shtml]http://msn.yesky.com/gamedm/a/11237007.shtml[/url]
新浪:
《烙印》媲美PC网游不再是传说 [url=http://games.sina.com.cn/m/n/2010-04-28/1135395025.shtml]http://games.sina.com.cn/m/n/2010-04-28/1135395025.shtml[/url]  
九九乐游开创高端手机网游先河 [url=http://games.sina.com.cn/m/n/2010-04-28/1137395031.shtml]http://games.sina.com.cn/m/n/2010-04-28/1137395031.shtml[/url]
中关村在线 [url=http://game.zol.com.cn/175/1758797.html]http://game.zol.com.cn/175/1758797.html[/url]
                 [url=http://game.zol.com.cn/175/1758798.html]http://game.zol.com.cn/175/1758798.html[/url]

招聘J2me网游客户端开发工程师

参与公司网游Java语言平台(包括J2me,Android等)的开发或移植。同时您有机会参加公司其他新平台的游戏开发工作(例如支持WIFI的手持游戏设备、Symbian、WM、iPhone、Brew等平台)。公司将为您的发展提供足够的发展空间及方向。

职责:
1、   负责游戏逻辑、AI和工具的开发
2、   与策划人员、美工共同讨论产品开发细节
3、   遇到技术无法实现的产品设计,能与相关人员进行协商,力争用最佳方法解决
4、   积极努力地根据测试人员提出的意见对程序进行修改,确保游戏的高品质
5、   主动总结和分享自己的开发经验,能和各客户端平台同事共享技术和交流开发经验
6、   有很强的团队工作精神,在团队中做好产品开发强有力的技术支持工作

要求
1、   热爱游戏开发,乐于接受挑战
2、   一年以上游戏开发经验
3、   扎实的j2me和j2se语言基础和算法分析能力
4、   熟练掌握数据结构和算法,良好的数学、物理、计算机及操作系统知识
5、   优秀的优化、调试能力和分析、解决问题能力,逻辑思维清晰严谨
6、   有良好的面向对象分析、设计能力、规范的编程风格和良好文档习惯
7、   有良好英语读写能力
8、   有良好的团队精神、敬业精神和沟通协调能力,能主动学习钻研与工作相关的知识和技术

满足以下要求者优先考虑:
1、   跨平台(BREW、Symbian)游戏开发经验
2、   熟练掌握3D图形学原理、有JSR 184开发经验
3、   有Online游戏服务器和客户端网络通信部分的开发经验

公司地址:
北京市西城区德外大街乙10号泰富大厦六层604室

简历发送到:yinxj@kaiqi.com

J2ME游戏开发教程:初级动作闯关类-《混血王子与梦幻国度》(五)(7.23更新)

在(四)的最后,我们给出了主角的按键处理,从代码中大致可以看出,我们的spirit类通过action方法向人物传达指令。人物在接收到指令后,切换不同的状态,然后游戏每一帧循环中执行人物的AI逻辑,根据状态更新相应的动画。大致就是这样的。
我们先把action方法添加到Spirit类中
[color=#0000FF]public void action(int newstate){
  if(timeCount==0){
       speedControl=0;
       frameCount=0;
       action_change(newstate);
  }
  else{//如果是下面这几种情况的话,立即中止当前动画,转换新的状态,进行新的动画
    if(((state==MOVER || state==MOVEL || state==MOVE) && newstate!=state)
        || (newstate==DEAD && state!=DEAD) || (newstate==FAINT && state!=FAINT)){
      timeCount=0;
      action(newstate);
    }
  }
}[/color]
timeCount是一个动画计时器,当一个动画开始时,timeCount是这个动画持续时间值,然后随游戏帧减小,为0时也就是动画播放结束的时候。frameCount保存了当前动画的帧数,speedControl是速度的控制,对应下面的TIMESFRAME,看到后面的代码,马上就会明白他们的具体作用了。
然后来看看action_change方法:
  [color=#0000FF]int TIMESFRAME=60/TheBallCanvas.TIME_PER_FRAME;
  int GIdx;
  public void action_change(int newstate){
    state = newstate;
    switch(newstate){
      case STAND:
        timeCount = 1;//TIMESFRAME;
        break;
      case MOVEL:
      case MOVER:
        direct = newstate;//人物的方向和方向的状态值是对应的,-1为左,1为右,注意没有break
      case MOVE:
        timeCount = TIMESFRAME*ACTION_RUN.length;
        break;
      case ATTACK:
        timeCount = TIMESFRAME*ACTION_ATTACK.length;
        break;
      case UJUMP:
      case RJUMP:
      case LJUMP:
        GIdx=0;//GIdx是重力加速度数组的id
        timeCount=TIMESFRAME*ACTION_JUMP.length;
        break;
      case FAINT:
        timeCount = TIMESFRAME*ACTION_FAINT.length;
        break;
      case DEAD:
        timeCount = TIMESFRAME*ACTION_DEAD.length;
        break;
      case TAKEOFF:
        timeCount = TIMESFRAME*ACTION_TAKEOFF.length;
        break;
    }
    actionLength = timeCount;//新动作的总时间长度,单位:帧
  }[/color]
TIMESFRAME表示动画的每一帧对应游戏的帧数,这里我们用60除以游戏帧的时间,得出游戏循环两帧,动画更新一帧

上面这两个方法都是触发调用的,也就是根据逻辑需要调用方法来改变人物状态。我们还需要一个人物的主逻辑方法,在游戏运行的每一帧中,每一个人物都要进行这个逻辑,更新动画,我们来添加这个方法:
[color=#0000FF]public void actionDo(){
  timeCount–;
  switch(state){
    case STAND:
      imageIdx=ACTION_STAND;
      break;
    case MOVER:
    case MOVEL:
      this.direct = state;
    case MOVE:
      if(speedControl == 0){ // 进行图片,位置等变换的时刻
        if(frameCount >= ACTION_RUN.length){
          frameCount = 0;
        }  
        imageIdx= ACTION_RUN[frameCount];
        frameCount ++;
      }
      if(style==7 && !TheBallCanvas.canAction && TheBallCanvas.theBallSpt.mapX==mapX && TheBallCanvas.theBallSpt.y==y-16){
        mapX+= direct*speed/TIMESFRAME;
        TheBallCanvas.theBallSpt.mapX=mapX;
      }
      else if(style==5){
        if(TheBallCanvas.gamestate==TheBallCanvas.GAME_GAMING){
          mapX+= direct*speed/TIMESFRAME;
          y+=direct*speed/TIMESFRAME;
        }
      }
      else
        mapX+= direct*speed/TIMESFRAME;
      speedControl ++;
      if(speedControl >= TIMESFRAME)
        speedControl = 0;
      break;
  }
}[/color]
actionDo每帧调用,timeCount递减,然后根据状态更新动画,我们先给出了站立和移动的逻辑。这里的大概结构是这样的,speedControl控制着TIMESFRAME设置的游戏每两帧更新动画,当speedControl等于0的时候可以更新动画,即frameCount增加,然后把对应的图片id传给imageIdx;每一帧speedControl递增,大于等于TIMESFRAME时规零。
我们在Canvas中添加actionDo的调用,在logicGameRun方法的logicFarScene();之后添加:
[color=#0000FF]setScreenPos();  
if(theBallSpt.timeCount>0){
  theBallSpt.actionDo();
}
else
  theBallSpt.state=Spirit.STAND;//当动画结束后回到站立状态[/color]

下面来说说人物和地图的坐标结构,每个人物有x,y值,分别表示人物在屏幕上的位置,还有一个mapX表示人物在地图上的位置。我们把主角的位置定在屏幕中间,那么一般情况下mapX就是半个屏幕的宽度,然后给地图也定义了一个mapX变量表示地图最左端距离屏幕最左端的值,没错这个值最大为0,是个负值,并且地图的mapX值根据主角的mapX和x值来确定。在Canvas类中添加setSpiritPos方法来更新地图坐标值
[color=#0000FF]private void setScreenPos() {
  if(theBallSpt.mapX>screenW/2 && lvlW-theBallSpt.mapX>screenW/2){
    theBallSpt.x=screenW/2;
    mapX=theBallSpt.x-theBallSpt.mapX;
  }
  else if(theBallSpt.mapX<=screenW/2){
    mapX=0;
    theBallSpt.x=theBallSpt.mapX;
  }
  else if(lvlW-theBallSpt.mapX<=screenW/2){
    mapX=screenW-lvlW+1;
    theBallSpt.x=mapX+theBallSpt.mapX;
  }
  for(int i=0;i<theEmySpt.length;i++)
    if(theEmySpt[i]!=null)
      theEmySpt[i].x=theEmySpt[i].mapX+mapX;
}[/color]
这里大概是这样的:主角的地图前半个屏幕和最后半个屏幕之间的时候,主角的x坐标为定值半个屏幕宽度,并且屏幕mapX值就是主角x值减去主角在地图上的坐标,这个应该很好理解吧;同样但是特殊的,如果主角在地图前半个屏幕,主角的x值就是他的mapX值,此时地图的mapX值就是0;类似的还有主角在最后半个屏幕的情况。相对主角,敌人的坐标就很好算了,就是其在地图上的坐标加上地图的mapX。现在我们运行以下程序,主角应该已经能在地图上走动了,而且地图也会跟着移动。不过奇怪按一下键主角走几步,按住不动,主角不会一直走。我们在logicGameRun方法最后增加
[color=#0000FF]if(keyPressed){
  keyGameProcess();
}[/color]
同时增加keyRelease方法
[color=#0000FF]public void keyReleased(int keyCode){
  keyPressed=false;
  if(gamestate==GAME_GAMING)
    keyGameProcess();
}[/color]
原因应该很明显,当keyPressed为true时,应该不断的调用游戏按键处理。这下按住左右键主角就会一直走了。我们接着来添加主角跳跃的逻辑
为了让跳跃更真实,我们加入了重力加速度,Util里添加一个G数组,根据重力加速度的原理大概算出跳跃时每一帧的高度位移
先在Util中添加这个数组:
[color=#0000FF]public static int[] G={-20,-12,-4,0,4,12,20,24,26,28,28,28,28};[/color]
然后在Spirit类的actionDo中添加向上跳跃的逻辑
[color=#0000FF]case UJUMP:
  if(speedControl == 0){ // 进行图片,位置等变换的时刻
    if(frameCount >= ACTION_JUMP.length){
      frameCount = 0;
    }
    imageIdx= ACTION_JUMP[frameCount];
    frameCount ++;
  }
  if(frameCount>1 && frameCount<9){
    y+= JUMPIDX*Util.G[frameCount-2]/TIMESFRAME;
    TheBallCanvas.baseLine=TheBallCanvas.screenY+TheBallCanvas.screenH-(TheBallCanvas.deltaY+y)*2/3;
  }
  speedControl ++;
  if(speedControl >= TIMESFRAME)
    speedControl = 0;
  break;[/color]
这里有个JUMPIDX,标识不同跳跃的类型,我们可以先加上定义
[color=#0000FF]static int JUMPIDX=1;[/color]
然后添加左跳和右跳:
[color=#0000FF]case LJUMP:
case RJUMP:
  this.direct = state-Spirit.UJUMP;//更新方向
  if(speedControl == 0){ // 进行图片,位置等变换的时刻
    if(frameCount >= ACTION_JUMP.length){
      frameCount = 0;
    }
    imageIdx= ACTION_JUMP[frameCount];
    frameCount ++;
    GIdx++;
  }
  if(frameCount>1 && frameCount<8){//这里要用GIdx,因为左右跳会出现从高处往下跳,用frameCount会超出范围
    mapX+=(direct*speed/TIMESFRAME);
    y+= JUMPIDX*Util.G[GIdx-2]/TIMESFRAME;
    TheBallCanvas.baseLine=TheBallCanvas.screenY+TheBallCanvas.screenH-(TheBallCanvas.deltaY+y)*2/3;
  }
  if(y>=0){//如果掉落深渊,其实就是屏幕下边缘,转入死亡状态,也就是摔死了
    y=0;
    if(TheBallCanvas.lvlMap[0][mapX/TheBallCanvas.mapCellL]<0){
      GIdx=0;
      action(DEAD);
      break;
    }
  }
            
  if(mapX<TheBallCanvas.lvlW && TheBallCanvas.lvlMap[-y/TheBallCanvas.mapCellL][mapX/TheBallCanvas.mapCellL]>=0){//这里是一个简单的物理碰撞判断,如果出现平地就停在那格上
    for(int x=mapX>>4,y=-this.y/TheBallCanvas.mapCellL;y<TheBallCanvas.lvlMap.length;y++){
      if(TheBallCanvas.lvlMap[y][x]<0){
        this.y=-(y<<4);
        break;
      }
    }
    TheBallCanvas.baseLine=TheBallCanvas.screenY+TheBallCanvas.screenH-(TheBallCanvas.deltaY+y)*2/3;
    imageIdx=12;
    timeCount=0;
    action(TAKEOFF);//左右跳和上跳不同,不一定是上去下来对称的,所以落地时要转入落地状态
    break;
  }
  else if(frameCount==7){//从高处掉落,当到达第7帧后要反复重复6,7帧
    JUMPIDX=1;
    frameCount=6;
    timeCount+=TIMESFRAME;
  }
  speedControl ++;
  if(speedControl >= TIMESFRAME)
    speedControl = 0;
  break;[/color]
这段代码的说明就嵌入在代码里,应该没什么特别的地方。这里出现了TAKEOFF的状态,我们就继续添加这个状态的case,结构都是一样的:
[color=#0000FF]case TAKEOFF:
  if(speedControl == 0){ // 进行图片,位置等变换的时刻
    if(frameCount >= ACTION_TAKEOFF.length){
      frameCount = 0;
    }
    imageIdx= ACTION_TAKEOFF[frameCount];
    frameCount ++;
  }
  speedControl ++;
  if(speedControl >= TIMESFRAME)
    speedControl = 0;
  break;[/color]

J2ME游戏开发教程:初级动作闯关类-《混血王子与梦幻国度》(四)

在(四)中,我们就重点说一个类:Spirit。Spirit是对人物的封装,我们要在地图上增加一个敌人,只需创建对应的Spirit对象即可。在详细给出Spirit类之前,我先来介绍一下人物动作的代码结构,某一种人物有若干个动画,每个动画又有若干帧,我们把所有的这些动画帧放在一个图片上,然后把对应的编号序列存放在一个数组里,在游戏中按照这个序列依次绘制图片上对应的那部分,就形成了人物的动作。并且这些动画都在一张大图上,因此我们还要把这些帧图的坐标信息(x,y,w,h)记录下来存放在数组里,大家应该能想到,这些数组的大小应该是一样的,因为都对应的是某一帧。我们来给出之前的GameInfo类的更多的内容。在GameInfo类中添加
[color=#0000FF]int[][] ACTION_RUN={{7,4,6,4},
          {1,0,2,0},
          {1,1,0,1},
          {2},
          {1,1,0,1},
          {1,0},
          {1},
          {0},
          {0,1,2,1},
          {0,1,0,1},
          {0},
          {1},
          {0}};[/color]
看名字大家就能明白这是每种人物的走动动画的帧序列,第一维是13,表示我们游戏中有13种人物,第二维的内容就是其运动动画的帧序列,例如7,4,6,4是指这种人物的运动要依次绘制第7,4,6,4帧图,接下来我们继续添加其他动画的帧序列数据
[color=#0000FF]int[] ACTION_STAND={4,0,1,2,1,0,1,0,1,1,0,1,0};//站立没有动画,只有一帧,所以这个数组是一维的
int[][] ACTION_ATTACK={{8,9,9,4},
            {5,6,7,0},
            {2,2,2,1,1,1,1},
            {2,3,4,5,-1,5,4,3,2,9,8,9,2},
            {0,1,5,2,1},
            {1,0,1,0,1,0,1,0,3,4,2,4,0,1,0,1,0},
            {0,0,0,2,2,1,1},
            {0},
            {},
            {1},
            {0},
            {1},
            {0}};
int[][] ACTION_JUMP={{12,0,0,1,2,3,4,12,12,4},
          {},
          {},
          {},
          {},
          {1,0},
          {},
          {},
          {},
          {},
          {0},
          {},
          {0}};
int[][] ACTION_TAKEOFF={{12,12,4},//这个数组是存放各种人物的特殊动作,第一个是主角跳跃着地的动画
            {0},
            {3,4,3,4,3,4,3,4,1},//强人攻击成功
            {1,0,1,2},//水滴人防御
            {},
            {7,8},//boss奸笑
            {},
            {0},
            {},
            {4,3},
            {0},
            {1},
            {0}};
int[][] ACTION_FAINT={{16,18,17,12,4},
            {0,3,4},
            {1,5,5,5,1},
            {2,6,6,6,2},
            {1,3,3,3,1},
            {1,5,5,5,1},
            {1},
            {0},
            {3,1,3,1},
            {2,2,2,2},
            {0},
            {1},
            {0}};
int[][] ACTION_DEAD={{4,13,14,15,13,14,15,13,14,15,13,14,15,13,14,15},
          {0,3,4,4,4,4,4},
          {1,5,6,6,6,6,6},
          {2,6,7,7,7,7,7},
          {1,3,4,4,4,4,4},
          {1,5,6,6,6,6,6},
          {1},
          {0,0,0,0,0},
          {},
          {4,3},
          {0},
          {2,2,2},
          {0}};[/color]
接下来添加的这个就是每个人物每一帧图在其各自的大图中的位置,以供逻辑中绘制时使用。这个数组的二维长度也就是这种人物的总帧数,换句话说,就是这种人物有多少个静态单帧动作
[color=#0000FF]int[][] sptPaintX={{14,13,14,14,13,0,13,13,12,2,11,0,17,11,11,11,20,21,20},
          {16,12,12,15,12,18,28,28},
          {20,20,18,21,21,18,18},
          {20,18,17,12,13,13,16,14,18,24},
          {15,15,15,15,15,14},
          {20,20,14,27,17,18,19,18,19,31,33,33,29,20,20,20},
          {12,12,12},
          {16},
          {14,15,14,14},
          {12,12,12,15,15},
          {8},
          {8,8,8},
          {8}};[/color]
因为在这个游戏中,我把每个人物的单帧动作做成相同的大小,所以就省去了每一帧的宽度和高度数据,只需有一个一位数组保存每种人物的宽度和高度
  [color=#0000FF]int[] sptCell1W={24,22,36,26,30,38,24,32,24,22,16,16,15};
  int[] sptCell1H={24,20,32,30,30,38,24,16,24,22,17,16,9};
  int[] sptCell2W={32,30,0,34,32,40,0,0,0,30,0,0,0};
  int[] sptCell2H={32,30,0,34,38,24,0,0,0,22,0,0,0};
  int[] sptSpeed={8,4,0,0,0,6,0,4,6,6,0,0,0};//移动速度
  int[] lvlFarSceneSpd={-15000,0,-15000,0,0};//每一关远景的移动速度[/color]
这里要注意的是,有的人物有两张大图,因此分别用了两个数组来表示W和H。最后我们再给出人物的其他信息数据:
[color=#0000FF]int[][] sptInfo={{3,0,9,39,0},//20},//0主角    //0 血量 1 难度 2 身体半宽 3 最远攻击 4 最近攻击
        {1,4,6,28,0},//14},//1斧子男
        {2,4,12,48,0},//6},//2强人,大嘴妹
        {3,5,5,20,0},//2},//3水滴人
        {3,5,15,60,0},//7},//4箱子
        {10,8,9,62,0},//22},//5boss
        {0,100,12,12,0},//6弹簧
        {1,100,16,16,0},//7平台
        {1,100,8,0,0},//8卫兵
        {1,100,7,0,0},//9公主
        {1,100,8,8,0},//10加命
        {1,100,8,8,0},//11门
        {0,100,7,8,0}};//12钉子
static int LIFE=0;
static int DIFFICULTY=1;
static int BODYW=2;
static int ATKFAR=3;
static int ATKCLOSE=4;[/color]
这样,我们的人物数据基本上就全了。接下来我们就来看看Spirit类的雏形,Spirit类通过构造函数传入人物的类型和在地图上的坐标来创建对应的人物对象,并且进行初始化
[color=#0000FF]package com.eshouji.theball;

import javax.microedition.lcdui.Image;

public class Spirit {
  //主人公的可以用static
  int style;  //人物类型
  int direct;//人物方向
  int speed;
  int difficulty;
  int life;
  int bodyW;
  int atkFar,atkClose;
  int imageIdx;//当前绘制的图的序号
  int x,y;//对象在屏幕上的坐标
  int mapX;//对象在地图上的坐标
  int state;//对象状态
  
  int ACTION_STAND;//以下这些数组存放人物自己的动画帧序列
  int[] ACTION_RUN;
  int[] ACTION_ATTACK;
  int[] ACTION_JUMP;
  int[] ACTION_DEAD;
  int[] ACTION_TAKEOFF;
  int[] ACTION_FAINT;
  int[] paintX;
  
  static int DIR_LEFT  = -1;    //方向向左
  static int DIR_RIGHT = 1;
  boolean isAttack;
  boolean isDead;
  boolean unDead;
  
  public Spirit(int style){
    this.style=style;
    init();
  }
  
  public Spirit(int style,int mapX){
    this.style=style;
    if(style==7 || style==10 || style==11 || style==5){
      this.mapX=mapX/1000;
      this.y=-mapX%1000;
    }
    else
      this.mapX=mapX;
    init();
  }
  
  private void init() {
    if(style==0)
      direct=DIR_RIGHT;
    else
      direct=DIR_LEFT;
    GameInfo gameInfo=new GameInfo();
    ACTION_STAND=gameInfo.ACTION_STAND[style];
    ACTION_RUN=gameInfo.ACTION_RUN[style];
    ACTION_ATTACK=gameInfo.ACTION_ATTACK[style];
    ACTION_JUMP=gameInfo.ACTION_JUMP[style];
    ACTION_TAKEOFF=gameInfo.ACTION_TAKEOFF[style];
    ACTION_FAINT=gameInfo.ACTION_FAINT[style];
    ACTION_DEAD=gameInfo.ACTION_DEAD[style];
    
    difficulty=gameInfo.sptInfo[style][GameInfo.DIFFICULTY];
    bodyW=gameInfo.sptInfo[style][GameInfo.BODYW];
    life=gameInfo.sptInfo[style][GameInfo.LIFE];
    paintX=gameInfo.sptPaintX[style];
    atkFar=gameInfo.sptInfo[style][GameInfo.ATKFAR];
    atkClose=gameInfo.sptInfo[style][GameInfo.ATKCLOSE];
    speed=gameInfo.sptSpeed[style];
    
    if(TheBallCanvas.spiritImg[style]==null){//下面这段是初始化图片,把人物的图片根据帧图片的宽度高度切割开存成一个image数组
      Image[] tempImg1=Util.myCreateImage(String.valueOf(style),gameInfo.sptCell1W[style],gameInfo.sptCell1H[style]);
      if(style==0 || style==1 || style==4 || style==3 || style==5 || style==9){
        Image[] tempImg2=Util.myCreateImage(style+"-1",gameInfo.sptCell2W[style],gameInfo.sptCell2H[style]);
        Image[] image=new Image[tempImg1.length+tempImg2.length];
        for(int i=0;i<tempImg1.length;i++)
          image[i]=tempImg1[i];
        for(int i=0;i<tempImg2.length;i++)
          image[i+tempImg1.length]=tempImg2[i];
        TheBallCanvas.spiritImg[style]=image;
        image=null;
        tempImg2=null;
        System.gc();
      }
      else{
        TheBallCanvas.spiritImg[style]=tempImg1;
      }
      tempImg1=null;
      
    }
    gameInfo=null;
    imageIdx=ACTION_STAND;//初始化起始图片
    isDead=false;//初始化状态
    isAttack=false;
    unDead=false;
    System.gc();
  }
}[/color]

这样,Spirit的基本结构就完成了,动画的逻辑我们以后慢慢完善。

最近刚完成了一个项目,最后一段时间非常的忙,有一周几乎每天都是到凌晨2,3点才回家,所以很长一段时间都没有更新文章了。趁现在新项目还没有正式开始之前这段调整期,赶紧补上一些。
接下来我们尝试添加游戏中的人物了,也就是主角的敌人,首先在GameInfo类中补充每关敌人的数据:有哪些敌人,都在什么位置;还有是主角在每关初始的y坐标和每关的地图长度
[color=#0000FF]static int[] spring={10,16,24};  
int[] lvlW={1056,1408,1584,1760,1792};
int[] lvlTheBallY={-32,-16,-16,-16,-48};
int[][] lvlEnemyPos={{1,272,6,428,1,572,1,848},
            {11,296080,1,360,/*7,490064,*/12,424,12,440,12,456,12,472,12,520,12,536,10,780112,2,636,11,776016,1,784,2,1068,12,1304,12,1320,12,1336,2,1244},
            {4,144,4,400,3,752,4,1054,3,1240},
            {6,68,1,164,11,352064/*,7,208080},//,7,400064}};*/,4,608,10,670016,12,790,12,856,1,944,7,1056048,3,1330,12,1464,2,1536,7,1648032},
            {12,40,1,144,3,278,1,336,/*12,472,12,488,12,504,*/2,640,10,680048,3,852,/*7,992064,*/10,1080096,1,1184,4,1312,11,1568032,2,1568,5,1756096}};[/color]
这些数据需要说明一下,偶数位是敌人的类型,和之前给出的敌人属性数据相对应,奇数位是在地图中的x坐标位置,一般的都小于等于4位数,默认的他们的y坐标是地图从下往上最低的可以站立的位置;有些位数特别长的则是指出了这个敌人的y坐标

然后我们补充一下initLevel方法,在GameInfo gameInfo=new GameInfo();和gameInfo=null;之间添加
[color=#0000FF]farSceneSpeed=gameInfo.lvlFarSceneSpd[nowLevel];
lvlW=gameInfo.lvlW[nowLevel];
theBallSpt.y=gameInfo.lvlTheBallY[nowLevel];
theEmySpt=new Spirit[gameInfo.lvlEnemyPos[nowLevel].length>>1];
deltaY=-theBallSpt.y;
for(int i=0;i<gameInfo.lvlEnemyPos[nowLevel].length;i+=2){
  theEmySpt[i>>1]=new Spirit(gameInfo.lvlEnemyPos[nowLevel][i],gameInfo.lvlEnemyPos[nowLevel][i+1]);
  theEmySpt[i>>1].x=theEmySpt[i>>1].mapX+mapX;
  for(int x=(theEmySpt[i>>1].mapX>>4),y=0;y<lvlMap.length;y++){
    if(lvlMap[y][x]<0 && theEmySpt[i>>1].style!=7 && theEmySpt[i>>1].style!=10 && theEmySpt[i>>1].style!=11){//根据地图结构初始化人物y坐标
      theEmySpt[i>>1].y=-(y<<4);
      break;
    }
  }
}[/color]
在gameInfo=null;之后添加
[color=#0000FF]gameWin=false;//游戏胜利的开关
gameLost=false;//游戏失败的开关
canAction=true;//主角是否可以行动的开关
theBallSpt.isDead=false;
theBallSpt.unDead=false;
theBallSpt.mapX=10;
theBallSpt.x=10;
farSceneIdx=0;[/color]
这样,initLevel方法就全部完整了,这里有几点说明,theBallSpt是我们主角的Spirit对象,单独创建,单独处理;还有一些关卡的流程逻辑开关需要初始化,在类中定义
[color=#0000FF]static Spirit theBallSpt;
Spirit[] theEmySpt;
static int deltaY;
static boolean canAction=true;
static int lvlW;
boolean gameWin=false;
boolean gameLost=false;[/color]
在initGame方法中添加主角对象的初始化
[color=#0000FF]theBallSpt=new Spirit(0);[/color]

到目前为止,我们已经完成了人物的创建和初始化,然后就可以尝试把他们画在屏幕上,在paintGamingScreen方法的绘制地图(paintGameMapScreen)之后添加
[color=#0000FF]paintGameSptScreen(g);[/color]
并添加这个方法:
[color=#0000FF]private void paintGameSptScreen(Graphics g) {
  if(!theBallSpt.unDead || timecount%2!=0){
    if(theBallSpt!=null && theBallSpt.direct==Spirit.DIR_RIGHT){
      if(theBallSpt.state==Spirit.ATTACK && theBallSpt.frameCount==2)
        g.drawImage(spiritImg[theBallSpt.style][5],theBallSpt.x-theBallSpt.paintX[theBallSpt.imageIdx]+24,baseLine+theBallSpt.y,Graphics.LEFT|Graphics.BOTTOM);
      if(theBallSpt.state==Spirit.ATTACK && theBallSpt.frameCount==3)
        g.drawImage(spiritImg[theBallSpt.style][11],theBallSpt.x-theBallSpt.paintX[theBallSpt.imageIdx]+24,baseLine+theBallSpt.y,Graphics.LEFT|Graphics.BOTTOM);
    }
    if(theBallSpt!=null && theBallSpt.direct==Spirit.DIR_LEFT){
      if(theBallSpt.state==Spirit.ATTACK && theBallSpt.frameCount==2)
        g.drawRegion(spiritImg[theBallSpt.style][5],0,0,24,24,Sprite.TRANS_MIRROR,theBallSpt.x+theBallSpt.paintX[theBallSpt.imageIdx]-24,baseLine+theBallSpt.y,Graphics.RIGHT|Graphics.BOTTOM);
      if(theBallSpt.state==Spirit.ATTACK && theBallSpt.frameCount==3)
        g.drawRegion(spiritImg[theBallSpt.style][11],0,0,24,24,Sprite.TRANS_MIRROR,theBallSpt.x+theBallSpt.paintX[theBallSpt.imageIdx]-24,baseLine+theBallSpt.y,Graphics.RIGHT|Graphics.BOTTOM);
    }
  }
  for(int i=0;i<theEmySpt.length;i++){
    if(theEmySpt[i]!=null && theEmySpt[i].imageIdx!=-1){
      if(theEmySpt[i].direct==Spirit.DIR_LEFT){
        g.drawImage(spiritImg[theEmySpt[i].style][theEmySpt[i].imageIdx],theEmySpt[i].x-theEmySpt[i].paintX[theEmySpt[i].imageIdx],baseLine+theEmySpt[i].y,Graphics.LEFT|Graphics.BOTTOM);
        if(theEmySpt[i].style==5){
          if(theEmySpt[i].frameCount==11)
            g.drawImage(spiritImg[5][theEmySpt[i].frameCount-2],theEmySpt[i].x-theEmySpt[i].paintX[theEmySpt[i].imageIdx]-16,baseLine+theEmySpt[i].y+4,Graphics.LEFT|Graphics.BOTTOM);
          else if(theEmySpt[i].frameCount==12)
            g.drawImage(spiritImg[5][theEmySpt[i].frameCount-2],theEmySpt[i].x-theEmySpt[i].paintX[theEmySpt[i].imageIdx]-28,baseLine+theEmySpt[i].y+14,Graphics.LEFT|Graphics.BOTTOM);
          else if(theEmySpt[i].frameCount>=13)
            g.drawImage(spiritImg[5][theEmySpt[i].frameCount-2],theEmySpt[i].x-theEmySpt[i].paintX[theEmySpt[i].imageIdx]-44,baseLine+theEmySpt[i].y+29,Graphics.LEFT|Graphics.BOTTOM);
        }
      }
      if(theEmySpt[i].direct==Spirit.DIR_RIGHT){
        g.drawRegion(spiritImg[theEmySpt[i].style][theEmySpt[i].imageIdx],0,0,spiritImg[theEmySpt[i].style][theEmySpt[i].imageIdx].getWidth(),spiritImg[theEmySpt[i].style][theEmySpt[i].imageIdx].getHeight(),Sprite.TRANS_MIRROR,theEmySpt[i].x+theEmySpt[i].paintX[theEmySpt[i].imageIdx],baseLine+theEmySpt[i].y,Graphics.RIGHT|Graphics.BOTTOM);
        if(theEmySpt[i].style==5){
          if(theEmySpt[i].frameCount==11)
            g.drawRegion(spiritImg[5][theEmySpt[i].frameCount-2],0,0,spiritImg[5][theEmySpt[i].frameCount-2].getWidth(),spiritImg[5][theEmySpt[i].frameCount-2].getHeight(),Sprite.TRANS_MIRROR,theEmySpt[i].x+theEmySpt[i].paintX[theEmySpt[i].imageIdx]+16,baseLine+theEmySpt[i].y+4,Graphics.RIGHT|Graphics.BOTTOM);
          else if(theEmySpt[i].frameCount==12)
            g.drawRegion(spiritImg[5][theEmySpt[i].frameCount-2],0,0,spiritImg[5][theEmySpt[i].frameCount-2].getWidth(),spiritImg[5][theEmySpt[i].frameCount-2].getHeight(),Sprite.TRANS_MIRROR,theEmySpt[i].x+theEmySpt[i].paintX[theEmySpt[i].imageIdx]+28,baseLine+theEmySpt[i].y+14,Graphics.RIGHT|Graphics.BOTTOM);
          else if(theEmySpt[i].frameCount>=13)
            g.drawRegion(spiritImg[5][theEmySpt[i].frameCount-2],0,0,spiritImg[5][theEmySpt[i].frameCount-2].getWidth(),spiritImg[5][theEmySpt[i].frameCount-2].getHeight(),Sprite.TRANS_MIRROR,theEmySpt[i].x+theEmySpt[i].paintX[theEmySpt[i].imageIdx]+44,baseLine+theEmySpt[i].y+29,Graphics.RIGHT|Graphics.BOTTOM);
        }
      }
    }
  }
  if(!theBallSpt.unDead || timecount%2!=0){
    if(theBallSpt!=null && theBallSpt.direct==Spirit.DIR_RIGHT){
      g.drawImage(spiritImg[theBallSpt.style][theBallSpt.imageIdx],theBallSpt.x-theBallSpt.paintX[theBallSpt.imageIdx],baseLine+theBallSpt.y,Graphics.LEFT|Graphics.BOTTOM);
    }
    if(theBallSpt!=null && theBallSpt.direct==Spirit.DIR_LEFT){
      g.drawRegion(spiritImg[theBallSpt.style][theBallSpt.imageIdx],0,0,spiritImg[theBallSpt.style][theBallSpt.imageIdx].getWidth(),spiritImg[theBallSpt.style][theBallSpt.imageIdx].getHeight(),Sprite.TRANS_MIRROR,theBallSpt.x+theBallSpt.paintX[theBallSpt.imageIdx],baseLine+theBallSpt.y,Graphics.RIGHT|Graphics.BOTTOM);
    }
  }
}[/color]
这里绘制人物的逻辑还是比较简单的,就是根据方向不同,用两种不同的系统方法去画图。这里出现的frameCount就是我们动画数据里说到的帧,表示当前正在画哪一帧。另外还有一些人物的状态常量。我们在Spirit类中添加他们:
[color=#0000FF]static final int STAND=0;
static final int MOVEL=-1;
static final int MOVER=1;
static final int MOVE=2;
static final int ATTACK=3;
static final int LJUMP=4;
static final int UJUMP=5;
static final int RJUMP=6;
static final int TAKEOFF=7;      //主角为着陆,boss为奸笑
static final int FAINT=8;
static final int DEAD=11;

int speedControl,frameCount,actionLength,timeCount;[/color]
跑一下程序,看看我们的主角是不是很可爱的站在了地图的最左端了,呵呵

在(四)的最后部分,我们来把游戏中的按键处理加入进来,这其中也包含了主角的控制部分和游戏菜单这一个状态
在Canvas类的keyPressed方法中添加case:
[color=#0000FF]case GAME_GAMING:
  if(keyCode==keySoftLeft || keyCode==-keySoftLeft){
    changeGameState(GAME_PAUSE);
  }
  else{
    keyPressed=true;
    keyGameProcess();
  }
  break;
case GAME_PAUSE:
  if(keyCode==keySoftLeft || keyCode==-keySoftLeft)
    changeGameState(GAME_GAMING);[/color]
注意,GAME_PAUSE的case块一定要放在GAME_MAIN的case块的上方并且挨着,这里没有用break,GAME_PAUSE要共用GAME_MAIN的代码

然后在paint方法也添加新状态的绘制:
[color=#0000FF]case GAME_PAUSE:
  paintPauseScreen(g);
  break;[/color]
  
添加新方法,逻辑和画法和之前的主菜单类似,不同的地方是主菜单只画一项,游戏中菜单都画出来了:
[color=#0000FF]private void paintPauseScreen(Graphics g) {
  for(int i=0;i<CANVASW;i+=splashbgImg.getWidth())
    for(int j=0;j<CANVASH;j+=splashbgImg.getHeight())
      g.drawImage(splashbgImg,i,j,Graphics.LEFT|Graphics.TOP);
  for(int i=0;i<MAIN_COUNT;i++){
    if(tempIdx==i)
      g.drawImage(menubgImg,CANVASW>>1,(CANVASH>>3)+(menuH+5)*i,Graphics.HCENTER|Graphics.VCENTER);
    g.setClip((CANVASW-menuW)>>1,(CANVASH>>3)+(menuH+5)*i-(menuH>>1),menuW,menuH);
    g.drawImage(menuImg,CANVASW>>1,(CANVASH>>3)+(menuH+5)*i-(i<<4)+i-(menuH>>1),Graphics.HCENTER|Graphics.TOP);
    g.setClip(0,0,CANVASW,CANVASH);
  }
}[/color]

然后添加游戏按键处理函数keyGameProcess:
[color=#0000FF]private void keyGameProcess() {
  if(keyPressed){
    if(keyAction==Canvas.RIGHT)
      theBallSpt.action(Spirit.MOVER);
    if(keyAction==Canvas.LEFT)
      theBallSpt.action(Spirit.MOVEL);
    if(keyAction==Canvas.FIRE)
      theBallSpt.action(Spirit.ATTACK);
    if(keyAction==Canvas.UP){
      Spirit.JUMPIDX=1;
      theBallSpt.action(Spirit.UJUMP);
    }
    if(keyCode==Canvas.KEY_NUM3){
      Spirit.JUMPIDX=1;
      theBallSpt.action(Spirit.RJUMP);
    }
    if(keyCode==Canvas.KEY_NUM1){
      Spirit.JUMPIDX=1;
      theBallSpt.action(Spirit.LJUMP);
    }
  }
  else{
    theBallSpt.action(Spirit.STAND);
  }
}[/color]
我们的Spirit类并没有添加action方法,这里我们先引入这一方法的调用,大概的意思就是通过这一方法让人物进行某一种动作,我们将会在(五)中给出Spirit的动画播放逻辑
要让游戏进行过程动起来,当然我们好要把这一状态逻辑添加到run方法,我们先来让远景动起来,以说明这一部分结构,在run中添加case块:
[color=#0000FF]case GAME_GAMING:
  logicGameRun();
  break;[/color]
并添加方法,其中我们先只给出远景的逻辑:
[color=#0000FF]private void logicGameRun() {
  if(farSceneStyle!=-1)
    logicFarScene();
}
private void logicFarScene() {
  farSceneDelta+=TIME_PER_FRAME*screenW;
  farSceneX+=farSceneDelta/farSceneSpeed;
  farSceneDelta%=farSceneSpeed;
  if(farSceneX+lvlfarImg[farSceneIdx].getWidth()<screenX){
    farSceneX+=lvlfarImg[farSceneIdx].getWidth();
    farSceneIdx=(farSceneIdx+1)%lvlfarImg.length;
  }
  if(farSceneX>screenX){
    farSceneIdx=(farSceneIdx+lvlfarImg.length-1)%lvlfarImg.length;
    farSceneX-=lvlfarImg[farSceneIdx].getWidth();
  }
}[/color]

J2ME游戏开发教程:初级动作闯关类-《混血王子与梦幻国度》(二)

[b][size=3]状态机制[/size][/b]
这一篇的开头先说一下游戏流程的核心:状态机制。我们给游戏定下若干状态,例如菜单界面、帮助界面、游戏主画面等等,比较大的状态下还可以细分子状态,然后通过状态切换,实现游戏的流程。作为例子,我们先定义几个状态,并实现这些界面
在Canvas类中添加如下状态,看名字应该大致能了解是什么界面了

[color=#0000FF]static final int GAME_BEFORE=-1;
static final int GAME_LOGO1=0;
static final int GAME_LOGO2=1;
static final int GAME_SPLASH=2;
static final int GAME_MAIN=3;
static int gamestate=-1;
int laststate=-1;[/color]

gamestate保存了当前状态,laststate保存了上一个状态,也就是从哪个状态切换到了当前状态,然我们在之前run函数的省略代码处添加switch结构,更具当前的状态做响应的逻辑处理

[color=#0000FF]switch(gamestate){
case GAME_BEFORE:
  changeGameState(GAME_LOGO1);
  break;
case GAME_LOGO1:
  if(timecount<TIME_LOGO1/TIME_PER_FRAME)
    timecount++;
  else
    changeGameState(GAME_LOGO2);
  break;
case GAME_LOGO2:
  if(timecount<TIME_LOGO2/TIME_PER_FRAME)
    timecount++;
  else
    changeGameState(GAME_SPLASH);
  break;
case GAME_SPLASH:
  if(timecount<TIME_SPLASH/TIME_PER_FRAME)
    timecount++;
  else
    changeGameState(GAME_MAIN);
  break;
}[/color]

这个结构应该非常清晰,我们需要添加一个帧计数器timecount,游戏一次循环称为一帧,timecount就加1,LOGO1和LOGO2状态都是显示某个图片一定帧数然后通过changeGameState跳转到另一个状态,这里需要强调这个方法,整个游戏的状态管理是通过这个方法实现的,离开某个状态要做什么,进入新的状态要做什么,等等。在看这个方法的内容之前,我们要先添加几个刚才用到的常量和变量
  [color=#0000FF]int TIME_LOGO1=500;
  int TIME_LOGO2=1000;
  int TIME_SPLASH=300;
  static int timecount=0;[/color]
changeGameState方法的结构,猜也能猜到,两个switch结构,一个处理上一状态,一个处理新状态。我们依旧先添加这几个状态的代码,内容非常相似,离开状态的时候都把帧计数器置0并且释放了图片,进入新状态的时候创建了新状态需要使用的图片,然后在方法的最后把当前状态赋给laststate,再把新状态赋给gamestate,实现了状态切换。

[color=#0000FF]public void changeGameState(int newgamestate){
  switch(gamestate){
    case GAME_LOGO1:
      timecount=0;
      firstImg=null;
      break;
    case GAME_LOGO2:
      timecount=0;
      boxImg=null;
      logoImg=null;
      break;
    case GAME_SPLASH:
      timecount=0;
      splashImg=null;
      splashbgImg=null;
      splashcpImg=null;
      break;
    case GAME_MAIN:
      tempIdx=0;//main主菜单选项的当前选定值
      splashImg=null;
      splashbgImg=null;
      splashcpImg=null;
      menuImg=null;
      menubgImg=null;
      initGame();//正式进入游戏的初始化
      break;
  }
  switch(newgamestate){
    case GAME_LOGO1:
      firstImg=Util.myCreateImage("first");
      break;
    case GAME_LOGO2:
      boxImg=Util.myCreateImage("box");
      logoImg=Util.myCreateImage("logo");
      break;
    case GAME_SPLASH:
      splashImg=Util.myCreateImage("splash");
      splashbgImg=Util.myCreateImage("splashbg");
      splashcpImg=Util.myCreateImage("splashcp");
      break;
    case GAME_MAIN:
      splashImg=Util.myCreateImage("splash");
      splashbgImg=Util.myCreateImage("splashbg");
      splashcpImg=Util.myCreateImage("splashcp");
      menuImg=Util.myCreateImage("menu");
      menubgImg=Util.myCreateImage("menubg");
      break;
  }
  laststate=gamestate;
  gamestate=newgamestate;
}[/color]

这里我们需要添加几个图片对象的声明,和Image包
[color=#0000FF]import javax.microedition.lcdui.Image;

int tempIdx=0;
Image firstImg,boxImg,logoImg;
Image splashImg,splashbgImg,splashcpImg,menuImg,menubgImg;[/color]

这里用到了Util类的myCreateImage方法,这是一个对图片创建的封装,之前提到过Uitl类是一个工具类,是对一些静态方法的封装,这个游戏中我们只有这一个方法,整个Uitl类的内容如下:
[color=#0000FF]package com.eshouji.theball;
import java.io.IOException;
import javax.microedition.lcdui.Image;

public class Util {
  public static Image myCreateImage(String filename){
    Image image=null;
    try{
      image=Image.createImage("/"+filename+".png");
    }catch(IOException e){System.out.println("create image error!");}
    return image;
  }
  public static Image[] myCreateImage(String filename,int width,int height){//把一个大图切割成制定大小的小图数组
    Image srcImage=myCreateImage(filename);
    int srcWidth=srcImage.getWidth();
    int srcHeight=srcImage.getHeight();
    Image[] outImage=new Image[srcWidth*srcHeight/width/height];
    for(int y=0;y<srcHeight;y+=height)
      for(int x=0;x<srcWidth;x+=width)
        outImage[y*srcWidth/width/height+x/width]=Image.createImage(srcImage,x,y,width,height,0);
    srcImage=null;
    return outImage;
  }
}[/color]
这里还有一个方法我们没有建立,就是initGame,我们先在Canvas类中创建一个空的方法,内容以后再说
[color=#0000FF]private void initGame() {
}[/color]
到目前为止,我们的代码在IDE中应该是没有异常的,现在游戏的前几个状态的逻辑已经有了,状态切换也已经有了,要让游戏在前几个状态跑起来我们还需要完善paint方法,完成在各个状态下的屏幕绘制,在paint发放中添加:
  [color=#0000FF]g.setColor(0xFFFFFF);
  g.fillRect(0,0,CANVASW,CANVASH);
  switch(gamestate){
    case GAME_LOGO1:
      paintLogo1Screen(g);
      break;
    case GAME_LOGO2:
      paintLogo2Screen(g);
      break;
    case GAME_SPLASH:
      paintSplashScreen(g);
      break;
    case GAME_MAIN:
      paintMainScreen(g);
      break;
  }[/color]
这种结构我们应该已经非常熟悉了,之前已经多次用到。我们在paint的最前面用白色填充屏幕,清除上一帧的画面,然后根据不同的状态调用不同的方法绘制,我们接着添加这些方法
[color=#0000FF]private void paintLogo1Screen(Graphics g) {
  g.drawImage(firstImg,CANVASW>>1,CANVASH>>1,Graphics.HCENTER|Graphics.VCENTER);
}
private void paintLogo2Screen(Graphics g) {
  g.drawImage(logoImg,CANVASW>>1,CANVASH>>1,Graphics.HCENTER|Graphics.VCENTER);
  g.drawImage(boxImg,0,0,Graphics.LEFT|Graphics.TOP);
  g.setColor(0x626466);
  g.drawRect((CANVASW>>2)-1,((CANVASW+logoImg.getHeight())>>1)+20,(CANVASW>>1)+1,2);
  g.setColor(0x00AB9D);
  g.drawRect(CANVASW>>2,((CANVASW+logoImg.getHeight())>>1)+21,timecount*((CANVASW>>1))*TIME_PER_FRAME/TIME_LOGO2,0);//进度条
}
private void paintSplashScreen(Graphics g) {
  paintBGScreen(g);
}
private void paintMainScreen(Graphics g) {
  paintBGScreen(g);
  g.drawImage(menubgImg,CANVASW>>1,(CANVASH*3)>>2,Graphics.HCENTER|Graphics.VCENTER);
  g.setClip((CANVASW-menuW)>>1,((CANVASH*3)>>2)-(menuH>>1),menuW,menuH);//确定菜单位置,设置只在该区域可以绘制图片
  g.drawImage(menuImg,CANVASW>>1,((CANVASH*3)>>2)-(tempIdx<<4)+tempIdx-(menuH>>1),Graphics.HCENTER|Graphics.TOP);//根据tempIdx计算菜单图片当前选项的偏移量,然后绘制图片
  g.setClip(0,0,CANVASW,CANVASH);
}
private void paintBGScreen(Graphics g) {
  for(int i=0;i<CANVASW;i+=splashbgImg.getWidth())
    for(int j=0;j<CANVASH;j+=splashbgImg.getHeight())
      g.drawImage(splashbgImg,i,j,Graphics.LEFT|Graphics.TOP);
  g.drawImage(splashImg,CANVASW>>1,CANVASH/3,Graphics.VCENTER|Graphics.HCENTER);
  g.drawImage(splashcpImg,CANVASW>>1,CANVASH-10,Graphics.HCENTER|Graphics.BOTTOM);
}[/color]
这里大量用到了CANVASH等几个常量,我们一定要习惯于把这些固定不变的值定义成一个常量,千万不要把数值直接写在代码里,否则对于之后的修改非常的困难。在Canvas类中添加这几个常量
[color=#0000FF]static int CANVASW=176;//这是我们屏幕的宽和高
static int CANVASH=204;//这应该是代码里最最最基本的一对常量了
static int menuW=58;//菜单元素的宽
static int menuH=15;//菜单元素的高[/color]
paintLogo2Screen和paintLogo1Screen的代码比较简单,就是画几张图和一个进度条,可以看出来,这个进度条是假的,根据timecount计数和这一状态的停留时间平均的画出了进度条(呵呵,我们那时候的游戏都是这个样子)。只有paintMainScreen稍微有一点点复杂,这里根据tempIdx这个选项计数画出菜单文字图上的一部分。

好了,让我们现在在IDE中运行一下我们的程序。你的程序现在应该能够运行到主菜单看到“开始游戏”这个选项。我写这个教程的时候,另建了一个工程,每写一点就考响应的代码过去,所以我能跑起来你也应该可以,当然一定要记得把这个游戏的资源包下载回来(也许有的地方只转载了文章,请到http://www.yinowl.com文章的开头下载资源,当然全文也在那里)。如果你的程序跑起来了,你一定会发现不能切换主菜单的选项,没错,因为我们还差最后一部分内容,各状态的按键处理,让我们现在就来加上,结构依旧是一样的switch-case结构,在Canvas类的keyPressed方法中加上:
[color=#0000FF]switch(gamestate){
case GAME_MAIN:
  if (keyAction == Canvas.UP)
    tempIdx=(tempIdx-1+MAIN_COUNT)%MAIN_COUNT;
  else if (keyAction == Canvas.DOWN)
    tempIdx=(tempIdx+1)%MAIN_COUNT;
  else if(keyAction==Canvas.FIRE || keyCode==keySoftRight || keyCode==-keySoftRight)
    keyActionMain(tempIdx);
  break;
}[/color]
现在只有一个状态的代码,因为前面几个状态都是自动跳转的,不需要接收用户按键,在GAME_MAIN这个状态,用户按上下键切换菜单项,按中间键和左右软键都可以激活选项,因为不同手机的左右软键和中间键键值不同,我们定义了这几个变量方便修改。激活选项后调用了keyActionMain方法,并且把选项值传入作不同处理,在Canvas类中添加变量常量和方法:
  [color=#0000FF]static final int MAIN_START=0;
  static final int MAIN_RETURN=1;
  static final int MAIN_HELP=2;
  static final int MAIN_SET=3;
  static final int MAIN_ABOUT=4;
  static final int MAIN_EXIT=5;
  static final int MAIN_COUNT=6;
  
  int keySoftLeft=21;    //左软键
  int keySoftRight=22;  //右软键
  int keySoftAction=20;  //中间确认键

  private void keyActionMain(int tempIdx) {
    switch(tempIdx){
      case MAIN_START:
        if(gamestate==GAME_PAUSE)
          changeGameState(GAME_LEVEL);
        else{
          nowLevel=0;
          changeGameState(GAME_START);
        }
        break;
      case MAIN_HELP:
        changeGameState(GAME_HELP);
        break;
      case MAIN_ABOUT:
        changeGameState(GAME_ABOUT);
        break;
      case MAIN_EXIT:
        changeGameState(GAME_EXIT);
        break;
      case MAIN_SET:
        changeGameState(GAME_SET);
        break;
      case MAIN_RETURN:
        if(gamestate==GAME_PAUSE)
          changeGameState(GAME_GAMING);
        else{
          nowLevel=maxLevel;
          changeGameState(GAME_LEVEL);
        }
        break;
    }
  }[/color]
哇,这里一下子又出现了好多游戏状态,是的,当用户在主菜单激活选项后,程序就更具不同的选项进入不同的状态了,比如进入帮助界面。
我们在之前添加状态常量的地方再把这几个状态加上,LEVEL是选关状态,既然有选关,那一定就有当前关变量和最大关变量了。其他的几个状态应该看字面就理解了。
  [color=#0000FF]static final int GAME_GAMING=4;
  static final int GAME_HELP=5;
  static final int GAME_ABOUT=6;
  static final int GAME_SET=7;
  static final int GAME_LEVEL=8;
  static final int GAME_EXIT=9;
  static final int GAME_PAUSE=10;
  static final int GAME_START=11;
  
  static int nowLevel=0;
  int maxLevel=0;[/color]
现在再跑一下程序,哈哈,主菜单能切换选项了,但是可千万激活选项啊,我们还没有这几个状态下的代码,进去了可就出不来了,现在点进去就白屏了,想想为什么呢?
接下来我们一口气把HELP,ABOUT,SET,EXIT这几个状态的代码添加上,虽然有点无聊,不过我们会同时把游戏的声音添加上,SET状态就是设置声音开关的
在Canvas类paint方法的switch结构中添加下面这几个case并添加被调用的几个方法
  [color=#0000FF]case GAME_HELP:
    paitnHelpScreen(g);
    break;
  case GAME_ABOUT:
    paitnAboutScreen(g);
    break;
  case GAME_SET:
    paintSetScreen(g);
    break;[/color]
paintHelpScreen方法绘制帮助界面,同样要一起添加一些常量和变量,这里我们又用到了tempIdx变量来保存当前页id,我们可以在多个状态公用变量,但这是很不安全的,我们一定要保证切换状态的时候要把这个状态重置,也就是置0,否则很有可能会数组越界。这里我们需要导入Font类
  [color=#0000FF]import javax.microedition.lcdui.Font;
  
  final String[] strGameHelp ={
      "游戏简介:",
      "有一天,球球公主在草原",
      "上散步时被方块恶魔所",
      "捕获,球球王子当当为了",
      "救出公主,开始向方块国",
      "进发。",
      "当玩家面对强敌时,根据",
      "不同的弱点,抓住时机向",
      "他们体内充气,令其漂浮",
      "到空中。",
      "按键操作:",
      "数字1:向左跳跃",
      "数字3:向右跳跃",
      "方向上/数字2:向上跳跃",
      "方向左/数字4:向左移动",
      "方向右/数字6:向右移动",
      "中间确定键/数字5:攻击",
      "游戏规则:",
      "游戏中有隐藏的陷井,控",
      "制人物跳跃时要掌握好",
      "角度,不然会碰壁被弹回",
      "失命后按不同的控制键",
      "主人公会有不同的复活",
      "效果,能否救出公主就看",
      "你的表现啦."};
  static final Font lowFont  = Font.getFont (Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL); //创建系统字体对象,设置字体大小等属性
  int HELP_LINE=9;//每页显示9行
  
  private void paitnHelpScreen(Graphics g) {
    g.setColor(0x00008080);
      g.fillRect(0,0,CANVASW,CANVASH);
      g.setFont(lowFont);
      g.setColor(0x00FFFF99);
      for(int i=0;i<HELP_LINE;i++){
        if(tempIdx*HELP_LINE+i<strGameHelp.length)
          g.drawString(strGameHelp[tempIdx*HELP_LINE+i],5,4+(lowFont.getHeight())*i,Graphics.TOP|Graphics.LEFT);
      }
      if(tempIdx>0){
        g.drawLine((CANVASW>>1)-2,3,CANVASW>>1,0);
        g.drawLine((CANVASW>>1)+2,3,CANVASW>>1,0);
      }
      if(tempIdx<strGameHelp.length/HELP_LINE){
        g.drawLine((CANVASW>>1)-2,CANVASH-4,CANVASW>>1,CANVASH-1);
        g.drawLine((CANVASW>>1)+2,CANVASH-4,CANVASW>>1,CANVASH-1);
      }
      g.setColor(0x00FFFFFF);
      g.drawString("返回",0,CANVASH,Graphics.LEFT|Graphics.BOTTOM);
  }[/color]
  
paitnAboutScreen方式绘制关于界面,里面有公司的简介,这个就不多说了,和HELP状态几乎一样
  [color=#0000FF]String[] strGameAbout={  "欢乐金网",
              "信息技术有限公司",
              "eShouJi InfoTech",
              "客服电话",
              "010-82630056",
              "客服信箱",
              "Service@eshouji.com",
              "制作人员",
              "策划:石宇",
              "美工:张文卓、王亮",
              "程序:yinowl",
              "想拥有更多精彩内容",
              "请依次执行以下步骤:",
              "进入功能页面->",
              "访问网络->转到网址,",
              "输入wap.eshouji.com",
              "即可轻松享有."};

  private void paitnAboutScreen(Graphics g) {
    g.setColor(0x00008080);
      g.fillRect(0,0,CANVASW,CANVASH);
      g.setFont(lowFont);
      g.setColor(0x00FFFF99);
    for(int i=0;i<HELP_LINE;i++){
        if(tempIdx*HELP_LINE+i<strGameAbout.length)
          g.drawString(strGameAbout[tempIdx*HELP_LINE+i],88,4+(lowFont.getHeight())*i,Graphics.TOP|Graphics.HCENTER);
      }
      if(tempIdx>0){
        g.drawLine((CANVASW>>1)-2,3,CANVASW>>1,0);
        g.drawLine((CANVASW>>1)+2,3,CANVASW>>1,0);
      }
      if(tempIdx<strGameAbout.length/HELP_LINE){
        g.drawLine((CANVASW>>1)-2,CANVASH-4,CANVASW>>1,CANVASH-1);
        g.drawLine((CANVASW>>1)+2,CANVASH-4,CANVASW>>1,CANVASH-1);
      }
    g.setColor(0x00FFFFFF);
    g.drawString("返回",0,CANVASH,Graphics.LEFT|Graphics.BOTTOM);
  }[/color]
  
最后就是paintSetScreen来绘制游戏设置界面,界面的绘制依旧和前两个差不多,没什么复杂的,我们这里使用了两个变量,canSound用来在之后的代码中控制是否有音乐,canShake控制手机的震动

  [color=#0000FF]static String[] strGameSet={"音 效","振 动"};
  boolean canSound=true;
  boolean canShake=true;

  private void paintSetScreen(Graphics g) {
    g.setColor(0x00008080);
      g.fillRect(0,0,CANVASW,CANVASH);
      g.setFont(lowFont);
      g.setColor(0x00FFFF99);
    g.drawString("游戏设置",CANVASW>>1,CANVASH>>2,Graphics.HCENTER|Graphics.TOP);
        for(int i=0;i<strGameSet.length;i++){
          if(tempIdx==i)
            g.setColor(0x00FFFFFF);
          else
            g.setColor(0x00FFFF99);
          g.drawString(strGameSet[i],CANVASW>>2,(CANVASH>>2)*(i+2),Graphics.LEFT|Graphics.TOP);
          g.drawString("开",(CANVASW>>1),(CANVASH>>2)*(i+2),Graphics.LEFT|Graphics.TOP);
          g.drawString("关",(CANVASW*3)>>2,(CANVASH>>2)*(i+2),Graphics.RIGHT|Graphics.TOP);
          if(tempIdx==0)
            g.setColor(0x00FFFFFF);
          else
            g.setColor(0x00FFFF99);
          if (canSound){
              g.drawRect(CANVASW>>1,CANVASH>>1,lowFont.stringWidth("开"),lowFont.getHeight());
            }
            else{
              g.drawRect(((CANVASW*3)>>2)-lowFont.stringWidth("关"),CANVASH>>1,lowFont.stringWidth("关"),lowFont.getHeight());
            }
          if(tempIdx==1)
            g.setColor(0x00FFFFFF);
          else
            g.setColor(0x00FFFF99);
          if (canShake){
              g.drawRect(CANVASW>>1,(CANVASH>>2)*3,lowFont.stringWidth("开"),lowFont.getHeight());
            }
            else{
              g.drawRect(((CANVASW*3)>>2)-lowFont.stringWidth("关"),(CANVASH>>2)*3,lowFont.stringWidth("关"),lowFont.getHeight());
            }
        }
        g.setColor(0x00FFFFFF);
        g.drawString("确定",CANVASW,CANVASH,Graphics.RIGHT|Graphics.BOTTOM);
  }[/color]
  
这样,这3个状态的绘制就完成了,赶紧运行一下看看,没问题,我们已经能够看到内容了,但是不能翻页也不会返回到主菜单,因为我们还没添加这几个状态的按键处理代码,还是在Canvas类的keyPressed方法中的switch接口添加下面几个case
  [color=#0000FF]case GAME_HELP:
    if(keyAction==Canvas.DOWN){
      if(tempIdx<strGameHelp.length/HELP_LINE)
        tempIdx++;
    }
    else if(keyAction==Canvas.UP){
      if(tempIdx>0)
        tempIdx–;
    }
    if(keyCode==keySoftLeft || keyCode==-keySoftLeft){//因为有的机型键值是负的但是绝对值一样,所以这里多了一个判断
      changeGameState(laststate);
    }
    break;
  case GAME_ABOUT:
    if(keyAction==Canvas.DOWN){
      if(tempIdx<strGameAbout.length/HELP_LINE)
        tempIdx++;
    }
    else if(keyAction==Canvas.UP){
      if(tempIdx>0)
        tempIdx–;
    }
    if(keyCode==keySoftLeft || keyCode==-keySoftLeft){
      changeGameState(laststate);
    }
    break;
  case GAME_SET:
    keyActionSet();
    break;[/color]
这段代码也应该很好理解,就是按键后通过改变tempIdx来翻页,我们来看看keyActionSet的内容,按上下修改tempIdx来改变是设置声音还是震动,按左右根据tempIdx来改变canSound和canShake,也应该很好理解。下面有一个play对象,这就是用来播放声音的Player类的对象。
  [color=#0000FF]private void keyActionSet() {
    if(keyAction==Canvas.UP){
      tempIdx=(tempIdx-1+strGameSet.length)%strGameSet.length;
    }
    else if(keyAction==Canvas.DOWN){
      tempIdx=((tempIdx+1)%strGameSet.length);
    }
    else if(keyAction==Canvas.LEFT){
      if(tempIdx==0)
        canSound=!canSound;
      else if(tempIdx==1){
        canShake=!canShake;
      }
    }
    else if(keyAction==Canvas.RIGHT){
      if(tempIdx==0)
        canSound=!canSound;
      else if(tempIdx==1){
        canShake=!canShake;
      }
    }
    if(keyCode==keySoftRight || keyCode==-keySoftRight){
      try{
        if(canSound){
          player.stop();
          player.start();
        }
        else{
          player.stop();
        }
      }catch(MediaException e){}
      changeGameState(laststate);
    }
  }[/color]
我们需要导入异常类和Player类
[color=#0000FF]import javax.microedition.media.MediaException;
import javax.microedition.media.Player;[/color]

接着我们就来补全声音部分的代码。在Canvas的构造方法TheBallCanvas中添加如下代码,这是一段标准的声音创建过程,J2ME的API文档里应该有实例
  [color=#0000FF]try{
        InputStream in = getClass().getResourceAsStream("/musicbg.mid");
        while(player==null)
          player = Manager.createPlayer(in,"audio/midi");
        player.realize();
        toneControl = (ToneControl)player.getControl("ToneControl");
        player.setLoopCount(-1);
      }catch(IOException e){}
      catch(MediaException e){}[/color]
要让这段代码生效,我们同样需要导入几个类库和声明对象
[color=#0000FF]import java.io.IOException;
import java.io.InputStream;
import javax.microedition.media.control.ToneControl;
import javax.microedition.media.Manager;

Player player;
ToneControl toneControl ;[/color]
这样,我们就可以在后面的代码中实用player对象来播放声音了,我们在changeGameState方法switch(gamestate)结构的case GAME_SPLASH:中添加如下代码,然后运行一下试试,应该就有背景音乐了
[color=#0000FF]if(canSound){
  try{
    player.stop();
    player.start();
  }catch(MediaException e){}
}[/color]

J2ME游戏开发教程:初级动作闯关类-《混血王子与梦幻国度》(一)

游戏资源下载:[url=/wp-share/TheBallRes.rar]图片声音资源[/url]

标题:
和我三年前写的两篇教程一样,这仍就是一篇J2ME的入门教程,只是游戏的类型变了,代码的结构也许会比之前的两个游戏要复杂一些,但算法的难度甚至不如双人扫雷来的巧妙,也正因如此,大家可以在这篇文章中了解一下这类游戏的一种结构的实现方法。关于游戏名字,我要声明和我一点没有关系,纯粹是公司的营销行为,过去几年在业内工作过的朋友一定能了解,呵呵。

我:
三年前,也就是2004年底2005年初,我在学习J2ME的过程中写了拙作两篇《J2ME-MIDP1.0小游戏入门-五子棋》和《J2ME-MIDP1.0游戏完整实现-双人扫雷》,之后进了一家当时在业内很有业绩但没有名声的SP+CP公司,《混血王子与梦幻国度》是我在那的第一个游戏,我自己的第三个游戏。游戏很简单,给了我一套图和一套关卡设计,其他的全部自己来。

文章:
其实很早就想继续写几篇游戏开发教程,总有这样那样的原因是我犹豫,一来之前还在原来公司,即使是很早的游戏也不方便写出来,二来游戏中还有美术和策划内容,这些不属于我的版权,我无权使用。现在好了,我已经离开原来公司,并且公司早已转型其他领域,不存在利益关系;至于第二点,这个游戏应该没有关系,我甚至不知道是谁,我想在教程中用,即使他看到了也无大碍吧。
这篇文章依旧采用前两篇的形式,更具结构和功能,附带解释和讲解,一点点写全所有代码。在写的过程中,我会在IDE中新建一个工程,和文章同步把代码从原有工程copy到新工程,尽量做到代码的完整且可运行。由于游戏是2005年写的,以现在的经验看,肯定会显得幼稚和落后,为了保证当时代码的想法和完整性,我就不做修改了。

正文:
结构:
代码一共有5个类文件和一堆的资源文件,根据不同IDE的不同设置,可以把资源放在代码相同目录下,或是不同目录下,只要能正确加载就可以,代码中资源都是从跟目录读取(也就是"/xxx.png")。我使用的是eclipse,并且在J2ME的设置项New Midlet Suite中勾选了Automatically Use Resources Directory in New ProjectsResources Directory我填写的是res,因此我的资源都在res目录下,但其实作用如同和代码在一个目录下一样,详细的可以查阅eclipse相关文档。另外最重要的一点是,但是首先开发的是在Moto V300机型上,所以这个代码要用模拟器跑的话,需要下载Moto的SDK,用WTK2.0的模拟器应该也能跑吧

工程目录结构
src
|-com.eshouji.theball
|-GameInfo.java
|-Spirit.java
|-TheBallCanvas.java
|-TheBallMIDlet.java
|-Util.java
res
|-0.png
|-…
|-window.png

GameInfo:是游戏的数据类,存放所有关卡数据,人物数据,图片数据,用法以后用到的时候再详细解释
Spirit:怎么说呢,类似于midp2.0的精灵类吧,人物的封装类
TheBallCanvas:游戏Canvas类,包含了所有的绘图和AI
TheBallMIDlet:当然就是MIDlet类了,作用就不用说了吧,就像没有人会不知道C/C++的main函数一样
Util:工具类,有一些常用工具函数,主要是为了积累以便可以拿到以后的项目中使用

TheBallMIDlet类:
首先是MIDlet主类TheBallMIDlet,继承自MIDlet,我们需要实现MIDlet的几个抽象方法,这几个方法系统会在游戏启动,暂停,和推出是自动调用,应此我们可以在这些方法中加入游戏的初始化,游戏暂停,退出清理资源等处理,我们的TheBallMIDlet现在应该是这样:
package com.eshouji.theball;

import javax.microedition.midlet.MIDlet;

public class TheBallMIDlet extends MIDlet {

protected void startApp(){
}
protected void pauseApp(){
}
protected void destroyApp(boolean unconditional){
}

}

TheBallCanvas类:
这是我们的游戏的核心类,包含了所有的绘图和算法,继承自Canvas类,同样我们必须实现父类的抽象方法:
package com.eshouji.theball;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class TheBallCanvas extends Canvas {

protected void paint(Graphics g) {
}

}

在有了Canvas类后,就可以稍微的完善一下我们的MIDlet类了,在TheBallMIDlet类中增加TheBallCanvas类的实例的声明,并且增加一个构造函数来初始化;我们还需要创建一个Display对象把我们的主Canvas画在屏幕上
TheBallMIDlet类增加:
TheBallCanvas theBallCanvas;
Display display;
public TheBallMIDlet(){
display=Display.getDisplay(this);
theBallCanvas=new TheBallCanvas(this);
}
TheBallMIDlet类的startApp方法中增加
display.setCurrent(theBallCanvas);
这样,游戏运行后,系统创建MIDlet实例,在构造方法中初始化Canvas,并调用startApp把我们的Canvas通过display画在屏幕上

线程结构
接下来我们要建立Canvas的线程结构,我们都已经了解,一个游戏其实是一个不断循环的线程,在每次循环进行逻辑的计算,并不断把新的内容绘制到屏幕上,我们让TheBallCanvas实现Runnable接口,是他成为我们的核心线程
在extends Canvas后添加implements Runnable,并且添加实现Runnable的抽象方法run()
public void run() {
}

要让这个线程跑起来,我们要给Canvas类添加一个构造函数,在构造函数中启动线程,也就是说,当MIDlet类创建Canvas时就启动了这个线程
添加类对象
Thread thread;
TheBallMIDlet theBallMIDlet;
public TheBallCanvas(TheBallMIDlet theBallMIDlet) {
this.theBallMIDlet=theBallMIDlet;
thread=new Thread(this);
thread.start();
}

来看一下我们的run函数的结构:
while(RunGame){
starttime = System.currentTimeMillis();
……
repaint();
serviceRepaints();
takentime = System.currentTimeMillis()-starttime;
if(takentime<TIME_PER_FRAME){
try{
Thread.sleep(TIME_PER_FRAME-takentime);
}catch(InterruptedException e){};
}
repaint();
serviceRepaints();
}

一个while循环,省略部分是我们以后要完善的游戏逻辑和绘制运算,在运算前后我们都记录了当前时间,如果所用的时间小于一个定值,就让游戏线程暂停(sleep)和这个定制相差的时间,也就是说我们会让每次刷新间隔的时间相同,而不会出现一会儿快一会儿慢的情况,当然,中间的运算不是时间不能超过这个定值,否则还是会变慢。整个循环用一个RunGame开关控制,用这个开关我们可以使游戏暂停和退出
同样我们要添加如下类变量:
boolean RunGame=true;
long starttime=0;
long takentime=0;
static int TIME_PER_FRAME = 30;
当然我们游戏一开始就要把RunGame开关打开,同样游戏被暂停的时候,要把游戏线程也暂停,顺便我们再完善一下MIDlet类
在startApp中添加
theBallCanvas.RunGame=true;
在pauseApp中添加
theBallCanvas.RunGame=false;
在destroyApp中添加
notifyDestroyed();

按键响应
作为核心结构,我们还差一部分,就是用户按键的响应,这是系统级的,当用户有按键操作的时候,会向调用系统函数并传递按键的键值,当然,前提是我们实现这个方法
添加方法和一些代码:
public void keyPressed(int keyCode){
if(RunGame){
TheBallCanvas.keyCode=keyCode;
TheBallCanvas.keyAction=getGameAction(keyCode);
}
else
RunGame=true;
……
}
省略部分是对响应按键的操作,我们以后慢慢完善。我们用两个变量来记录用户的按键和对应的gameaction值,同样添加声明
static int keyCode,keyAction;

未完待续…..

关于CC(About CC)

CC:抄送,即写邮件时将邮件同时发送给CC对象,CC对象不需要处理邮件内容,只需了解关注即可

有天吃饭时和明明讨论工作执行力的问题,偶然间说到邮件的CC功能,她们平时也会CC,但是CC后所以起到的效果并不明显。GL的工作中也会CC,而且是无时无刻的CC,只要是和工作有关都会CC,CC是一种习惯,而且我感觉作用非常的明显。

那么,CC到底有什么好处?要CC给什么人?我们收发邮件的时候又要注意什么?

首先,CC给什么人?对方的上级,自己的上级,邮件内容有关的人员。这个必须和CC的目的也就是好处联系起来,我们CC的最主要目的是希望CC的对象对此邮件内容(一般是某项工作)起到关注监督的作用,邮件接收者在处理邮件中的工作时就会有一种无形的压力。认真对待邮件中的工作,不要轻视,不要马虎,有人在密切关注它。作为邮件的接收者,又应该怎么样呢。首要做的就是立即回一封邮件,一是表示你已经看到邮件,并且告诉对方你什么时候处理,交流过后,你可以按照沟通结果继续别的工作或者立即处理邮件工作。CC的流程中有个很重要的环节,就是作为CC对象的人,他千万不可以无视或者忽视邮件,尤其是上级,他要关注工作的进程;如果疏忽,很容易造成员工处理邮件时的马虎、搁置、甚至置之不理,在想要纠正就很难了。

CC的大概情况就是这样,这个过程中的每一个角色都很重要,有一放养成不好的习惯,就会慢慢导致整个邮件交流工作效率的下降,后果自然不堪设想。有的人可能接受不了CC给上级,认为有一种打小报告的感觉,其实不然,平时工作交流就养成CC的习惯,不要等工作中出现了问题,才发邮件或CC给上级,那才是打小报告,收邮件的人会很不爽。

65天和40小时(65 days and 40 hours)

消失在QQ上65天,消失在这里65天,消失在家和公司以外的地方65天
从7月份进项目开始做E700的Master开发以来,基本没有休息过,一路艰难险阻,跌跌撞撞总算坚持到了项目的GOLD。不得不提的是,幸亏有朱江大哥的帮助,否则很难想象中版的开发和移植过程会是个什么情形,真是万分感谢啊。
以前从来没有加过班,以前从来都是下班就回家,而现在到项目的后期和GOLD阶段,经常会工作到凌晨1点,这一切会让人疲惫不堪,但是我很兴奋,这就是我小时候所想象的IT从业人员的工作,为了自己的理想而奋斗。过去的工作清闲而枯燥,浑浑噩噩,每天每天都不知道是怎么过来的。呵呵,我其实很喜欢现在的工作状态,让人疯狂,让人兴奋。
项目GOLD那天连续40个小时未眠,9月19日晚上2点回家,20日上午8点就来到公司,然后就一直到22日凌晨1点GOLD,现在已经回味不起来这40个小时是怎么度过的,反正回家后一觉睡到了下午4点,呵呵。
完成这个项目后,现在最最最深刻的感受就是,其实挣钱真的很不容易。金网的工资真的不算低。

回到游戏行业(I''m back!)

换工作到Gameloft整一个月了,一到这里,第一感觉是自己落后了2年,技术上的落后,思想上的落后。一个年轻人真应该趁年轻的时候多看看多学学,才20来岁就想着傍着一个可能会成功的小公司,才20来岁就想着可以永久改变自己的财务状况,这样完全是错的,这是惰性,这是不思进取。一个月前在做选择的时候,一度担心自己的选择是否正确,现在看来,想明白了这个道理,那就完全是正确的。
接着就是祝贺小7终于找到自己满意的工作了,GLU,也是家很不错的企业,而且在国内刚起步,一定很有发展。山鬼也要抓紧了,赶快回到游戏行业,最近真是喜事连连,如果山鬼也顺利入GLU的话,那就更棒了。
很少在BLOG里写文字,多是看图说话,呵呵,以后也要适当的写一些。写一些摄影的感想,写一些工作的总结,写一些朋友间的趣事,写一些大家看着高兴的事情。我不会写那些虚无缥缈的文字,看着别人写自以为是诗句的文字,就觉得好搞笑,猜想这人一定是上了年纪的,肯定是大龄单身青年,哈哈,否则就是问了写而写,为了看而写,为了表现而写,真假,也真傻。