相關訊息
|
相關程式:GpsGame.zip
GPS 所使用的協定
GPS 所使用的傳輸格式稱為 NMEA,該格式會回傳如下的文字串流,
$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05
$GPGSV,3,1,12,26,89,000,36,29,73,000,00,28,38,000,00,18,35,000,00*79
$GPGSV,3,2,12,09,27,000,00,21,27,000,41,15,23,000,00,10,18,000,00*79
$GPGSV,3,3,12,08,15,000,00,22,07,000,,19,07,000,,03,-01,000,*51
$GPRMC,113347.950,V,0000.0000,N,00000.0000,E,0.00,,120305,,*00
$GPGGA,113348.950,0000.0000,N,00000.0000,E,0,00,50.0,0.0,M,0.0,M,0.0,0000*76
$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05
$GPRMC,113348.950,V,0000.0000,N,00000.0000,E,0.00,,120305,,*0F
$GPGGA,113349.950,0000.0000,N,00000.0000,E,0,00,50.0,0.0,M,0.0,M,0.0,0000*77
$GPGSA,A,1,,,,,,,,,,,,,50.0,50.0,50.0*05
$GPRMC,113349.950,V,0000.0000,N,00000.0000,E,0.00,,120305,,*0E
其中,最重要的資訊是座標資訊,紀錄在以 GPSGGA 開頭的欄位中,以下是其欄位的說明:
標頭 ,收訊時間點,經度座標,北或南,緯度座標 ,東或西,品質,衛星數量,....
$GPGGA,113348.950,0000.0000,N ,00000.0000,E ,0 ,00 ,50.0,0.0,M,0.0,M,0.0,0000*76
在配備有 GPS 的電腦或手機當中,這個資訊會從某個 COM port (例如:COM1) 當中傳入,程式只要不斷從該 COM port 中讀取該串流即可。
微軟的 GPS 函式庫
雖然程式可以直接讀取 COM port 取得 GPS 訊息,但若許多程式都企圖直接從該 COM port 讀取 GPS 資訊,則會造成鎖定現象,因此最好是由作業系統提供讀取衛星資訊的物件或函數。
微軟的 GPS 函式庫目前用 C++ 寫成,並沒有 .NET 的元件給 C# 使用,因此要使用 C# 接收 GPS 資訊,必須透過 C# 呼叫 GPS 函式庫,該程式位在 GpsGame.zip 檔案的 /GpsLib/ 資料夾中,我們利用 /gps/ 專案進一步將其封裝為更好用的物件,然後透過事件導向的方式,驅動指定的函數指標 (C# 中的 delegation 機制),以便讓需要 GPS 功能的程式使用。
/gps/ 專案中最重要的物件是 GpsDevice,該物件利用『registering-callback』的方法實作『註冊-驅動』的機制,程式可以透過addLocationCallback(Control ctrl, GpsLocationHandler locationHandler) 傳入函數指標進行註冊動作。當有 Gps 座標資訊進入時,/GpsLib/ 會呼叫 void gpsLocationChangedEventHandler(object sender, LocationChangedEventArgs args) 函數,然後 GpsDevice 就會呼叫該 callback 函數。
public class GpsDevice
{
public Microsoft.WindowsMobile.Samples.Location.Gps gps;
public List<LocationCallback> locationCallbacks = new List<LocationCallback>();
public List<GpsLocationRecord> records = new List<GpsLocationRecord>();
public GpsDevice()
{
gps = new Microsoft.WindowsMobile.Samples.Location.Gps();
if (gps.Opened) gps.Close();
gps.LocationChanged += new LocationChangedEventHandler(gpsLocationChangedEventHandler);
}
~GpsDevice() {}
public virtual void open()
{
if (!gps.Opened)
gps.Open();
}
public virtual void close()
{
if (gps.Opened)
gps.Close();
}
public virtual bool isOpen()
{
return gps.Opened;
}
public virtual void addLocationCallback(Control ctrl, GpsLocationHandler locationHandler)
{
locationCallbacks.Add(new LocationCallback(ctrl, locationHandler));
}
public virtual void removeLocationCallback(GpsLocationHandler locationHandler)
{
for (int i = 0; i < locationCallbacks.Count; i++)
{
if (locationCallbacks[i].handler == locationHandler)
locationCallbacks.RemoveAt(i);
}
}
public virtual void gpsLocationChangedEventHandler(object sender, LocationChangedEventArgs args)
{
if (!isOpen()) return;
if (args.Position == null) return;
GpsLocationRecord rec = new GpsLocationRecord(args.Position);
if (rec.isValid)
gpsLocationChanged(rec);
}
public virtual GpsLocationRecord getLastLocation()
{
lock (records)
{
if (records.Count == 0) return null;
return records[records.Count - 1];
}
}
public virtual void gpsLocationChanged(GpsLocationRecord rec)
{
lock (records)
{
records.Add(rec);
if (records.Count > 10) records.RemoveAt(0);
}
for (int i = 0; i < locationCallbacks.Count; i++)
{
Control ctrl = locationCallbacks[i].ctrl;
GpsLocationHandler handler = locationCallbacks[i].handler;
if (ctrl == null)
handler(rec);
else // WinForm 中的 Control 不能直接被其他 thread 呼叫,因此、必須用 Invoke 或 BeginInvoke.
// ctrl.Invoke(handler, new Object[] { rec });
ctrl.BeginInvoke(handler, new Object[] { rec });
}
}
}
GpsGame.zip 中的 /GpsWatcher/ 是最簡單的 GPS 程式,該程式會在接收衛星資訊後,以表格的方式將定位資訊顯示在手機上。想要學習該專案用法的讀者可以從 GpsWatcher 開始學習。
衛星資訊接收器 (GpsWatcher)
GpsWatcher 使用了 GpsDevice 類別,該程式利用 gps.addLocationCallback(this, locationChanged) 指令向 GpsDevice 註冊 locationChanged 函數,於是當有 GPS 座標資訊進來時,就會啟動 locationChanged 事件。GpsWatcher 在該事件中將座標資訊加入到 ListView 中顯示出來,於是就達成了以表格顯示 Gps 座標資訊的目的。
using System;
using System.IO.Ports;
using System.Windows.Forms;
using System.Data;
using System.Text.RegularExpressions;
using System.IO;
using ccc;
namespace ccc.gps
{
public partial class FormGps : Form
{
GpsDevice gps = new GpsDevice();
StreamWriter logFile = null;
public FormGps()
{
InitializeComponent();
FileStream fs = new FileStream(Env.getCodePath()+"\\log.txt", FileMode.Append);
logFile = new StreamWriter(fs, System.Text.UTF8Encoding.UTF8);
gps.addLocationCallback(this, locationChanged);
}
private void FormGps_Load(object sender, EventArgs e)
{
init();
}
public void init()
{
double width = listViewGps.Width;
listViewGps.Columns[0].Width = (int)(width * 0.1);
listViewGps.Columns[1].Width = (int)(width * 0.35);
listViewGps.Columns[2].Width = (int)(width * 0.35);
listViewGps.Columns[3].Width = (int)(width * 0.2);
}
private void buttonGetData_Click(object sender, EventArgs e)
{
listViewGps.Items.Clear();
gps.open();
buttonGetData.Enabled = false;
buttonStopGetData.Enabled = true;
}
private void buttonStopGetData_Click(object sender, EventArgs e)
{
gps.close();
buttonGetData.Enabled = true;
buttonStopGetData.Enabled = false;
}
int counter = 0;
void locationChanged(GpsLocationRecord rec)
{
counter++;
ListViewItem item = new ListViewItem();
item.Text = counter + "";
item.SubItems.Add(String.Format("{0:F5}", rec.x));
item.SubItems.Add(String.Format("{0:F5}", rec.y));
item.SubItems.Add(String.Format("{0}", rec.satelliteCount));
listViewGps.Items.Insert(0, item);
if (listViewGps.Items.Count > 15)
listViewGps.Items.RemoveAt(15);
if (checkBoxLog.Checked)
logFile.WriteLine(rec.ToString());
}
private void menuItemExit_Click(object sender, EventArgs e)
{
buttonStopGetData_Click(sender, e);
Close();
}
private void FormGps_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
logFile.Close();
}
}
}
吃蘋果遊戲
Apple Game 則是利用 GPS 功能設計出的吃蘋果遊戲,玩家必須拿著手機跑來跑去,才能吃到蘋果,並且必須躲避會到處誼動的魔鬼,這增添了遊戲的樂趣。
該程式使用了 GpsGame 類別,GpsGame 類別提供了 Gps 遊戲的程式框架,於是程式只要建立 GpsGame 類別後將 gpsLocated, gpsDataReceived 與 gameOver 等函數指定好,就可以在適當的時候驅動這些函數,這讓 Gps 遊戲設計者可以專注於遊戲的設計上,讓 Gps 遊戲的設計更為方便。以下是吃蘋果遊戲 AppleGame 的程式碼。
using System;
using System.Windows.Forms;
using System.Threading;
using ccc.gps;
using ccc.game;
using ccc.media;
namespace ccc.game.apple
{
public partial class FormApple : Form
{
public GpsGame game;
public Space space;
public SpaceView view;
public GpsDevice device;
public int APPLES_SIZE = 5;
public int GHOST_SIZE = 1;
double centerX, centerY;
ImageObject people;
ImageObject[] apples;
ImageObject[] ghosts;
Sound sound;
DateTime startLocateTime;
public FormApple()
{
InitializeComponent();
sound = new Sound(Env.getCodePath() + "\\wav\\chord.wav");
space = new Space();
view = new SpaceView(space, panelBackground);
double range = 2 * 20 * GpsConst.Degree1M;
space.setRange(range, range);
game = new GpsGame(this);
game.gpsLocated += gpsLocated;
game.gpsDataReceived += gpsDataReceived;
game.gameOver += gameOver;
}
public void newGame()
{
game.bindCallback(device);
apples = new ImageObject[APPLES_SIZE];
ghosts = new ImageObject[GHOST_SIZE];
// 放入蘋果
for (int i = 0; i < APPLES_SIZE; i++)
apples[i] = view.newObj("apple" + i, pictureBoxApple);
// 放入鬼
for (int i = 0; i < GHOST_SIZE; i++)
ghosts[i] = view.newObj("ghost" + i, pictureBoxGhost);
// 放入主角
people = view.newObj("people", pictureBoxPeople);
startLocateTime = DateTime.Now;
timerLocating.Enabled = true;
timerGhost.Enabled = false;
game.beginGame();
}
public void endGame()
{
timerGhost.Enabled = false;
view.removeAll();
game.endGame();
game.unbindCallback();
}
void output(string msg)
{
labelOutput.Text = msg;
labelOutput.Show();
}
void peopleEatApple(int i)
{
apples[i].die();
sound.Play();
}
void gpsLocated(GpsLocationRecord rec)
{
centerX = rec.x;
centerY = rec.y;
space.region.left = centerX - space.region.width / 2.0;
space.region.top = centerY + space.region.height / 2.0;
people.obj.x = rec.x;
people.obj.y = rec.y;
randomPutAll(apples);
randomPutAll(ghosts);
timerGhost.Enabled = true;
labelOutput.Hide();
view.showAll();
// MessageBox.Show(rec.ToString());
}
void gpsDataReceived(GpsLocationRecord rec)
{
people.obj.x = rec.x;
people.obj.y = rec.y;
people.show();
labelGpsData.Text = String.Format("目前座標:{0:F5} , {1:F5}", people.obj.x, people.obj.y);
labelGpsData.Refresh();
checkGameOver();
for (int i = 0; i < APPLES_SIZE; i++)
{
if (apples[i].obj.isAlive && Space.isTouch(people.obj, apples[i].obj))
peopleEatApple(i);
}
if (game.device is GpsSimulator)
{
GpsSimulator gpsSim = (GpsSimulator)game.device;
for (int i = 0; i < APPLES_SIZE; i++)
{
if (apples[i].obj.isAlive)
{
double xdiff = apples[i].obj.x - people.obj.x;
double ydiff = apples[i].obj.y - people.obj.y;
gpsSim.setBias(Math.Sign(xdiff) * 0.7, Math.Sign(ydiff) * 0.7);
break;
}
}
}
}
void gameOver()
{
timerGhost.Enabled = false;
int eatCount = 0;
view.hideAll();
for (int i = 0; i < APPLES_SIZE; i++)
if (!apples[i].obj.isAlive)
eatCount++;
string msg = "";
if (people.obj.isAlive)
msg += "恭喜你!闖關成功!\n\n";
else
msg += "你被鬼咬死了!闖關失敗!\n\n";
msg += "吃" + eatCount + "顆蘋果,耗費" + game.endTime.Subtract(game.startTime) + "秒鐘";
output(msg);
}
void checkGameOver()
{
// if (game.isGameOver || game.isGameEnd) return;
bool hasApple = false;
for (int i = 0; i < APPLES_SIZE; i++)
if (apples[i].obj.isAlive)
hasApple = true;
if (!hasApple)
game.isGameOver = true;
bool isHitGhost = false;
for (int i = 0; i < GHOST_SIZE; i++)
{
if (Space.isTouch(ghosts[i].obj, people.obj))
isHitGhost = true;
}
if (isHitGhost)
{
game.isGameOver = true;
for (int i = 0; i < 5; i++)
{
sound.Play();
Thread.Sleep(200);
}
people.die();
}
}
void randomPutAll(ImageObject[] objs)
{
foreach (ImageObject o in objs)
{
while (true)
{
space.randomPut(o.obj);
if (Space.distance(o.obj, people.obj) > space.region.width * 0.3 &&
view.toScreenY(o.obj.y) < labelGpsData.Top - o.img.Height)
break;
}
o.show();
}
}
void timerGhost_Tick(object sender, EventArgs e)
{
checkGameOver();
for (int i = 0; i < GHOST_SIZE; i++)
{
space.randomMove(ghosts[i].obj);
ghosts[i].show();
}
}
void FormApple_Deactivate(object sender, EventArgs e)
{
endGame();
Hide();
}
void FormApple_Closed(object sender, EventArgs e)
{
FormApple_Deactivate(sender, e);
}
void timerLocating_Tick(object sender, EventArgs e)
{
if (!game.isGpsLocated)
{
TimeSpan spandTime = DateTime.Now.Subtract(startLocateTime);
output("定位已耗費"+spandTime.TotalSeconds+"秒!");
}
}
public void paint(object sender, PaintEventArgs e)
{
}
}
}
在上述程式中,我們利用下列程式設定 GpsGame 的相關函數
game = new GpsGame(this);
game.gpsLocated += gpsLocated;
game.gpsDataReceived += gpsDataReceived;
game.gameOver += gameOver;
然後在 gpsDataReceived 函數中移動玩家圖片 (大牙齒) 的位置,並顯示目前座標,然後檢查玩家是否被鬼抓到,其程式碼如下:
void gpsDataReceived(GpsLocationRecord rec)
{
people.obj.x = rec.x;
people.obj.y = rec.y;
people.show();
labelGpsData.Text = String.Format("目前座標:{0:F5} , {1:F5}", people.obj.x, people.obj.y);
labelGpsData.Refresh();
checkGameOver();
....
}
於是透過這種方式,就可以完成位星吃蘋果遊戲的設計。
Facebook
|
Post preview:
Close preview