html5移动游戏开发高级编程chapter1-2笔记

SpriteSheet

提供精灵图的加载和底层绘制

//精灵类,load初化图片,draw负责进行绘制
var SpriteSheet = new function() {
    this.map = {};
    this.load = function(spriteData, callback) {
        this.map = spriteData;
        this.image = new Image();
        this.image.onload = callback;
        this.image.src = 'sprites.png';
    };
    this.draw = function(ctx, sprite, x, y, frame) {
        var s = this.map[sprite];
        if (!frame) frame = 0;
        ctx.drawImage(this.image, s.sx + frame * s.w, s.sy, s.w, s.h, x, y, s.w, s.h);
    }
}

Sprite

游戏中活动对象父类,setup方法相当于构造函数,merge可以用来初始化参数,hit为碰撞事件,draw为通用顶层绘制

var Sprite = function () {}
Sprite.prototype.setup = function (sprite, props) {
    this.sprite = sprite;
    this.merge(props);
    this.frame = this.frame || 0;
    this.w = SpriteSheet.map[sprite].w;
    this.h = SpriteSheet.map[sprite].h;
}

Sprite.prototype.merge = function (props) {
    if (props) {
        for (var prop in props) {
            this[prop] = props[prop];
        }
    }
}

Sprite.prototype.hit = function() {
    this.board.remove(this);
}

Sprite.prototype.draw = function (ctx) {
    SpriteSheet.draw(ctx, this.sprite, this.x, this.y, this.frame);
}

Game

游戏类, initialize,融合canvas属性,setupInput检测输入,loop对Game中的boards组件进行循环运行step和draw方法,initialize中初始了loop循环,其中调用step和draw方法,setBoard用来加入板块

//游戏类, initialize,融合canvas属性,setupInput检测输入,loop对Game中的boards组件进行循环运行step和draw方法
var Game = new function() {
    this.initialize = function(canvasElementId, sprite_data, callback) {
        this.canvas = document.getElementById(canvasElementId);
        this.width =this.canvas.width;
        this.height = this.canvas.height;
        this.ctx = this.canvas.getContext && this.canvas.getContext('2d');
        if (!this.ctx) {return alert('请升级浏览器');}
        this.setupInput();
        this.loop();
        SpriteSheet.load(sprite_data, callback);
    }   
    var KEY_CODES = {37:'left', 39:'right', 32:'fire'};
    this.keys = {};
    this.setupInput = function(){
        window.addEventListener('keydown', function(e){
            if (KEY_CODES[e.keyCode]) {
                Game.keys[KEY_CODES[e.keyCode]] = true;
                e.preventDefault();
            }
        },false);
        window.addEventListener('keyup', function(e){
            if (KEY_CODES[e.keyCode]) {
                Game.keys[KEY_CODES[e.keyCode]] = false;
                e.preventDefault();
            }
        },false); 
    };
    var boards = [];
    this.loop = function(){
        var dt = 30/1000;
        for (var i = 0;i < boards.length; i ++) {
            if (boards[i]) {
                boards[i].step(dt);
                boards[i] && boards[i].draw(Game.ctx);
            }
        }
        setTimeout(Game.loop, 30);
    };
    this.setBoard = function(num, board) {
        boards[num] = board;
    }
}

Starfield

星空类,组件式,用一个canvas在作为背景来绘制,板块之一

var Starfield = function(speed, opacity, numStarts, clear){
    var stars = document.createElement("canvas");
    stars.width = Game.width;
    stars.height = Game.height;
    var starCtx = stars.getContext('2d');
    var offset = 0;
    //绘制黑色背景
    if (clear) {
        starCtx.fillStyle = "#000";
        starCtx.fillRect(0,0, stars.width, stars.height);
    }

    starCtx.fillStyle = "#FFF";
    starCtx.globalAlpha = opacity;

    for (var i = 0; i < numStarts; i ++) {
        starCtx.fillRect(Math.floor(Math.random()*stars.width), Math.floor(Math.random()*stars.height), 2, 2);
    }

    this.draw = function(ctx){
        var intOffset = Math.floor(offset);
        var remaining = stars.height - intOffset;
        if (intOffset > 0) {
            ctx.drawImage(stars,0, remaining, stars.width, intOffset, 0, 0, stars.width, intOffset);
        }
        if (remaining > 0 ) {
            ctx.drawImage(stars, 0, 0, stars.width, remaining, 0, intOffset, stars.width, remaining);
        }
    }
    //console.log(offset)
    this.step = function(dt) {
        offset += dt * speed;
        //console.log(dt)
        offset = offset % stars.height;
    }
}

TitleScreen

标题板块

