补充昨天忘记下的结论:不管要绘制的图案多大,都建议画(储存)在一个和原图一样大的canvas上,取代原本的图案,当作未来的绘制来源。
let leafImg = new Array();
let pngImg = new Array();
for(let N = 0; N < 4; N++){ // 准备了四种树叶素材
leafImg[N] = new Image();
// 等待每一张图片读取好
leafImg[N].onload = () => {
// 每张图片创建一个对应的画布
pngImg[N] = document.createElement('canvas');
pngImg[N].width = leafImg[N].width;
pngImg[N].height = leafImg[N].height;
let ctx = pngImg[N].getContext("2d");
// 画一次就可以了,以後就拿pngImg[N]来当图片(N为0~3之间)
ctx.drawImage(leafImg[N], 0, 0, leafImg[N].width, leafImg[N].height);
}
}
// 依序设置图片的src(略)
也就是把昨天我们设定大小为30x30的那些画布,改成和图片相同的大小,即使如此,仍然可以减轻绘图的负担,实测都是100x100px的情况下:
相当推荐大家使用这个方法,先把要用到的图片转存一次!
因为只是要做这个游戏的原型,先有个主体就好,就先不设定背景,一般游戏档案载入都会有一定时间,那麽我们就先假装我们正在载入,设计一下这个页面吧!
// 获取开始按钮的宽度
let Start = document.querySelector("#Start");
let Start_CSS = window.getComputedStyle(Start);
let minWidth = Math.floor(Start_CSS.width.substring(0, Start_CSS.width.lastIndexOf("p")));
// 动画框架
requestAnimationFrame(LoadingScreen);
function LoadingScreen(){
try{
Resize("#game-box", canvas, context, '#000');
clear(context);
// 1. 让树长出来
treeGrowth.NextFrame(1, -1, 3);
myTree.Transform();
myTree.Draw();
// 2. 读取进度条
if(loading.timer > 0){
loading.NextFrame(1, 0, 2);
let percent = Math.floor(loading.pointX * 100);
Start.style.width = minWidth + percent + "px";
Start.textContent = percent + "%";
}
else Start.textContent = "Start";
}catch(e){
console.log(e);
return;
}
requestAnimationFrame(LoadingScreen);
}
今天时间有点赶可能没办法一一解释,主要是把之前的技术整合起来,分别说明两大项:
第二章所设计的缓冲函式,稍微改了个名字和微调,在建构式中先设定起始点,再用NewTarget方法来设定终点,同时也把Restore方法的定义写在当中,便可用来持续重置动画:
let Trail = function(x = 0, y = 0, visibility = false){
this.pointX = x;
this.pointY = y;
this.originX = x;
this.originY = y;
this.period = 1;
this.timer = 0;
this.timestamp = Date.now();
this.NewTarget = function(targetX = 1, targetY = 1, frames = 60){
this.targetX = targetX;
this.targetY = targetY;
this.originX = this.pointX;
this.originY = this.pointY;
this.timer = frames;
this.period = frames;
this.Restore = function(){
this.pointX = x;
this.pointY = y;
this.originX = x;
this.originY = y;
this.timer = frames;
this.period = frames;
};
};
// 以下略
// NextFrame方法
}
把Restore包在NewTarget,是因为在输入参数时,比如说frames = 90,会把Restore中的this.timer设定成frames当下的值,也就是说无须另外设定一个变数存取该值。
举实际例子,接下来我会分别为树的成长和画面读取写入:
let treeGrowth = new Trail(0, 0, false);
let loading = new Trail(0, 0, false);
treeGrowth.NewTarget(1, 0, 90);
loading.NewTarget(1, 0, 90);
我们只需要一个变数X,由0到1,而Y皆设为0,两个动画侦数设为90
当new Traail(x, y, visibility)
设置为(0, 0, false)
,Restore函式就会变成:
this.Restore = function(){
this.pointX = 0;
this.pointY = 0;
this.originX = 0;
this.originY = 0;
this.timer = frames;
this.period = frames;
};
当NewTarget(targetX, targetY, frames)
设置为(1, 0, 90)
,Restore函式就会变成:
this.Restore = function(){
this.pointX = 0;
this.pointY = 0;
this.originX = 0;
this.originY = 0;
this.timer = 90;
this.period = 90;
};
那麽让树长出来应该就不难理解了,把昨天的Transform方法中grow变数的代码处修改成:
Stick.prototype.grow = treeGrowth.pointX;
就可以顺利使用一开始贴上的代码:
// 1. 让树长出来
treeGrowth.NextFrame(1, -1, 3);
myTree.Transform();
myTree.Draw();
加上事件监听,就可以利用Restore让玩家在开头画面有些许的互动:「点击画面任一处」重新长出一颗新的树。
let myTree = new Tree(WIDTH/2, 0.8 * HEIGHT, HEIGHT/6, 90, maxTimes)
let startScreen = document.querySelector("#startScreen");
startScreen.addEventListener('click', () =>{
treeGrowth.Restore();
myTree = new Tree(WIDTH/2, 0.8 * HEIGHT, HEIGHT/6, 90, maxTimes)
});
在HTML设计一个ID为Start的按钮後,内文设"0%",上面我们在制作树的时候,把树根的Y座标设置在高度的80%,同样CSS就是设计水平置中垂直80%,来跟树相接:
<div id="startScreen">
<button id="Start">0%</button>
</div>
接着用window.getComputedStyle
方法取得目前该按钮的宽度,做字串处理来取得数值的部分:
// 获取开始按钮的宽度
let Start = document.querySelector("#Start");
let Start_CSS = window.getComputedStyle(Start);
let minWidth = Math.floor(Start_CSS.width.substring(0, Start_CSS.width.lastIndexOf("p")));
接着在动画框架中就可以利用缓冲函式loading的X座标映射到百分比percent,然後使按钮的宽度逐次增加,直到动画结束(也是loading.timer == 0
为True的时候),就把该按钮的内文改成Start。
// 2. 读取进度条
if(loading.timer > 0){
loading.NextFrame(1, 0, 2);
let percent = Math.floor(loading.pointX * 100);
Start.style.width = minWidth + percent + "px";
Start.textContent = percent + "%";
}
else if(Start.disabled == true){ // 增设一个条件,避免重复设定Dom的属性
Start.textContent = "Start";
Start.disabled = false; // 让按钮从disabled状态回到可使用状态
}
最後别忘了在玩家点击开始按钮後,要隐藏开始按钮并且使该出现的东西出现,比如这边我们在CSS替header隐藏了起来,那就要记得把display改回block,并且用cancelAnimationFrame
方法取消读取画面的动画:
Start.addEventListener("click", function(){
Start.style.display = "none";
let header = document.getElementsByTagName("header")[0];
header.style.display = "block";
cancelAnimationFrame(loadingAnime);
});
这边我们用loadingAnime变数来指向动画,才符合该方法的格式,那loadingAnime指向的动画是谁呢?让我们修改原本的动画框架:
let loadingAnime = requestAnimationFrame(LoadingScreen);
function LoadingScreen(){
// ......
// 以上略
loadingAnime = requestAnimationFrame(LoadingScreen);
}
就是requestAnimationFrame方法所回传的值,暂存在loadingAnime里面,拿去console查看会发现,他实际上只是一个数字、一个表示动画的编号,似乎是从0开始累加,很可能表示了一连串的阵列
还记得参数(a, b, c)
吗?这次有稍作修改,作为可输入的三个参数,若无输入的情况,同样能使用input作为预设值,刚刚树的成长和进度条读取分别用了不同的参数,不过frames皆设为90,因此视觉上仍然能保有一致性:
let Trail = function(x = 0, y = 0, visibility = false){
// ......
// 以上略
this.NextFrame = function(a=input.linear, b=input.easein, c=input.easeout){
if(this.timer > 0){
let dX = this.targetX - this.originX;
let dY = this.targetY - this.originY;
let t = this.timer;
let p = this.period;
let linear = 1/p;
let easeout = Math.pow(t/p, 2) - Math.pow((t-1)/p, 2);
let easein = Math.pow(1 - (t-1)/p, 2) - Math.pow(1 - t/p, 2);
this.pointX+= (a * linear + b * easein + c * easeout) / (a+b+c) * dX;
this.pointY+= (a * linear + b * easein + c * easeout) / (a+b+c) * dY;
}
this.timer--;
if(visibility){
let width = WIDTH * 0.05;
let height = WIDTH * 0.05 * mouseImg.height / mouseImg.width;
context.save();
context.translate(this.pointX, this.pointY);
context.drawImage(mouseImg, -width/2, -height/2, width, height);
context.restore();
}
};
}
唯一的不同处在於多了一个专为滑鼠设计的visibility,在作滑鼠追踪时,不管是映射真实座标或是虚拟座标都很好用,上一个章节的爱心鼠标就是把这个设为true来绘制真实座标的
接下来两天拚一下,等游戏完成在来个大一点的demo吧!
Spreadsheet(电子试算表) Service API 可以让你完整的控制 Google s...
一、什麽是演算法 ( Algorithm ) ? 演算法是一组 step by step 用来解决问...
故事是这样的 ... 有个专案需要在执行过程中输入某些文字, 但不能使用按键精灵之类的软件去使用. ...
目前为止Glue的三个工具,可以依使用者的开发习惯与技术背景来选用,而AWS是以客户为导向的公司,对...
前言 目标:串接虾皮订单、标签资讯,目前串接虾皮 OpenAPI 2.0 版本,串接手册 串接步骤:...