從零開始開發一款H5小遊戲(三) 攻守陣營,賦予粒子新的生命

本系列文章對應遊戲代碼已開源 Sinuous gamejavascript

每一個遊戲都會包含場景和角色。要實現一個遊戲角色,就要清楚角色在場景中的位置,以及它的運動規律,並能經過數學表達式表現出來。java

場景座標

canvas 2d的場景座標系採用平面笛卡爾座標系統,左上角爲原點(0,0),向右爲x軸正方向,向下爲y軸正方向,座標系統的1個單位至關於屏幕的1個像素。這對咱們進行角色定位相當重要。git

roadmap.path

Enemy粒子

遊戲中的敵人爲無數的紅色粒子,往同一個方向作勻速運動,每一個粒子具備不一樣的大小。github

入口處經過一個循環來建立Enemy粒子,隨機生成粒子的位置x, y。並保證每一個粒子都位於上圖座標系所在象限中。因爲 map.width <= x <= 2 * map.width,因此粒子最開始是看不到的。canvas

//index.js
function createEnemy(numEnemy) {
    enemys = [];
    for (let i = 0; i < numEnemy; i++) {
        const x = Math.random() * map.width + map.width;
        const y = Math.random() * map.height;
        enemys.push(new Enemy({x, y}));
    }
}

接下來只要在update中給粒子一個位移偏量speed,粒子就會作勻速運動。speed越大,速度越快。segmentfault

update() {
    this.x -= this.speed; //speed爲位移偏量
    this.y += this.speed;
}

因爲紅色粒子看起來是無窮無盡的,而咱們只是建立了有限個粒子,因此須要在粒子離開視界的時候重置粒子的位置。視界以外的位置開始運動,並保證該位置的隨機性。數組

//Enemy.js
update() {
    this.x -= this.speed; //speed爲位移偏量
    this.y += this.speed;
    
    //粒子從左邊離開視界
    if (this.x < -10) {
        this.x = map.width + 10 + Math.random() * 30;
    }
    //粒子從底部離開視界
    if (this.y > map.height + 10) {
        this.y = -10 + Math.random() * -30;
    }
}

能夠用一張圖來直觀地表示Enemy粒子的運動過程dom

roadmap.path

Player粒子

玩家粒子則由鼠標控制,在上一節中咱們已經簡單介紹了遊戲中的鼠標交互。this

而在手機上的實現還略有差異。手機上的作法是監聽手指的位移量並讓Player粒子作偏移。而不是每次touch都重置粒子的位置,這樣體驗就會好不少。spa

//Player.js
if (isMobile) {
    self.moveTo(self.x, self.y);
    window.addEventListener('touchstart', e => {
        e.preventDefault();
        self.touchStartX = e.touches[0].pageX;
        self.touchStartY = e.touches[0].pageY;
    });
    //手機上用位移計算位置
    window.addEventListener('touchmove', e => {
        e.preventDefault();
        let moveX = e.touches[0].pageX - self.touchStartX;
        let moveY = e.touches[0].pageY - self.touchStartY;
        self.moveTo(self.x + moveX, self.y + moveY);
        self.touchStartX = e.touches[0].pageX;
        self.touchStartY = e.touches[0].pageY;
    });
} else {
    let left = (document.getElementById("game").clientWidth - 
            document.getElementById("world").clientWidth)/2;
    window.addEventListener('mousemove', (e = window.event) => {
        self.moveTo(e.clientX - left - 10, e.clientY - 30);
    });
}

Player 粒子值得一講的就是它飄逸的尾巴。在通過反覆嘗試了屢次後才實現這個效果。

首先想到要讓尾巴長度固定,那麼在每次render的時候,都在尾部渲染固定數量的粒子。那粒子的位置怎麼判斷呢?
在每次render的時候,咱們往數組添加一個粒子,記錄此時的Player座標,當數組達到必定長度時,刪除尾部粒子,添加新粒子。這樣尾巴就記錄了Player一個短期內的各個時間點位置。看起來就像是"跟隨"在Player粒子後面了。

//Player.js
render() {
    self.recordTail();
}

recordTail() {
    let self = this;
    //保持尾巴粒子個數不變
    if (self.tail.length > self.tailLen) {
        self.tail.splice(0, self.tail.length - self.tailLen);
    }
    self.tail.push({
        x: self.x,
        y: self.y
    });
}

這樣只是記錄了一些尾巴上點的位置,咱們須要把各個點連起來。這裏須要用到lineTo方法。

具體代碼實現:

//Player.js
renderTail() {
    let self = this;
    let tails = self.tail, prevPot, nextPot;
    map.ctx.beginPath();
    map.ctx.lineWidth = 2;
    map.ctx.strokeStyle = self.color;

    for(let i = 0; i < tails.length - 1; i++) {
        prevPot = tails[i];
        nextPot = tails[i + 1];
        if (i === 0) {
            map.ctx.moveTo(prevPot.x, prevPot.y);
        } else {
            map.ctx.lineTo(nextPot.x, nextPot.y);
        }

        //保持尾巴最小長度,並有波浪效果
        prevPot.x -= 1.5;
        prevPot.y += 1.5;
    }

    map.ctx.stroke();
    
    self.renderLife();
}

若是隻是鏈接各點,那隻能畫出Player劃過的軌跡,咱們還要給尾巴加上慣性效果,注意到上面有這兩行代碼

prevPot.x -= 1.5;
prevPot.y += 1.5;

每一次render中,讓尾巴中的每一個點x-1.5, y-1.5。實際上就是讓粒子沿着左下方的方向運動,這跟Enemy粒子的方向是一致的。實現了尾巴慣性擺動的效果。

接下來就是添加尾巴上的生命點,這個就比較簡單,只需在尾巴上間隔的某些點,畫出圓形就能夠了

//Player.js
//渲染生命值節點
renderLife() {
    let self = this;
    for(let j = 1; j <= self.livesPoint.length; j++) {
        let tailIndex = j * 5;
        let life = self.livesPoint[j - 1];
        life.render(self.tail[tailIndex]);
    }
}

//Life.js
render(pos) {
    let self = this;
    
    //粒子撞擊後不渲染
    if (!this.dead) {
        map.ctx.beginPath();
        map.ctx.fillStyle = self.color;
        map.ctx.arc(pos.x, pos.y, 3, 0, 2 * Math.PI, false);
        map.ctx.fill();
    }
}

Skill粒子

Skill粒子實際上能夠看作是Enemy中的一種特殊粒子,具備和Enemy同樣的運動規律。代碼中的Skill也是繼承自Enemy的(這有點奇怪..)

Skill粒子具備不一樣的屬性和顏色,實現起來也很簡單。

//Skill.js
const COLORS = {
    shield: '#007766',
    gravity: '#225599',
    time: '#665599',
    minimize: '#acac00',
    life: '#009955'
};
const TEXTS = {
    shield: '盾',
    gravity: '力',
    time: '慢',
    minimize: '小',
    life: '命'
};

render() {
    var self = this;

    map.ctx.beginPath();

    self.color = COLORS[self.type];

    map.ctx.fillStyle = self.color;
    map.ctx.arc(self.x, self.y, self.radius, 0, Math.PI*2, false);
    map.ctx.fill(); 
}

到此遊戲中的角色都介紹完了,下一節要講的是 《從零開始開發一款H5小遊戲(四) 撞擊吧粒子-炫酷技能的實現》

相關文章
相關標籤/搜索