如何用html5 canvas游戏开发开发“精灵”游戏

  声明:本文为原创文章,如需转载,请注明来源,谢谢!
  楼主记忆力不好,最近刚好用了一下createJs框架,怕以后一段时间没用后会忘记,所以在此做个记录,或许以后用得着。
  createJs网上的中文教程挺少的,以前UC有个Xcanvas的论坛有createJs的详细教程,但是随着XCanvas团队的解散,那个网站也关闭了。。网上的大部分都是非常基础的教程,有点千遍一律的感觉。所以楼主就去把createJs下载下来,硬着头皮看英文文档了。凭着楼主这英语六级只考了三百多分的渣渣来说,看起来很费力啊,不过还是勉强摸索出了大概的用法。所以现在就是学了多少就记录多少,之后或许也会不定期更新一下该框架的新的学习心得。毕竟对自己以后还是有帮助的。
  希望本文能帮到那些想学createJs的新手。因为楼主也是刚学的,所以本文或许有不正确之处,因此本文仅当参考,若有不正之处欢迎斧正。
  闲话说到这,直接进入主题。
  楼主用createJs写了个简单的跑酷游戏DEMO,就拿它做例子吧。&。
 &createJs的由来,基础什么的就不说了,就直接说createJs的用法吧。
  首先到createJs官网下载,createJs分成easelJs(图形动画)、preloadJs(文件加载)、soundJs(音频控制)以及tweenJs(补间动画)四部分,大家下载的时候,建议下载两个文件,一个是压缩版文件,用于项目中的引用,再下载个源码文件,用于查看用法、API、demo等。因为楼主目前只用了easelJs和preloadJs,所以暂时就只说这两个,其实就这两个已经非常够用了。
  接下来开始分析代码:
  首先引入js文件
&script src="easeljs-0.7.1.min.js"&&/script&
&script src="preloadjs-0.4.1.min.js"&&/script&
  然后进行舞台初始化操作:
function init(){
stage = new createjs.Stage("cas");
C_W = stage.canvas.
C_H = stage.canvas.
var manifest = [
{src:"image/man.png" , id:"man"},
{src:"image/ground.png" , id:"ground"},
{src:"image/bg.png" , id:"bg"},
{src:"image/high.jpg" , id:"high"},
{src:"image/coins.png" , id:"coin"}
loader = new createjs.LoadQueue(false);
loader.addEventListener("complete" , handleComplete);
loader.loadManifest(manifest);
drawLoading();
上面就用到了preloadJs中的方法,实例化一个loader,把需要加载的图片文件放在manifest里面,进行加载,加载完成后调用回调handleCompelete函数:
function handleComplete(){
//当图片素材load完后执行该方法
var manImage = loader.getResult("man"),
lowground = loader.getResult("ground"),
highground = loader.getResult("high"),
bgImage = loader.getResult("bg"),
coins = loader.getResult("coin");
sky = new createjs.Shape();
sky.graphics.bf(bgImage).drawRect(0,0,C_W,C_H);
sky.setTransform(0, 0, 1 , C_H/bgImage.height);
stage.addChild(sky);
man = createMan(200,326,manImage);
//该框为判定角色的判定区域
kuang = new createjs.Shape();
kuang.graphics.beginStroke("rgba(255,0,0,0.5)").drawRect(0 , 0 , man.size().w , man.picsize().h*1.5);
// stage.addChild(kuang);
mapHandle(lowground , highground , coins);
createjs.Ticker.timingMode = createjs.Ticker.RAF;//设置循环方法,可以是requestAnimationFrame或者是setTimeout
createjs.Ticker.setFPS(30);//舞台帧率控制
createjs.Ticker.addEventListener("tick", tick);//绑定舞台每一帧的逻辑发生函数
window.addEventListener("keydown" , function(event){
event = event||window.
if(event.keyCode===32&&man.jumpNum&man.jumpMax){
man.jump();
获得加载完成后端的图片数据就直接用loader.getResult就可以获取了,跑酷游戏需要一个背景,所以,我们实例化一个sky,然后进行位图绘制,bf方法是beginBitmapFill的缩写,该方法就是开始绘制位图,后面的drawRect是位图的绘制区域,区域当然是整个画布啦,所以就是drawRect(0,0,C_W,C_H)。实例化出来sky后就直接添加到舞台stage里面就行了。接下来是实例化一个角色,createMan方法后面有说,是自己封装的。
  然后进行舞台循环设置,上面有注释了,就不说了。
  舞台设置中,mapHandle是地图数据的初始化:
var mapIndex = 0,
//地图序列
//地图数组的索引
allStones = [],
//存放所有的石头
allCoins = [],
//所有金币
showSt = [];
//存放显示出来的石头
function mapHandle(lowground , highground , coins){
//初始化地图
allStones.length = 0;
var stoneImage = {"A":lowground , "B":highground},kind = null;
for(var i=0;i&30;i++){
//把需要用到的石头预先放入容器中准备好
switch(i){
case 0:kind="A";break;
case 10:kind="B";break;
case 20:kind="C";break;
var st = createStone(C_W , kind , stoneImage);
allStones.push(st)
for(var i=0;i&10;i++){
//把需要用到的金币预先放入容器中
var coin = createCoin(coins);
allCoins.push(coin);
Mix = Math.floor(Math.random()*mapData.length);
//随机地图序列
for(var i=0;i&8;i++){
setStone(false)
function setStone(remove){
//添加陆地的石头
var arg = mapData[Mix].charAt(mapIndex),
coarg = coinCode[Mix].charAt(mapIndex),
cc = null;
if(coarg==="#"){
for(var i=0;i&allCoins.i++){
if(!allCoins[i].shape.visible){
cc = allCoins[i];
cc.shape.visible = true;
for(var z=0;z&allStones.z++){
if(!allStones[z].shape.visible&&allStones[z].kind===arg){
var st = allStones[z];
st.shape.visible = true;
st.shape.x = showSt.length===0?0:showSt[showSt.length-1].shape.x+showSt[showSt.length-1].w;
cc.shape.x = showSt.length===0?allStones[z].w/2-cc.size().w/2:showSt[showSt.length-1].shape.x+showSt[showSt.length-1].w+allStones[z].w/2-cc.size().w/2;
cc.shape.y = arg==="C"? C_H-loader.getResult("high").height-50 : allStones[z].shape.y-cc.size().h/2-50;
if(remove) showSt.shift();
showSt.push(st);
mapIndex++;
if(mapIndex&=mapData[Mix].length){
Mix = Math.floor(Math.random()*mapData.length)
mapIndex=0;
  下面是人物模块的封装
(function(w){
var FRAME_RATE = 13,
//精灵表播放速度
SCALE_X = 1.5,
SCALE_Y = 1.5,
GRAVITY = 3,
//重力加速度
JUMP_SPEED = 2.6,
//垂直速度
WIDTH = 40,
HEIGHT = 96,
PICWIDTH = 64,
PICHEIGHT = 64,
PROPORTION = 150/1;
//游戏与实际的距离比例
var Man = function(x , y , img){
this.endy =
this.vx = 0.5;
this.vy = 0;
this.ground = [];
this.state = "run";
this.jumpNum = 0;
this.jumpMax = 1;
this.init(img);
Man.prototype = {
constructors:Man,
init:function(img){
var manSpriteSheet = new createjs.SpriteSheet({  //实例化精灵表绘制器
"images":[img],
"frames":{"regX":0,"height":PICWIDTH,"count":45,"regY":1,"width":PICHEIGHT},
"animations":{
frames:[21,20,19,18,17,16,15,14,13,12],    //精灵表每一帧的位置
next:"run",                    //当精灵表循环完后的下一步动作
speed:1,                      //精灵表播放速度
frames:[34,35,36,37,38,39,40,41,42,43],
next:"run",
frames:[8,7,6,5,4,3,2,1,0],
next:"die",
this.sprite = new createjs.Sprite(manSpriteSheet , this.state);  //实例化精灵
this.sprite.framerate = FRAME_RATE;      //精灵表绘制速率
this.sprite.setTransform(this.x, this.y, SCALE_X, SCALE_Y);  //设置精灵的位置
stage.addChild(this.sprite);    //添加到舞台
update:function(){
var sprite = this.
var time = createjs.Ticker.getInterval()/1000;    //获取当前帧与上一帧的时间间隔
if(this.state==="run"){          
if(sprite.x&this.x){
sprite.x +=this.
sprite.x = this.x
if(this.endy&sprite.y||this.state==="jump"){  //角色的动作处理
var nexty = sprite.y+time*this.vy*PROPORTION;
this.vy += time*GRAVITY;
sprite.y += time*this.vy*PROPORTION;
if(Math.abs(sprite.y-this.endy)&10&&this.vy&0){
this.state = "run";
sprite.y=this.
this.vy = 0;
if(sprite.x+(PICWIDTH*SCALE_X-WIDTH)/2&0||sprite.y&C_H+200){
this.die();
createjs.Ticker.reset();
alert("you are Die!");
switch(this.state){
case "run":
this.jumpNum = 0;
case "die":
if(sprite.currentFrame===0){
sprite.paused = true;
run:function(){
this.sprite.gotoAndPlay("run")
jump:function(){
this.vy = -JUMP_SPEED;
this.state = "jump";
this.sprite.gotoAndPlay("jump");  //让精灵表播放特定的动画
this.jumpNum++;
die:function(){
this.state = "die";
this.sprite.gotoAndPlay("die")
size:function(){
picsize:function(){
w:PICWIDTH,
h:PICHEIGHT
w.createMan = function(x , y , img){
return new Man(x , y , img)
})(window)
人物模块封装就是简单的在createJs的封装之上进行进一步的封装,封装很简单,就是用createJs实例化一个精灵类,再绑定精灵表,上面的代码中也有注释,基本上都说的很明白了。
下面贴出封装的石头以及金币模块,简单说下背景的循环,预先实例化一堆石头和金币,然后移动响应的石头,当石头移动到超出舞台区域时,把他的visible属性置为false,再重新添加一个石头在最后的位置进行新的一次移动。金币也一样。地图数据则是通过预先定义好的字符串来实现。
(function(w){
var SPEED = 4,
COIN_STAY_X = 20,
COIN_STAY_Y = 20,
COIN_STAY_WIDTH = 30,
COIN_STAY_HEIGHT = 30,
COIN_SCALE_X = 0.08,
COIN_SCALE_Y = 0.08;
//地上的石头类
var Stone = function(x,kind,allImage){
this.kind =
this.allImage = allI
this.init();
var sp = Stone.
sp.init=function(){
this.shape = new createjs.Shape();
if(this.kind!=="C"){
this.h = this.allImage[this.kind].
this.w = this.allImage[this.kind].width*2;
this.y = C_H - this.h;
this.shape.graphics.beginBitmapFill(this.allImage[this.kind]).drawRect(0, 0, this.w, this.h);
this.shape.setTransform(this.x, this.y, 1, 1);
this.h = -1000;
this.w = 170;
this.y = C_H - this.h;
this.shape.graphics.beginFill("#000").drawRect(0, 0, this.w, this.h);
this.shape.setTransform(this.x, this.y, 1, 1);
this.shape.visible = false;
this.shape.cache(0 , 0 , this.w , this.h);
stage.addChild(this.shape);
sp.update=function(){
this.shape.x -= SPEED;
var Coin = function(image){
this.sizeX = COIN_SCALE_X;
this.sizeY = COIN_SCALE_Y;
this.isget = false;
this.init = function(){
this.shape = new createjs.Shape();
this.shape.graphics.beginBitmapFill(image).drawRect(0, 0, image.width, image.height);
this.shape.setTransform(0, 0, COIN_SCALE_X, COIN_SCALE_Y);
this.shape.visible = false;
stage.addChild(this.shape);
this.init();
this.update = function(){
if(this.isget){
this.sizeX = this.sizeX + ((COIN_STAY_WIDTH/image.width) - this.sizeX)*0.1;
this.sizeY = this.sizeY + ((COIN_STAY_HEIGHT/image.height) - this.sizeY)*0.1;
this.shape.setTransform(
this.shape.x + (COIN_STAY_X - this.shape.x)*0.1,
this.shape.y + (COIN_STAY_Y - this.shape.y)*0.1,
this.sizeX,
this.sizeY
if(Math.abs(this.shape.x-COIN_STAY_X)&0.5&&Math.abs(this.shape.y-COIN_STAY_Y)&0.5){
this.shape.visible = false;
this.isget = false;
this.sizeX = COIN_SCALE_X;
this.sizeY = COIN_SCALE_Y;
this.shape.setTransform(0,0,this.sizeX,this.sizeY);
this.shape.x -= SPEED;
if(this.shape.x&-image.width*COIN_SCALE_X){
this.shape.visible = false;
this.size = function(){
w:image.width*COIN_SCALE_X,
h:image.height*COIN_SCALE_Y
w.createCoin = function(image){
return new Coin(image)
w.createStone = function(x,kind,allImage){
return new Stone(x,kind,allImage);
})(window)
封装方法跟上面的人物模块封装差不多,不过人物是用精灵类,石头金币则是用形状类了。就是通过位图的绘制,来绘制位图的图片,原理都一样。
最后是舞台逐帧处理的tick方法:
function tick(event){
//舞台逐帧逻辑处理函数
man.update();
kuang.x = man.sprite.x+(man.picsize().w*1.5-man.size().w)/2;
kuang.y = man.sprite.y;
man.ground.length=0;
var cg = stoneHandle();
if(man.ground[0]&&!cg) {
man.ground.sort(function(a,b){return b.h-a.h});
man.endy = man.ground[0].y-man.picsize().h*1.5;
allCoins.forEach(function(cc , index){
if(cc.shape.visible){
Math.abs((kuang.x+man.size().w/2) - (cc.shape.x+cc.size().w/2)) &= (man.size().w+cc.size().w)/2&&
Math.abs((kuang.y+man.size().h/2) - (cc.shape.y+cc.size().h/2)) &= (man.size().h+cc.size().h)/2&&
cc.isget = true;
countCoin.innerHTML = parseInt(countCoin.innerHTML)+1
cc.update();
document.getElementById("showFPS").innerHTML = man.endy
stage.update(event)
在每一帧的处理,就像自己写游戏一样啦,就是把舞台里的所有对象逐个进行逻辑运算,进行相应处理。&
基本上createJs的用法还是相对比较简单并且强大的。比自己去造轮子能省很多功夫。
源码地址:
Views(...) Comments()刚刚接触H5-canvas游戏的开发,写一下这段时间游戏开发的总结
自制的一个h5小游戏:
基本概念:
视频 -- 即是通过无数的静态图片组合起来,通过一定的频率切换而组成的一种视觉动态效果
游戏 -- 同样是无数的静态图片的组合,但不同的是,每一帧生成的图片都是通过游戏内部逻辑进行生成的,如:
  玩家通过鼠标点击一个按钮,游戏内部逻辑会对此事件进行处理,后生成鼠标点击这个行为的图片
  现如今对于游戏的频率,最佳的体验是在一秒60帧,这样的频率,可以让视觉和操作体验上达到最佳
游戏开发:
使用面向对象的开发模式,将游戏中的每个元素设计成类和对象的关系,可以更好的开发游戏 & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & & &&
canvas方法:
window.setTimeout(callback, 1000 / 60) -- 游戏帧数控制,一千毫秒刷新60次,使用setTimeout,等运行的方法结束后再计时.
isPointInPath(x,y)-- 判断当前坐标是否在当前路径中,x,y为目标坐标,(注:canvas使用beginPath()、closePath开启路径和关闭路径,此方法判断的时候是以最近的路径作为区域判断)
    与html直接监听标签的各种事件不一样,canvas的监听是通过此方法结合事件监听判断坐标的方式确定当前坐标是否在目标区域&
arc(圆心横坐标,圆心纵坐标,半径,起始角,结束角,方向) -- 画圆弧路径,画圆的写法arc(圆心横坐标,圆心纵坐标,半径,0,2*Math.PI)
rect(x, y, width, height) -- 画矩形路径,x,y为矩形左上角坐标
moveTo(x,y) -- 指定路径的起始坐标
lineTo(x,y) -- 画一条直线路径,x,y为结束坐标,一般以上一个lineTo的结束坐标作为起始坐标,可以使用moveTo指定起始坐标
drawImage(img,sx,sy,swidth,sheight,x,y,width,height) -- 引进图片并显示在画布指定位置上,需new一个image对象,
      var img = new Image();
      img.src="...";
      img.onload = function(){
        canvas.drawImage(img,x,y,width,height);
      }
监听事件:
canvas.addEventListener(1,2,3) &-- &监听画布事件,第一个参数为事件名,第二个参数为调用方法,第三个为指定执行时机,默认false
常用事件--
canvas.addEventListener("click", function(){},false) ;监听鼠标点击 canvas.addEventListener("mousemove", &function(){},false);监听鼠标移动 canvas.addEventListener('keydown', &function(){}, false);监听键盘按键,需在&canvas &tabindex="0"&标签上加tabindex="0"启动,且需聚焦canvas画布focus()
以下是通过做好的一个小游戏整理出来的简单开发框架
先列出目录结构
variable.js--放置全局变量
main.js--主函数运行
gameClass.js--游戏使用类
common.js--公共方法
展示简单开发模版的代码
game.html:
&!DOCTYPE html&
&html lang="en"&
&meta charset="UTF-8"&
&title&H5-Game&/title&
.canvas{position:absolute;left:50%;margin-left:-480px}
&canvas id="bg-canvas" class="canvas" tabindex="0" width="960" height="600" style="position:absolute"&&/canvas&
&canvas id="canvas" class="canvas" tabindex="0" width="960" height="600"&&/canvas&
&script src="js/variable.js"&&/script&
&script src="js/main.js"&&/script&
&script src="js/common.js"&&/script&
&script src="js/gameClass.js"&&/script&
function getMovePosition(ev){
//返回移动坐标
if (ev.layerX || ev.layerX == 0){
moveX = ev.layerX;
moveY = ev.layerY;
}else if (ev.offsetX || ev.offsetX == 0) { // Opera
moveX = ev.offsetX;
moveY = ev.offsetY;
function getClickPosition(ev){
//返回点击坐标
clickChip=1;
if(ev.layerX || ev.layerX == 0){
clickX = ev.layerX;
clickY = ev.layerY;
}else if (ev.offsetX || ev.offsetX == 0) { // Opera
clickX = ev.offsetX;
clickY = ev.offsetY;
function isTrueListener(x,y){
//判断坐标是否当前位置,返回true or false
if(ctx.isPointInPath(x,y)){
return true;
return false;
function getKeyDown(e) {
//返回鼠标按下的键值
keyDown=e.keyCode ? e.keyCode :e.
function requestAnimFrame(callback,element){
//游戏刷新速率
return window.setTimeout(callback, 1000 / 60);
function getXMLHttpRequest(){
//返回适用当前环境request对象,数据交互
return new ActiveXobject("Microsoft.XMLHTTP");
return new ActiveXobject("Msxm12.XMLHTTP");
return new XMLHttpRequest();
variable.js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var bgCanvas = document.getElementById("bg-canvas");
var bgCtx = bgCanvas.getContext("2d");
var clickX;
var clickY;
var moveX;
var moveY;
var keyDown=null;
var canvasWidth = canvas.offsetW
var canvasHeight = canvas.offsetH
document.body.onload=
//启动游戏
function game(){
//初始化元素对象
//游戏函数
function backDraw(){
//背景,静态元素
bgImgae = new Image();
bgImgae.src="bg.jpg";
bgImgae.onload = function(){
bgCtx.drawImage(bgImgae, 0, 0);
function init(){
canvas.addEventListener("click", getClickPosition,false);
//监听鼠标点击
canvas.addEventListener("mousemove", getMovePosition,false);
//监听鼠标移动
canvas.addEventListener('keydown', getKeyDown, false);
//监听键盘
//聚焦canvas画布时触发
canvas.focus();
backDraw();
//初始化静态元素,执行一次
function loops(){
//循环游戏动态元素
stop = requestAnimFrame(loops);
ctx.clearRect(0,0,960,600)
ctx.fillStyle="rgba(0,0,0,0.4)";
ctx.rect(0,0,canvasWidth,canvasHeight);
ctx.fill();
不对之处请指正
个人原创,转载请注明来源
注:小疯纸的yy
阅读(...) 评论()编程入门网-用Delphi + DirectX开发简单RPG游戏
您的位置:&&
用Delphi + DirectX开发简单RPG游戏
文章来源:电脑爱好者 作者:郭磊
 提到 RPG (角色扮演游戏,Role Play Game),在座各位恐怕没有不熟悉的。从古老经典的 DOS 版《仙剑奇侠传》到新潮花哨的《轩辕剑》系列,无不以曲折优美的故事情节,美丽可人的主角,悦耳动情的背景音乐,震撼了每一个玩家的心灵。而说到 RPG,就不能不提 DirectX,因为 PC 上大部分的 RPG 都是用这个冬冬开发的。早在《轩辕剑叁外传――天之痕》推出的时候,我就曾想过用 DirectX 写一个自己的 RPG,自己来安排故事情节的发展,却总是因为这样或那样的事情,一直没有能够实现这个心愿。在耗费了宝贵的几年青春,搞定了诸如考试、恋爱、出国等琐碎杂事之后,我终于可以在这个 SARS 肆虐的时代,坐在陪伴了我整个大学生涯的电脑前,听着颓废而又声嘶力竭的不知名歌曲,写一些一直想写却没有写的东西。
& DirectX 简介
   DirectX 对于大多数游戏爱好者来说都不陌生(当然,那些只在DOS下艰苦作战的朋友例外),在安装一个游戏前,系统总是会提示你是否需要同时升级 DirectX。简单地说,DirectX 就是一系列的 DLL (动态连接库),通过这些 DLL,开发者可以在无视于设备差异的情况下访问底层的硬件。DirectX 封装了一些 COM(Component Object Model)对象,这些 COM 对象为访问系统硬件提供了一个主要的接口。首先,我们先来看一下 DirectX 的结构:
图1:DirectX 基本结构
   DirectX 目前主要由以下七个主要部分组成:
& DirectDraw C 为程序直接访问显存提供接口,同时和其它的Windows应用程序保持兼容。
& Direct3D C 为访问3D加速设备提供接口。
& DirectInput C 为各种输入设备提供接口,比如鼠标,键盘,力反馈游戏手柄和操纵杆等。
& DirectPlay C 为游戏提供网络功能接口,比如支持通过 TCP/I、IPX 等协议进行游戏中的数据传输。&
& DirectSound C 为访问声卡提供接口,支持WAV、MIDI 等文件的直接播放。&
& DirectSound3D C通过此接口,可以模拟出某一个声音在三维空间中任何一个位置的播放所产生的效果,从而达到逼真的环绕立体声。&
& DirectMusic C 此接口主要是生成一系列的原始声音采样反馈给相应的用户事件。&
& 开发工具(Delphi & DelphiX)
   下一步,我们来介绍开发工具。我们通常所安装的其实只有 DirectX 的运行库(一系列封装好的DLL文件),其内部函数结构非常复杂,所以我们还需要 DirectX 的开发工具。所谓工欲善其事,必先利其器,虽然微软公布了 DirectX SDK,但是由于所有的头文件都是用 C/C++ 写成的,作为 Delphi 的热情拥护者,我们还是无从下手。把 C/C++ 写成的代码转换成 Pascal 可不是一件容易的事,但是不必担心,这项工作已经有人做好了。日本人 Hiroyuki Hori 为 Delphi 写了一个免费的组件包,称作 DelphiX。这些组件可以使得开发者可以轻松地访问 DirectX 的 DirectDraw、Direct3D、DirectSound、DirectInput(支持力反馈手柄)和 DirectPlay 对象。目前的 DelphiX 包支持 Borland Delphi 3/4/5/6/7 和 DirectX 7.0 以上版本(见图2)。安装了 DelphiX 之后,我们将不需要再安装微软的 DirectX SDK。在这篇文章里我们将使用的就是 DelphiX。
& TDXDraw DirectDraw 和 Direct3D 组件&
& TDXDIB 容纳DIB(设备无关位图,Device Independent Bitmap)的组件&
& TDXImageList 图片列表组件&
& TDX3D Direct3D 组件 (和TDXDraw一起使用)&
& TDXSound DirectSound 组件&
& TDXWave 容纳 Wave(波形音频文件)的组件&
& TDXWaveList Wave文件列表组件&
& TDXInput 输入组件,包括键盘和手柄输入&
& TDXPlay 通讯组件,用于网络游戏开发&
& TDXSpriteEngine 精灵引擎,用于管理游戏中产生的精灵(Sprite)详见游戏开发过程&
& TDXTimer 高速定时器,比 TTimer 要更快,更准确&
& TDXPaintBox TpaintBox 的 DIB 版本&
& TDXForm 优化过的 TForm,专门用于游戏开发&
& 动画原理
  在介绍动画的原理之前,我们先来介绍几个DirectX的术语。
& 表面(Surfaces)
& 这里我们暂且称作表面吧。在 DirectX 中,显存中被用来存储各种位图和视频缓冲的部分均被称作表面。而当前被显示在屏幕上的表面被称作主表面(primary surface)。在实验动画的过程中,我们通常要预先生成至少一个不显示的表面,称作后台表面(offscreen surface)。在需要产生动画的时候,我们可以直接将后台表面中的内容显示到屏幕上,而将原来主表面翻转到后台而不再显示。这么说可能大家更容易明白,我们大家在小的时候都玩过在书上画小人的游戏吧。在每页书的下方空白处画上一个个小人,每页上的内容逐渐发生变化,然后快速翻动整本书,就可以看到有小人在跳动。我们现在的主表面就是你现在正看到那页书,后面的那些还没有翻到的页面就是后台表面,通过翻动实现了动画效果。在运行一些大型游戏的时候,在显存已经用完的情况下,游戏将自动调用系统内存。目前而言,想要流畅地运行一个 DirectX 游戏,非 3D 类的最好有 4M 以上的显存,3D 类的则显存越大越好。
& 每秒帧数(Frames per Second)
& 通常简称 FPS,用我们刚才举的例子来描述,也就是指我们每秒所翻过的书的页数。当帧数达到24帧每秒时,人眼已经无法分辨,从而认为画面是连续的。电影采用的24帧每秒,而电视采用的是25帧每秒。实际上,在日本的动画制作中有一拍二(12帧每秒)甚至一拍三(8帧每秒)的做法,同样可做出欺骗肉眼的效果,这个我们暂且不作讨论。在游戏中,我们将采用24帧或25帧每秒的速度以达到欺骗肉眼的效果。
& 位图和精灵(Bitmaps and Sprites)
& 精灵是指在游戏中用来实现动画效果的一组图片,这些图片通常采用位图格式,并且有用特定颜色填充的透明区域。如下图,就是一个精灵。
& 图中的用黑色填充部分就是预先所定义的透明区域,在游戏中,我们按照白色线条可以将整个图片分别切开成9块,每块的大小均为80×50。我们先从左向右,然后在从上往下对起进行编号为1~9。如果我们将这九张图片依次循环显示出来,并且设置播放的速度为每秒24张,我们就可以得到一个飞船在旋转的动画,也就是一个飞船精灵。
& 开发过程
   说了许多废话以后,下面我们脱离纸上谈兵,开始正式的开发。在本例中,我们主要实现用鼠标来控制精灵往八个方向行走。所有图片均来自大宇公司《轩辕剑叁――天之痕》,其中精灵采用故事中陈靖仇的形象特此致谢。同时,请各位读者勿把这些图片用于商业用途,否则后果自负。
   打开 Delphi 并新建一个应用程序,依次选中 DelphiX 组件栏的 TDXDraw、TDXImageList、TDXInput、TDXTimer、TDXSpriteEngine 组件,添加到用户区,分别命名为 DXDraw、DXImageList、DXInput、DXTimer、DXSpriteEngine,按照下表设置其各项属性。对于 DXImageList,点击 Object Inspector 中的 Items,在其中加入两张位图(background.bmp和player.bmp),分别命名为 background 和 player,设置 player 的 PatternHeight 和 PatternWidth 均为120象素,设置其 transparentcolor 为粉红色(clFuchsia)。
& 控件 属性 值&
& DXDraw Align alClient&
& Display.Width 800&
& Display.Height 600&
& Display.BitCount 24&
& Options [doAllowReboot, doWaitVBlank, doCenter, doFlip]&
& AutoInitialize True&
& DXTimer Enabled True&
& Interval 0&
& DXInput Mouse.BindInputState True&
& Mouse.Enabled True&
& DXSpriteEngine DXDraw DXDraw
   下面就是全部的源程序,请先在 Delphi 中产生相应事件然后填入代码,最后按下F9运行就可以运行程序了。用鼠标点击你的目的地,陈靖仇就会自动跑到指定地点。尝试一下开发一些简单的游戏吧,用 DelphiX 这把牛刀!所有程序在 Delphi 4.0 + DirectX 8.0 环境下测试通过。本文所需控件可以在 这里 下载。
& Interface
& Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
& StdCtrls, ExtCtrls, Menus, DXClass, DXSprite, DXInput, DXD
& TDirection = (DrUp, DrDown, DrLeft, DrRight, DrUpLeft, DrUpRight, DrDownLeft, DrDownRight);
& {自定义游戏中所用到的方向}
& TPlayerSprite = class (TImageSprite)
& CanMove: B
& protected
& procedure DoMove(MoveCount: Integer);
& procedure MoveTo(MoveCount:I Direction: TDirection);
& procedure DoCollision(Sprite: TS var Done: Boolean);
& TMainForm = class(TDXForm) {此处使用优化过的TDXForm来代替TForm}
& DXTimer: TDXT
& DXDraw: TDXD
& DXSpriteEngine: TDXSpriteE
DXInput: TDXI
& ImageList: TDXImageL
& procedure FormKeyDown(Sender: TO var Key: W Shift: TShiftState);
& procedure DXDrawFinalize(Sender: TObject);
& procedure DXDrawInitialize(Sender: TObject);
& procedure FormCreate(Sender: TObject);
& procedure DXTimerTimer(Sender: TO LagCount: Integer);
& procedure DXDrawMouseDown(Sender: TO Button: TMouseB Shift: TShiftS X, Y: Integer);
& procedure DXDrawMouseMove(Sender: TO Shift: TShiftS X, Y: Integer);
& procedure DXDrawMouseUp(Sender: TO Button: TMouseB Shift: TShiftS X, Y: Integer);
& procedure FormClose(Sender: TO var Action: TCloseAction);
& AnchorX: I
& AnchorY: I {鼠标点击发生的位置}
& MouseX: I
& MouseY: I {鼠标当前位置}
& PlayerSprite: TPlayerS {游戏中我们所用鼠标控制的人物}
& BackSprite: TBackGroundS {游戏的背景图}
& speed=5; {游戏人物向各个方向运动时的动画播放速度}
& MainForm: TMainF
& Steps: I {用于控制切换精灵动画图片的参数}
& implementation
& {$R *.DFM}
& procedure TPlayerSprite.DoCollision(Sprite: TS var Done: Boolean);
& Done:=F {已经侦测到碰撞,不再重复检测碰撞}
& {检测游戏人物是否与其它精灵发生了碰撞,此处可以扩展为对话等情节}
& procedure TPlayerSprite.DoMove(MoveCount: Integer);
& l,r,d,u: B
& absX,absY: I {游戏人物的当前位置与目的地的绝对距离}
& inherited DoMove(MoveCount);
& MoveCount:=Trunc(MoveCount*1.5);
& l:= r:= u:= d:=
& if (Trunc(X)-MainForm.AnchorX&0) then l:=true else r:=
& if (Trunc(Y)-MainForm.AnchorY&0) then u:=true else d:=
absX:=abs(Trunc(X)-MainForm.AnchorX);
& absY:=abs(Trunc(Y)-MainForm.AnchorY);
& if absX&4 then begin l:= r:=
& if absY&4 then begin u:= d:=
& {如果绝对距离已经小于四个象素,则认为已经到达目的地}
& if u and l and not d and not r then MoveTo(MoveCount,DrUpLeft);
& if u and r and not l and not d then MoveTo(MoveCount,DrUpRight);
& if d and l and not r and not u then MoveTo(MoveCount,DrDownLeft);
& if d and r and not u and not l then MoveTo(MoveCount,DrDownRight);
& if d and not l and not r and not u then MoveTo(MoveCount,DrDown);
& if u and not l and not r and not d then MoveTo(MoveCount,DrUp);
& if l and not u and not r and not d then MoveTo(MoveCount,DrLeft);
& if r and not l and not u and not d then MoveTo(MoveCount,DrRight);
& {根据目的地来判断运动的方向,从而播放相应方向运动的动画}
& C {检测碰撞}
& Engine.X := -X+Engine.Width div 2 - Width div 2;
& Engine.Y := -Y+Engine.Height div 2 - Height div 2;
& {移动引擎,从而是游戏人物处于舞台的正中央}
& procedure TMainForm.DXTimerTimer(Sender: TO LagCount: Integer);
& if not DXDraw.CanD
& {检测DXDraw是否可以画,否则退出}
& DXInput.U
& {捕捉各类设备输入,这里我们用来检测鼠标的输入}
& LagCount := 1000 div 60;
& {用来控制整个游戏运行速度的参数}
& DXSpriteEngine.Move(LagCount);
& DXSpriteEngine.D
& DXDraw.Surface.Fill(0);
& {将整个屏幕填充为黑色}
& DXSpriteEngine.D
& with DXDraw.Surface.Canvas do
& brush.style:=
& pen.style:=
& pen.color:=
& Font.Color:=clW
& Font.Size:=10;
& textout(10,10,'Press ESC to Quit');
& textout(100,100,'X: '+IntToStr(AnchorX)+'Y: '+IntToStr(AnchorY));
{鼠标点击的位置经转换后在游戏世界中的坐标}& textout(100,200,'Sprit x:'+IntToStr(Trunc(PlayerSprite.x))+'Y: ' +IntToStr(Trunc(PlayerSprite.y)));& {精灵在游戏世界中的坐标}& textout(100,300,'Relative x:'+IntToStr(AnchorX-Trunc(PlayerSprite.x))+'Y: ' +IntToStr(AnchorY-Trunc&&& (PlayerSprite.y)));& {精灵当前位置与目的地之间的绝对距离}& textout(200,100,'Mouse x:'+IntToStr(MainForm.MouseX)+'Y: ' +IntToStr(MainForm.MouseY));& {鼠标当前位置,相对于窗口左上角,未转换为游戏世界坐标}& R&& {在字母上输出相应参数,用于程序调试}& DXDraw.F& {将内存中的后台表面翻转到当前并且显示}&
& procedure TMainForm.DXDrawFinalize(Sender: TObject);& begin& DXTimer.Enabled := F& {关闭定时器}&
& procedure TMainForm.DXDrawInitialize(Sender: TObject);& begin& DXTimer.Enabled := T& {启动定时器}&
& procedure TMainForm.FormCreate(Sender: TObject);& begin& Steps:=0;& AnchorX:=0;& AnchorY:=0;
& MouseX:=320;& MouseY:=240;& {默认使鼠标处于屏幕的中央}
& ImageList.Items.MakeColorT
& DXDraw.ColorTable := ImageList.Items.ColorT& DXDraw.DefColorTable := ImageList.Items.ColorT& DXDraw.UpdateP& {更新系统调色板}
& BackSprite:=TBackgroundSprite.Create(DXSpriteEngine.Engine);
& with TBackgroundSprite(BackSprite) do
& SetMapSize(1, 1);{设定背景显示样式为1×1}
& Image := ImageList.Items.Find('background'); {载入背景图片}
& Z := -2; {设定背景层次}
& Tile := T {设定背景填充样式为平铺}
& PlayerSprite := TPlayerSprite.Create(DXSpriteEngine.Engine);
& with TPlayerSprite(PlayerSprite) do
& Image := ImageList.Items.Find('player');
& Width := Image.W
& Height := Image.H
& {载入游戏人物}
& procedure TMainForm.FormKeyDown(Sender: TO var Key: W
& Shift: TShiftState);
& {如果按了Esc,则退出}
& if Key=VK_ESCAPE then
& {全屏模式和窗口模式的切换}
& if (ssAlt in Shift) and (Key=VK_RETURN) then
& DXDraw.F
& if doFullScreen in DXDraw.Options then
& RestoreW
& DXDraw.Cursor := crN
& BorderStyle := bsS
& DXDraw.Options := DXDraw.Options - [doFullScreen];
& end else
& DXDraw.Cursor := crN
& BorderStyle := bsN
& DXDraw.Options := DXDraw.Options + [doFullScreen];
& DXDraw.I
& procedure TMainForm.DXDrawMouseDown(Sender: TO Button: TMouseB
& Shift: TShiftS X, Y: Integer);
& AnchorX := x + Trunc(PlayerSprite.x)-320;
& AnchorY := y + Trunc(PlayerSprite.y)-240;
& {将鼠标在屏幕上点击的位置转换到游戏世界中}
& PlayerSprite.CanMove:=T
& {此参数允许鼠标拖动}
& procedure TMainForm.DXDrawMouseMove(Sender: TO Shift: TShiftS X,
& Y: Integer);
& if PlayerSprite.CanMove then
& AnchorX := x + Trunc(PlayerSprite.x)-320;
& AnchorY := y + Trunc(PlayerSprite.y)-240;
& {在鼠标拖动过程中将鼠标在屏幕上点击的位置转换到游戏世界中}
& MouseX:=X;
& MouseY:=Y;
& {鼠标当前位置}
& procedure TMainForm.DXDrawMouseUp(Sender: TO Button: TMouseB
& Shift: TShiftS X, Y: Integer);
& PlayerSprite.CanMove:=F
& procedure TMainForm.FormClose(Sender: TO var Action: TCloseAction);
& DXSpriteEngine.F
& procedure TPlayerSprite.MoveTo(MoveCount: I Direction: TDirection);
& {控制精灵往各个方向移动}
& case Direction of
& Y := Y-(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+20+1;
& {当前动画中播放的图片序号}
& if steps&4*speed-2 then steps:=0;
& Y := Y+(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+1;
& if steps& 4*speed-2 then steps:=0;
& X := X-(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+10+1;
& if steps&4*speed-2 then steps:=0;
& DrRight:
& X := X+(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+30+1;
& if steps&4*speed-2 then steps:=0;
& DrUpLeft:
& X := X-(150/1000)*MoveC
& Y := Y-(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+15+1;
& if steps&4*speed-2 then steps:=0;
& DrUpRight:
& X := X+(150/1000)*MoveC
& Y := Y-(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+25+1;
& if steps&4*speed-2 then steps:=0;
& DrDownLeft:
& X := X-(150/1000)*MoveC
& Y := Y+(150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+5+1;
& if steps&4*speed-2 then steps:=0;
& DrDownRight:
& X := X + (150/1000)*MoveC
& Y := Y + (150/1000)*MoveC
& Inc(steps);
& AnimPos:=steps div speed+35+1;
& if steps&4*speed-2 then steps:=0;
   通过以上的讲解和例子,相信大家已经对 Delphi 下的 DirectX 游戏开发有了初步的概念。国内讲解开发 DirectX 游戏的权威资料很少,如果这篇讲解可以起到抛砖引玉的效果的话,我将再高兴不过了。本文部分内容借鉴了国外一些游戏开发网站的技术文档,在此一并致谢。如果各位有什么问题需要和我交流,请发信至eagle_。
Copyright&
Powered by 编程入门网 All Rights Reserved. |

我要回帖

更多关于 canvas 游戏开发 的文章

 

随机推荐