标题: 用SDL打造我们自己的战棋游戏!, 11月30日更新第3个版本:可以绕过障碍移动的赵辉
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-11-28 18:25 资料 个人空间 短消息 只看该作者
用SDL打造我们自己的战棋游戏!

前天发帖表达了用高级语言重写战棋游戏的意愿后,得到了不少朋友的建议和鼓励,十分欣幸。光打雷不下雨是不行滴,遂决定用Visual C++ 2005开始我的探索之旅。
语言和集成开发环境确定了,以什么样的库作为图形界面基础还颇费脑筋,本来我所了解的有ATL、MFC和Qt,它们都功能强大,感觉作为初步尝试都太重量级了,且都缺少特别为开发游戏而提供的功能(也有可能是本人了解不够所致)。
后来就想到了SDL(Simple DirectMedia Layer),虽然此前对它了解是空白,但既然游泳的鱼(本人一贯膜拜的神人之一啊)能用它做出金庸群侠传的复刻版,想必功能不弱。
稍微搜了搜相关介绍,发现它是开源的,跨语言、跨平台的,专门为多媒体开发特别是游戏开发打造的。既然有这么多优点,不妨就先用它来试试看吧。


同时决定在第1个可执行版本做出来后发这个帖子,目的如下:
1.汇报本人研究和工作成果,望各位不时关注
2.希望高手看到我帖出的代码或源代码包里的内容,给出好的意见建议
3.希望有一定编程基础的同道能与我探讨、分享经验,甚至合作,共同实现开发属于我们自己的战棋游戏的愿望

2楼开始是正式内容,下面是索引:
准备工作 2楼
第1个版本 颖川战场上原地踏步的策士赵辉 3楼
第2个版本 让赵辉开口说话 4楼
第3个版本 可以绕过障碍移动的赵辉 8楼

[ 本帖最后由 赵辉 于 2009-11-30 21:53 编辑 ]


顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-11-29 19:49 资料 个人空间 短消息 只看该作者
工欲善其事,必先利其器——准备工作

首先是SDL在VS2005下的安装配置,网上没搜到相对完整的说明,就自己稍微摸索了一下,总结如下:
1.从http://www.libsdl.org/上下载SDL-devel-1.2.14-VC8.zip (Visual C++ 2005 Service Pack 1),就是专门用于Windows和VC++ 2005的版本
2.建立开发目录,将上述压缩包解压到这个目录里。我的开发目录就叫做Project,而解压后Project目录里就有了SDL-1.2.14子目录
3.打开VS2005,作几项配置。选择“工具->选项”菜单项,在打开的对话框左边选择“项目和解决方案->VC++目录”,在右面的“包含以下内容的目录”里选“包含文件”,将Project\SDL-1.2.14\include目录添加进来(如图1)
    类似地,选“库文件”,将Project\SDL-1.2.14\lib目录添加进来


图片附件: [图1] 1.JPG (2009-11-29 20:02, 56.84 K)



这样总体配置就完成了,下面新建项目。
4.在Project目录里新建一个VC++项目,由于不需要ATL、MFC,目前我也不知道怎么在CLR项目中嵌入SDL(这是个大问题,似乎SDL做对话框、菜单之类的不太方便,如果能和托管C++的GUI功能结合起来就好了,这点还望高手指点),那就新建个“Win32控制台应用程序”好了。项目名叫做WC_SDL(WC是War Chess,战棋的意思,可不是那个……),不要预编译头。
5.还要配置一下项目的属性,选“项目->属性”菜单项,在打开的对话框左上的“配置”项选"Debug",之后在左边选“配置属性->C/C++->代码生成”,右边的“运行时库”项选“多线程DLL(/MD)”。
再在左边选“配置属性->链接器->输入”,右边的“附加依赖项”增加SDLmain.lib和sdl.lib两文件。
左上的“配置”项改为"Release",对“配置属性->C/C++->代码生成”“配置属性->链接器->输入”作类似的设置,这是为发布版本作准备。
6.最后,把Project\SDL-1.2.14\lib\sdl.dll拷到WC_SDL项目目录。

[ 本帖最后由 赵辉 于 2009-11-29 20:03 编辑 ]


顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-11-29 19:50 资料 个人空间 短消息 只看该作者
第1个版本:颍川战场上原地踏步的策士赵辉

准备工作终于完成了,下面就来实现我WC_SDL的第一个版本吧,本来预想的就是帖一张战场地图和一个人物上去,后来觉得太过简单,便又决定让那个人原地踏起步来,其实就是实现个2帧动画。

首先先准备图片素材,用RV导出曹操传第1关 颖川之战的地图(为什么选择它?那可是无数和我一样朋友梦开始的地方啊),存为background.bmp,再导出一个我《杨家将新传》里赵辉的Unit_mov图(我的游戏,我做主),存为player.bmp,把这两个图片拷到WC_SDL工程目录下。

下面修改WC_SDL.cpp的代码,全文如下:
#include <stdio.h>
#include <stdlib.h>
#include "SDL.h"

// 定义3个表面(相当于绘图板)
SDL_Surface *backGround;
SDL_Surface *player;
SDL_Surface *screen;

// 定义窗体大小常量
const int scrWidth=960;
const int scrHeight=960;
const Uint32 frameRate=500;
Uint32 oldTime=-frameRate;

// 图片表面初始化
int InitImages()
{
backGround = SDL_LoadBMP("backGround.bmp");
player = SDL_LoadBMP("player.bmp");
return 0;
}

// 绘图方法1
void DrawIMG(SDL_Surface *img, int x, int y)
{
SDL_Rect dest;
dest.x = x;
dest.y = y;
SDL_BlitSurface(img, NULL, screen, &dest);
}

// 绘图方法2
void DrawIMG(SDL_Surface *img, int destX, int destY, int srcX, int srcY,int w, int h)
{
SDL_Rect dest;
dest.x = destX;
dest.y = destY;
SDL_Rect src;
src.x = srcX;
src.y = srcY;
src.w = w;
src.h = h;
SDL_BlitSurface(img, &src, screen, &dest);
}

