공 방향 바꾸기 [Windows Forms with C#]

안녕하세요. 언제나 휴일입니다.

이번에는 Windows Forms 앱에서 간단한 그리기 실습을 합시다.

실행 화면
실행 화면

1. 실습할 내용

게임 공간은 100,100에서 너비 400, 높이 400입니다.

한 칸은 40으로 정할게요. 게임 공간의 너비는 10칸, 높이도 10칸입니다.

시작하면 0,0 위치에 공이 위치하고 움직이는 방향은 오른쪽입니다.

방향 키를 누르면 방향을 바꿀 수 있습니다.

타이머에 의해 0.2초 주기로 공은 이동합니다.

2. 컨트롤 배치 및 이벤트 핸들러 등록

Windows Forms 앱(.NET Framework) 프로젝트를 생성 후에 도구 상자에서 Timer를 Form1에 배치하세요.

Timer의 이름은 tm_ball이라 명명할게요.

tm_ball의 Interval(단위는 밀리초)은 200으로 지정하세요.

tm_ball의 Enabled 속성을 True로 지정할게요.

tm_ball의 Tick 이벤트 핸들러를 등록하세요.

Form1의 Paint 이벤트 핸들러를 등록하세요.

Form1의 KeyDown 이벤트 핸들러를 등록하세요.

Form1의 DoubleBuffered 속성을 True로 지정하세요. 화면이 깜빡거리는 것을 줄일 수 있습니다.

3. Form1.cs 구현

3.1 멤버 상수 및 공 위치, 방향 선언

한 칸을 unit 상수로 표현할게요.

시작 좌표 sx, sy도 표현합시다.

보드 공간은 너비를 bwidth, 높이를 bheight로 표현할게요.

공이 있는 위치 x,y를 0으로 선언합니다.

네 개의 방향을 열거형으로 정의한 후 초기 방향을 오른쪽으로 설정하세요.

        const int unit = 40;
        const int sx = 100;
        const int sy = 100;
        const int bwidth = 10;
        const int bheight = 10;
        int x = 0, y = 0;
        Direction dir = Direction.DIR_RIGHT;
        enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };

3.2 Paint 이벤트 핸들러 구현

게임 공간을 그리는 부분과 공을 그리는 부분을 별도의 메서드로 정의하기로 할게요.

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics grapics = e.Graphics;
            DrawBoard(grapics);
            DrawBall(grapics);            
        }

보드 공간은 역사선(BackwardDiagonal) 형태의 HatchBrush로 테두리를 표현할게요.

보드 내부는 칸을 표시하기 위해 수직선과 수평선을 점선으로 표시할게요.

        private void DrawBoard(Graphics grapics)
        {
            Brush brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.DarkGoldenrod);
            Pen pen = new Pen(brush, 6);
            grapics.DrawRectangle(pen, new Rectangle(sx, sy, bwidth*unit, bheight*unit));

            Pen pen2 = new Pen(Color.DarkGray);
            pen2.DashStyle = DashStyle.Dot;
            for(int i = 0; i < bwidth;i++)//수직선
            {
                int x = sx + i * unit;
                grapics.DrawLine(pen2, new Point(x, sx), new Point(x, sy+bheight*unit));
            }
            for (int i = 0; i < bheight; i++)//수평선
            {
                int y = sy + i * unit;
                grapics.DrawLine(pen2, new Point(sx, y), new Point(sx+bwidth*unit, y));
            }
        }

공은 SolidBrush를 이용하여 원으로 표시할게요.

        private void DrawBall(Graphics grapics)
        {
            Brush brush = new SolidBrush(Color.DarkCyan);
            grapics.FillEllipse(brush, new Rectangle(x * unit + sx, y * unit + sy, unit, unit));
        }

3.2 tm_ball_Tick 이벤트 핸들러 구현

공의 이동 방향에 따라 좌표를 이동시켜줍니다.

폼을 무효화시켜 다시 그려주게 합니다.

        private void tm_ball_Tick(object sender, EventArgs e)
        {
            switch(dir)
            {
                case Direction.DIR_DOWN: y = (y + 1) % bheight; break;
                case Direction.DIR_UP: y = (y + bheight - 1) % bheight; break;
                case Direction.DIR_RIGHT: x = (x + 1) % bwidth; break;
                case Direction.DIR_LEFT: x = (x + bwidth - 1) % bwidth; break;
            }
            Invalidate();
        }

3.3 form1_KeyDown 이벤트 핸들러 구현

누른 키가 방향 키일 때 공의 움직이는 방향을 변경합니다.

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch(e.KeyCode)
            {
                case Keys.Left: dir = Direction.DIR_LEFT; break;
                case Keys.Right: dir = Direction.DIR_RIGHT; break;
                case Keys.Up: dir = Direction.DIR_UP;  break;
                case Keys.Down: dir = Direction.DIR_DOWN; break;
            }
        }

4. 전체 코드

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace 그기기_응용에_들어가기_앞서
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics grapics = e.Graphics;
            DrawBoard(grapics);
            DrawBall(grapics);            
        }
        const int unit = 40;
        const int sx = 100;
        const int sy = 100;
        const int bwidth = 10;
        const int bheight = 10;
        int x = 0, y = 0;
        Direction dir = Direction.DIR_RIGHT;
        enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
        private void DrawBall(Graphics grapics)
        {
            Brush brush = new SolidBrush(Color.DarkCyan);
            grapics.FillEllipse(brush, new Rectangle(x * unit + sx, y * unit + sy, unit, unit));
        }
        
        private void DrawBoard(Graphics grapics)
        {
            Brush brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.DarkGoldenrod);
            Pen pen = new Pen(brush, 6);
            grapics.DrawRectangle(pen, new Rectangle(sx, sy, bwidth*unit, bheight*unit));

            Pen pen2 = new Pen(Color.DarkGray);
            pen2.DashStyle = DashStyle.Dot;
            for(int i = 0; i < bwidth;i++)//수직선
            {
                int x = sx + i * unit;
                grapics.DrawLine(pen2, new Point(x, sx), new Point(x, sy+bheight*unit));
            }
            for (int i = 0; i < bheight; i++)//수평선
            {
                int y = sy + i * unit;
                grapics.DrawLine(pen2, new Point(sx, y), new Point(sx+bwidth*unit, y));
            }
        }

        private void tm_ball_Tick(object sender, EventArgs e)
        {
            switch(dir)
            {
                case Direction.DIR_DOWN: y = (y + 1) % bheight; break;
                case Direction.DIR_UP: y = (y + bheight - 1) % bheight; break;
                case Direction.DIR_RIGHT: x = (x + 1) % bwidth; break;
                case Direction.DIR_LEFT: x = (x + bwidth - 1) % bwidth; break;
            }
            Invalidate();
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch(e.KeyCode)
            {
                case Keys.Left: dir = Direction.DIR_LEFT; break;
                case Keys.Right: dir = Direction.DIR_RIGHT; break;
                case Keys.Up: dir = Direction.DIR_UP;  break;
                case Keys.Down: dir = Direction.DIR_DOWN; break;
            }
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            Size = new Size(600, 600);
        }
    }
}