var TitleScreen = function (title, subtitle, callback) {
    this.step = function (dt) {
        if (Game.keys['fire'] && callback) callback();
    };

    this.draw = function (ctx) {
        ctx.fillStyle =  "#fff";
        ctx.textAlign = "center"
        ctx.font = "bold 40px bangers";
        ctx.fillText(title, Game.width/2, Game.height/2);
        ctx.font = "bold 20px bangers";
        ctx.fillText(subtitle, Game.width/2, Game.height/2 + 40);
    }
}

GameBoard

add,remove,resetRemoved,finalizeRemoved增,删(模拟),重置,销毁(实际),collide碰撞检测,overlap检测,detect方法遍历方法,但是会在函数返回真时停止,iterate方法全部遍历,GameBoard为一个大板块,然后管理着其它的活动对象

PlayerShip

游戏人对象,继承Sprite

var PlayerShip = function() {
    this.setup('ship', {vx: 0, frame: 0, reloadTime: 0.25, maxVel: 200});
    // this.w = SpriteSheet.map['ship'].w;
    // this.h = SpriteSheet.map['ship'].h;
    this.x = Game.width / 2 - this.w / 2;
    this.y = Game.height - 10 - this.h;
    // this.vx = 0;
    //重装弹间隔
    // this.reloadTime = 0.1;
    this.reload = this.reloadTime;
    // this.maxVel = 200;
    this.step = function(dt) {
        if (Game.keys['left']) {this.vx = -this.maxVel; }
        else if (Game.keys['right']) {this.vx = this.maxVel;}
        else { this.vx = 0;}

        this.x += this.vx * dt;
        if (this.x < 0) {this.x = 0;}
        else if (this.x > Game.width - this.w) {
            this.x = Game.width - this.w;
        }
        this.reload -= dt;
        //在发射时检测是否到达冷却时间
        if (Game.keys['fire'] && this.reload < 0) {
            Game.keys['fire'] = false;
            this.reload = this.reloadTime;
            this.board.add(new PlayerMissile(this.x, this.y + this.h / 2));
            this.board.add(new PlayerMissile(this.x + this.w, this.y + this.h / 2));
        }
    }
    // this.draw = function(ctx) {
    //  SpriteSheet.draw(ctx, 'ship', this.x, this.y, 0);
    // }
}

PlayerShip.prototype = new Sprite();
PlayerShip.prototype.type = OBJECT_PLAYER;
PlayerShip.prototype.hit = function() {
    this.board.remove(this);
    this.board.add(new Explosion(this.x +this.w/2 , this.y + this.h/2,loseGame))
;}

PlayerMissile

游戏人炮弹类

var PlayerMissile = function(x, y) {
    this.setup('missile', {vy: -700, damage: 10})
    // this.w = SpriteSheet.map['missile'].w;
    // this.h = SpriteSheet.map['missile'].h;
    this.x = x - this.w / 2;
    this.y = y -this.h;
    // this.vy = -700;
};

PlayerMissile.prototype = new Sprite();
PlayerMissile.prototype.type = OBJECT_ENEMY_PROJECTILE;

PlayerMissile.prototype.step = function(dt) {
    this.y += this.vy * dt;
    var collision = this.board.collide(this, OBJECT_ENEMY);
    if (collision) {
        collision.hit(this.damage);
        this.board.remove(this);
    } else if (this.y < -this.h) {
        this.board.remove(this);
    }
    if (this.y < - this.h) {
        this.board.remove(this);
    }
};

Enemy

敌人类

var Enemy = function (blueprint, override) {
    this.merge(this.baseParameters);
    this.setup(blueprint.sprite, blueprint);
    this.merge(override);

    var baseParameters = {A: 0, B: 0, C: 0, D: 0, E: 0, F: 0, G: 0, H: 0};
    // for (var prop in baseParameters) {
    //  this[prop] = baseParameters[prop];
    // }

    // for (var prop in blueprint) {
    //  this[prop] = blueprint[prop];
    // }

    // if (override) {
    //  for (prop in override) {
    //      this[prop] = override[prop];
    //  }
    // }
    // console.log(this.sprite)
    // this.w = SpriteSheet.map[this.sprite].w;
    // this.h = SpriteSheet.map[this.sprite].h;
    // this.t = 0;
} 

Enemy.prototype = new Sprite();
Enemy.prototype.type = OBJECT_ENEMY;
Enemy.prototype.baseParameters = {A: 0, B: 0, C: 0, D: 0, E: 0, F: 0, G: 0, H: 0, t: 0};

Enemy.prototype.step = function (dt) {
    this.t += dt;
    this.vx = this.A + this.B * Math.sin(this.C * this.t + this.D);
    this.vy = this.E + this.F * Math.sin(this.G * this.t + this.H);
    this.x += this.vx * dt;
    this.y += this.vy * dt;
    var collision = this.board.collide(this, OBJECT_PLAYER);
    if (collision) {
        collision.hit(this.damage);
        this.hit(20);
    }
    if (this.y > Game.height || this.x < -this.w || this.x > Game.width) {
        this.board.remove(this);
    }
}

