はじめに
先日YouTubeでC#でテトリスを1時間で作成する動画を観ました。
解説しながらテキストエディタを使って少しずつコーディングしては、ビルド&実行を繰り返してどのような動きをするのかとてもわかりやすい動画でした。
私も動画を一時停止させながらVisualStudioでコーディングして無事に動作させることが出来ました。
他にもRPGを作成する動画もあったので拝見し「登録」させてもらいました。
テトリスのソースコードを改良したら映画のエンドロールが作れそうだなと思い作ってみました。
出来上がりイメージ
ソースのダウンロード
VisualStudio 2019 Comunityで作成したソースファイルをzip圧縮してあります。
ちょこっと解説
プログラムを実行するとMainフォームが表示されます。
テキストボックスと開始ボタンが設置してあります。
「開始」ボタンをクリックすると別のフォームが表示されて、テキストボックスに記入された内容を下から上にスクロールされていきます。
背景が真っ黒だと味気ないので星空(?)の感じにしてみました。
ソースコード
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace EndRoll { public partial class Main : Form { public Main() { InitializeComponent(); } private void buttonStart_Click(object sender, EventArgs e) { using (RollForm frm = new RollForm()) { frm.RollText = textBoxRollText.Text; frm.ShowDialog(); } } } }
メインフォームでは、開始ボタンが押されたときにエンドロール用フォームをテキストの内容を渡して表示するだけのシンプルなものです。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace EndRoll { public partial class RollForm : Form { static readonly int TILE_SIZE = 8; static readonly int TIMER_INTERVAL = 16; static readonly int MAP_WIDTH = 80; static readonly int MAP_HEIGHT = 50; static readonly int SCR_WIDTH = MAP_WIDTH * TILE_SIZE; static readonly int SCR_HEIGHT = MAP_HEIGHT * TILE_SIZE; static readonly int WND_WIDTH = SCR_WIDTH * 2; static readonly int WND_HEIGHT = SCR_HEIGHT * 2; //static readonly int WAIT = 60; Bitmap mScreen = new Bitmap(SCR_WIDTH, SCR_HEIGHT); Bitmap[] mTile; byte[,] mTileLayout; int mTimer; int mKeyEsc; bool mCancel; bool mEnd; int mX, mY, mDX, mDY; int mRowsCount; Random mRnd = new Random(); int mLen; public string RollText { get; set; } public RollForm() { InitializeComponent(); } private void RollForm_Load(object sender, EventArgs e) { string s = RollText; int ndx; Bitmap bm = new Bitmap("tile.png"); ClientSize = new Size(WND_WIDTH, WND_HEIGHT); DoubleBuffered = true; mKeyEsc = 0; mCancel = false; mX = 0; mY = MAP_HEIGHT + 2; mDY = 0; mRowsCount = (s.Length - s.Replace("\r", "").Length) * TILE_SIZE; mLen = bm.Width / TILE_SIZE; mTile = new Bitmap[mLen]; for (int i = 0; i < mLen; i++) { mTile[i] = bm.Clone(new Rectangle(i * TILE_SIZE, 0, TILE_SIZE, TILE_SIZE), bm.PixelFormat); } mTileLayout = new byte[MAP_WIDTH, MAP_HEIGHT]; for (int y = 0; y < MAP_HEIGHT; y++) { for (int x = 0; x < MAP_WIDTH; x++) { ndx = mRnd.Next(mLen); mTileLayout[x,y] = (byte)ndx; } } Task.Run(() => { mTimer = System.Environment.TickCount; while (!mEnd) { OnTimer(); mTimer += TIMER_INTERVAL; Task.Delay(Math.Max(1, mTimer - System.Environment.TickCount)).Wait(); } }); } private void RollForm_Paint(object sender, PaintEventArgs e) { Graphics g = Graphics.FromImage(mScreen); Font f = new Font("Meiryo", 10); StringFormat sfmt = new StringFormat(); sfmt.Alignment = StringAlignment.Center; g.Clear(BackColor); for (int y = 0; y < MAP_HEIGHT; y++) { for (int x = 0; x < MAP_WIDTH; x++) { g.DrawImage(mTile[mTileLayout[x,y]], x *TILE_SIZE, y *TILE_SIZE); } } if (mCancel) { this.DialogResult = DialogResult.Cancel; return; } //Debug.Print((mY * TILE_SIZE + mDY).ToString()); g.DrawString(RollText, f, Brushes.White, mX * TILE_SIZE + TILE_SIZE * MAP_WIDTH / 2, mY * TILE_SIZE + mDY, sfmt); e.Graphics.DrawImage(mScreen, 0, 0, WND_WIDTH, WND_HEIGHT); } private void RollForm_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) mKeyEsc++; } private void OnTimer() { Tick(); if (mKeyEsc > 0) mCancel = true; Invalidate(); } private void Tick() { if (Math.Abs(mDY) < TILE_SIZE-1) { mDY--; return; } mDY = 0; if (mY < mRowsCount * -1) { mEnd = true; return; } mY--; } } }
フォームが表示されたときに(RollForm_Load)次の設定を実施します。
・ウィンドウのサイズ設定
・背景表示用の画像を読み込んでTILE_SIZEで分解して配列(mTile)に保存
・分解した画像を画面いっぱいにランダム配置するための配列(mTileLayout)に保存
必要な設定が終わったら、無限ループに突入します。
ループ内では一定間隔でOnTimer()を呼び出します。
OnTimer()では、Tick()を呼び出してESCキーが押されたフラグ(mKeyEsc)が0では無い場合にフォーム画面を更新します。
Tick()では、テキストがスムーズにスクロールするようにY軸(縦軸)カウンタを更新してます。
フォームの描画処理(RollForm_Paint)では、背景画像の表示とテキストの表示を実施しています。
テキストはセンタリングさせるようにしております。
最後に
今回は、ほとんど流用したソース(汗)ではありますが大変勉強になりました。
VisualStudioを使うとコントールを配置して何かを行うという固定観念にとらわれがちですが、プログラムはもっと自由で良いのだなぁと再認識したところです。
また、スクロールさせるテキストがひとまとめにしてあるため、フォントサイズを細かく変更できません。映画のエンドロールでは、ロゴ画像もスクロールさせるようにしてあげる必要もありそうです。
工夫次第ではまだまだ楽しめそうなので改良版を公開出来る日が来るかもしれませんし来ないかもしれません(笑)