// 绘制背景
void DrawBackGround()
{
DrawIMG(backGround, 0, 0);
}

// 绘制角色
void DrawPlayer()
{
static int curFrame=0;
  //先重绘该区块的背景图
DrawIMG(backGround,0,0,0,0,48,48);
  //绘制对应帧的主角图片
DrawIMG(player,0,0,0,curFrame*48,48,48);
curFrame=1-curFrame;
}

// 主程序入口
int main(int argc, char *argv[])
{
// 初始化SDL子系统,这里只对视频进行初始化
if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
{
  fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
  exit(1);
}

// 注册SDL_Quit,当退出时调用,使得退出时程序自动清理
atexit(SDL_Quit);

// 使用32位象素创建窗口
screen = SDL_SetVideoMode(scrWidth, scrHeight, 32, SDL_SWSURFACE);

// 若失败,则退出
if ( screen == NULL ) {
  fprintf(stderr, "Unable to set screen: %s\n", SDL_GetError());
  exit(1);
}
InitImages();

// 设置player图片的紫色(247,0,255)为透明色
SDL_SetColorKey(player, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(player->format, 247, 0, 255));

// 绘制战场地图背景
DrawBackGround();
SDL_Flip(screen);

// 主循环
while (1) {
  // SDL中的事件轮询(消息)机制
  SDL_Event event;
  while (SDL_PollEvent(&event)) {
   //对消息进行处理
   switch (event.type) {
    // 如果按下某键的消息响应
    case SDL_KEYDOWN:
     break;
    //如果某键按下后弹起的消息响应
    case SDL_KEYUP:
     //若按下ESC键,则退出
     if (event.key.keysym.sym == SDLK_ESCAPE)
      return 0;
     break;
    //退出消息响应(点窗口右上的X,或按Alt+F4)
    case SDL_QUIT:
     return(0);
   }
  }

  // 每隔500ms,主角切换一帧
  if(oldTime + frameRate > SDL_GetTicks()) {
   continue;
  }
  oldTime = SDL_GetTicks();
  DrawPlayer();
  SDL_UpdateRect(screen, 0, 0, 48, 48);
}
return 0;
}

本人实在是没时间对代码分块做详细的注解了,而且本文也确实不是面向广大人民群众的。有C++程序设计基础但不懂SDL的朋友,可以先阅读网上的《使用SDL打造游戏世界之入门篇》,了解些基础知识,然后再看上述代码,应该很容易明白了。
感觉这段程序两个出彩的地方是帧动画的实现和主角图片透明色的选择,尤其是后者,居然一条语句就实现了,令在下不禁大为欢欣鼓舞,坚定了用SDL做下去的信心。

OK,下面编译执行,就可以看到赵辉在地图左上角原地踏步的情景了(图1,截图看不出动画效果,有兴趣的朋友下载附件里的程序运行来看,运行后按esc或Alt+F4或点窗口右上角的X都可以退出)


图片附件: [图1] 2.JPG (2009-11-29 19:57, 141.55 K)



至此,本人开发开发战棋游戏的第一步就算迈出来了。当然,这真的是万里长征迈出的第一步,后面的路还好长好长。目前的程序除了一张背景图和2帧动画之外,什么都还没有。而且,有些程序设计经验的朋友应该就能看出来,我上面的代码结构其实是相当的乱,而且是穿着C++外衣的C。毕竟这是我在混沌中摸索的产物。我准备遵循敏捷开发原则,随着功能增添的需要不断重构代码,最终实现良好的框架结构和类封装。以后只要有进展,有新的可执行版本做出,我就会上来和大家分享。希望高手们多提意见建议,甚至,如果您有兴趣,咱们可以经常探讨甚至合作。

[ 本帖最后由 赵辉 于 2009-11-29 19:59 编辑 ]


附件: [第1个版本] WC_SDL 0.01.rar (2009-11-29 19:57, 510.36 K)
该附件被下载次数 410
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-11-29 19:53 资料 个人空间 短消息 只看该作者
第2个版本:让赵辉开口说话

本来说接下来实现鼠标控制下的人物移动的,但昨天找到个系统讲SDL的网站,http://lazyfoo.net/SDL_tutorials/index.php,从上面先学到了文字显示,那么就现学现卖,先实现类似曹操传的对话显示功能吧。

首先,实现文字显示功能需要两个额外的库,SDL_ttf(SDL专门的TrueType字体支持子库)和libiconv(Unicode和其他的传统编码之间转换库,由赫赫有名的GNU提供),可以分别到http://www.libsdl.org/http://gettext.sourceforge.net/下载它们
我下的版本分别在SDL_ttf-devel-2.0.9-VC8.zip和libiconv-1.9.1.bin.woe32.zip两个压缩包中。
解压后,将其中include子目录里的.h文件拷到前面说过的SDL-1.2.14目录的include子目录里,将lib子目录的.lib文件拷到SDL-1.2.14目录的lib子目录里,将lib子目录里的.dll文件拷到项目目录(WC_SDL)里,两个压缩包都这样办理。

