1.预备知识
(1)Canvas旋转的实现过程
window.onload = function(){ var ctx = document.getElementById('canvas1').getContext('2d') //旋转 ctx.save() ctx.translate(200,200)//把(200,200)点作为临时的(0,0)点 ctx.rotate(30*Math.PI/180)//顺时针旋转30度所对应的弧度 ctx.fillRect(0,0,100,150) ctx.restore() }
(2)抛物线运动中的重力加速度的模拟实现模型
/* 抛物线运动 这个版本做了修改,为了减少一次角度和弧度之间的转换计算(通过Math.atan2可以直接算出弧度), 构造参数中的角度改成了弧度 deleted: angle = angle, //初始角度 radians = angle*Math.PI/180, //初始弧度 object ParabolicMotion @velocity:初始速度 @radians:初始弧度 @gravity:初始加速度{x:0,y:2} */ function ParabolicMotion(velocity,radians,gravity){ var velocity = velocity, radians = radians, gravity = gravity var offsetX = velocity*Math.cos(radians), offsetY = -velocity*Math.sin(radians) function next(offset,gravity){ var offset1 = offset var offset2 = offset+gravity return {offset:offset2,dv:(offset1+offset2)*.5} } return { /* 获取运动轨迹到下一个时间点,x,y轴所偏移的距离 */ moveNext : function(){ var offsetXD = next(offsetX,gravity.x) var offsetYD = next(offsetY,gravity.y) offsetX = offsetXD.offset offsetY = offsetYD.offset //console.log(offsetX+','+offsetY) return {x:offsetXD.dv,y:offsetYD.dv} } } }
2.实现思路
涉及的对象,包括球(Ball),弹弓(Slingshot),抛物线运动(ParabolicMotion)。
操作过程是,鼠标键按下拖拽小球,和弹弓的作用点成一定的角度,鼠标键按起后,小球做抛物线运动
3.主要代码
/*弹弓*/ function Slingshot(){ var opts, ctx, crtBall, ballSelected = false function refresh(){ ctx.clearRect(0,0,opts.width,opts.height) drawSling() crtBall.draw() } function drawSling(){ var point = opts.actionPoint ctx.beginPath() ctx.moveTo(point.x,point.y) ctx.lineTo(point.x,opts.height) ctx.closePath() ctx.stroke() if(crtBall!=null&&ballSelected){ //绘制连接球和弹弓的"橡皮筋" ctx.beginPath() ctx.moveTo(point.x,point.y) ctx.lineTo(crtBall.x,crtBall.y) ctx.closePath() ctx.stroke() } } function isBallSelected(offsetX,offsetY){ var point = opts.actionPoint var a = Math.abs(point.x-offsetX) var b = Math.abs(point.y-offsetY) //var c = Math.sqrt(a*a+b*b) return (a*a+b*b)<=crtBall.radius*crtBall.radius } // 添加拉弹弓事件 function initEvents(){ var canvas = opts.canvas /* addEventListener函数的第3个参数, false表示内层元素事件先触发,ture则表示外层的事件先触发 alert(e.offsetX+','+e.offsetY) 必须使用offsetX,offsetX是相对于canvas画布的距离(但firefox不支持) 事件参数e没有考虑浏览器兼容问题 */ canvas.addEventListener('mousedown',function(e){ // 判断球是否被选中 ballSelected = isBallSelected(e.offsetX,e.offsetY) if(ballSelected){ crtBall.locate(e.offsetX,e.offsetY) refresh() } },false) canvas.addEventListener('mousemove',function(e){ if(ballSelected){ crtBall.locate(e.offsetX,e.offsetY) refresh() } },false) canvas.addEventListener('mouseup',function(e){ if(ballSelected){ ballSelected = false crtBall.locate(e.offsetX,e.offsetY) refresh() //发射炮弹 fire(function(x,y){ //TODO 判断是否打中目标 }) } },false) } function fire(onCompleted){ // 获取当前的炮弹发射速度和弧度 var velocity = ($.square(opts.actionPoint.y-crtBall.y)+$.square(opts.actionPoint.x-crtBall.x))/100 var radians = -Math.atan2(opts.actionPoint.y-crtBall.y,opts.actionPoint.x-crtBall.x)//30*Math.PI/180 var gravity = {x:0,y:2} var parabolicMotion = new ParabolicMotion(velocity,radians,gravity) var completed = false var timer = setInterval(function(){ // 在当前抛物线轨迹下,获取炮弹下一次单位时间内x,y轴需要偏移的单位长度 var offset = parabolicMotion.moveNext() crtBall.move(offset.x,offset.y) refresh() // 检查是否超出画布边界 completed = (crtBall.x>=opts.width||crtBall.y>=opts.height) if(completed){ clearInterval(timer) if(typeof onCompleted=='function'){ onCompleted(crtBall.x,crtBall.y) } } },100) } return { init : function(options){ opts = $.extend(options,{ canvas : null,//画布 width : 1000,//画布长 height : 300,//画布高 actionPoint : {x:150,y:200}/*作用力点坐标*/ }) ctx = opts.canvas.getContext('2d') drawSling() // 添加拉弹弓事件 initEvents() return this }, loadBall : function(ball){ var point = opts.actionPoint crtBall = ball.init({ctx:ctx,x:point.x,y:point.y}) .draw() return this } } }
// app window.onload = function(){ var canvas = document.getElementById('canvas1') var ball = new Ball(10) var slingshot = new Slingshot().init({canvas:canvas}) // 装载一个炮弹 slingshot.loadBall(ball) }
4.优化和完善
(1)需实现球打中目标物体后,目标物体进行旋转
(2)可以实现同时有多个小球发射,就像游戏里发射子弹的效果