1. Game background introduction (nonsense written in front):
One day in early May, I saw a website recommending this game, Pongo. It looked pretty good, so I downloaded it and tried it on my iPad. After playing two games, I felt that it was quite enjoyable because it was Everyone knows about handheld games.
But after a while, I discovered that the game seemed to have some bugs on the iPad. After playing for a while, it would get stuck and I had to force quit. It was really worrying, and the record was still waiting to be broken.
What to do? The evil thought that playing games is not as good as playing your own games came up again, and then I threw the pad to my friend. I was heartbroken. I silently returned to the computer and started to write my own game that would not freeze.
It took about two hours to write the basic framework, then I tried it in Sina app and the effect was basically playable, then I went to sleep.
When I woke up the next day, I spent some time designing the interface because I had nothing to do on the weekend. Unfortunately, I detected some serious bugs, and finally spent some time correcting them.
In the end, the game was named "Pongo" (click on your mobile phone to play). The computer version is not supported for the time being, so I uploaded the source code on Github and removed the submit score module.
2. Game trial website:
Pongo (mobile version only): http://mypongo.sinaapp.com/
github open source (welcome to fork to make the game better): https://github.com/ChenReason/pongo/blob/gh-pages/index.html
3. Game rules and gameplay:
Clicking on the screen will change the movement direction of the bezel. Clicking once will change the direction of the bezel. The purpose is to just block the rolling ball and prevent it from running out of the big circle. The longer the better! Finally, you can submit your own results for ranking!
4. Technology used in the game:
HTML, CSS, JavaScript, Canvas, PHP
5. Game design ideas:
a) Use Canvas to draw the main interface of the game. The bottom is a single-color rectangle with a large circle covering it. A small circle and a baffle are drawn on the big circle. There is also a super small circle with a size of 1px in the middle of the baffle. (To implement collision detection).
b) There are 8 directions of movement of the small circle, namely up, down, left, right, upper left, lower left, upper right and lower right.
c) There are only two directions of movement of the baffle, clockwise and counterclockwise.
d) Collision detection does not involve the use of the engine, but determines the distance between the small circle and the super small circle in the middle of the baffle, thereby achieving simple collision detection.
e) Determine the direction of the ball’s rebound after collision. Use common sense to list 8 situations in total.
6. Difficulties in game implementation:
a) Collision detection.
b) The timing of clearing the timer setInterval and whether it is clear and thorough.
c) The relationship between the timer period length and the game experience.
d) Game fluency issues caused by different performance between Android and IOS devices.
7. Current problems of the game:
a) Since collision detection compares the distance between the centers of two circles and involves the use of a timer, due to the extremely short timer interval, dozens of collisions have actually occurred behind one collision seen by the naked eye. This will cause the final actual rebound direction of the ball to be different from the actual physical theorem. After optimization, the probability of occurrence is lower, but it is still unavoidable. Therefore, some players will find that if the ball does not hit the barrier accurately, The center of the board may cause the game to fail.
b) Because the function is too verbose, the running efficiency is low, and the timer is used, the gaming experience on Andorid is different from that on iOS or other mobile terminals (on the whole, iOS is better than Android).
c) The rankings are not automatically updated in real time. (I don’t know how to use the database yet)
8. Game interface preview:
(Picture 1 is the first version, Picture 2 has removed the button, Picture 3 is the final version, Picture 4 is the ranking)
Picture 1
Picture 2
Picture 3
9. Part of the game JavaScript source code:
var ifingame=0;
var maxgrade=0,grade=0;
var grade1,grade2;
var nickname;
var gamespeed=1.4;//小球速度
var linespeed=Math.PI/95; //跟踪线速度
var crashdistancefaild=-7;//碰撞检测参数
var crashdistancesucc=15
var fantanjuli=7;
var themaxgradeline=12.1;
function getCookie1(nickname)
{
if (document.cookie.length>0)
{
c_start=document.cookie.indexOf(nickname "=")
if (c_start!=-1)
{
c_start=c_start nickname.length 1;
c_end=document.cookie.indexOf(",",c_start);
if (c_end==-1)
c_end=document.cookie.length;
return unescape(document.cookie.substring(c_start,c_end));
}
}
return ""
}
function getCookie2(mymaxgrade)
{
if (document.cookie.length>0)
{
c_start=document.cookie.indexOf(mymaxgrade "=")
if (c_start!=-1)
{
c_start=c_start mymaxgrade.length 1;
c_end=document.cookie.indexOf(";",c_start);
if (c_end==-1)
c_end=document.cookie.length;
return unescape(document.cookie.substring(c_start,c_end));
}
}
return ""
}
function setCookie(nickname,value,mymaxgrade,maxgrade,expiredays)
{
var exdate=new Date()
exdate.setDate(exdate.getDate() expiredays)
document.cookie=nickname "=" escape(value) "," mymaxgrade "=" escape(maxgrade) ((expiredays==null) ? "" : "; expires=" exdate.toGMTString());
}
function checkCookie()
{
nickname=getCookie1('nickname');
maxgrade=parseInt(getCookie2('mymaxgrade'));
if(isNaN(maxgrade)==true)
{
maxgrade=0;
}
if (nickname!=null && nickname!="")
{
alert('欢迎' nickname '回来!' 'n' "如果喜欢请分享一下哈~");
}
else
{
nickname=prompt('请输入你的昵称:(名字太长上榜可是会显示不完整的哦)',"")
if (nickname!=null && nickname!="")
{
var maxgradestring=maxgrade.toString();
setCookie('nickname',nickname,'mymaxgrade',maxgradestring,365);
}
}
}
var objpane=document.getElementById("pane");
var ctxpane=objpane.getContext("2d");
ctxpane.translate(150,150);//必备 画布中心点平移
function sendmail()
{
if(grade2>themaxgradeline)
var max_grade=grade2;
window.location.href='index.php?max_grade=' max_grade '&nick_name=' nickname;
/* {
$grade=$_GET['max_grade'];
$nickname=$_GET['nick_name'];
$mail = new SaeMail();
$ret = $mail->quickSend( 'reasonpongo@163.com' , $grade , $nickname ,'reasonpongo@163.com' , 'mypongo' );
$mail->clean();
?>
}*/
alert(nickname "你的成绩为:" grade2 "提交成功~");
}
var gamedirection={
shang : 1,
xia : 5,
zuo : 7,
you : 3,
zuoshang: 8,
zuoxia : 6,
youshang: 2,
youxia : 4,
clock : 0,
anticlock: 9,
};//方向
var canvas={
width : 300,
height: 300,
};//Canvas
var bigcircle = {//Big circle parameters
x: 0, //x-axis coordinate value of the center of the circle
y: 0, //y-axis coordinate value of the center of the circle
r: 150, //The radius of the circle
c: 'rgb(255,255,255)',
};//Big circle
var smallcircle = {//Small circle parameter
x: 0, / /The x-axis coordinate value of the center of the circle
y: 0, //The y-axis coordinate value of the center of the circle
r: 12, //The radius of the circle
c: 'rgb(204,105,106)',
direction : gamedirection.xia,
};//Small circle
var line = {//Parameters of the baffle line
x: 0, //The x-axis coordinate value of the center of the circle
y : 0, //The y-axis coordinate value of the center of the circle
r: 150, //The radius of the arc
start:(Math.PI/2-Math.PI/16),
end: (Math .PI/2 Math.PI/16),
c : 'rgb(55,55,55)',
direction: gamedirection.anticlock,
};//Tracing line
var dot = {//Tracking point parameters
x: (bigcircle.r*Math.cos(line.start Math.PI/16)),//Taking the big circle as the origin
y: (bigcircle.r*Math. sin(line.start Math.PI/16)),
r : 1,
}//Tracking point
function changelinedirection()
{
if(line.direction==gamedirection .clock)
{
line.direction=gamedirection.anticlock;
}
else
{
line.direction=gamedirection.clock;
}
}
function getdistance(){
var distance=Math.sqrt((smallcircle.x)*(smallcircle.x ) (smallcircle.y )*(smallcircle.y ));
return distance;
}//Return the square distance between the small ball and the center of the big circle getdistance()
function ifgameover(){//Judge whether it is out of bounds
if((getdistance() - bigcircle.r)>5 )
return true;
else
return false;
} //Determine whether the game is over ifgameover()
function ifcrash(){ //Collision detection
var dx = dot. x-smallcircle.x;
var dy = dot.y-smallcircle.y;
var dd=Math.sqrt(dx*dx dy*dy);
if(dd< crashdistancesucc)
return true;
else
return false;
}//Collision detection ifcrash()
function randomback()
{
var x=Math.floor(Math. random()*3);
switch (smallcircle.direction){
case gamedirection.shang:
{
switch (x)
{
case 0:
smallcircle .direction=gamedirection.xia;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.zuoxia;
smallcircle.x=smallcircle. x-fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.youxia;
smallcirc le.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.xia:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.shang;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.zuoshang;
smallcircle.x=smallcircle.x-fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.youshang;
smallcircle.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.zuo:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.you;
smallcircle.x=smallcircle.x fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.youshang;
smallcircle.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.youxia;
smallcircle.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.you:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.zuo;
smallcircle.x=smallcircle.x-fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.zuoxia;
smallcircle.x=smallcircle.x-fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.zuoshang;
smallcircle.x=smallcircle.x-fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.zuoshang:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.youxia;
smallcircle.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.xia;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.you;
smallcircle.x=smallcircle.x fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.zuoxia:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.youshang;
smallcircle.x=smallcircle.x fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.shang;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.you;
smallcircle.x=smallcircle.x fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.youshang:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.zuoxia;
smallcircle.x=smallcircle.x-fantanjuli;
smallcircle.y=smallcircle.y fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.zuo;
smallcircle.x=smallcircle.x-fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.xia;
smallcircle.y=smallcircle.y fantanjuli;
break;
default:
break;
} break;
}
case gamedirection.youxia:
{
switch (x)
{
case 0:
smallcircle.direction=gamedirection.zuoshang;
smallcircle.x=smallcircle.x-fantanjuli;
smallcircle.y=smallcircle.y-fantanjuli;
break;
case 1:
smallcircle.direction=gamedirection.zuo;
smallcircle.x=smallcircle.x-fantanjuli;
break;
case 2:
smallcircle.direction=gamedirection.shang;
smallcircle.y=smallcircle.y-fantanjuli;
break;
default:
break;
} break;
}
default:
{
break;
}
}
}//小球随机反向 randomback()
function smallcircledirection()
{
switch (smallcircle.direction){ //根据小球方向做移动
case gamedirection.shang:
{
smallcircle.y=smallcircle.y-gamespeed;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.xia:
{
smallcircle.y=smallcircle.y gamespeed;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.zuo:
{
smallcircle.x=smallcircle.x-gamespeed;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.you:
{
smallcircle.x=smallcircle.x gamespeed;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.zuoshang:
{
smallcircle.x=smallcircle.x-gamespeed*0.8;
smallcircle.y=smallcircle.y-gamespeed*0.8;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.zuoxia:
{
smallcircle.x=smallcircle.x-gamespeed*0.8;
smallcircle.y=smallcircle.y gamespeed*0.8;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.youshang:
{
smallcircle.x=smallcircle.x gamespeed*0.8;
smallcircle.y=smallcircle.y-gamespeed*0.8;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
case gamedirection.youxia:
{
smallcircle.x=smallcircle.x gamespeed*0.8;
smallcircle.y=smallcircle.y gamespeed*0.8;
grade ;
if(grade>maxgrade)
{
maxgrade=grade;
newrecoder();
}
addone();
break;
}
default:
{
break;
}
}
}//小球移动 smallcircledirection()
/*画出底部圆*/
ctxpane.beginPath(); //大圆
ctxpane.arc(bigcircle.x,bigcircle.y,bigcircle.r,0,Math.PI*2,true);
ctxpane.fillStyle = bigcircle.c;
ctxpane.fill();
ctxpane.closePath();
/*画出底部追踪线条*/
ctxpane.beginPath();
ctxpane.lineWidth=6;
ctxpane.strokeStyle = line.c;
ctxpane.arc(line.x, line.y, line.r, line.start, line.end,false);
ctxpane.stroke();
ctxpane.closePath();
function tapme()//tapme
{
ctxpane.beginPath();
ctxpane.strokeStyle="rgb(255,222,195)";
ctxpane.font = "80px Papyrus";
ctxpane.strokeText('TAP',-95,30);
ctxpane.fillStyle="rgb(255,205,105)";
ctxpane.font = "35px Papyrus";
ctxpane.fillText('me',70,30);
ctxpane.closePath();
}
function newrecoder()
{
ctxpane.beginPath();
ctxpane.fillStyle="rgb(255,0,0)";
ctxpane.font = "18px Papyrus";
ctxpane.fillText("New!",58,80);
ctxpane.closePath();
}
function addone()
{
grade1=(grade/150).toFixed(1);
grade2=(maxgrade/150).toFixed(1);
var say1="now";
var say2="best"
ctxpane.beginPath();
ctxpane.strokeStyle="rgb(250,222,185)";
ctxpane.font = "60px Papyrus";
ctxpane.strokeText(grade1,-45,-60);
ctxpane.strokeText(grade2,-45,100);
ctxpane.fillStyle="rgb(255,0,100)";
ctxpane.font = "15px Papyrus";
ctxpane.fillText(say1,58,-60);
ctxpane.fillStyle="rgb(255,0,100)";
ctxpane.font = "15px Papyrus";
ctxpane.fillText(say2,58,100);
ctxpane.closePath();
}
function movetest(){
if(ifgameover())
{
ifingame=0;
if(maxgrade>parseInt(getCookie2('mymaxgrade')))
{
setCookie('nickname',nickname,'mymaxgrade',maxgrade.toString(),365);
}
clearInterval(timer);
tapme();
}
else
{
if(ifcrash())
{
randomback();
}
ctxpane.clearRect(-150,-150,300,300); //清屏
ctxpane.beginPath(); //大圆
ctxpane.arc(bigcircle.x,bigcircle.y,bigcircle.r,0,Math.PI*2,true);
ctxpane.fillStyle = bigcircle.c;
ctxpane.fill();
ctxpane.closePath();
if(line.direction==gamedirection.clock) //跟踪线顺时针
{
line.start=line.start linespeed;
line.end=line.end linespeed;
ctxpane.beginPath();
ctxpane.lineWidth=4;
ctxpane.strokeStyle = line.c;
ctxpane.arc(line.x, line.y, line.r, line.start, line.end,false);
ctxpane.stroke();
ctxpane.closePath();
}
if(line.direction==gamedirection.anticlock) //跟踪逆顺时针
{
line.start=line.start - linespeed;
line.end=line.end -linespeed;
ctxpane.beginPath();
ctxpane.lineWidth=4;
ctxpane.strokeStyle = line.c;
ctxpane.arc(line.x, line.y, line.r, line.start, line.end,false);
ctxpane.stroke();
ctxpane.closePath();
}
dot.x=bigcircle.r*Math.cos(line.start Math.PI/32) //跟踪点
dot.y=bigcircle.r*Math.sin(line.start Math.PI/32)
ctxpane.beginPath();//线上跟踪点
ctxpane.arc(dot.x,dot.y,dot.r,0,Math.PI*2,true);
ctxpane.fillStyle = smallcircle.c;
ctxpane.fill();
ctxpane.closePath();
smallcircledirection();//小圆
ctxpane.save();
ctxpane.beginPath();
ctxpane.arc(smallcircle.x,smallcircle.y,smallcircle.r,0,Math.PI*2,true);
ctxpane.fillStyle = smallcircle.c;
ctxpane.fill();
ctxpane.closePath();
ctxpane.restore();
}
}//主函数
///////////////////////////////////////////
tapme();
var timer;
function startgame(){//开始游戏
if(ifingame==0)
{
ifingame=1;
grade=0;
var xx=Math.floor(Math.random()*8);
/* switch(xx)
{
case 0:
smallcircle.direction=gamedirection.shang;
break;
case 1:
smallcircle.direction=gamedirection.xia;
break;
case 2:
smallcircle.direction=gamedirection.zuo;
break;
case 3:
smallcircle.direction=gamedirection.you;
break;
case 4:
smallcircle.direction=gamedirection.zuoshang;
break;
case 5:
smallcircle.direction=gamedirection.zuoxia;
break;
case 6:
smallcircle.direction=gamedirection.youshang;
break;
case 7:
smallcircle.direction=gamedirection.youxia;
break;
default:
break;
}*/
smallcircle.direction=gamedirection.xia;
smallcircle.x=smallcircle.y=0;
line.start=Math.PI/2-Math.PI/26;
line.end=Math.PI/2 Math.PI/26;
line.direction=gamedirection.anticlock;
clearInterval(timer);
timer=setInterval(movetest,10);
}
}//开始游戏 startgame()
function opentop()
{
window.location="http://pongotop.sinaapp.com";
}
10.写在最后
这纯属又是一个自娱自乐,写完后的第三天因为开始忙着投简历找实习就没空再管,扔到朋友圈让朋友玩去了。这一个月过去了再重新看这游戏,感觉它不该就这样死掉,本人没什么技术,做得很拙略,因此发出这篇文字希望能帮到一些对pongo感兴趣的朋友,再者就是希望如果有这方面的高手看到了能够给予赐教,一切疑惑和赐教都欢迎给我留言,谢谢!