Enemy.prototype.hit = function(damage) {
    this.health -= damage;
    if (this.health <= 0) {
        if (this.board.remove(this)) {
            this.board.add(new Explosion(this.x + this.w/2, this.y +this.h/2));
        }
    }
}

Explosion

爆炸类

var Explosion = function (centerX, centerY, callback) {
    this.setup('explosion', {frame: 0});
    this.x = centerX - this.w / 2;
    this.y = centerY - this.h / 2;
    this.subFrame = 0;
    this.callback = callback;
}

Explosion.prototype = new Sprite();

Explosion.prototype.step = function (dt) {
    this.frame = Math.floor((this.subFrame++) / 3);
    if (this.subFrame >= 36) {
        this.board.remove(this);
        if (this.callback) this.callback();
    }
}

GameBoard对象:

//object[],cnt[],removed[]
var GameBoard = function() {
    var board = this;
    this.objects = [];
    this.cnt = [];

    this.add = function(obj){
        obj.board = this;
        this.objects.push(obj);
        this.cnt[obj.type] = (this.cnt[obj.type] || 0) + 1;
        return obj;
    };
    this.remove = function(obj) {
        var wasStillAlive = this.removed.indexOf(obj) == -1;
        //console.log(wasStillAlive)
        if (wasStillAlive) {
            this.removed.push(obj);
        }
        return wasStillAlive;
    }

 
    this.iterate = function(funcName){
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, len = this.objects.length; i < len; i++) {
            var obj = this.objects[i];
            obj[funcName].apply(obj, args);
        }
    };

    this.detect = function(func){
        for (var i= 0, val = null, len = this.objects.length; i < len; i ++) {
            if (func.call(this.objects[i])) return this.objects[i];
        }
        return false;
    }

    this.step = function(dt) {
        //console.log('sdf')
        this.resetRemoved();
        this.iterate('step', dt);
        this.finalizeRemoved();
    }

    this.draw = function(ctx) {
        this.iterate('draw', ctx);
    }

    this.overlap = function(o1, o2) {
        return !((o1.y + o1.h -1 < o2.y) || (o1.y > o2.y + o2.h - 1) || (o1.x + o1.w -1 < o2.x) || (o1.x > o2.x + o2.w -1));
    }

    this.collide = function(obj, type) {
        return this.detect(function(){
            if (obj != this) {
                var col = (!type || this.type & type) && board.overlap(obj, this)
                return col ? this : false;
            }
        });
    };

    this.resetRemoved = function(){this.removed = [];}

    this.finalizeRemoved = function () {

        for (var i = 0, len = this.removed.length; i < len; i ++) {
            var idx = this.objects.indexOf(this.removed[i]);
            if (idx != -1) {
                this.cnt[this.removed[i].type]--;
                this.objects.splice(idx, 1);
            }
        }
    }
}
GameBoard对象的作用

Level

关卡类

var Level = function (levelData, callback) {
    this.levelData = [];
    for (var i = 0; i < levelData.length; i ++) {
        this.levelData.push(Object.create(levelData[i]));
    }
    this.t = 0;
    this.callback = callback;
}

Level.prototype.step = function (dt) {
    //console.log(this.levelData)
    var idx = 0, remove = [], curShip = null;
    this.t += dt * 1000;
    while ((curShip = this.levelData[idx]) && (curShip[0] < this.t + 2000)) {
        console.log(this.t)
        if (this.t > curShip[1]) {
            remove.push(curShip);
        } else if (curShip[0] < this.t) {
            var enemy = enemies[curShip[3]], override = curShip[4];
            this.board.add(new Enemy(enemy, override));
            curShip[0] += curShip[2];
        }
        //console.log(curShip)
        idx++;

    }
    for (var i = 0,len = remove.length; i< len; i++) {
        var idx = this.levelData.indexOf(remove[i]);
        if (idx != -1) this.levelData.splice(idx,1);
    }
    if (this.levelData.length === 0 && this.board.cnt[OBJECT_ENEMY] === 0) {
        if (this.callback) this.callback();
    }
}

Level.prototype.draw = function(ctx) {}

关于js函数中的arguments参数的巧用

arguments参数的巧用

三次法则

三次法则

一个简单高效的碰撞检测

this.collide = function(obj, type) { return this.detect(function(){ if (obj != this) { if ((!type || this.type & type)){ console.log(type,this.type) console.log((!type || this.type & type)) } var col = (!type || this.type & type) && board.overlap(obj, this) return col ? this : false; } }); };
利用按位与过滤和指定不符的对象

延缓动画时长小技巧

