返回文章列表

FireMonkey開發多點觸控手勢記憶遊戲

本文介紹如何使用 FireMonkey 框架開發具有多點觸控和手勢辨識功能的記憶遊戲。文章涵蓋了手勢事件處理、多點觸控應用、TImageList 影像管理以及 UI 設計等關鍵技術,並以 Delphi 語言為例,逐步講解了遊戲開發的過程,包括遊戲介面設計、圖片管理、遊戲邏輯以及計時器功能的實作。

行動應用開發 遊戲開發

FireMonkey 框架的跨平台特性和豐富的 UI 元件,讓開發者可以輕鬆地建立具有高度互動性的應用程式。本文以記憶遊戲的開發為例,探討瞭如何運用 FireMonkey 實作多點觸控和手勢辨識功能。文章首先介紹了手勢事件的處理流程,並以旋轉手勢為例,展示瞭如何根據手勢資訊更新 UI 元素的狀態。接著,文章講解了如何利用 OnTouch 事件處理多點觸控,並以在畫布上繪製連線為例,說明瞭多點觸控的應用方式。在遊戲開發的部分,文章詳細說明瞭如何使用 TImageList 元件有效管理圖片資源,以及如何設計可適應不同螢幕尺寸的遊戲介面。最後,文章還介紹了遊戲計時器的實作方法,以及如何格式化遊戲時間並顯示在 UI 上。

使用FireMonkey開發多點觸控與手勢辨識應用

FireMonkey是一種跨平台的UI框架,讓開發者能夠輕鬆建立具有豐富互動性的應用程式。在本章中,我們將探討如何利用FireMonkey實作手勢辨識與多點觸控功能,並以「Game of Memory」遊戲為例,展示相關技術的實際應用。

手勢辨識

在FireMonkey中,手勢辨識是透過TGestureEvent事件處理的。下面是一個典型的TGestureEvent事件處理程式宣告:

TGestureEvent = procedure(Sender: TObject;
  const EventInfo: TGestureEventInfo;
  var Handled: Boolean) of object;

內容解密:

  • Sender: 觸發事件的物件。
  • EventInfo: 包含手勢相關資訊的結構,例如手勢ID、位置和其他屬性。
  • Handled: 布林變數,用於指示事件是否已被處理。

我們可以透過檢查EventInfo.GestureID來確定特定的手勢型別。例如,對於旋轉手勢(igiRotate),我們可以調整物件的旋轉角度。以下是一個範例實作:

