如何使用Gloss库实现自定义图片的屏幕移动?
问题描述
我正在使用Gloss库开发一款简易游戏,采用BMP文件作为精灵,加载后需实现动画与移动效果,尝试用animate或simulate函数来完成。simulate函数的一个参数类型为(model -> Picture),即接收存储游戏状态的自定义数据类型并返回Picture的函数,但加载文件常用的loadBMP函数类型为FilePath -> IO Picture。我难以从整体层面把握实现图片移动的具体方案,尤其困惑于loadBMP返回IO Picture而非Picture的问题。
提供的代码
module Main(main) where import Graphics.Gloss import System.IO import Control.Monad import Graphics.Gloss.Data.ViewPort data GameState = State{ boardpic::Picture , ballpic :: Picture , ballCoords :: (Float, Float) , ballSpeed :: (Float, Float) , paddle1pic::Picture , p1Coords :: (Float, Float) , paddle2pic::Picture , p2Coords:: (Float,Float) , paused::Bool , turnscore::Integer } deriving (Show) moveBall time state = state {ballCoords = (xnew, ynew)} where (xposbefore, yposbefore) = ballCoords state (xspeed, yspeed) = ballSpeed state (xnew, ynew) = (xposbefore + xspeed*time, yposbefore+xspeed*time) render::GameState->Picture render state = pictures[board, paddle1, paddle2, ball] where board = boardpic state ball = translate 0 0 $ ballpic state paddle1 = translate (-300) 0 $ paddle1pic state paddle2 = translate 300 0 $ paddle2pic state --calculates running sum of score for the player from list score xs = tail.reverse $ foldl f [0] xs where f (y:ys) x = (x+y):y:ys window :: Display window = InWindow "SuperPong" (800, 800) (0,0) background::Color background = blue fps :: Int fps = 60 printState::GameState->IO () printState state = do putStrLn $ show $ boardpic state putStrLn $ show $ ballpic state putStrLn $ ("ball coordinates: ")++(show $ ballCoords state) putStrLn $ ("paddle1 coords: " )++(show $ p1Coords state) putStrLn $ ("paddle2 coords: " )++(show $ p2Coords state) putStrLn $ ("paused or not: " ) ++ (show $ paused state) putStrLn $ ("score: " )++(show $ turnscore state) --paddlecollision:: --outofbounds:: main :: IO () main = do pad1 <- loadBMP "paddle1.bmp" pad2 <- loadBMP "paddle2.bmp" board <- loadBMP "board.bmp" ball1 <- loadBMP "ball.bmp" let initstate = State{ boardpic = board , ballpic = ball1 , ballCoords = (-10, 10) , ballSpeed = (50, -50) , paddle1pic = pad1 , p1Coords = (-300, 0) , paddle2pic = pad2 , p2Coords = (300, 0) , paused = False , turnscore = 0 } let update _ = moveBall printState initstate let allpics = render initstate simulate window background fps initstate render update
解决方案
1. 理解IO Picture的处理逻辑
loadBMP返回IO Picture是因为文件读取属于IO操作,必须在IO上下文执行。你当前的代码已经做对了核心步骤:在main函数(IO上下文)里加载所有BMP资源,然后将它们存入GameState中。把IO操作限制在程序入口,后续用纯函数处理游戏状态,这是Haskell处理IO的标准方式,你不需要带着IO类型的Picture进入render或update函数,当前的代码结构已经解决了这个困惑。
2. 修复图片移动的核心实现
修正moveBall的计算错误
你在计算球的Y坐标时误用了X方向的速度,改成如下:
moveBall time state = state {ballCoords = (xnew, ynew)} where (xposbefore, yposbefore) = ballCoords state (xspeed, yspeed) = ballSpeed state (xnew, ynew) = (xposbefore + xspeed*time, yposbefore + yspeed*time)
让render根据游戏状态动态平移图片
当前render用的是固定坐标,需要改成读取GameState里的坐标值来平移精灵:
render::GameState->Picture render state = pictures[board, paddle1, paddle2, ball] where board = boardpic state -- 根据ballCoords动态平移球 (ballX, ballY) = ballCoords state ball = translate ballX ballY $ ballpic state -- 根据p1Coords动态平移左 paddle (p1X, p1Y) = p1Coords state paddle1 = translate p1X p1Y $ paddle1pic state -- 根据p2Coords动态平移右 paddle (p2X, p2Y) = p2Coords state paddle2 = translate p2X p2Y $ paddle2pic state
3. 修正update函数的类型匹配
simulate要求的update函数类型是ViewPort -> Float -> GameState -> GameState,当前代码忽略了ViewPort参数,同时可以加入暂停逻辑:
update :: ViewPort -> Float -> GameState -> GameState update _ time state = if paused state then state else moveBall time state
4. 整体流程梳理
- 资源加载:在
main的IO上下文里加载所有BMP资源,构造包含初始坐标、速度的GameState - 状态更新:
simulate每帧调用update,根据时间差更新游戏状态中的坐标(比如球的位置),暂停时不更新 - 画面渲染:每帧调用
render,根据GameState里的坐标和图片资源,用translate将精灵放到正确位置,生成要显示的Picture - 自动渲染:Gloss库自动将生成的
Picture渲染到窗口,形成连续动画效果
内容的提问来源于stack exchange,提问作者floxam
相关产品推荐
相关产品推荐