然后还要仿照2楼 准备工作的 步骤5作些调整:
选“项目->属性”菜单项,在打开的对话框左上的“配置”项选"Debug",之后在左边选“配置属性->C/C++->代码生成”,右边的“运行时库”项改成“多线程调试DLL(/MDd)”。(本来我没改这里,后来调试时除了链接错误,百度了一下,发现得这样改。原因不明,往高手指点
再在左边选“配置属性->链接器->输入”,右边的“附加依赖项”在原来的基础上,增加SDL_ttf.lib和iconv.lib两文件。
左上的“配置”项改为"Release",对“配置属性->链接器->输入”作类似的设置。

既然要模仿CCZ的对话效果,人物头像不可少,从《杨家将新传》的Tou.dll里提取赵辉的真彩头像,从mark.e5里提取几张对话框用的小图形(如图1),分别命好名,存到WC_SDL项目目录的res子目录里,上次的background.bmp和player.bmp也转移到res子目录里,以便资源的统一管理(项目目录文件的详细情况见下面的文件列表)


图片附件: [图1] 4.JPG (2009-11-29 21:07, 1.92 K)



准备工作完成,下面还要解决一个重要问题:
本来SDL_TTF似乎不能支持汉字,www.cnblogs.com的 龙飞 朋友在他的《SDL入门教程》系列中给出了解决方案(就是用前面说到的libiconv),我就拿来主义了。真的非常感谢他。开源和共享万岁!
他提供的解决上述问题的代码,大部分单独取出为gb2312_to_Unicode.h和gb2312_to_Unicode.cpp(详见源代码包),主程序里还有一部分,已用注释注明。

下面是本版工程包括的主要文件列表(文件夹用蓝色):
WC_SDL   
   res   //资源文件
     backGround.bmp  
     head.bmp  
     player.bmp  
     rcL.bmp  
     rcR.bmp  
     triangleR.bmp  

    gb2312_to_Unicode.h    //头文件

    gb2312_to_Unicode.cpp  //源文件
    WC_SDL.cpp  

    zlib1.dll  //引用的动态链接库
    iconv.dll  
    libfreetype-6.dll  
    SDL.dll  
    SDL_ttf.dll

    simfang.ttf  //TrueType楷体文件

    WC_SDL.sln  //VC8解决方案文件
    WC_SDL.vcproj  //VC8项目文件


下面是我主程序WC_SDL的代码:

#include <stdio.h>
#include <stdlib.h>
#include "SDL_ttf.h"
#include "gb2312_to_Unicode.h"
using namespace std;

// 定义表面(相当于绘图板)
SDL_Surface *screen=NULL;
SDL_Surface *name=NULL;
SDL_Surface *talkContent=NULL;
SDL_Surface *talk=NULL;
SDL_Surface *backGround=NULL;
SDL_Surface *player=NULL;
SDL_Surface *head=NULL;
SDL_Surface *rcLeft=NULL;
SDL_Surface *rcRight=NULL;
SDL_Surface *triRight=NULL;

// 定义mask
const int rmask = 0x000000ff;
const int gmask = 0x0000ff00;
const int bmask = 0x00ff0000;
const int amask = 0xff000000;

// 定义字体和文字颜色
TTF_Font *pFont=NULL;
SDL_Color talkContentColor = { 0, 0, 0 };
SDL_Color nameColor = { 0, 0, 240 };

// 定义窗体大小常量
const int SCREEN_WIDTH=960;
const int SCREEN_HEIGHT=960;
const int SCREEN_BPP=32;
const int frameRate=500;
int oldTime=-frameRate;

// 初始化工作
bool GameInit()
{
// 初始化SDL子系统,这里只对视频进行初始化
if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
  return false;
// 使用32位象素创建窗口
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
// 若创建失败,则退出
if ( screen == NULL )
  return false;
// 初始化TTF
if( TTF_Init() == -1 )
        return false;   
pFont = TTF_OpenFont( "simfang.ttf", 14 );
if( pFont == NULL )
        return false;
// 设置标题栏文字
SDL_WM_SetCaption( "WC_SDL 0.01", NULL );
return true;
}

// 图片表面初始化
bool InitImages()
{
if ((backGround = SDL_LoadBMP("res\\backGround.bmp"))==NULL)
  return false;
if ((player = SDL_LoadBMP("res\\player.bmp"))==NULL)
  return false;
if (( rcLeft= SDL_LoadBMP("res\\rcL.bmp"))==NULL)
  return false;
if (( rcRight= SDL_LoadBMP("res\\rcR.bmp"))==NULL)
  return false;
if (( triRight= SDL_LoadBMP("res\\triangleR.bmp"))==NULL)
  return false;
if (( head= SDL_LoadBMP("res\\head.bmp"))==NULL)
  return false;
// 设置图片的紫色(247,0,255)为透明色
SDL_SetColorKey(player, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(player->format, 247, 0, 255));
SDL_SetColorKey(rcLeft, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(rcLeft->format, 247, 0, 255));
SDL_SetColorKey(rcRight, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(rcRight->format, 247, 0, 255));
SDL_SetColorKey(triRight, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(triRight->format, 247, 0, 255));
return true;
}

// 文字框表面初始化
bool InitTalk()
{
talk = SDL_CreateRGBSurface(SDL_SWSURFACE, 336, 80, 32,rmask, gmask, bmask, amask);
if(talk == NULL)
  return false;
}

// 退出前处理
void CleanUp()
{
SDL_FreeSurface( backGround );
SDL_FreeSurface( player );
SDL_FreeSurface( head );
SDL_FreeSurface( triRight );
SDL_FreeSurface( rcLeft );
SDL_FreeSurface( rcRight );
SDL_FreeSurface( name );
SDL_FreeSurface( talkContent );
SDL_FreeSurface( talk );
TTF_CloseFont( pFont );
TTF_Quit();
SDL_Quit();
}

// 汉字绘制函数
// 本函数引自http://www.cppblog.com/lf426/archive/2008/03/31/45801.html
// 谨向作者 龙飞 致谢
SDL_Surface* myTTF_RenderString_Solid(TTF_Font* font, const string& str, SDL_Color fg)
{
    SDL_Surface* textbuf;

    //Get Unicode
    vector<Uint16> unicodeUnit = getUnicode(str);
    int arraySize = unicodeUnit.size();
    Uint16* perOne = new Uint16[arraySize+1];
    for ( int i = 0; i < arraySize; i++ )
        perOne = unicodeUnit;
    perOne[arraySize] = 0;
   
    //Render the new text
    textbuf = TTF_RenderUNICODE_Solid(font, perOne, fg);
    //Free the text buffer and return
    delete [] perOne;
    return textbuf;
}

// 绘图方法1:绘制整张图像
void DrawImage(SDL_Surface *dest, int x, int y,SDL_Surface *image)
{
// 设置在目标屏幕上的绘图偏移
SDL_Rect offset;
offset.x = x;
offset.y = y;
// 绘图
SDL_BlitSurface(image, NULL, dest, &offset);
}

// 绘图方法2:绘制部分图像
void DrawImage(SDL_Surface *dest, int x, int y, SDL_Surface *image, int sourceX, int sourceY,int width, int height)
{
// 设置在目标屏幕上的绘图偏移
SDL_Rect offset;
offset.x = x;
offset.y = y;
// 设置源图像的截取范围
SDL_Rect source;
source.x = sourceX;
source.y = sourceY;
source.w = width;
source.h = height;
// 绘图
SDL_BlitSurface(image, &source, dest, &offset);
}

// 绘制背景
void DrawBackGround()
{
DrawImage(screen, 0, 0,backGround);
}

// 绘制角色
void DrawPlayer()
{
static int curFrame=0;
DrawImage(screen,0,0,backGround,0,0,48,48);
DrawImage(screen,0,0,player,0,curFrame*48,48,48);
curFrame=1-curFrame;
SDL_Flip(screen);
}

// 建立对话文本框
void createTalk()
{
// 绘制圆角白色区域
SDL_Rect rect;
rect.x=16;
rect.y=0;
rect.w=288;
rect.h=80;
SDL_FillRect(talk,&rect,SDL_MapRGB(talk->format, 255, 255, 255));
DrawImage(talk,0,0,rcLeft,0,0,16,40);
DrawImage(talk,0,40,rcLeft,0,24,16,40);
DrawImage(talk,304,0,rcRight,0,0,16,40);
DrawImage(talk,304,40,rcRight,0,24,16,40);
DrawImage(talk,320,48,triRight);
// 绘制文字
name = myTTF_RenderString_Solid( pFont, "赵辉", nameColor );
talkContent = myTTF_RenderString_Solid( pFont, "在下赵辉,请多关照。", talkContentColor );
DrawImage(talk,16,4,name);
DrawImage(talk,16,20,talkContent);
}

// 绘制对话
void DrawTalk()
{
// 将对话文本框绘制到屏幕
DrawImage(screen,16,150,talk);
// 绘制头像
DrawImage(screen,352,130,head);
}

// 点击鼠标左键,轮流显示和擦除对话
void handleTalk()
{
static bool status=false;
if (status==false) {
  DrawTalk();
}
else {
  DrawImage(screen,16,130,backGround,16,130,456,120);
}
SDL_Flip(screen);
status=!status;
}

// 主程序入口
int main(int argc, char *argv[])
{
if (GameInit()==false)
  return 1;
if (InitImages()==false)
  return 1;
if (InitTalk()==false)
  return 1;
createTalk();

// 绘制战场地图背景
DrawBackGround();

// 更新显示
SDL_Flip(screen);

// 主循环
bool quit=false;
while (quit==false) {
  // SDL中的事件轮询(消息)机制
  SDL_Event event;
  while (SDL_PollEvent(&event)) {
   //对消息进行处理
   switch (event.type) {
    case SDL_KEYUP: //如果某键按下后弹起的消息响应
     //若按下ESC键,则退出
     if (event.key.keysym.sym == SDLK_ESCAPE)
      quit=true;
     break;
    case SDL_QUIT: //退出消息响应(点窗口右上的X,或按Alt+F4)
     quit=true;

    case SDL_MOUSEBUTTONUP:
        if( event.button.button == SDL_BUTTON_LEFT ) {
      handleTalk();
     }
   }
  }

  // 每隔500ms,主角切换一帧
  if(oldTime + frameRate > SDL_GetTicks()) {
   continue;
  }
  oldTime = SDL_GetTicks();
  DrawPlayer();
}
CleanUp();
return 0;
}

其中,createTalk()函数、handleTalk()函数、和主消息循环里case SDL_MOUSEBUTTONUP:下面的部分是处理鼠标点击后对话显示和擦除的关键代码。
另外,看过第1个版本的朋友会发现,我已经对原有的代码作了一些重构,结构变得清晰多了。但不足的地方还是很多,欢迎高手指正。

目前主程序代码只有300来行,尚可全部帖上。预计随着功能增加,代码量会迅速膨胀,后面估计就只能把一些关键代码贴上来,剩下的以源代码包的形式挂上来,供有兴趣的朋友下载了。

程序运行后,赵辉还像以前一样原地踏步,只不过在地图区域点一下鼠标左键,就会出现如下对话框(图2):


图片附件: [图2] 3.JPG (2009-11-29 21:07, 144.19 K)


(标题栏上显示的版本忘了改了,把SDL_WM_SetCaption( "WC_SDL 0.01", NULL );改成SDL_WM_SetCaption( "WC_SDL 0.02", NULL );就是)

下面是源代码和可执行文件,为了最大限度节省空间,将工程代码、引用的动态链接库、图片资源和所需的TrueType字体分别打包上传。
如果想仅仅想查看源码,下载0.02版源码包解压来看即可。
如果还想编译运行源码,则还要下载图片资源(res.rar)、动态链接库(dlls.rar)、TrueType楷体文件(2个分卷),分别解压后,将所有文件、目录一并拷到源码包解压出来的WC_SDL目录中。
如果想运行可执行版本,则需下载处源码包外的所有文件(可执行版本、图片资源、动态链接库、TrueType楷体文件),将解压后的文件放在一起,再运行WC_SDL.exe。

目前有个问题,我本来以为TrueType字体文件不一定要放到工程目录里,操作系统应该会自动寻找的,而结果好像不行。
于是就导致,我本来想让显示汉字的字体是CCZ原版一样的宋体,但我Windows的宋体和新宋体是在一个TrueType文件里的,有10M之大,想作为附件发上来的话…… ,只好先拿个楷体的凑合着。
不知道有没有朋友拥有小一点的宋体TrueType文件,或是知道如何让程序能调用系统目录下的TrueType,要注意不同人不同版本的系统系统目录是不定的哦。

今天就说到这儿了,欢迎大家与我共同探讨,一起把这件事做下去。

下一个版本,真的就该实现人物的移动了

[ 本帖最后由 赵辉 于 2009-11-29 21:52 编辑 ]


附件: [0.02源码包] WC_SDL 0.02源码.rar (2009-11-29 21:07, 353.84 K)
该附件被下载次数 585


附件: [图片资源] res.rar (2009-11-29 21:07, 414.72 K)
该附件被下载次数 466


附件: [动态链接库] dlls.rar (2009-11-29 21:07, 868.43 K)
该附件被下载次数 349


附件: [TrueType楷体文件 分卷1] simfang.part1.rar (2009-11-29 21:07, 1.91 M)
该附件被下载次数 353


附件: [TrueType楷体文件 分卷2] simfang.part2.rar (2009-11-29 21:07, 361.38 K)
该附件被下载次数 341


附件: [0.02可执行版本] WC_SDL 0.02可执行文件.rar (2009-11-29 21:07, 6.86 K)
该附件被下载次数 308
顶部
性别:未知-离线 大明英烈

Rank: 1
组别 限制发言用户
级别 仁勇校尉
功绩 2
帖子 182
编号 292057
注册 2008-9-24


发表于 2009-11-30 17:40 资料 短消息 只看该作者
支持,不过我看不懂。
顶部
性别:男-离线 dimeterio
(李秀辰)

Rank: 10Rank: 10Rank: 10Rank: 10
组别 校尉
级别 镇西将军
好贴 1
功绩 45
帖子 3985
编号 266634
注册 2008-2-7


发表于 2009-11-30 17:55 资料 个人空间 短消息 只看该作者 QQ
楼主觉得这么做相比MOD的优越性在哪里?
顶部
性别:未知-离线 GOODCHAOGEGOOD

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 128
编号 320198
注册 2009-4-30


发表于 2009-11-30 19:43 资料 短消息 只看该作者
可以自己设计内核,这才是重点。
我也曾经考虑使用VB6.0来开发游戏,不过失败,VC++又不会,故放弃了。
有一个叫SuperRM的工具很适合开发这种回合制的游戏,而我的设想是开发即时制的游戏,故不能使用。
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-11-30 21:29 资料 个人空间 短消息 只看该作者
第3个版本:可以绕过障碍移动的赵辉

代码量逐渐大了,想全贴上来已经不太方便,就对此次更新的地方作些说明吧。代码包里有全部更新或修改过的文件,感觉注释还是比较详细的,有兴趣的朋友下载看看,有什么意见建议可以上来和我讨论。

    首先,为了角色能够移动,它所处的坐标不再像以前那样固定为(0,0)了,朝向也不能总向下了。干脆就新设了角色类CRole(CRole.h),目前只有含有坐标和朝向信息,以后会慢慢扩充。
    既然要移动,就要考虑地形。于是新设了战场类CBattleField(CBattleField.h和.cpp),并设置了战场信息文件YingChuan.bf,目前CBattleField类和.bf文件里仅包含地图大小和地形信息矩阵,以后也会慢慢扩充。
    我又从CCZ的mark.e5里把角色选择框给提取出来了(图1),实现了它随鼠标的移动而变化位置的功能。当选择框移动到人物上,点击鼠标左键,就相当于选择了人物(但目前还未实现标识移动范围的蓝色区域,所以选择人物后,好像一点效果都没有),此时再点击地图的其它(可以移动的)区格,角色便可移动到该区格上。


图片附件: [图1] 选择框.JPG (2009-11-30 21:29, 1.18 K)



既然有的区格可移动,有的不可,就存在出发点和目标点之间寻找路径问题,也就是经典的迷宫问题。写了一个广度优先搜索算法来实现(mazeArithmatic.h和.cpp)。

移动的效果用截图是不好表现的,这里就是意思意思,有兴趣的朋友还是下载程序运行来看效果吧(图2A、B)。


图片附件: [图2A] 示例:移动前.JPG (2009-11-30 21:29, 43.06 K)




图片附件: [图2B] 示例:移动后.JPG (2009-11-30 21:29, 41.12 K)



最后附上目前工程的文件结构(图3)


图片附件: [图3] 工程文件结构.JPG (2009-11-30 21:29, 21.05 K)



附件是更新的源码包、更新的资源和exe,为节省空间,重复的文件都不发了。也就是说,需要把2楼的dll、TrueType字体、和原有的图片资源拷到工程或可执行文件目录里,才能正常编译和运行。

[ 本帖最后由 赵辉 于 2009-11-30 21:32 编辑 ]


附件: WC_SDL 0.03更新源码包.rar (2009-11-30 21:29, 391.13 K)
该附件被下载次数 303


附件: WC_SDL 0.03可执行文件.rar (2009-11-30 21:29, 9.57 K)
该附件被下载次数 288


附件: [新增了选择框图片和战场描述文件] 新增资源.rar (2009-11-30 21:29, 4.46 K)
该附件被下载次数 250
顶部
性别:男-离线 阿尔法孝直
(雀力日进)

闽国公
遂安军节度使
★★★★★★

Rank: 19Rank: 19Rank: 19Rank: 19
柱国(正二品) 轩辕春秋年度最佳(游戏人生区)
组别 节度使
级别 卫将军
好贴 2
功绩 1796
帖子 6034
编号 19070
注册 2004-10-16
家族 轩辕雀党


发表于 2009-12-1 12:25 资料 个人空间 短消息 只看该作者 QQ
现在最感兴趣的是,从一个位置移到另一个位置,如何计算其移动路线?
顶部
性别:男-离线 JFLS28mj

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 137
编号 75377
注册 2006-7-13
来自 山东济南


发表于 2009-12-1 12:35 资料 短消息 只看该作者 QQ
不考虑移动力的话广搜就可以,考虑的话就A*了吧,以我的数据结构知识储备来说

然后ZOC的消耗动力为无穷大
顶部
性别:男-离线 JFLS28mj

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 137
编号 75377
注册 2006-7-13
来自 山东济南


发表于 2009-12-1 12:37 资料 短消息 只看该作者 QQ
另外,申请楼主外包几个函数给我,个人感觉我还是能帮上一点忙的
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-12-1 12:47 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 JFLS28mj 于 2009-12-1 12:35 发表
不考虑移动力的话广搜就可以,考虑的话就A*了吧,以我的数据结构知识储备来说

然后ZOC的消耗动力为无穷大

其实一上来我就想用A*算法的,自己懒得写去网上搜,结果没找到一个像样的 最后自己写,一犯懒就广搜了。
考虑移动力后,其实移动问题就拆成了两个小问题,移动范围的计算、在移动范围中最短路径的求解,前者因为要考虑地形消耗,相当于图的边是有权值的。我想是不是前者用动态规划、后者还广搜就行了,毕竟移动范围没有那么大。
顶部
性别:男-离线 JFLS28mj

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 137
编号 75377
注册 2006-7-13
来自 山东济南


发表于 2009-12-1 12:47 资料 短消息 只看该作者 QQ
重写的话AI应该就有一些可以改进的地方了,最基本的一点就是AI行动顺序,不过这很复杂,还是先把角色类做完善再说

另外我一直很困惑的一点是,反击函数到底是怎么个情况?

以我的感觉应该是,受到攻击——锁定攻击方——检测是否在范围内——检测对方是否攻击不会反击——反击
逻辑关系应该是这样没错,不过总觉得有点不对劲
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-12-1 12:50 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 JFLS28mj 于 2009-12-1 12:37 发表
另外,申请楼主外包几个函数给我,个人感觉我还是能帮上一点忙的

欢迎欢迎
目前尚处探索期,功能划分尚不明朗,能独立分出来交给别人写的东西尚不多。
东西做大以后,相信很快就有这种独立模块了,到时我联系你吧。
顶部
性别:男-离线 JFLS28mj

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 137
编号 75377
注册 2006-7-13
来自 山东济南


发表于 2009-12-1 12:51 资料 短消息 只看该作者 QQ


QUOTE:
原帖由 赵辉 于 2009-12-1 12:47 发表


其实一上来我就想用A*算法的,自己懒得写去网上搜,结果没找到一个像样的 最后自己写,一犯懒就广搜了。
考虑移动力后,其实移动问题就拆成了两个小问题,移动范围的计算、在移动范围中最短路径的求解, ...

对对对,动态规划完全可以,这样就控制在64^3次循环之内了
顶部
性别:男-离线 阿尔法孝直
(雀力日进)

闽国公
遂安军节度使
★★★★★★

Rank: 19Rank: 19Rank: 19Rank: 19
柱国(正二品) 轩辕春秋年度最佳(游戏人生区)
组别 节度使
级别 卫将军
好贴 2
功绩 1796
帖子 6034
编号 19070
注册 2004-10-16
家族 轩辕雀党


发表于 2009-12-1 12:57 资料 个人空间 短消息 只看该作者 QQ
例如,我需要往右上移一格,究竟是右1上1还是上1右1呢?谁知道原版曹操传移动轨迹的具体算法?
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-12-1 13:02 资料 个人空间 短消息 只看该作者
回复 #16 阿尔法孝直 的帖子

我广搜算法的优先顺序是上、下、左、右,所以应该是上1右1.

原版的我不知道,这就等岱儿他们来回答吧
顶部
性别:男-离线 JFLS28mj

Rank: 2Rank: 2
组别 百姓
级别 奋威校尉
功绩 1
帖子 137
编号 75377
注册 2006-7-13
来自 山东济南


发表于 2009-12-1 13:02 资料 短消息 只看该作者 QQ


QUOTE:
原帖由 阿尔法孝直 于 2009-12-1 12:57 发表
例如,我需要往右上移一格,究竟是右1上1还是上1右1呢?谁知道原版曹操传移动轨迹的具体算法?

这个只要耗得移动力是一样的就无所谓,你可以写一个算法规定上移优先级高,也同样可以规定右移优先级高

还有引导攻击其实也是,不过要是让我写代码的话,我会优先攻击能一击毙命的(如果无限引导的话)再优先攻击可以造成杀伤力最高的
顶部
性别:男-离线 岱瀛
(deving)

长平侯
川峡东路经略使
监管使

Rank: 19Rank: 19Rank: 19Rank: 19
组别 经略使
级别 左将军
好贴 1
功绩 2293
帖子 1370
编号 55810
注册 2005-12-22
来自 人间
家族 慕容世家


发表于 2009-12-1 21:42 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 赵辉 于 2009-12-1 13:02 发表
我广搜算法的优先顺序是上、下、左、右,所以应该是上1右1.

原版的我不知道,这就等岱儿他们来回答吧

我简单的说一下曹操传的AI算法吧。

我们知道,曹操传的AI是分了很多种的,比如坚守原地啊,主动出击啊,被动初级啊,跟随某人啊,攻击某人啊,而这里面的一个重要核心思想是 寻路算法。

我认为光荣的寻路算法应该是从 long long ago就沿用的,因为当初我看了那长长的汇编代码,读懂那个寻路算法的时候,忽然就想到了三国志第二代的一个现象。 我们操作某个人的时候,一点,周围可移动的格子里有一些奇怪的数字,7,6,5,4.有时候又7,4,1这样的。后来看曹操传代码,觉得就是一脉相承的东西了。

简单在这里说一下,印象中的曹操传寻路算法。

他是一个递归算法。

首先,我们简单看一下相关的数据结构,既然会C++,我就用C++来解释,这样都方便一些。
#define MAX_LANDFORM 16
#define MAX_METIER 23  //假设有23种职业

//假设一个30*30的地图
int nMapWidth  =  30;
int nMapHeight =   30;

struct Map
{
     int nLandformID;  //地形
     int nState;         //状态,0表示没有人占,-1表示敌人占,1表示我方或者友方占
};

struct Metier  //职业
{
     int nLandformConsume[MAX_LANDFORM];    //某职业的地形消耗数组
     int nAction;                                              //移动力
     //....   职业里还有很多层数据结构
};

Metier g_metier[MAX_METIER];  

Class CBattelPerson : public CPerson  //战场上的人,继承于人
{
private:
   int nID;            //人物的ID;
    int nMetierID;  //人对应的职业
    int nXposition;  //当前地图所在的X坐标位置
    int nYposition;  //当前地图所在的Y坐标位置
    int nStatus;     //当前状态  0表示未行动,1表示行动
    int nActionState;      //当前移动力状态  (可能强行,可能下降,可能是正常)
    int nRole;   //人物角色,1表示我军或者友军, -1表示敌军
   //....   战场上的人其实还有很多种属性,从略
private:
   void mfGetActionPath(int nActionValue, int nXpos,int nYpos) ;
public:
   void GetAcitonPath();
};

//会计算出一个所谓的移动队列,我简单的用vector来表示,相信引用下stl库也不是太麻烦的事情。

struct Position
{
     int nXpos;
     int nYpos;
     int nAIValue;  //这个位置的AI参考值
};
vector<Position> actionQueue;

全局的地图
Map map[nWidth][nHeight];
int nFlag[nWidth][nHeight];  //用来标记地图的AI计算状态

void  //求路径的递归函数,参数一,行动力,参数二,x坐标,参数三,y坐标
CBattelPerson::mfGetActionPath(int nActionValue, int nXpos,int nYpos)
{
    if (nFlag[nXpos][nYpos] != 0)  //说明这个位置遍历过,不遍历
        return;
    else
       nFlag[nXpos][nYpos] = nID;
     if (nActionValue <=0) return;  //没有行动力了,退出
     if (nRole *  map[x][y]. nState < 0)
          return;    //这个位置上有敌人,不能通过,退出  ,这里面的设计没有考虑突击移动的特性,如果有突击移动的特性,这个判断就可以跳过
     
     //为了解释清楚容易看点,下面代码拆细一些

    //取出当前职业
    Metier   metier = g_metier[nMetierID];
   
    //取出地形
    if (nXpos - 1 >= 0 )
   {
        int nCurLandFormIDLeft  = map[x-1][y]. nLandformID;
         mfGetActionPath(nActionValue - metier . nLandformConsume[nCurLandFormIDLeft],x-1,y);
   }

  if (nXpos  + 1 < nWidth )
   {
        int nCurLandFormIDRight  = map[x+1][y]. nLandformID;
        mfGetActionPath(nActionValue - metier . nLandformConsume[nCurLandFormIDRight],x+1,y);
   }

   if (nYpos - 1 >= 0)
   {
          int nCurLandFormIDTop  = map[x][y-1]. nLandformID;
          mfGetActionPath(nActionValue - metier . nLandformConsume[nCurLandFormIDTop],x,y-1);
   }
   
   if (nYpos + 1 < nHeight)
   {
         iint nCurLandFormIDBottom  = map[x][y+1]. nLandformID;  
         mfGetActionPath(nActionValue - metier . nLandformConsume[nCurLandFormIDBottom],x,y+1);
   }

   Position position;
   position.x = nXpos;
   position.y = nYpos;

   actionQueue.push_back(position);  //把坐标压入向量   
   
}

void
CBattelPerson ::GetAcitonPath()
{
     int nActionValue = 0;
     //当前移动力
     nActionValue  = g_metier[nMetierID]. nAction * nActionState; //假设移动力状态是个百分数,其实曹操转里面不是,不过这个无所谓了。
  
     memset(nFlag,0,nWidht*nHeight);  //把整个数组清零
     mfGetActionPath(nActionValue ,nXpos,nYpos);
}

这样,外部函数只要调用到GetAcitonPath(),就能得到当前这个人物的可移动向量。

如果是给用户交互操作,直接就是给界面作下渲染。

如果是给AI,那么就根据这个向量,再根据当前选择的AI类别,遍历这个vector,计算出每个位置的nAIValue;

最后,对这个vector作一下排序,那么AI的结果就出来了。

[ 本帖最后由 岱瀛 于 2009-12-2 01:03 编辑 ]
顶部
性别:男-离线 岱瀛
(deving)

长平侯
川峡东路经略使
监管使

Rank: 19Rank: 19Rank: 19Rank: 19
组别 经略使
级别 左将军
好贴 1
功绩 2293
帖子 1370
编号 55810
注册 2005-12-22
来自 人间
家族 慕容世家


发表于 2009-12-1 21:54 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 JFLS28mj 于 2009-12-1 13:02 发表




这个只要耗得移动力是一样的就无所谓,你可以写一个算法规定上移优先级高,也同样可以规定右移优先级高

还有引导攻击其实也是,不过要是让我写代码的话,我会优先攻击能一击毙命的(如果无限引导的话 ...

其实按照曹操传的AI算法,伤害计算是不考虑爆击和格挡,双击的。 我们自己写的话,当然可以考虑,不过我倒也是建议照猫画虎,甚至早期第一个版本,士气,敏捷这种东西都不要也行。

增加无谓的算法复杂性,对于早期不好,先把基本模型做出来,思路理清了,再慢慢添加,先易后难比较科学。

伤害计算,印象我在汇编解析里曾经解析过。反正所谓的AI,最重要的一个设计就是那个权值来影响到伤害值大小,最终成为行动的指标。
顶部
性别:男-离线 湘江子龙

赵王枢密使

Rank: 30Rank: 30Rank: 30Rank: 30Rank: 30Rank: 30
组别 诸侯
级别 骠骑将军
好贴 9
功绩 630
帖子 8100
编号 8098
注册 2004-6-4


惊现8岁复活
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-12-1 23:14 资料 个人空间 短消息 只看该作者
回复 #19 岱瀛 的帖子

多谢指点。
你对CCZ的研究真是太透彻了,若说重写exe,你才是不二人选。小弟目前的工作尚属自娱自乐型。
不过我感觉CCZ原版这个递归算法可能重复遍历一些点,ms效率不是最好的。

[ 本帖最后由 赵辉 于 2009-12-1 23:16 编辑 ]
顶部
性别:男-离线 岱瀛
(deving)

长平侯
川峡东路经略使
监管使

Rank: 19Rank: 19Rank: 19Rank: 19
组别 经略使
级别 左将军
好贴 1
功绩 2293
帖子 1370
编号 55810
注册 2005-12-22
来自 人间
家族 慕容世家


发表于 2009-12-2 00:31 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 赵辉 于 2009-12-1 23:14 发表
多谢指点。
你对CCZ的研究真是太透彻了,若说重写exe,你才是不二人选。小弟目前的工作尚属自娱自乐型。
不过我感觉CCZ原版这个递归算法可能重复遍历一些点,ms效率不是最好的。

曹操传的大致算法思路是那个,我刚才写的确实有重复的点,曹操传有没有我也忘记了,修改了下,你重新看下代码,引入一个nFlag的二维数组,标记下每个点的状态,就可以规避掉这种重复计算点的问题了。

另外,自娱自乐挺好的,先练手嘛。 整个走一遍之后,再从设计的角度去看,去设计好,也许就可以写出来一个不错的引擎了,关键是要持之以恒。

另外,我上面的代码只是解释算法用的,设计角度来说不一定最优,一些struct,class的设计,不一定完美。还有存在大量的全局变量,其实是不对的。

[ 本帖最后由 岱瀛 于 2009-12-2 01:08 编辑 ]
顶部
性别:未知-离线 lufy


Rank: 5Rank: 5
组别 校尉
级别 裨将军
功绩 13
帖子 310
编号 347822
注册 2009-11-21


看到这里研究寻路算法,自己也手痒了,写了一个A*寻路算法
顶部
性别:男-离线 岱瀛
(deving)

长平侯
川峡东路经略使
监管使

Rank: 19Rank: 19Rank: 19Rank: 19
组别 经略使
级别 左将军
好贴 1
功绩 2293
帖子 1370
编号 55810
注册 2005-12-22
来自 人间
家族 慕容世家


发表于 2009-12-4 19:08 资料 个人空间 短消息 只看该作者
A*算法个人觉得更适合于即时战略游戏。  他解决的是行走路线问题,并不能解决战棋的AI问题。

A*有一个最大的问题,就是有一个目标点,他求出来的是一个起始点到目标点的路径。但是站棋AI根本不是要找一条行动路径,本质上是有所有可行路径推出一个最佳目标点。

等到目标点确认出来了,怎么从起始点移动到目标点,只是一种简单绘图的问题。上下左右的走,反正是肯定可达的。

比如被动出击这种AI,就是把可移动的vector作一次点的遍历,每个可达点得到的伤害计算权值,最终对vector排序,就可以得到应该移动到哪个点了,至于怎么移动,其实只是上下左右移动靠近而已。
顶部
性别:男-离线 dagaidui

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 忠义校尉
功绩 3
帖子 286
编号 334864
注册 2009-8-15


发表于 2009-12-4 19:19 资料 短消息 只看该作者
原来这么复杂,对moder更是钦佩啊
顶部
性别:男-离线 赵辉
(长平)

昭信伯
安德军节度使
★★★★

Rank: 14Rank: 14Rank: 14Rank: 14Rank: 14
组别 节度使
级别 右将军
好贴 2
功绩 361
帖子 1092
编号 338625
注册 2009-9-11
来自 北京
家族 轩辕学院


发表于 2009-12-4 19:23 资料 个人空间 短消息 只看该作者
这几天研究遇到瓶颈了,就是用SDL做窗体控件(对话框、按钮什么的),自己重头写很麻烦,已有的一些库配置、使用起来都不是很顺手。
努力求解中……
顶部
性别:男-离线 岱瀛
(deving)

长平侯
川峡东路经略使
监管使

Rank: 19Rank: 19Rank: 19Rank: 19
组别 经略使
级别 左将军
好贴 1
功绩 2293
帖子 1370
编号 55810
注册 2005-12-22
来自 人间
家族 慕容世家


发表于 2009-12-4 19:28 资料 个人空间 短消息 只看该作者


QUOTE:
原帖由 赵辉 于 2009-12-4 19:23 发表
这几天研究遇到瓶颈了,就是用SDL做窗体控件(对话框、按钮什么的),自己重头写很麻烦,已有的一些库配置、使用起来都不是很顺手。
努力求解中……

呵呵,用VC就得遇见这种问题。

不总是遇见问题,怎么可以进步,呵呵。
顶部
性别:男-离线 阿尔法孝直
(雀力日进)

闽国公
遂安军节度使
★★★★★★

Rank: 19Rank: 19Rank: 19Rank: 19
柱国(正二品) 轩辕春秋年度最佳(游戏人生区)
组别 节度使
级别 卫将军
好贴 2
功绩 1796
帖子 6034
编号 19070
注册 2004-10-16
家族 轩辕雀党


发表于 2009-12-4 20:46 资料 个人空间 短消息 只看该作者 QQ
唉,用VB就省事多了
顶部
性别:男-离线 關云長

Rank: 1
组别 百姓
级别 在野武将
功绩 0
帖子 46
编号 349310
注册 2009-12-4


发表于 2009-12-5 09:52 资料 短消息 只看该作者
使用45度的视角要好看一些。


顶部

正在浏览此帖的会员 - 共 1 人在线




当前时区 GMT+8, 现在时间是 2024-12-22 01:08
京ICP备2023018092号 轩辕春秋 2003-2023 www.xycq.org.cn

Powered by Discuz! 5.0.0 2001-2006 Comsenz Inc.
Processed in 0.026777 second(s), 9 queries , Gzip enabled

清除 Cookies - 联系我们 - 轩辕春秋 - Archiver - WAP