procedure TFormSunGestures.FormGesture(Sender: TObject;
  const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
  if EventInfo.GestureID = igiRotate then
  begin
    if (TInteractiveGestureFlag.gfBegin in EventInfo.Flags) then
      FLastAngle := CircleSun.RotationAngle
    else if EventInfo.Angle <> 0 then
      CircleSun.RotationAngle := FLastAngle - (EventInfo.Angle * 180) / Pi;
  end;
end;

內容解密:

  1. 檢查手勢ID是否為igiRotate,確認是否為旋轉手勢。
  2. 如果是旋轉手勢開始(gfBegin),儲存當前的旋轉角度到FLastAngle
  3. 如果旋轉角度不為零,根據事件的角度資訊更新CircleSun的旋轉角度。

多點觸控

多點觸控允許使用者同時使用多個手指與應用程式互動。在FireMonkey中,我們可以透過OnTouch事件處理多點觸控。以下是一個範例:

procedure TFormTouch.FormTouch(Sender: TObject;
  const Touches: TTouches; const Action: TTouchAction);
var
  Count: Integer;
begin
  FBitmap.Canvas.BeginScene;
  try
    FBitmap.Clear(TAlphaColors.White);
    Count := Length(Touches);
    if Count > 1 then
      for var I := 0 to Count-2 do
        FBitmap.Canvas.DrawLine(
          Touches[i].Location,
          Touches[i+1].Location,
          TAlphaColorRec.Red);
  finally
    FBitmap.Canvas.EndScene;
  end;
  PaintBox1.Repaint;
end;

內容解密:

  1. 取得觸控點的數量(Count)。
  2. 如果有多於一個觸控點,遍歷這些點並使用DrawLine方法在相鄰點之間繪製紅線。
  3. 清除畫布並重新繪製,以反映最新的觸控狀態。

Game of Memory遊戲開發

「Game of Memory」是一款經典的記憶配對遊戲。玩家需要翻開相同的圖片來消除它們,最終目標是在最短的時間內消除所有圖片。

遊戲設計

  1. 遊戲介面:使用FireMonkey的2D UI功能建立遊戲板和控制按鈕。
  2. 圖片管理:利用TImageList元件管理遊戲中的圖片資源。
  3. 遊戲邏輯:實作圖片的隨機分配、翻開與消除邏輯。

使用TImageList管理圖片

在遊戲中,我們使用TImageList來集中管理圖片資源。這不僅便於存取,也有利於跨多個表單或專案重複使用這些資源。

// 在資料模組中新增TImageList並載入圖片

內容解密:

  1. 將所有遊戲相關的圖片資源新增到TImageList中。
  2. 在需要時,從TImageList中檢索圖片並在遊戲中使用。

使用影像與設計使用者介面

在開發「Game of Memory」應用程式時,我們需要有效地管理和使用影像資源。本章節將探討如何使用 TImageList 元件來儲存和管理影像,以及如何設計一個能夠適應不同裝置和螢幕大小的使用者介面。

使用 TImageList 管理影像

TImageList 元件是一個非常有用的工具,用於儲存和管理應用程式中使用的影像。它包含兩個主要的影像集合:Source 和 Destination。Source 集合儲存了所有新增到影像列表元件的影像,這些影像可以是不同的格式,如 BMP、PNG、JPEG、GIF 和 TIFF 等。Destination 集合則包含了根據 Source 集合中的影像建立的影像,這些影像將在應用程式中使用。

設定 TImageList

  1. TImageList 元件拖曳到資料模組上,並將其 Name 屬性更改為 ImageListMain
  2. 雙擊 ImageListMain 元件以開啟影像列表編輯器,在此您可以管理 Source 和 Destination 集合中的影像。
  3. 將所需的影像新增到 Source 集合中,然後根據需要將它們新增到 Destination 集合中。

在我們的範例中,我們新增了 18 個隨機選擇的國旗影像,它們的大小約為 80x80 畫素。前兩個影像具有特殊用途:第一個影像是完全白色的,用於在瓷磚被移除時顯示;第二個影像包含瓷磚的背面,用於顯示隱藏的影像。

設計多裝置使用者介面

在設計「Game of Memory」的主表單時,我們需要考慮到應用程式將在不同大小和方向的裝置上執行。因此,我們需要使用 FireMonkey 的佈局和對齊功能來建立一個能夠適應不同螢幕大小的使用者介面。

對齊、錨定和邊距

FireMonkey 提供了多種對齊選項,例如 ClientTopBottomLeftRight,用於控制元件在其父容器中的位置。TAnchors 屬性允許您將元件的某一側錨定到其父容器,從而在父容器大小改變時控制元件的大小和位置。TMargins 屬性則用於指定元件與其父容器之間的距離。

佈局元件

FireMonkey 提供了多種佈局元件,如 TLayoutTGridLayout,用於組織和管理使用者介面中的控制項。在我們的遊戲中,我們使用 TGridLayout 元件來建立一個瓷磚網格。將 TGridLayout 元件拖曳到主表單上,將其 Name 屬性更改為 GridLayoutTiles,並將其對齊到 Client 以佔據整個螢幕。

控制網格佈局

使用 ItemHeightItemWidth 屬性可以控制網格佈局中每個專案的大小。

程式碼範例:建立 TImageList

// 建立 TImageList 元件
var
  ImageList: TImageList;
begin
  ImageList := TImageList.Create(Self);
  ImageList.Name := 'ImageListMain';
  // 新增影像到 Source 集合
  ImageList.AddImage(LoadBitmapFromFile('image1.png'));
  ImageList.AddImage(LoadBitmapFromFile('image2.png'));
  // ...
end;

內容解密:

此範例展示瞭如何在程式碼中建立一個 TImageList 元件,並將其命名為 ImageListMain。接著,我們可以使用 AddImage 方法將影像新增到 Source 集合中。這些影像可以是從檔案載入的點陣圖。

圖表示例:FireMonkey 佈局元件

圖表翻譯: 此圖展示了 FireMonkey 中的佈局元件層次結構。TLayout 是最通用的佈局元件,而 TGridLayout 是用於建立網格佈局的特定元件。網格佈局中可以包含多個專案,如 Item1、Item2 等。

圖表內容解密:

此圖表說明瞭 FireMonkey 中不同佈局元件之間的關係。TLayout 是基礎佈局元件,而 TGridLayout 等特定佈局元件則繼承自 TLayout。這些佈局元件用於組織和管理使用者介面中的控制項。

透過使用 TImageList 和 FireMonkey 的佈局功能,我們可以建立一個強大且適應性強的使用者介面,以滿足不同裝置和螢幕大小的需求。

使用FireMonkey開發記憶遊戲介面

在這個章節中,我們將使用TGlyph元件來顯示來自資料模組中影像列表的點陣圖。雖然我們也可以使用TImage元件,但那樣就需要為每個TImage直接載入點陣圖,這樣不如使用一套點陣圖來得有效率。讓我們開始吧:

設定主表單的背景顏色與新增TGlyph控制項

  1. 將主表單的背景顏色改為白色。展開Fill屬性,然後將Color改為白色,並將Kind設為Solid。
  2. 在表單的使用單元(uses clause)中加入uDMGameOfMem單元,並將TGlyph控制項拖曳到網格佈局(grid layout)上。將其Images屬性指向DMGameOfMem.ImageListMain,並將ImageIndex設為1。這樣,我們應該能在Glyph控制項上看到影像列表中Destination集合的第二張圖片,這代表一個隱藏的瓷磚。將Glyph控制項的四個邊距(margins)都設為2,以便在網格中的不同瓷磚之間保持一定的距離。

新增工具列按鈕與其他控制項

  1. 將一個TSpeedButton元件拖曳到工具列上,將其Name屬性設為SpdbtnPlay,對齊到左邊,並將StyleLookup屬性設為playtoolbutton。水平調整按鈕大小,使其變小。這樣會固定按鈕的寬度,使其與高度相同。接著,將一個TLabel元件拖曳到工具列上,將其Name設為LblScore,Text設為「Game of Memory」,然後對齊到客戶端(Client)。
  2. 將一個TComboBox元件拖曳到工具列上,對齊到右邊。在這裡,我們將控制網格的大小。將該組合框的Name屬性設為CmbbxLevel。在其Items屬性中,輸入八個難度級別,從4對開始,一直到18對,如圖5.11所示。為了避免列表過長,難度級別以兩對為單位遞增。

TComboBox的Items屬性設定範例程式碼

// 在CmbbxLevel的Items屬性中新增難度級別
CmbbxLevel.Items.Add('4 對');
CmbbxLevel.Items.Add('6 對');
// ... 繼續新增直到18對

設定遊戲的計時器與相關功能

  1. 將一個TTimer元件拖曳到表單上,將其名稱改為TimerGame,並將Interval設為50。當計時器啟用時,遊戲正在進行;當計時器未啟用時,遊戲停止。在Score標籤上,我們將顯示遊戲的時間。為此,我們需要一個函式來格式化經過的時間。

格式化遊戲時間的函式

function GameTimeToStr(Value: TTime): string;
var
  H, Min, Sec, Msec: Word;
  S: string;
begin
  DecodeTime(Value, H, Min, Sec, Msec);
  S := '時間:';
  if H > 0 then
    S := S + H.ToString + '小時 ';
  if Min > 0 then
    S := S + Min.ToString + '分鐘 ';
  S := S + Sec.ToString + '.' + Pad3Zeros(Msec.ToString) + '秒';
  Result := S;
end;

function Pad3Zeros(Value: string): string; inline;
var
  I: integer;
begin
  I := Length(Value);
  if I = 3 then
    Result := Value
  else if I = 2 then
    Result := '0' + Value
  else if I = 1 then
    Result := '00' + Value
  else
    Result := '000';
end;

圖表翻譯:遊戲時間格式化流程圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title FireMonkey開發多點觸控手勢記憶遊戲

package "Docker 架構" {
    actor "開發者" as dev

    package "Docker Engine" {
        component [Docker Daemon] as daemon
        component [Docker CLI] as cli
        component [REST API] as api
    }

    package "容器運行時" {
        component [containerd] as containerd
        component [runc] as runc
    }

    package "儲存" {
        database [Images] as images
        database [Volumes] as volumes
        database [Networks] as networks
    }

    cloud "Registry" as registry
}

dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置

@enduml

圖表翻譯: 此圖示呈現了遊戲時間格式化的流程。首先檢查時間中的小時數,如果大於0,則新增到結果中。接著檢查分鐘數,同樣如果大於0則新增到結果。最後,無論前面的檢查結果如何,都會新增秒數和毫秒數到結果中,並傳回最終的格式化結果。

新增私有欄位與實作遊戲邏輯

  1. 在表單類別中新增一個名為FGameStart的私有欄位,用於儲存遊戲開始的時間。在OnTimer事件中加入以下程式碼:
procedure TFormMain.TimerGameTimer(Sender: TObject);
begin
  var Delta := Now - FGameStart;
  var S := GameTimeToStr(Delta);
  LblScore.Text := S;
end;

GameStart方法實作範例

procedure TFormMain.GameStart;
const
  SHUFFLE_TIMES = 10;
var
  Indices: array of integer;
begin
  // 移除網格中的所有glyph控制項(如果有的話)
  GrdltTiles.DeleteChildren;
  
  var PairsCount := CurrPairsCount;
  var TilesCount := PairsCount * 2;
  FVisibleGlyph := nil;
  FPairsLeft := PairsCount;
  
  // 初始化索引列表,隨機化並加入偏移量2
  SetLength(Indices, TilesCount);
  for var I := 0 to PairsCount-1 do
  begin
    Indices[I] := I;
    Indices[I + PairsCount] := I;
  end;
  // ... 繼續實作隨機化與其他邏輯

#### 內容解密:

此段程式碼初始化了GameStart方法,首先清除了網格佈局中的所有子控制項,然後根據當前的配對數量設定了瓷磚的總數。接著,初始化了一個索引列表,用於儲存瓷磚對應的影像索引,並將這個列表隨機化,以實作瓷磚的隨機排列。

重點回顧

本章節介紹瞭如何使用FireMonkey建立記憶遊戲的主介面,包括設定背景顏色、新增TGlyph控制項、工具列按鈕和其他控制項。此外,還實作了遊戲的計時器功能和相關邏輯,為遊戲的核心功能打下了基礎。透過本章節的學習,讀者應該能夠掌握使用FireMonkey進行介面設計和基本邏輯實作的能力。