多米诺的数据结构(DominoPiece.lua)
基本数据:
function DominoPiece:Initialize(id, topValue, bottomValue)
--self.ID = id 这个可能不需要,因为每个关卡都是要手动设计的,以后需要的话就加上
self.TopValue = topValue -- 上半部分的点数(1-6)
self.BottomValue = bottomValue -- 下半部分的点数(1-6)
self.TopUsed = false -- 上半部分是否已使用
self.BottomUsed = false -- 下半部分是否已使用
self.Position = UE.FVector2D() -- 在UI中的位置,解释一下这个UE.FVector2D是创建一个(0,0)的向量
self.IsFlipped = false -- 是否翻开(可见)
end
匹配方法:
function DominoPiece:CanMatch(value)
-- 检查是否可以与给定值匹配
if not self.TopUsed and self.TopValue == value then
return true, "Top"
elseif not self.BottomUsed and self.BottomValue == value then
return true, "Bottom"
end
return false, nil
end
使用函数
UseHalf("Top")来标记半边已经用完
function DominoPiece:UseHalf(half)
-- 标记牌的一半为已使用
if half == "Top" then
self.TopUsed = true
elseif half == "Bottom" then
self.BottomUsed = true
end
end
检测函数
function DominoPiece:IsFullyUsed()
-- 检查牌是否完全使用(两半都已用)
return self.TopUsed and self.BottomUsed
end
游戏核心图结构(DominoGraph.lua)
基本数据
对于DominoDream我的想法是,如果一张牌压在一张牌的下面,就说明从下面到上面存在一条有向边,所以需要有边的数据,所有点的数据,所有点的入度
function DominoGraph:Initialize()
self.Pieces = {} -- 存储所有牌的引用
self.Edges = {} -- 存储有向边:Edges[fromID] = {toID1, toID2, ...}
self.InDegree = {} -- 存储每张牌的入度:InDegree[id] = count
end
添加一个Domino到游戏中
在右上角可能有完成某个指标就能够增加一张牌
function DominoGraph:AddPiece(piece)
-- 添加一张牌到图中
self.Pieces[piece.ID] = piece
self.Edges[piece.ID] = {}
self.InDegree[piece.ID] = 0
end
添加一条边
function DominoGraph:AddEdge(fromID, toID)
-- 添加一条从fromID到toID的有向边(表示toID压在fromID上面)
table.insert(self.Edges[fromID], toID)
self.InDegree[toID] = (self.InDegree[toID] or 0) + 1
end
删除一条边
这里解释一下ipairs的意思是遍历table中的所有数据
下面的主要作用是将所有被它压住的Domino的入度减一,并且修改总边计数器与其相关的边
function DominoGraph:RemovePiece(pieceID)
-- 从图中移除一张牌及其关联的边
-- 更新受影响的牌的入度
for _, toID in ipairs(self.Edges[pieceID] or {}) do
self.InDegree[toID] = self.InDegree[toID] - 1
end
-- 移除该牌
self.Pieces[pieceID] = nil
self.Edges[pieceID] = nil
-- 从其他牌的边列表中移除
for fromID, toList in pairs(self.Edges) do
for i = #toList, 1, -1 do
if toList[i] == pieceID then
table.remove(toList, i)
end
end
end
return self:GetFlippablePieces()
end
返回所有可以翻开的牌(入度为0)
返回当前可以翻开的牌,实际上就是查询所有的边的入度,并且进行标记,这个后面可能要加入动画
function DominoGraph:GetFlippablePieces()
-- 返回所有出度为0的牌(可以翻开的牌)
local flippable = {}
for id, piece in pairs(self.Pieces) do
if #(self.Edges[id] or {}) == 0 and not piece.IsFlipped then
piece.IsFlipped = true
table.insert(flippable, piece)
end
end
return flippable
end
返回所有已经翻开的牌
function DominoGraph:GetVisiblePieces()
-- 返回所有已翻开的牌
local visible = {}
for _, piece in pairs(self.Pieces) do
if piece.IsFlipped then
table.insert(visible, piece)
end
end
return visible
end
检查图是否为空
这个next有两个参数和返回值,输入的第一个值是table表,第二个值是index,表示从这里开始next,如果在遍历的过程中有值,就返回其index和value,如果没有就返回nil
function DominoGraph:IsEmpty()
-- 检查图是否为空(所有牌都被移除)
return next(self.Pieces) == nil
end
玩家的手牌管理(PlayerHand.lua)
玩家可使用的Domino
function PlayerHand:Initialize(initialPieces)
self.Pieces = initialPieces or {} -- 手中的牌列表
end
返回手牌中最右边的牌
function PlayerHand:GetRightmostPiece()
-- 获取最右边的牌
if #self.Pieces > 0 then
return self.Pieces[#self.Pieces]
end
return nil
end
移除最右边的牌并且记录
function PlayerHand:RemoveRightmostPiece()
-- 移除并返回最右边的牌
if #self.Pieces > 0 then
return table.remove(self.Pieces)
end
return nil
end
添加一张手牌
function PlayerHand:AddPiece(piece)
-- 添加一张牌到手中
table.insert(self.Pieces, piece)
end
获得最右边的牌的可用值
function PlayerHand:GetMatchValue()
-- 获取最右边牌的可用值
local piece = self:GetRightmostPiece()
if piece then
if not piece.TopUsed then
return piece.TopValue
elseif not piece.BottomUsed then
return piece.BottomValue
end
end
return nil
end
return PlayerHand
GameManager(DominoGameManager.lua)
初始化数据
这个我有点疑问
function DominoGameManager:Initialize()
self.Graph = require "DominoGraph".New()
self.PlayerHand = require "PlayerHand".New()
self.AvailablePieces = {} -- 可以匹配的牌
end
初始化所有数据
function DominoGameManager:SetupGame(tableSetup, handPieces)
-- 设置游戏初始状态
-- tableSetup: {pieces={piece1, piece2, ...}, edges={{from1, to1}, {from2, to2}, ...}}
-- 添加桌面上的牌
for _, piece in ipairs(tableSetup.pieces) do
self.Graph:AddPiece(piece)
end
-- 添加覆盖关系
for _, edge in ipairs(tableSetup.edges) do
self.Graph:AddEdge(edge[1], edge[2])
end
-- 初始化可翻开的牌
local flippablePieces = self.Graph:GetFlippablePieces()
self.AvailablePieces = flippablePieces
-- 设置玩家手牌
self.PlayerHand = require "PlayerHand".New(handPieces)
return self
end
查找可以匹配的牌
function DominoGameManager:FindMatchingPieces(value)
-- 查找可以与给定值匹配的牌
local matches = {}
for _, piece in ipairs(self.AvailablePieces) do
local canMatch, half = piece:CanMatch(value)
if canMatch then
table.insert(matches, {piece=piece, half=half})
end
end
return matches
end
获取当前玩家状态
function DominoGameManager:GetGameState()
-- 获取当前游戏状态
local matchValue = self.PlayerHand:GetMatchValue()
local matchingPieces = {}
if matchValue then
matchingPieces = self:FindMatchingPieces(matchValue)
end
return {
handPieces = self.PlayerHand.Pieces,
availablePieces = self.AvailablePieces,
matchValue = matchValue,
matchingPieces = matchingPieces,
hasMatches = #matchingPieces > 0
}
end
这一步以后要换成射线检测之类的
function DominoGameManager:PlayMove(tablePieceID, tablePieceHalf)
-- 执行一步移动
-- 1. 找到要拿起的桌面牌
local tablePiece = nil
for _, piece in ipairs(self.AvailablePieces) do
if piece.ID == tablePieceID then
tablePiece = piece
break
end
end
if not tablePiece then
return false, "无法找到选择的牌"
end
-- 2. 验证匹配
local value = self.PlayerHand:GetMatchValue()
local canMatch, half = tablePiece:CanMatch(value)
if not canMatch or half ~= tablePieceHalf then
return false, "选择的牌不匹配"
end
-- 3. 从桌面移除牌
self.Graph:RemovePiece(tablePieceID)
-- 4. 更新可用牌列表
local newFlippable = self.Graph:GetFlippablePieces()
for _, piece in ipairs(newFlippable) do
table.insert(self.AvailablePieces, piece)
end
-- 从可用列表中移除已用牌
for i = #self.AvailablePieces, 1, -1 do
if self.AvailablePieces[i].ID == tablePieceID then
table.remove(self.AvailablePieces, i)
break
end
end
-- 5. 更新手牌
local handPiece = self.PlayerHand:GetRightmostPiece()
if not handPiece.TopUsed and handPiece.TopValue == value then
handPiece:UseHalf("Top")
elseif not handPiece.BottomUsed and handPiece.BottomValue == value then
handPiece:UseHalf("Bottom")
end
-- 6. 如果手牌已完全使用,舍弃它
if handPiece:IsFullyUsed() then
self.PlayerHand:RemoveRightmostPiece()
end
-- 标记桌面牌的已用半部分
tablePiece:UseHalf(tablePieceHalf)
-- 7. 检查胜利条件
local isWin = self.Graph:IsEmpty()
return true, {
isWin = isWin,
newFlippable = newFlippable
}
end
游戏模式参考
-- DominoGameMode.lua
-- 游戏模式,负责管理游戏全局逻辑
local DominoGameMode = Class()
local DominoGameManager = require "DominoGameManager"
function DominoGameMode:Initialize(Initializer)
-- 调用BP_DominoGameMode的父类构造函数
self.Super:Initialize(Initializer)
-- 初始化游戏管理器
self.GameManager = DominoGameManager.New()
-- 游戏配置参数
self.NumHandPieces = 7 -- 初始手牌数量
self.NumTablePieces = 28 -- 桌面牌数量
-- 游戏状态
self.IsGameStarted = false
self.IsGameOver = false
self.Winner = nil
end
function DominoGameMode:BeginPlay()
self.Super:BeginPlay()
UE.UGameplayStatics.SetGamePaused(self:GetWorld(), true)
end
function DominoGameMode:StartNewGame()
self.IsGameStarted = true
self.IsGameOver = false
self.Winner = nil
-- 重置游戏状态并创建新的游戏
self:SetupNewGame()
-- 取消游戏暂停
UE.UGameplayStatics.SetGamePaused(self:GetWorld(), false)
-- 广播游戏开始事件
self:OnGameStarted()
end
function DominoGameMode:SetupNewGame()
-- 创建所有牌
local allPieces = self:CreateAllDominoPieces()
-- 随机选择手牌和桌面牌
local handPieces, tablePieces = self:DistributePieces(allPieces)
-- 创建桌面布局(牌的位置和覆盖关系)
local tableSetup = self:CreateTableSetup(tablePieces)
-- 初始化游戏管理器
self.GameManager:SetupGame(tableSetup, handPieces)
-- 刷新UI
self:UpdateGameUI()
end
function DominoGameMode:CreateAllDominoPieces()
-- 创建一副完整的多米诺牌
local DominoPiece = require "DominoPiece"
local pieces = {}
local id = 1
for i = 1, 6 do
for j = 1, 6 do
local piece = DominoPiece.New(id, i, j)
table.insert(pieces, piece)
id = id + 1
end
end
-- 随机打乱牌的顺序
for i = #pieces, 2, -1 do
local j = math.random(i)
pieces[i], pieces[j] = pieces[j], pieces[i]
end
return pieces
end
function DominoGameMode:DistributePieces(allPieces)
-- 分配手牌和桌面牌
local handPieces = {}
local tablePieces = {}
-- 分配手牌
for i = 1, math.min(self.NumHandPieces, #allPieces) do
table.insert(handPieces, table.remove(allPieces, 1))
end
-- 分配桌面牌
for i = 1, math.min(self.NumTablePieces, #allPieces) do
table.insert(tablePieces, table.remove(allPieces, 1))
end
return handPieces, tablePieces
end
function DominoGameMode:CreateTableSetup(tablePieces)
-- 创建桌面牌的布局
-- 这里使用简单的随机覆盖关系
local tableSetup = {
pieces = tablePieces,
edges = {}
}
-- 创建一些随机的覆盖关系(边)
local numEdges = math.floor(#tablePieces * 0.3) -- 约30%的牌会覆盖其他牌
for i = 1, numEdges do
local fromIndex = math.random(#tablePieces)
local toIndex = math.random(#tablePieces)
-- 避免自环和重复边
if fromIndex ~= toIndex then
local fromID = tablePieces[fromIndex].ID
local toID = tablePieces[toIndex].ID
-- 检查是否会导致循环依赖
local willCreateCycle = false
-- 简单检测:如果已经存在从toID到fromID的路径,则会形成循环
for _, edge in ipairs(tableSetup.edges) do
if edge[1] == toID and edge[2] == fromID then
willCreateCycle = true
break
end
end
if not willCreateCycle then
table.insert(tableSetup.edges, {fromID, toID})
end
end
end
return tableSetup
end
function DominoGameMode:UpdateGameUI()
-- 获取当前游戏状态
local gameState = self.GameManager:GetGameState()
-- 更新UI(通过调用蓝图实现的函数)
self:UpdateHandPiecesUI(gameState.handPieces)
self:UpdateTablePiecesUI(gameState.availablePieces)
self:UpdateMatchIndicator(gameState.matchValue, gameState.hasMatches)
-- 高亮可匹配的牌
if gameState.hasMatches then
local matchingIDs = {}
for _, match in ipairs(gameState.matchingPieces) do
table.insert(matchingIDs, match.piece.ID)
end
self:HighlightMatchingPieces(matchingIDs)
end
end
-- 这些函数将在蓝图中实现
function DominoGameMode:UpdateHandPiecesUI(handPieces)
-- 在蓝图中实现
end
function DominoGameMode:UpdateTablePiecesUI(tablePieces)
-- 在蓝图中实现
end
function DominoGameMode:UpdateMatchIndicator(matchValue, hasMatches)
-- 在蓝图中实现
end
function DominoGameMode:HighlightMatchingPieces(pieceIDs)
-- 在蓝图中实现
end
function DominoGameMode:OnGameStarted()
-- 在蓝图中实现
end
function DominoGameMode:OnGameOver(isWin)
-- 在蓝图中实现
end
-- 游戏操作API,可以从UI调用
function DominoGameMode:PlayPiece(tablePieceID, half)
if self.IsGameOver then return false end
local success, result = self.GameManager:PlayMove(tablePieceID, half)
if success then
-- 更新UI
self:UpdateGameUI()
-- 检查胜利条件
if result.isWin then
self.IsGameOver = true
self.Winner = "Player"
self:OnGameOver(true)
end
end
return success
end
function DominoGameMode:DiscardHandPiece()
if self.IsGameOver then return false end
local success = self.GameManager:DiscardHandPiece()
if success then
-- 更新UI
self:UpdateGameUI()
-- 检查是否还有手牌
local gameState = self.GameManager:GetGameState()
if #gameState.handPieces == 0 then
self.IsGameOver = true
self.Winner = nil -- 平局
self:OnGameOver(false)
end
end
return success
end
return DominoGameMode
-- DominoPieceWidget.lua
-- 多米诺牌的UI组件
local DominoPieceWidget = Class()
function DominoPieceWidget:Construct()
self.Super:Construct()
self.PieceData = nil
self.IsHighlighted = false
self.IsHandPiece = false
-- 绑定点击事件
self.Button_Top.OnClicked:Add(self, self.OnTopClicked)
self.Button_Bottom.OnClicked:Add(self, self.OnBottomClicked)
end
function DominoPieceWidget:SetPieceData(pieceData, isHandPiece)
self.PieceData = pieceData
self.IsHandPiece = isHandPiece or false
-- 更新UI显示
self:UpdateVisuals()
end
function DominoPieceWidget:UpdateVisuals()
if not self.PieceData then return end
-- 设置点数显示
self.Text_TopValue:SetText(tostring(self.PieceData.TopValue))
self.Text_BottomValue:SetText(tostring(self.PieceData.BottomValue))
-- 设置已使用状态
local topOpacity = self.PieceData.TopUsed and 0.3 or 1.0
local bottomOpacity = self.PieceData.BottomUsed and 0.3 or 1.0
self.Panel_Top:SetRenderOpacity(topOpacity)
self.Panel_Bottom:SetRenderOpacity(bottomOpacity)
-- 设置可见性
local visibility = self.PieceData.IsFlipped and UE.ESlateVisibility.Visible or UE.ESlateVisibility.Hidden
self:SetVisibility(visibility)
-- 设置按钮可交互性
self.Button_Top:SetIsEnabled(not self.PieceData.TopUsed and not self.IsHandPiece)
self.Button_Bottom:SetIsEnabled(not self.PieceData.BottomUsed and not self.IsHandPiece)
-- 更新高亮状态
self:SetHighlighted(self.IsHighlighted)
end
function DominoPieceWidget:SetHighlighted(highlight)
self.IsHighlighted = highlight
local borderColor = highlight and UE.FLinearColor(1.0, 0.8, 0.0, 1.0) or UE.FLinearColor(0.0, 0.0, 0.0, 1.0)
local borderThickness = highlight and 3.0 or 1.0
self.Border_Main:SetBrushColor(borderColor)
self.Border_Main:SetDesiredSizeScale(UE.FVector2D(1.0, borderThickness))
end
function DominoPieceWidget:OnTopClicked()
if self.OnPieceHalfClicked and self.PieceData then
self.OnPieceHalfClicked(self.PieceData.ID, "Top")
end
end
function DominoPieceWidget:OnBottomClicked()
if self.OnPieceHalfClicked and self.PieceData then
self.OnPieceHalfClicked(self.PieceData.ID, "Bottom")
end
end
return DominoPieceWidget
-- DominoGameHUD.lua
-- 主游戏UI
local DominoGameHUD = Class()
function DominoGameHUD:Construct()
self.Super:Construct()
-- 获取游戏模式引用
self.GameMode = UE.UGameplayStatics.GetGameMode(self)
-- 绑定按钮事件
self.Button_StartGame.OnClicked:Add(self, self.OnStartGameClicked)
self.Button_DiscardPiece.OnClicked:Add(self, self.OnDiscardPieceClicked)
-- 初始化UI状态
self:UpdateUIState(false)
end
function DominoGameHUD:UpdateUIState(gameStarted)
self.Panel_MainMenu:SetVisibility(gameStarted and UE.ESlateVisibility.Hidden or UE.ESlateVisibility.Visible)
self.Panel_GameUI:SetVisibility(gameStarted and UE.ESlateVisibility.Visible or UE.ESlateVisibility.Hidden)
end
function DominoGameHUD:SetupPieceWidgets(container, pieces, isHandPieces)
-- 清空容器
container:ClearChildren()
-- 创建并添加牌组件
for _, piece in ipairs(pieces) do
local widget = self:CreatePieceWidget()
widget:SetPieceData(piece, isHandPieces)
-- 如果是桌面牌,绑定点击事件
if not isHandPieces then
widget.OnPieceHalfClicked = function(pieceID, half)
self:OnTablePieceClicked(pieceID, half)
end
end
container:AddChild(widget)
end
end
function DominoGameHUD:CreatePieceWidget()
-- 创建一个多米诺牌组件实例
return UE.UWidgetBlueprintLibrary.Create(self, self.DominoPieceWidgetClass)
end
function DominoGameHUD:HighlightMatchingPieces(matchingIDs)
-- 高亮所有可匹配的牌
for i = 0, self.Container_TablePieces:GetChildrenCount() - 1 do
local widget = self.Container_TablePieces:GetChildAt(i)
local isMatching = false
for _, id in ipairs(matchingIDs) do
if widget.PieceData and widget.PieceData.ID == id then
isMatching = true
break
end
end
widget:SetHighlighted(isMatching)
end
end
function DominoGameHUD:UpdateMatchIndicator(matchValue, hasMatches)
-- 更新匹配指示器
local text = "当前匹配值: " .. (matchValue or "无")
self.Text_MatchValue:SetText(text)
local color = hasMatches and UE.FLinearColor(0.0, 1.0, 0.0, 1.0) or UE.FLinearColor(1.0, 0.0, 0.0, 1.0)
self.Text_MatchValue:SetColorAndOpacity(color)
-- 更新舍弃按钮状态
self.Button_DiscardPiece:SetIsEnabled(true) -- 通常始终允许舍弃
end
function DominoGameHUD:OnStartGameClicked()
if self.GameMode then
self.GameMode:StartNewGame()
self:UpdateUIState(true)
end
end
function DominoGameHUD:OnDiscardPieceClicked()
if self.GameMode then
self.GameMode:DiscardHandPiece()
end
end
function DominoGameHUD:OnTablePieceClicked(pieceID, half)
if self.GameMode then
self.GameMode:PlayPiece(pieceID, half)
end
end
function DominoGameHUD:ShowGameOverScreen(isWin)
-- 显示游戏结束界面
self.Panel_GameOver:SetVisibility(UE.ESlateVisibility.Visible)
local resultText = isWin and "胜利!" or "游戏结束"
self.Text_GameResult:SetText(resultText)
end
return DominoGameHUD
unlua的单例模式
-- singleton.lua
local Singleton = {}
local instance = nil
function Singleton.getInstance()
if not instance then
instance = {
-- 单例的属性和方法
value = 0,
setValue = function(self, val)
self.value = val
end,
getValue = function(self)
return self.value
end
}
end
return instance
end
return Singleton
-- 在其他地方使用
local SingletonModule = require "singleton"
local instance = SingletonModule.getInstance()
instance:setValue(10)