10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PowerShellでゲームを作る

Last updated at Posted at 2024-05-03

概要

PowerShellで簡単なゲームを作りたい。
PowerShellはゲームを作るのには向いていない。
なぜこんなことをするかというと、
OSに標準で入っている機能だけでゲームを作るということに魅力を感じるからだ。

作ったゲーム

image.png

テトリスを一般化したもの。
ブロックが4x4の領域にランダムに生成されたものになっている。
1ライン揃えるのも難しい。代わりに、自動落下せず、上にも移動できる。
Cキーで自分のタイミングで固着できる。それでも難しい。

必要なもの

  • RGB配列を一定間隔で画面に表示
  • キー入力を取得

これらは、PowerShellから利用できる.NET Frameworkの System.Windows.FormsSystem.Drawing
の機能を利用することで実現できる。

雛形となるコード

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$bmp = [Drawing.Bitmap]::new(32, 32)
$bmpScale = 16
$imageData = [byte[]]::new($bmp.Width * $bmp.Height * 4)

function ClearImageData($r, $g, $b) {
  for ($i = 0; $i -lt $imageData.Length; $i += 4) {
    $imageData[$i + 0] = $b
    $imageData[$i + 1] = $g
    $imageData[$i + 2] = $r
    $imageData[$i + 3] = 255
  }
}

function SetPixelToImageData($x, $y, $r, $g, $b) {
  $i = ($y * $bmp.Width + $x) * 4
  $imageData[$i + 0] = $b
  $imageData[$i + 1] = $g
  $imageData[$i + 2] = $r
  $imageData[$i + 3] = 255
}

function DrawImageData($graphics) {
  $bmpData = $bmp.LockBits(
    [Drawing.Rectangle]::new(0, 0, $bmp.Width, $bmp.Height),
    [Drawing.Imaging.ImageLockMode]::WriteOnly,
    $bmp.PixelFormat
  )
  $ptr = $bmpData.Scan0
  $bytes = [Math]::Abs($bmpData.Stride) * $bmp.Height
  [Runtime.InteropServices.Marshal]::Copy($imageData, 0, $ptr, $bytes)
  $bmp.UnlockBits($bmpData)
  $graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::NearestNeighbor
  $graphics.PixelOffsetMode = [Drawing.Drawing2D.PixelOffsetMode]::Half
  $graphics.DrawImage($bmp, 0, 0, $bmp.Width * $bmpScale, $bmp.Height * $bmpScale)
}

$keys = [int[]]::new(256)

function ShowForm() {
  $form = [Windows.Forms.Form]::new()
  $form.FormBorderStyle = [Windows.Forms.FormBorderStyle]::FixedDialog
  $form.Text = 'Game'
  $form.ClientSize = [Drawing.Size]::new($bmp.Width * $bmpScale, $bmp.Height * $bmpScale)
  $form.StartPosition = 'CenterScreen'
  $form.Topmost = $true
  [System.Windows.Forms.Form].GetMethod('SetStyle',
    [Reflection.BindingFlags]::NonPublic -bor
    [Reflection.BindingFlags]::Instance
  ).Invoke($form, @(
    [Windows.Forms.ControlStyles]::DoubleBuffer -bor
    [Windows.Forms.ControlStyles]::AllPaintingInWmPaint
    $true
  ))
  $form.Add_KeyDown({ param ($sender, $event); $keys[$event.KeyValue] = 1 })
  $form.Add_KeyUp({ param ($sender, $event); $keys[$event.KeyValue] = 0 })
  $form.Add_Paint({ param ($sender, $event); Update $event.Graphics })
  $timer = [Windows.Forms.Timer]::new()
  $timer.Interval = 100
  $timer.Add_Tick({ $form.Invalidate($true) })
  $timer.Start()
  $form.ShowDialog()
}

function Update($graphics) {
  ClearImageData 0 0 0
  # ...
  DrawImageData $graphics
}

ShowForm

これであとは Update 部分に更新処理を書けばゲームが作れる。

例えば、左キーが押されたかどうかは、

  if ($keys[[int][Windows.Forms.Keys]::Left]) { }

で判定できる。

例えば、(5, 5)の位置に赤いピクセルを表示したい場合は、

  SetPixelToImageData 5 5 255 0 0

とすればよい。

チラつき対策

チラつきをなくすためには Form をダブルバッファリングにしなければいけない。
ダブルバッファリングにするためには FormSetStyle で変更する必要がある。
FormSetStyleprotected なので継承しないと呼べない。
しかし、PowerShellは、現時点で、

に書かれているような事情により、Add-Type で追加されたクラスをクラス定義時に認識できない。
なので、PowerShellでは Add-Type されたクラスを継承することができない。

そこで、

  [System.Windows.Forms.Form].GetMethod('SetStyle',
    [Reflection.BindingFlags]::NonPublic -bor
    [Reflection.BindingFlags]::Instance
  ).Invoke($form, @(
    [Windows.Forms.ControlStyles]::DoubleBuffer -bor
    [Windows.Forms.ControlStyles]::AllPaintingInWmPaint
    $true
  ))

と、メソッドを取得して明示的に呼び出すことで回避している。

別解として、

Add-Type -ReferencedAssemblies System.Windows.Forms @'
  using System.Windows.Forms;
  public class DoubleBufferedForm : Form {
    public DoubleBufferedForm() {
      this.SetStyle( 
        ControlStyles.DoubleBuffer |
        ControlStyles.AllPaintingInWmPaint,
        true
      );
      this.UpdateStyles();
    }
  }
'@

と、C#のコードで継承し Add-Type し、$form を、

  $form = [DoubleBufferedForm]::new()

で生成し回避することもできる。

起動

PowerShellのスクリプトはそのままでは起動できない。

ファイル game.bat を作り、

powershell -ExecutionPolicy Unrestricted -File %~n0.ps1

のように記載しておけば、game.bat 経由で起動できるようになる。

リポジトリ

まとめ

PowerShellで簡単なゲームが作れた。

10
9
2

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
10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?