摘要:優化后的方案降低了捕捉門檻,也鼓勵用戶走動去發現和捕捉更多精靈。
寫在前面
去吧!皮卡丘!
小時候擁有一臺任天堂是多少熊孩子的夢想,每個夜晚被窩里透出的微弱光線,把小小的童年帶入另一個世界,家門口的鳥和狗,森林里的蟲和瀑布,山洞里的超音蝠,帶著小小的夢,走過一個個城市,一路冒險,飛天潛水,攀瀑碎巖,所向披靡。
每個醒來的清晨,都恍如出門冒險的那天~
要做什么
基于開放地圖二次開發,完成簡易像素版PokemonGo
準備工作 一、確定功能需求 第一階段1、用戶體系
2、背包
3、圖鑒
4、人物定位
5、精靈分布
6、精靈捕捉
7、排行榜
8、移動隨機事件
9、新手引導
1、地圖增加道館挑戰
2、日常任務系統
1、精靈交易
2、玩家對戰
3、AR捕捉場景
*目前只完成第一階段的功能
二、開放地圖選擇
功能 / 廠商 | 百度地圖 | 騰訊地圖 | 高德地圖 |
---|---|---|---|
自定義皮膚 | 支持 | 不支持 | 支持 |
實時定位 | 不支持 | 支持 | 支持 |
開發文檔 | 差 | 一般 | 友好 |
對比三個地圖廠商,我們選擇高德地圖進行二次開發
三、申請高德地圖SDK登錄http://lbs.amap.com/
控制臺-應用管理-創建新應用-添加新KEY
具體參考微信公眾平臺開發者文檔
https://mp.weixin.qq.com/wiki...
我們需要一些接口來保存用戶數據,所以需要找一個服務端的同學配合完成幾個簡單的接口
1、api/login 判斷登錄狀態,獲取用戶基本信息
2、api/getGlassPokemon 獲取草地精靈
3、api/getMyPokemon 獲取背包精靈
4、api/catchPokemon 捕捉精靈
5、api/getRank 獲取排行榜信息
1、簡單設計主界面UI,確定功能布局、地圖的配色方案:
2、準備150只精靈的素材圖片(大小各一套)
現在開始 一、接入高德地圖在中引入高德地圖js-sdk
二、地圖美化
默認的地圖樣式不能滿足我們的需求,高德地圖提供了地圖皮膚編輯器:高德地圖皮膚編輯器
在編輯器中修改道路,陸地,建筑,水域,綠地等顏色,同時在配置中隱藏了一些道路、建筑與標記,簡化地圖。
編輯完成后點擊發布,獲得地圖樣式ID:e6fa21422698f8a28585158d9d075f1d
在地圖初始化中引入地圖樣式即可
var gomap;
gomap = new AMap.Map("gomap", {
zoomEnable : false,
zoom:18,
center: [118.18088, 24.4896],
mapStyle : "amap://styles/e6fa21422698f8a28585158d9d075f1d"
});
查看DEMO
這樣看起來就有點游戲的樣子了
三、地圖定位
我們需要把地圖和主角定位在當前位置,并且在移動時實時更新定位,這就需要借助AMap的geolocation插件
gomap.plugin("AMap.Geolocation",function(){
var geo = new AMap.Geolocation({
showButton: false,
showCircle: false,
showMarker : true, //顯示定位圖標
markerOptions : {
content : "", //設置marker自定義節點內容
}
});
gomap.addControl(geo);
geo.watchPosition(); //實時獲取定位
AMap.event.addListener(geolocation, "complete", onComplete);//返回定位成功信息
AMap.event.addListener(geolocation, "error", onError); //返回定位出錯信息
})
小智一個人站在地圖上有點孤單,我們給他加一個光環放大的效果,看起來像是在發出檢測信號:
.Symbol.hero:after{
-webkit-animation:heroWave 2s ease infinite;
background:rgba(255,255,181,0.1);
content:"";
width:100px;
height:100px;
display:block;
position:absolute;
left:-30px;
top:-30px;
border-radius:100%;
box-shadow:0 0 0 1px rgba(255,255,181,0.7);
opacity:0.7;
}
@-webkit-keyframes heroWave{
0%{ -webkit-transform:scale(0.2);opacity:0}
50%{ opacity:1}
100%{ -webkit-transform:scale(1);opacity:0}
}
查看DEMO
四、羅盤
有了定位,我們還需要知道自己移動的方向,方便接近目標,所以我們在界面右上角放置了一個虛擬羅盤
通過監聽HTML5的deviceorientation獲取指南針角度信息,改變羅盤旋轉方向:
if (window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", function(event){
var dir = event.webkitCompassHeading;
$("#J_pin").css("-webkit-transform","rotate("+ (360-dir) +"deg)");
}, false);
}
查看DEMO (羅盤只在移動端生效,掃碼查看)
五、精靈數據
由于精靈的編號,屬性,星級等數據是固定的,在前端創建一個保存精靈圖鑒數據的JSON文件,以減少服務端返回數據的復雜度,通過編號在圖鑒中索引對應精靈的相關數據
var Pokedex = [
{
"number":"001",
"name" :"妙蛙種子",
"name_jp" : "フシギダネ",
"name_en" : "Bulbasaur",
"properties" : ["草","毒"],
"star" : 4,
},
{
"number":"002",
"name" :"妙蛙草",
"name_jp" : "フシギソウ",
"name_en" : "Ivysaur",
"properties" : ["草","毒"],
"star" : 4,
},
...
];
//精靈屬性顏色配置
var Pokedexcolor = {
"草" : "#1ba50e,#2ec920",
"冰" : "#13c6db,#57e9ff",
"超能力" :"#dd045b,#f7478d",
"蟲" : "#889610,#b5b214",
"地面" : "#af8a19,#d8b343",
"電" : "#b28200,#ffd621",
"毒" : "#752464,#9e448c",
"飛行" : "#4381ff,#72aefc",
"鋼" : "#6d6d8a,#aaaabb",
"格斗" : "#902918,#bb5544",
"火" : "#c72500,#f05526",
"龍" : "#2b1aa6,#7766ee",
"水" : "#2b1aa6,#3088e1",
"巖石" : "#907d2f,#a89755",
"一般" : "#969685,#bbbbaa",
"幽靈" : "#3d3d7c,#5f52a7",
"妖精" : "#3d3d7c,#5f52a7",
}
六、在地圖上添加精靈
主角誕生了,現在開始在周圍生成一些隨機的精靈,調用getGlassPokemon接口,傳遞當前位置坐標,服務端在坐標半徑1公里內生成一定個數的精靈,前端通過返回的坐標和精靈編號將對應精靈添加到地圖上。
由于后續接口需要驗證微信授權信息,為了便于DEMO查看請先訪問一次模擬登陸接口:http://www.guowc.cc/api/sysUs...
getGlassPokemon接口返回數據格式:
data : {
{
id : 231, //精靈唯一標識,用于捕捉成功后從數據庫中精準刪除地圖對應精靈
number: "77", //精靈編號,用于圖鑒中獲取更多精靈信息
lng : 118.094561807441 //精靈經度
lat : 24.4805797983452 //精靈緯度
},
...
}
首先在前面的geolocation插件中調用getCurrentPosition()方法,獲取一次初始定位坐標,
將坐標傳給getPokemons接口拉取草地精靈數據
var self = this
gomap.plugin("AMap.Geolocation",function(){
var geo = new AMap.Geolocation({
...
});
...
//首次定位
geo.getCurrentPosition(function( status, result ){
heroPoint.lng = result.position.lng;
heroPoint.lat = result.position.lat;
self.getPokemon(heroPoint)
});
})
請求接口數據:
getPokemon : function(point) {
var self = this
Method.fetch(Api.getGlassPokemons,{ lng:point.lng,lat:point.lat },function(data){
var res = data.data;
for(var i = 0; i < res.length; i++){
self.addPokemon(res[i]);
}
});
},
Method.fetch為封裝的ajax方法,只貼出關鍵流程代碼,具體詳見DEMO
獲取到數據后,循環調用addPokemon方法,將精靈添加到地圖上:
addPokemon : function(data) {
var pid = Method.getPid(data.number); //獲取精靈編號(格式化"12"=>"012")
var marker = new AMap.Marker({
map: Common.gomap,
position: [data.position_x, data.position_y],
icon: new AMap.Icon({
size: new AMap.Size(40, 40),
imageSize : new AMap.Size(40, 40),
image: "images/pokemon/PM_icon_"+ pid +".png",
}),
});
},
查看DEMO
現在我們就能在地圖上看到精靈了~
七、獲取背包精靈
這一步我們先把已捕捉到的精靈列表保存起來,以便后續使用:
getMyPokemons : function(){
Method.fetch(API.getMyPokemons,{},function(data){
for(var i = 0 ;i < data.data.length; i++){
State.bag.push(Method.getPid(data.data[i].number)); //將已獲得精靈編號保存在全局State.bag數組中
}
});
}
八、精靈收集
操作優化
精靈收集是整個游戲的核心功能,原版pokemonGo精靈捕捉過程為AR實景捕捉形式,我們把精靈的捕捉形式簡化了,保留街機時代的像素風格。最早在實現這個功能時,采用的策略是當玩家坐標與地圖精靈小于一定距離時,自動進入精靈捕捉場景,這種方式存在幾個問題:
用戶位置發生變化時,需要不斷計算用戶坐標與地圖上所有精靈的距離,計算量較大
可能存在同時與兩個精靈距離符合捕捉條件,而一次只能捕捉一只精靈
用戶如果不移動,基本很難捕捉到精靈
經過優化,將捕捉規則修改為:直接點擊地圖精靈即可捕捉,半徑500米外提示用戶超過捕捉范圍。
優化后的方案降低了捕捉門檻,也鼓勵用戶走動去發現和捕捉更多精靈。
數據傳遞
上一步的addPokemon方法中,我們已經向地圖中添加了精靈點標記(marker),但此時地圖上的精靈唯一區分只是圖片不同而已,我們還需為每個marker綁定對應的精靈信息,并為每個marker綁定點擊事件,下面完善一下addPokemon方法:
addPokemon : function(data) {
var self = this;
var nid = parseInt(data.number);
var pid = Method.getPid(data.number); //獲取精靈編號(格式化"12"=>"012");
var id = data.id;
var marker = new AMap.Marker({
map: Common.gomap,
position: [data.position_x, data.position_y],
icon: new AMap.Icon({
size: new AMap.Size(40, 40),
imageSize : new AMap.Size(40, 40),
image: "images/pokemon/PM_icon_"+ pid +".png",
}),
extData : {
nid : nid,
id : id,
}
});
marker.on("click",function(e){
self.clickPokemon(e)
})
},
由于地圖上顯示精靈圖標過小,無法展示更多信息,所以點擊精靈后,不直接進入戰斗,先彈出對應精靈的卡牌:
clickPokemon : function(e){
var self = this;
var data = e.target.getExtData();
self.initPokecard(data.nid,e.target);
},
初始化卡牌彈窗:
TIPS 捕捉半徑500米 當前距離0米
捕捉
initPokecard : function(nid, target) {
var $card = Element.$pokedex,
$catch = Element.$catch;
var data = Pokedex[nid - 1]; //從圖鑒JSON中獲取對應精靈詳細圖鑒數據
var props = "", dist = 0;
var imgUrl = "images/pokemon_big/PM_animation_"+ data.number +".png";
//拼接屬性節點
for(var i = 0; i< data.properties.length; i++){
var color = Pokedexcolor[data.properties[i]].split(",");
props += ""+ data.properties[i] +"";
}
$card.find(".name_cn").text(data.name); //精靈中文名
$card.find(".name_jp").text(data.name_jp + data.name_en); //精靈外文名
$card.find(".star")[0].className = "star star_" + data.star; //精靈星級
$card.find(".num").text("No." + data.number); //精靈編號
$card.find(".prop").html(props); //精靈屬性
//預加載精靈大圖
$card.find(".pokebox").removeClass("loaded");
Method.loadImg(imgUrl, function() {
$card.find(".pokeimg").attr("src",imgUrl);
$card.find(".pokebox").addClass("loaded");
});
$card.addClass("show");
}
到這里就完成了精靈卡片的初始化(與背包圖鑒共用),然而卡片只帶有固定數據,需要跟單純的圖鑒查看器做區分,我們在卡片下方加上操作區,操作區有3種狀態:精靈可捕捉,精靈已獲得,精靈超出捕捉范圍:
initPokecard : function(nid, target) {
...
//判斷點擊精靈行為來自地圖還是圖鑒
if(target) {
var pa = target.getPosition(),
pb = [State.heroPoint.lng,State.heroPoint.lat];
var dist = parseInt(pa.distance(pb)); //計算主角與點擊精靈距離
$card.addClass("catch");
$card.off().on("touchend",function(e){
$(this).removeClass("show");
e.preventDefault();
})
if( dist < 500 ) {
$card.find(".tips").hide();
if(State.bag.indexOf(data.number) != -1){
$catch.removeClass().addClass("ownbtn").text("已獲得");
}else{
$catch.removeClass().addClass("catchbtn").text("捕捉");
}
}else{
$card.find(".dist").text(dist);
$card.find(".tips").show();
$catch.removeClass().addClass("overbtn").text("走近點啊親");
}
}else{
$card.removeClass("catch");
$card.find(".tips").hide();
}
}
查看DEMO
九、精靈捕捉場景
首先為卡片下方的捕捉按鈕綁定事件,將點擊的精靈數據傳遞到meetPokemon方法中(初始化精靈捕捉場景方法):
initPokecard : function(nid, target) {
...
if(target) {
var ext = target.getExtData()
$catch.off(); //解除捕捉按鈕綁定事件
...
if( dist < 500 ){
if(State.bag.indexOf("data.number) != -1){
...
}else{
$catch.on("touchend", self.meetPokemon(ext.nid,ext.id));
target.setMap(null); //開始捕捉后將地圖上對應小精靈移除(捕捉成功or失敗小精靈都會消失)
}
}
}else{
}
}
然后初始化捕捉場景:
meetPokemon : function(nid,id){
Element.$modal.removeClass("show");
Element.$body.addClass("State catching"); //進入捕捉場景需要控制界面多處UI,所以把狀態class放到body上
Element.$catchBox.find(".texture").attr("src","images/pokemon_big/PM_animation_" + Pokedex[nid].number + ".png");
Element.$catchBox.find(".pname").text(Pokedex[nid].name);
Element.$catchBox.find(".star")[0].className = "star star_" + Pokedex[nid].star;
},
查看DEMO
十、去吧精靈球!
現在點擊丟出精靈球開始捕捉精靈,首先在meetPokemon中綁定精靈球的點擊事件:
meetPokemon : function(nid,id){
...
Element.$catchBox.find(".ballbox").off().on("touchend",function(){
self.catchPokemon(nid,id)
})
},
接下來實現catchPokemon方法:
catchPokemon : function(nid,id){
var self = this;
var name = Pokedex[nid].name,
pid = Pokedex[nid].number,
star = Pokedex[nid].star,
rate = 0.8 - star / 10; //根據星級決定捕捉成功概率
if(State.catching) return;
if(Method.random(rate)){
//捕捉成功
Element.$catchBox.addClass("catchwin"); //在catchbox上增加catchwin控制捕捉成功動畫(動畫具體實現參照DEMO)
Method.fetch(API.catchPokemon,{ id : id },function(){
console.log("捕捉成功!") //向服務端發送捕捉成功請求,移除getGlassPokemon返回的對應位置精靈,同時加入用戶背包
});
setTimeout(function(){
//動畫播放結束后調用捕捉成功彈窗
self.awardBox("images/pokemon_big/PM_animation_" + pid + ".png",name,function(){
//彈窗關閉后回到主界面
$body.removeClass();
Element.$catchBox.removeClass("catchwin");
State.catching = false;
});
},3200);
}else{
//捕捉失敗
Element.$catchBox.addClass("catchfail"); //在catchbox上增加catchfail控制捕捉失敗動畫
Method.fetch(API.catchPokemon,{ id : id , flag : true},function(){
console.log("捕捉失敗!") //向服務端發送捕捉失敗請求,僅移除getGlassPokemon返回的對應位置精靈
});
setTimeout(function(){
//動畫播放失敗后調用捕捉失敗彈窗
self.alertBox(name + " 逃跑了!",function(){
//彈窗關閉后回到主界面
Element.$body.removeClass();
Element.$catchBox.removeClass("catchfail");
State.catching = false;
});
},1500);
}
State.catching = true;
},
捕捉成功與失敗:
awardBox : function(img,name,callback){
var cbk = callback || function(){};
Element.$award.addClass("show");
Element.$award.find(".img").attr("src",img);
Element.$award.find(".pname").text(name);
Element.$award.find(".confirm").off().on("tap",function(){
Element.$award.removeClass("show");
cbk();
});
},
alertBox : function(title,callback){
var cbk = callback || function(){};
Element.$alert.addClass("show");
Element.$alert.find(".heading").text(title);
Element.$alert.find(".confirm").off().on("tap",function(){
Element.$alert.removeClass("show");
cbk();
});
},
捕捉失敗過程:
捕捉成功過程:
查看DEMO
十一、大木博士
還原經典,我們加入一個簡單的新手引導,通過與大木博士的對話,確定玩家性別,第一個伙伴,和簡單的游戲玩法介紹。
首先實現一個簡單的打字效果:
typing(char,delay){
var chars = char.split("");
var index = 0,
delay = 0;
Element.$typeText.html("");
Element.$typeNext.hide(); //打字過程隱藏下一步箭頭
State.typeOver = false;
var timer = setInterval(function(){
if(delay == delay){
if(index == chars.length){
clearInterval(timer);
State.typeOver = true;
Element.$typeNext.show();
return;
}
if(chars[index] == "/"){
Element.$typeText.append("
"); //判斷換行位置
}else{
Element.$typeText.append(chars[index]);
}
index ++;
}else{
delay ++ ;
}
},50);
},
然后初始化新手引導:
initGuide : function(){
var self = this;
var sex = 1, pokenum = "001", typeIndex = 0;
Method.typing("歡迎來到精靈世界/我是大木博士",10);
Element.$modal.removeClass("show"); //隱藏所有彈窗
Element.$modalGuide.addClass("show"); //顯示新手引導彈窗
Element.$body.addClass("blur"); //背景模糊
Element.$typeBox.bind("tap",function(){
//開始對話
if(!State.typeOver) return;
typeIndex ++;
switch (typeIndex) {
case 1:
Method.typing("這個世界到處都有精靈的存在/許多人把精靈當做伙伴",0);
break;
case 2:
Method.typing("那么你是男孩還是女孩?/(選擇角色)",0);
Element.$modalGuide.addClass("setrole"); //進入選擇角色界面
Element.$setRole.bind("tap",function(){
$(this).addClass("selected").siblings().removeClass("selected");
sex = $(this).data("sex");
});
break;
case 3:
Method.typing("選擇一只精靈作為你的伙伴吧/(選擇精靈)",0);
Element.$modalGuide.removeClass("setrole").addClass("setpoke"); //進入選擇精靈界面
Element.$setPoke.bind("tap",function(){
$(this).addClass("selected").siblings().removeClass("selected");
pokenum = $(this).data("number");
Element.$setFigure[0].className = "img-" + pokenum;
});
Method.setItem("sex",sex);
break;
case 4:
Method.typing("點擊周圍的小精靈即可抓捕!/(操作方式)",0);
Element.$modalGuide.removeClass("setpoke");
break;
case 5:
Method.typing("移動可能遇到隨機出現的稀有精靈哦!/(隨機事件)",0);
break;
case 6:
Method.typing("請帶上你的伙伴去冒險吧!",0);
break;
case 7:
Element.$modalGuide.removeClass("show");
Element.$body.removeClass("blur");
self.ready();
break;
default:
}
});
},
查看DEMO
十二、完善細節
頁面加載
精靈刷新倒計時
系統公告
Toast提示
訓練師信息
精靈背包
精靈圖鑒
精靈大師榜
總結
這個項目是去年6月份開始的,從零開始策劃、設計、到程序實現,斷斷續續寫了2個月,期間深度分析對比了幾個地圖廠商的開發文檔,推翻了數次方案,不斷對游戲細節,性能,交互,功能取舍做優化,總結了LBS游戲開發的一些經驗與建議:
1、文檔與社區活躍度很重要(這點高德地圖做的更好)
2、作為練手,不依賴框架庫開發游戲,更能鍛煉邏輯能力,了解底層實現
3、作為落地項目,輕量休閑游戲開發仍然需要借助成熟的游戲開發框架(Phaser、Pixel),或Vue、React(大量狀態管理)
4、交互動畫部分盡可能交給css3,js負責數據與狀態控制
5、用戶體驗、性能、游戲性同等重要
最后再附上完整版:(完整版仍為百度地圖開發,體驗相差不大)
have a fun!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/51141.html
摘要:優化后的方案降低了捕捉門檻,也鼓勵用戶走動去發現和捕捉更多精靈。 寫在前面 去吧!皮卡丘!小時候擁有一臺任天堂是多少熊孩子的夢想,每個夜晚被窩里透出的微弱光線,把小小的童年帶入另一個世界,家門口的鳥和狗,森林里的蟲和瀑布,山洞里的超音蝠,帶著小小的夢,走過一個個城市,一路冒險,飛天潛水,攀瀑碎巖,所向披靡。每個醒來的清晨,都恍如出門冒險的那天~ 要做什么 基于開放地圖二次開發,完成簡易...
摘要:優化后的方案降低了捕捉門檻,也鼓勵用戶走動去發現和捕捉更多精靈。 寫在前面 去吧!皮卡丘!小時候擁有一臺任天堂是多少熊孩子的夢想,每個夜晚被窩里透出的微弱光線,把小小的童年帶入另一個世界,家門口的鳥和狗,森林里的蟲和瀑布,山洞里的超音蝠,帶著小小的夢,走過一個個城市,一路冒險,飛天潛水,攀瀑碎巖,所向披靡。每個醒來的清晨,都恍如出門冒險的那天~ 要做什么 基于開放地圖二次開發,完成簡易...
摘要:前端日報精選理解的專題之偏函數譯理解事件驅動機制游戲開發前端面試中的常見的算法問題發布中文前端頁面傳參尚妝產品技術刊讀基礎系列二之實現大轉盤抽獎掘金指南眾成翻譯編程插入排序眾成翻譯源碼講解函數技術風暴初體驗個人文 2017-08-16 前端日報 精選 理解 JavaScript 的 async/awaitJavaScript專題之偏函數[譯]理解 Node.js 事件驅動機制Pokem...
摘要:幾個月之內就開發了幾十款小程序的開發者陳林,有著自己的解答。資深小程序開發者陳林,是小程序風口下的探索者之一,借助小程序無需安裝,易于傳播的特點,陳林以小游戲類目為核心,配合小程序間可相互跳轉的特性。 showImg(https://segmentfault.com/img/remote/1460000020165110);你是一條產品經理,現在要出一個大型需求的方案,你會怎么做? ...
閱讀 1211·2021-11-24 11:16
閱讀 3439·2021-11-15 11:38
閱讀 1948·2021-10-20 13:47
閱讀 558·2021-09-29 09:35
閱讀 2206·2021-09-22 15:17
閱讀 1024·2021-09-07 09:59
閱讀 3395·2019-08-30 13:21
閱讀 2917·2019-08-30 12:47