LoginSignup
2
0

More than 3 years have passed since last update.

NimでSDL2 ④ FPS(frames per second)の調整

Posted at

はじめに

NimでSDL2 ① 準備とウィンドウ表示
NimでSDL2 ② 画像を表示
NimでSDL2 ③ 入力処理

これまでメインループで毎回適当にwaitしてた箇所を
それなりにちゃんと調整してみる。

FPSとは

フレームレートは1秒間に何回画面を更新(フレーム)するかって感じで、
"frames per second"の略でFPSです。
一般的なゲームだと1秒間に60回、つまり60FPSですね。

概要

60FPSを例にすると、1秒間に60フレームなので、
1フレームあたり(1000/60=16.6...)なので、
とりあえず大体16msですね。

なので、ゲームのメインループで
ゲーム内の処理(入力、計算、画像描画)を行って、
16ms以下なら、その差分の時間waitすれば良さげですね。

例えばゲーム処理が6msで終わったなら10ms待てばおk。

コード

追記箇所はコメント参照

import sdl2, sdl2/image, os, sugar, sets

template sdlFailIf(cond: typed, desc: string) =
  if cond: raise Exception.newException(
    desc & ": " & $getError())

# FPS調整オブジェクト
type FPS* = ref object
  # 次のフレーム実行時間と目標フレーム期間
  frameTime, targetFramePeriod: uint32

# 引数のFPSから目標フレーム期間を計算してオブジェクトを作成
proc newFPS*(fps: int): FPS =
  # 1秒(1000ミリ秒)をFPSで割って目標フレーム期間を算出
  FPS(targetFramePeriod: uint32(1000 / fps))

# フレーム実行時間を調整
proc adjust*(self: FPS) =
  # 現在時刻を取得
  let now = getTicks()
  # 次のフレーム実行時間まで時間があるなら
  if self.frameTime > now:
    # 次のフレーム実行時間まで待つ
    delay(self.frameTime - now)
  # 次のフレーム実行時間に目標フレーム期間を足して更新する
  self.frameTime += self.targetFramePeriod

type SDL2Context = ref object
  window: WindowPtr
  renderer: RendererPtr
  cleanups: seq[proc ()]
  pressed: HashSet[cint]
  # FPSを保持
  fps: FPS

proc cleanup(self: SDL2Context) =
  while self.cleanups.len > 0:
    (self.cleanups.pop)()

proc init(self: SDL2Context, title: string, w, h: cint): SDL2Context {.discardable.} =
  result = self
  sdlFailIf(not sdl2.init(INIT_VIDEO), "SDL2 init")
  self.cleanups.add(() => sdl2.quit())
  self.window = createWindow(title = title,
                            x = SDL_WINDOWPOS_CENTERED,
                            y = SDL_WINDOWPOS_CENTERED,
                            w = w, h = h,
                            flags = SDL_WINDOW_SHOWN)
  sdlFailIf(self.window.isNil, "createWindow")
  self.cleanups.add(() => self.window.destroy)
  sdlFailIf(image.init(IMG_INIT_PNG) != IMG_INIT_PNG, "image init")
  self.cleanups.add(() => image.quit())
  self.renderer = self.window.createRenderer(
    index = -1,
    flags = Renderer_Accelerated or Renderer_PresentVsync)
  sdlFailIf(self.renderer.isNil, "createRenderer")
  self.cleanups.add(() => self.renderer.destroy)
  self.renderer.setDrawColor(r=0, g=128, b=128)
  self.pressed = initHashSet[cint]()
  # 60フレームでFPSを作成
  self.fps = newFPS(60)

proc loadTexture(self: SDL2Context, path: string): TexturePtr =
  result = self.renderer.loadTexture(path)
  sdlFailIf(result.isNil, "loadTexture")

proc keyStateUpdate*(self: SDL2Context, event: KeyboardEventPtr) =
  case event.kind
  of KeyDown: self.pressed.incl(event.keysym.sym)
  of KeyUp: self.pressed.excl(event.keysym.sym)
  else: discard

proc pollEvent(self: SDL2Context): bool =
  result = true
  var event = defaultEvent
  while pollEvent(event):
    case event.kind:
    of QuitEvent: return false
    of KeyDown, KeyUp: self.keyStateUpdate(event.key)
    else: discard

proc mainLoop(self: SDL2Context) =
  let neko = self.loadTexture("genbaneko.png")
  let
    w: cint = 256
    h: cint = 256
    speed: cint = 3
  var
    windowWidth: cint
    windowHeight: cint
  self.window.getSize(windowWidth, windowHeight)
  var dst = rect(windowWidth div 2 - w div 2, windowHeight div 2 - h div 2, w, h)
  while self.pollEvent:
    if K_RIGHT in self.pressed: dst.x += speed
    if K_LEFT  in self.pressed: dst.x -= speed
    if K_DOWN  in self.pressed: dst.y += speed
    if K_UP    in self.pressed: dst.y -= speed
    self.renderer.clear
    self.renderer.copyEx(neko, nil, dst.addr, 0.0, nil, SDL_FLIP_NONE)
    self.renderer.present
    # フレーム実行時間を調整
    self.fps.adjust

proc main() =
  let sdl2 = SDL2Context()
  defer: sdl2.cleanup
  sdl2.init("SDL2 Keyboard sample", 640, 480).mainLoop

main()

次は?

音とか出して行きたいですね。

以上〜。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0