LoginSignup
29
12

More than 1 year has passed since last update.

ゲームを作りたい(Godot + Nim)

Last updated at Posted at 2021-04-14

はじめに

最近Godotというゲームエンジンを知りました
公式サイトhttps://godotengine.org

調べてみるとNimで書けるそうなので、書いてみます

Nimで書く

Godotではゲーム内に登場する要素をノード(Node)と表現します
このノードに動作をつけるために必要なのがスクリプトで、

  • GDScript
  • ビジュアルスクリプト
  • C#
  • C++

のいずれかの言語で書きます

ただし、非公式ではありますがこれ以外の言語でもスクリプトを書くことが可能で
その1つにNimがあります

こちらのライブラリを使ってhttps://github.com/pragmagic/godot-nim
Godot公式サイトにあるFPSチュートリアルのパート1をNimで書いてみます

使用したver.は以下のとおりです

  • Nim: ver.1.2.2
  • Godot: ver.3.2

書くときに注意すること

コードの読み替え

チュートリアルのコードはGDScriptなので、そのまま写しても動きません
いい感じに読み替える必要があります

とはいえ、そこまで難しくはなく
基本的にスネークケースからキャメルケースに読み替えればokです
例えばキーボードの入力を検知する関数のis_action_pressedはisActionPressedになります

GDScript Nim
is_action_pressed isActionPressed

読み替えできないもの

単純な読み替えで対応できないものもあります
例えばチュートリアルにあるCameraノードを取得するときは、
GDScriptが**$Rotation_Helper/Cameraなのに対して
Nimは
self.getNode("Rotation_Helper/Camera") as Camera**になります
(getNodeで取得すると、どんなモノもNodeとして扱われるのでas XXXでcastする必要があります)

この辺はライブラリのドキュメントに載っているサンプルを参考にしました

あと、標準ライブラリを使うこともあり
GDScriptのdeg2radは標準ライブラリmathのdegToRadを使いました

できたもの

player.nim
import godot, math
import godotapi / [ kinematic_body, input_event_mouse_motion]
import godotapi/input as Input

const 
  GRAVITY = -24.8
  MAX_SPEED = 20
  JUMP_SPEED = 18
  ACCEL = 4.5
  DEACCEL = 16
  MAX_SPRINT_SPEED = 30
  SPRINT_ACCEL = 18

gdobj Player of KinematicBody:
  var camera: Spatial
  var rotationHelper: Spatial
  var dir, vel = vec3()
  var mouseSensitivity = 0.05
  var isSprinting = false
  var flashlight: SpotLight

  method ready*() =
    self.camera = self.getNode("Rotation_Helper/Camera") as Camera
    self.rotationHelper = self.getNode("Rotation_Helper") as Spatial
    self.flashlight = self.getNode("Rotation_Helper/Flashlight") as SpotLight
    Input.setMouseMode(Input.MOUSE_MODE_CAPTURED)
    
  method physicsProcess*(delta: float) =
    self.processInput(delta)
    self.processMovement(delta)

  proc processInput(delta: float) = 
    self.dir = vec3()

    let inputMovementVector = self.inputWASD().normalized()

    let camXform = self.camera.globalTransform()
    let vecZ = camXform.basis[2].normalized() * inputMovementVector.y
    let vecX = camXform.basis[0].normalized() * inputMovementVector.x
    
    let relativeDir = vecZ + vecX
    self.dir.x = relativeDir.x
    self.dir.z = -relativeDir.z

    self.isSprinting = Input.isActionPressed("movement_sprint")

    if Input.isActionJustPressed("flashlight"):
      self.flash()

    if Input.isActionJustPressed("ui_cancel"):
      self.uiCancel()

    if self.isOnFloor() and Input.isActionJustPressed("movement_jump"):
      self.vel.y = JUMP_SPEED
    
  proc processMovement(delta: float) = 
    self.dir.y = 0
    self.dir = self.dir.normalized()

    self.vel.y += delta * GRAVITY

    var hvel = self.vel
    hvel.y = 0

    let target = if self.isSprinting:
                  self.dir * MAX_SPRINT_SPEED
                 else:
                  self.dir * MAX_SPEED

    let accel = self.getAccel(hvel)

    hvel = hvel.lerp(target, accel * delta)
    self.vel.x = hvel.x
    self.vel.z = hvel.z
    self.vel = self.moveAndSlide(self.vel, vec3(0, 1, 0))

  method input(event: InputEvent) =
    if event of InputEventMouseMotion and Input.getMouseMode() == Input.MOUSE_MODE_CAPTURED:
      let ev = event as InputEventMouseMotion
      let rotateX = degToRad(ev.relative.y * self.mouseSensitivity)
      let rotateY = degToRad(ev.relative.x * self.mouseSensitivity * -1)
      self.rotationHelper.rotateX(rotateX)
      self.rotateY(rotateY)
      
      var cameraRot = self.rotationHelper.rotationDegrees()
      cameraRot.x = clamp(cameraRot.x, -70, 70)
      self.rotationHelper.rotationDegrees = cameraRot

  proc inputWASD(): Vector2 =
    result = vec2()
    if Input.isActionPressed("movement_forward"):
      result.y += 1
    if Input.isActionPressed("movement_backward"):
      result.y -= 1
    if Input.isActionPressed("movement_left"):
      result.x -= 1
    if Input.isActionPressed("movement_right"):
      result.x += 1

  proc flash() =
    let light = self.flashlight
    if light.isVisibleInTree():
      light.hide()
    else:
      light.show()

  proc uiCancel() =
    if Input.getMouseMode() == Input.MOUSE_MODE_VISIBLE:
      Input.setMouseMode(Input.MOUSE_MODE_CAPTURED)
    else:
      Input.setMouseMode(Input.MOUSE_MODE_VISIBLE)

  proc getAccel(hvel: Vector3): float =
    result = DEACCEL
    if self.dir.dot(hvel) > 0:
      if self.isSprinting:  
        result = SPRINT_ACCEL
      else:
        result = ACCEL

動く...!!
part1.gif

おわりに

Godot + Nimの解説は探してもあんまりないんですが、Godot自体は結構みつかりました(youtubeとか)
なので勉強を続けて、1本ゲームを完成させたいです

あと、今回はNim使いましたが、
Godot公式がオススメしているGDScriptも書きやすそうなので(pythonっぽい&静的型付け)使ってみたいです

引き続きチュートリアルがんばります(画像はパート2の途中)
part2.gif

29
12
1

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
29
12