Explosion.prototype.step = function (dt) { this.frame = Math.floor(this.subFrame++ / 3); console.log(this.subFrame); if (this.subFrame >= 36) { this.board.remove(this); } }

js深度复制

var Level = function (levelData, callback) { this.levelData = []; for (var i = 0; i < levelData.length; i ++) { this.levelData.push(Object.create(levelData[i])); } this.t = 0; this.callback = callback; }

深度复制

游戏控制函数

var playGame = function () {
    //Game.setBoard(3, new PlayerShip());

    var board = new GameBoard();
    board.add(new PlayerShip());
    //board.add(new Player)
    board.add(new Level(level1, winGame));
    // board.add(new Enemy(ememies.basic));
    // board.add(new Enemy(ememies.basic, {x: 200}));

    Game.setBoard(3, board);
}

var winGame = function() {
    Game.setBoard(3, new TitleScreen('你赢了','点击重新游戏', playGame));
}

var loseGame = function () {
    Game.setBoard(3, new TitleScreen("你输了",'点击重新游戏',playGame))
}

// var ememies = {basic: {x:100, y: -50, sprite: 'enemy_purple',B: 100, C: 2, E: 100, health: 20}};

// var enemies = {
//  straight: {x: 0, y: -50, sprite: 'enemy_ship', health: 10, E: 100},
//  'ltr': {x: 0, y: -100, sprite: 'enemy_purple', health: 10, B: 200, C: 1, E: 200},
//  circle: {x: 400, y:-50, sprite: 'enemy_circle', health: 10, A: 0, B: -200, C: 1, E: 20, F: 200, G: 1, H: Math.PI/2},
//  wiggle: {x: 0, y: -50, sprite: 'enemy_bee', health: 20, B: 100, C: 4, E: 100},
//  step: {x: 0, y:-50, sprite: 'enemy_circle', health: 10, B:300, C: 1.5, E: 60}
// }
var enemies = {
  straight: { x: 0,   y: -50, sprite: 'enemy_ship', health: 10, 
              E: 100 },
  ltr:      { x: 0,   y: -100, sprite: 'enemy_purple', health: 10, 
              B: 75, C: 1, E: 100  },
  circle:   { x: 250,   y: -50, sprite: 'enemy_circle', health: 10, 
              A: 0,  B: -100, C: 1, E: 20, F: 100, G: 1, H: Math.PI/2 },
  wiggle:   { x: 100, y: -50, sprite: 'enemy_bee', health: 20, 
              B: 50, C: 4, E: 100 },
  step:     { x: 0,   y: -50, sprite: 'enemy_circle', health: 10,
              B: 150, C: 1.2, E: 75 }
};



var startGame = function() {
    //SpriteSheet.draw(Game.ctx, "ship", 100, 100, 0);
    Game.setBoard(0, new Starfield(20, 0.4, 100, true));
    Game.setBoard(1, new Starfield(50, 0.6, 100));
    Game.setBoard(2, new Starfield(100, 1, 50));
    Game.setBoard(3, new TitleScreen("打飞机", "单击开始", playGame))
    
}

window.addEventListener("load", function(){
    Game.initialize("game", sprites, startGame);
});

游戏所需数据,配置

var sprites = {
    ship: {sx: 0, sy: 0, w: 37, h: 42, frames: 1},
    missile : {sx: 0, sy: 30, w: 2, h: 10, frames: 1},
    enemy_purple : {sx: 37, sy: 0, w:42, h: 43, frames: 1},
    enemy_bee: {sx: 79, sy: 0, w: 37, h: 43, frames: 1},
    enemy_ship: {sx: 116, sy: 0, w: 42, h: 43, frames: 1},
    enemy_circle: {sx: 158, sy:0, w: 32, h: 33, frames: 1},
    explosion: {sx: 0, sy: 64, w: 64, h: 64, frames: 12}
};
var level1 = [
    [0, 4000, 500, 'step'],
    [6000, 13000, 800, 'ltr'],
    [12000, 16000,400, 'circle'],
    [18200, 20000, 500, 'straight', {x: 150}],
    [18200, 20000, 500, 'straight', {x: 100}],
    [18400, 20000, 500, 'straight', {x: 200}],
    [22000, 25000, 400, 'wiggle', {x: 300}],
    [22000, 25000, 400, 'wiggle', {x: 200}],

]

结果图

Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,054评论 1 10
  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 6,305评论 0 6
  • 个人入门学习用笔记、不过多作为参考依据。如有错误欢迎斧正 目录 简书好像不支持锚点、复制搜索(反正也是写给我自己看...
    kirito_song阅读 2,449评论 1 37
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • js简介 Js是一种基于事件和对象驱动的解释性、松散性的语言。 一切皆对象 javascript 布兰登艾奇 ...
    塔库纳玛哈哈阅读 1,194评论 0 2