GPS 吃蘋果遊戲之設計原理

作品

書籍

課程

程式集

小說集

論文集

散文集

影片集

編輯雜誌

程式人

電子書

JavaScript

計算語言學

微積分

Blender 動畫

C# 語言

系統程式

高等 C 語言

Java

Android

Verilog

Wikidot

R 統計軟體

機率統計

計算機數學

組合語言

人工智慧

開放原始碼

網路資源運用

計算機結構

相關訊息

常用工具

友站連結

在家教育

RSS

最新修改

網頁列表

簡體版

English

相關程式: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

gps
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License