客户端入门十二小时 FOOLFLY DEMO DESIGN 讲师:林伟
个人资料 姓名:林伟 昵称:小林子、小林 网名:skywind 年龄:18 (!?) 职业:休闲组程序员,负责平台开发 POPO:skywind3000 EMAIL:weilin@corp.netease.com
课程历史 1998年,教同学写游戏,完成DEMO原型 1999年,应网友要求,重新注释改写 2001年,某培训公司盗版为培训内容 2003年,电子科大MSTC课堂第一讲内容 2003年,川大/西华大学交流讲座 2006年,第五次重讲
Talk to me like I’m a 3 years old ! Create Game In 12 Hours Talk to me like I’m a 3 years old ! ---- Digiben
艺术作品为了给人带来享受 ; 那作为艺术创作的游戏开发,又为了什么? --为了追求 “美”
学习游戏编程的八大误区 误区一:很多作品只能叫做 “程序” 而不能称为 “游戏” 误区二:执着于各种技巧而忽视了基本素质的培养 误区三:执着于流行的API,认为: 会写游戏 = 掌握一两套API 误区四:执着于大而全,系统就像一只吞噬了各种化学品的魔兽 误区五:执着于引擎创作爱重复劳动: 最失败的引擎 = 拿来主义 + 函数改名 误区六:不消于使用脚本语言,喜欢CPP至上论 误区七:缺少科学的测试方法,写出来的程序BUG不断 误区八:作品缺乏游戏性,程序设计缺乏美感
课程内容及目的 课程内容 主要目的 基本要求 时序控制:显示贞 / 逻辑贞 / AI 贞的基本概念与作用,常见错误分析 图形显示:常见绘制原理 / 三大功能接口 / 简单显卡原理 / 导致低效的原因 数据结构:游戏对象描述 / 并行状态机的转移与变迁 输入控制:游戏输入与应用程序输入的区别 / 硬件编程接口 代码分析:随堂讲解飞行游戏DEMO-FOOLFLY 主要目的 讲解客户端开发用到的基本原理 / 常见错误分析 / 优化技巧 使客户端入门者 / 其他对客户端开发感兴趣者 对2D游戏客户端有一个总体概念 基本要求 熟练使用 TurboC2.0 语言,并且了解计算机基本原理
十二小时入门技巧 花两小时听完本课程 花两小时阅读完200多行的FOOLFLY代码 花三小时为游戏增加两种新的NPC敌人 花三小时为游戏增加一个BOSS 剩下的时间吃饭/午睡 恭喜你成为初级客户端程序员 !!
麻雀虽小五脏俱全 双击右边图标打开DEMO 保存成foolfly.c用TC20编译 大小:200多行 内容:太空射击游戏 方向键控制飞机移动 CTRL键发射激光 代码很烂,大家忍着点看 浏览FOOLFLY代码(无病毒)
格 言 不管现在流行什麽语言,你都可以肯定十年二十年之後它不再风光。我总是在自己的书中写些不时髦的东西,但这些东西却值得後代子孙记取。 格 言 不管现在流行什麽语言,你都可以肯定十年二十年之後它不再风光。我总是在自己的书中写些不时髦的东西,但这些东西却值得後代子孙记取。 --- Donald Knuth
游戏时序控制 基本原理:显示贞/逻辑贞/AI贞 的概念与用途 常见错误:为何计时不准?为 何运行缓慢? 常见错误:为何计时不准?为 何运行缓慢? 正确方法:精确的时间控制 / 简单8254编程 代码分析:FOOLFLY DEMO
FPS – 游戏逻辑的基本单位 任务一:复位所有图形/非图形的数据 任务二:浏览所有的游戏对象并更新状态 任务三:处理用户输入 任务四:绘制并更新图形 任务五:事件驱动部分 基本作用:游戏的主循环每秒进行20-60次
经典的游戏主程序 传统流程图介绍 有什么局限? 值得改进的地方? 状态更新 输入控制 图形绘制 是否结束 事件驱动 时间控制
几个相关问题 游戏循环占用时间?循环本身优化方法? 为何区分显示贞、逻辑贞、AI贞、网络贞? AI贞与逻辑贞工作类似,为何独立出来? 如果显示贞是60FPS,其他贞应为多少?
时序控制在于基本功能的合理调度 时间控制: 常见贞率: 主动控制:计算时间差并 - Sleep 被动控制:等待时钟消息 - WM_TIMER 显示贞:20-60FPS 逻辑贞:10-30FPS
下面代码有什么问题? def main_loop ( FPS = 10 ): while not gamend: time_delay = 1.0 / FPS while not gamend: time_start = time.time() ….. time_last = time.time() – time_start time.sleep ( time_dalay – time_last ) return True
8254可编程时钟芯片(了解) unsigned long fclock ( void ) /* 读取 1.19MHz的32位系统时钟 */ { unsigned long t ; disable() ; /* 静止中断发生 */ outportb ( 0x43, 0 ) ; /* 告诉8254我想读时钟 */ t = inportb ( 0x40 ) ; /* 读取 1193181Hz的8254时钟 */ t = t + ( inportb ( 0x40 ) << 8 ) ; /* 时钟分两次从同端口读入 */ t = 0xffff ^ t ; /* 读出来的16bit值需要取反 */ enable() ; /* 打开中断开关 */ return ( clock() << 16 ) + t ; /* time = (not t) + (clock()<<16) */ }
传统CPU的计时方式(了解) 8254芯片如何与CPU协同工作? 为什么DOS下clock()函数是18.2次? 3号计时器输出引脚与CPU的IRQ引脚相连接 3号计时器倒计时为0时输出低频电压 低频电压(逻辑1)触发CPU的时钟中断 为什么DOS下clock()函数是18.2次? 很简单:1193181 / (2 ^ 16) = 18.2 因为8254芯片的寄存器是16bit的 通过改变3号计时器的倒计时初始值,可改变中断频率
FOOLFLY DEMO - 需要改进么 while ( ! GameOver ) /* GameStart(void) 主函数内容 */ { while ( fclock() – start <= 45000L) ; /* 1193181 / 45000 = 26 (FPS) */ start = fclock() ; /* 时间控制 */ clear() ; /* 1.清屏. 以下5点为游戏的主循环 */ timepass++ ; /* 2.时间基数++ */ control() ; /* 3.事件处理 */ drivers() ; /* 4.事件引擎 */ show() ; /* 5.显示 */ if (--Sound <= 0) nosound(); /* 声音处理 */ if ((timepass&3)==0) printf("%d\r",GameScore); /* 到一定时间更新分数 */ if (keys[KEY_ESC]) GameOver = 1 ; }
精确计时通用代码(掌握) def main_loop ( time_last = 0.1 ): time_current = time.time () time_slap = time_current + time_last while not gamend: if time_current < time_slap: time.sleep ( time_slap – time_current ) time_slap = time_slap + time_last process_fps () return True
SUMARY 时序控制在于基本逻辑的合理调度 FPS(贞)是游戏逻辑的基本周期单位 熟悉时序控制等于了解游戏流程的脉络 拆分显示贞与逻辑贞以优化流程 错误的计时方法 / 精确计时法 简单8254时钟芯片的编程方法
小调查 课程内容前后将涉及三部分硬件编程内容:8254 / 简单显卡编程 / 键盘控制,这些附属内容其实对今天的游戏开发关系并不大,是否照原计划讲解还是快速带过就行: * 希望按PPT照常讲解请按 -- 1 * 希望少讲点节约时间请按 -- 0
格 言 人什么时候碰到麻烦?- 把简单的事情想复杂了;或者把复杂事情想简单了 。。。 -- 葛优
TEA - TIME 喝水时间,休息十分钟 (接下来将介绍图形技术)
游戏图形显示 常见绘制:DB / PF / DR 三大接口:绘点/绘位图/刷新 绘制原理:简单VGA编程 实战分析:用DEBUG绘图 代码分析:FOOLFLY DEMO
常用技术原理 三种常用显示方式 三个基本绘制功能 Double Buffer(双缓存):主存绘制并一次拷到显存 Page Flip(页面翻转):显存绘制,完成后切换 Dirty Rect(脏矩形):每次绘制更改部分 三个基本绘制功能 PutPixel:常用画点函数 Blit(位图拷贝):将某图片中某举行拷贝到目标图片 Refresh(刷新):将之前绘制的东西一次性显示出来
给我一个支点,我能撑起整个地球 给我一个画点函数,我能绘制整个世界
显卡如何工作的? 显存上的数据映射出显示器画面 更改显存数据就改变了图象 分辨率:320x200x256c 点大小:一个点对应一个字节 显存地址:0x000A0000H 绘制原理:找到并改写对应显存
一行代码完成画点(掌握) char far *video = ( char far* ) 0xa0000000L ; void putpixel ( long x, long y, char col ) { video [ y * 320 + x ] = col ; } 注:上面程序效率不高,该如何优化?
BLIT - 主要任务 图形计算:源/目标图片大小及内存地址 明确模式:是否透明 明确效果:是否镜面翻转 具体绘制:将源图形拷贝到目标位置 PS: BLIT(BitBlt)位图拷贝,主要图形接口
透明色并带镜像的 BLIT void putimage ( int x, int y, char *b, int mode) { int len, wid, i, j ; len = b[0] + ( int ) b[1] * 256 ; /* 计算大小 */ wid = b[2] + ( int ) b[3] * 256 ; x -= len / 2 ; y -= wid / 2 ; /* 中心对称 */ for ( j = 0, b = b + 4 ; j < wid ; j++ ) for ( i = 0 ; i < len ; i++, b++ ) { if ( *b && !mode ) pixel ( x + i, y + j,*b ) ; if ( *b && mode ) pixel ( x + i, y + wid – j -1,*b ) ; }
双缓存机制一行代码 char far *VideoBuf = ( char far* ) 0xa0000000L ; char far *MemBuf = NULL ; /* 初始化分配64K */ /* 更新系统内存中的图形到显存 */ void show ( void ) { memcpy ( VideoBuf, MemBuf, 64000L ) ; }
最简单的特效绘制 双层卷屏特效:两层星空背景的简单实现 主角激光武器:简单的画两条直线 NPC爆炸特效:闪烁并消失
明明三行代码做得完的工作,偏偏要用30行去完成,并称之为代码复用;然而在第二个项目中往往把 98%打破重写,并美其名曰 - “重构”。。。 封装的陷阱(小笑话) 明明三行代码做得完的工作,偏偏要用30行去完成,并称之为代码复用;然而在第二个项目中往往把 98%打破重写,并美其名曰 - “重构”。。。
常用优化方法(了解) 避免显存访问慢:频繁绘制在主存完成 避免画面不平滑:显示器垂直同步时绘制 提高代码速度:代码优化/CPU新特性/DMA 使用硬件加速:换用支持硬件加速的API 其他优化技巧:预渲染/脏矩形 。。。 没办法的优化:降低分辨率 / 减少色素
C++充满了太多技巧,可以让一个人表现自己很聪明,底层技术同样 - 不要陷入太深进而作茧自缚 拿得起还要放得下 C++充满了太多技巧,可以让一个人表现自己很聪明,底层技术同样 - 不要陷入太深进而作茧自缚 -- 某同事
案例分析 ALLEGRO 相关代码 FOOLFLY相关代码
SUMARY 基本图形绘制原理及常见接口 简单显卡编程方法 简单优化策略 具体案例分析
TEA - TIME 喝水时间,休息十分钟 (下讲将介绍数据结构)
写代码追求 “三种美感” 简约美: 结构美: 境界美: 指简单性,认为简单即是美 简单并且真切有力 指可拆分性,结构富于美感,代码可分可合 指开发的同时应伴随富于审美的精神追求 一念动,万念相生;一波动,万波相随
游戏数据结构 对象描述:游戏对象描述 状态描述:状态变迁与转移 资源数据:图形/声音数据 属性数据:角色属性数据
基本数据结构 游戏对象描述:定义基本游戏单位 对象数组操作:分配 / 释放 / 索引 对象状态更新:并行状态机
FOOLFLY 的对象描述 struct TObj { /* 所有对象将于每贞遍历 */ int mode ; /* 0为未用 1为敌人 2为导弹 */ int type ; /* 子状态 */ int index ; /* 辅助变量 */ int next ; /* 状态变量 */ int x, y ; /* 位置坐标 */ } Objs[MAX_OBJ];
DEMO案例分析 为Objs数组分配空域位置:mode == 0 对象销毁后释放方法:mode -> 0 当对象增多了以后该如何描述? 当事件复杂了该如何改进?
TEA - TIME 喝水时间,休息十分钟 (接下来将介绍键盘输入)
游戏输入控制 输入控制的分类及原理 键盘控制器原理 简单IRQ编程 FOOLFLY代码分析
基本原理介绍 应用程序输入控制:串行输入 游戏程序输入控制:并行输入 唯一的接口:查询某键是否被按下
键盘控制器简单编程(了解) /* 键盘服务程序 */ void interrupt NewInt9 (void) /* 键盘 IRQ服务程序 */ { unsigned char key ; key = inportb ( 0x60 ) ; /* 读键盘扫描码 */ if ( key < 0x80 ) keys [ key ] = 1 ; /* 如果最高位是0,则为按下 */ else keys [ key & 0x7f ] = 0 ; /* 如果最高位是1,则为放开 */ key = inportb ( 0x61 ) ; key |= 0x80 ; outportb ( 0x61, key ) ; /* 告诉键盘已接收 */ outportb ( 0x61, key & 0x7f ) ; outportb ( 0x20, 0x20 ) ; /* 发送中断结束信号 */ }
键盘控制器与CPU协作(了解) 键盘按下或者放开某键都会输出低电平 该输出传向CPU的键盘IRQ引脚引发中断 中断程序返回后继续刚才的地方执行 那么默认的键盘 IRQ程序做些什么那?
SUMARY 本章所有内容属于了解范围 只要了解如何在游戏中使用查询接口即可 实际开发中完全可将本章代码 copy/paste 现在的操作系统都有现成的接口提供
FOOLFLY 代码分析 随堂DEMO的主体代码回顾 通过代码介绍回顾课程各部分内容
FOOLFLY 编译说明 请使用 TurboC2.0或者 BC3.1编译 如果用 TC20,注意设置路径等 该程序是 DOS程序,内存用 Large方式
课后作业 - 留给想继续深入者 请重写游戏,将我的飞机从横版改为纵版 请完成至少三种NPC敌人,一种奖励道具 请完成至少一个有难度的 BOSS 请提供双打,与别人分享你的 “小游戏”
只开风气,不为人师 -- 胡适
胡言乱语,不知所云
MAIL: weilin@corp.netease.com 课后交流 《入门十二小时》的所有内容到此结束 感谢 CCTV,感谢 ChannelV MAIL: weilin@corp.netease.com