*해당 글은 강좌가 아닌 개발자의 일지 비스무래한 글입니다!
안녕하세요! Printed입니다.
이번에는 윈도우즈 Form앱 (visual basic)을 이용해 슈팅게임을 만들어보는 프로젝트를 기획했습니다!
사실... 게임을 만들기에는 적합한 언어는 아닐 수 있으나 충분히 만들수는 있습니다!
사실은 학교 기말 과제 미리해버리기
이번 프로젝트에는 폼 클래스 코드 이외에도 여러 클래스, 모듈, 리소스 파일등이 필요하기에 전체 코드는 이곳이 아닌 아래 깃허브 링크를 통해 확인해 주시기 바랍니다.
중요한 코드는 따로 편집하여 코드 블록으로 올려드리겠습니다!
1. 폼에 픽쳐박스 배치
일단, 기초적인 윈도우즈 폼 앱(visual basic) 제작법을 숙지하고 계신것을 바탕으로 설명하겠습니다.
프로젝트는 비주얼 스튜디오에서 쉽게 만드실 수 있습니다.
먼저 게임이 동작할 픽쳐박스(picture box)를 화면에 배치합니다.
이 픽쳐박스가 매 틱(tick)마다 새로고침 되며 게임 그래픽이 구현될 예정입니다.
픽쳐박스 위치는 화면에 알맞게 대충 조정해 주시면 됩니다.
2. 폼 코드와 모듈 코드 작성
아래는 제가 수많은 시행착오 끝에 구현한 코드들 입니다... 대부분 메모리 누수 때문에 애를 먹었으며 참조 문제 때문에 비주얼 스튜디오를 다시 깐적도 있답니다.
정확한 코드 확인은 해당 글 맨 아래의 깃허브 링크에서 확인해 주시기 바랍니다.
Form_play의 코드에서 가장 중요한 부분은 StartGameLoop()와 DrawGraphics() 함수입니다.
각각 게임 틱을 관리해주고 그래픽을 그리는 용도의 함수들입니다.
Private Sub StartGameLoop()
tick_start = DateTime.Now.Ticks 'start tick initialise
Do While isRunning = True
tick = DateTime.Now.Ticks 'current time tick
If tick > tick_recent + 200000 Then 'game initialize every 0.02 Sec
tick_recent = tick
playtime_s = (tick - tick_start) \ 10000000 'playtime count
playtime_m = playtime_s \ 60
playtime_s = playtime_s Mod 60
Application.DoEvents()
DrawGraphics()
End If
Loop
Close()
End Sub
Private Sub DrawGraphics()
Dim bmp As New Bitmap(PictureBox_play.Image)
If Not screen_bmp.Equals(bmp) Then
screen_bmp.Dispose()
screen_bmp = bmp
End If
Using g As Graphics = Graphics.FromImage(bmp)
Me.DoubleBuffered = True
g.Clear(GRAY_LIGHT)
DrawLine(g, New Point(S_WIDTH \ 2 + bg_x, 0), New Point(S_WIDTH \ 2 + bg_x, S_HEIGHT), GRAY_DEEP, MAX_ALPHA)
DrawLine(g, New Point(0, S_HEIGHT \ 2 + bg_y), New Point(S_WIDTH, S_HEIGHT \ 2 + bg_y), GRAY_DEEP, MAX_ALPHA)
DrawText(g, Format(playtime_m, "00") & " : " & Format(playtime_s, "00"), S_WIDTH \ 2, 50, font_16, WHITE, MAX_ALPHA)
DrawSprite(g, spr_character, S_WIDTH \ 2, S_HEIGHT \ 2)
End Using
If Not PictureBox_play.Image.Equals(bmp) Then
PictureBox_play.Image = bmp
PictureBox_play.Refresh()
End If
End Sub
티스토리의 코드블록이 VB을 지원하지 않아 코드 컬러링이 이상하네요.. ㅋㅋ
StartGameLoop() 함수는 DateTime.Now.Ticks 를 통해 현재 시각의 틱을 읽어와 저장하고, 200000틱(0.02초) 가 지날 때마다 게임을 초기화 해주는 역할을 합니다.
DrawGraphics() 함수는 게임 루프마다 한번씩 호출되며, PictureBox_play의 이미지를 수정하며 게임 그래픽을 구현하는 역할을 합니다. ...여기에서 메모리 누수가 일어나 잡느라고 엄청 고생했었습니다.
비트맵(화면에 그릴 이미지)가 다를 경우 원본 비트맵을 Dispose(메모리 해제)하고 다시 그리는 것으로 메모리 누수를 고쳤습니다. 현 진행상황해서 실행하면 메모리를 약 30MB정도 잡아 먹더군요. 높은건지 낮은건지 모르겠습니다...
Public Sub DrawSprite(ByVal g As Graphics, ByVal sprite As Sprite, ByVal x As Integer, ByVal y As Integer)
g.DrawImage(sprite.spr, x - sprite.width \ 2, y - sprite.height \ 2)
End Sub
Public Sub DrawText(ByVal g As Graphics, ByVal str As String, ByVal x As Integer, ByVal y As Integer, ByVal fnt As Font, ByVal color As Color, ByVal alpha As Int16)
Dim text_color As Color = Color.FromArgb(alpha, color.R, color.G, color.B)
Using brush As Brush = New Drawing.SolidBrush(text_color), f As Font = New Font(fnt.FontFamily, fnt.Size, fnt.Style)
g.DrawString(str, f, brush, x, y, strFormat)
End Using
End Sub
Public Sub DrawLine(ByVal g As Graphics, ByVal pnt_x As Point, ByVal pnt_y As Point, ByVal color As Color, ByVal alpha As Int16)
Dim line_color As Color = Color.FromArgb(alpha, color.R, color.G, color.B)
Using brush As Brush = New Drawing.SolidBrush(line_color), pen As Pen = New Drawing.Pen(brush)
g.DrawLine(pen, pnt_x, pnt_y)
End Using
End Sub
Public Function GetSprite(ByVal file_name As String) As Sprite
Dim strImageName As String = Application.ExecutablePath
strImageName = strImageName.Substring(0, strImageName.LastIndexOf("\bin")) & "\image\" & file_name
If IO.File.Exists(strImageName) Then
Dim img As Image = Image.FromFile(strImageName)
Dim bm As New Bitmap(width:=img.Width, height:=img.Height, format:=img.PixelFormat)
Using g As Graphics = Graphics.FromImage(bm)
g.DrawImage(img, Point.Empty)
End Using
img.Dispose()
Return New Sprite(bm, bm.Size.Width, bm.Size.Height)
Else
Throw New Exception(String.Format("Cannot load _image '{0}'", strImageName))
Return Nothing
End If
End Function
DrawGraphics()의 DrawLine, DrawText, DrawSprite함수들은 모듈에 선언되어있는 함수들로 using으로 생성된 Grapics에 해당하는 것들을 그립니다. 기본 함수를 사용하지 않고 직접 만든 이유는, Pen과 Brush를 동적으로 생성해 색의 자유와 투명도를 구현하기 위해서 입니다. 대신.. 속도는 느려지겠지만요.
GetSprite함수는 프로젝트 내의 \image 폴더에서 해당 스트링 이름의 이미지파일을 Sprite 클래스로 저장합니다. Sprite클래스는 뒤에 나옵니다.
나중에 게임 속에서 등장할 여러 이미지들을 받아오는 함수라고 생각하시면 됩니다.
3. 리소스와 클래스
현재 진행상황에서는 폰트 파일과 이미지 파일을 리소스로 사용하고 있습니다. 나중에는 음원 파일도 추가외어 본격적인 게임의 틀이 잡히겠죠,
약간 도트스러운 게임을 만들고 싶어 무표 폰트들 중에 Munro라는 이름의 폰트를 추가했습니다. 프로젝트 파일의 font폴더 속에 "Munro.ttf" 파일입니다.
해당 폰트는 모듈에서 PrivateFontCollection 클래스를 통해 정의되고 폼이 로드될 때 AddFontFile()함수로 추가됩니다. 그리고 이 폰트를 바탕으로 여러 크기의 폰트를 생성해 사용합니다.
이미지 리소스는 현재 "character.png" 하나로, 모듈에서 추가됩니다. 적과 이팩트, UI들이 추가되면 점점 늘어날 것 같습니다.
Sprite라는 클래스는 모듈의 GetSprite() 함수로 받아온 이미지와 그 이미지의 크기를 받아오는 함수입니다.
DrawSprite() 함수는 이 클래스의 정보를 통해 비트맵에 이미지를 그립니다.
현재 구현된 진행사항은 여기까지 입니다.
아직 완성되지 않아 좀 더 좋은 방법이 있다면 조금씩 수정될 것 같습니다.
아래는 테스트 영상입니다!
화면 깜박임이나 잔상이 없음을 확인할 수 있습니다.
디버그 모드로 실행시 메모리가 초반에는 크게 증가하다, 일정 수준에서 안정화 됩니다. 왜그러는지는 저두 잘 모르겠네요.
아래 링크에서 소스코드를 확인하실 수 있습니다.
'개발 일지' 카테고리의 다른 글
비주얼 베이직(vb.net)으로 슈팅게임 만들기 - 3. HP, exp바 (0) | 2020.10.09 |
---|---|
비주얼 베이직(vb.net)으로 슈팅게임 만들기 - 2. 캐릭터 이동 (0) | 2020.10.04 |
4 Beats [Python] (6) | 2020.09.19 |
4 Beats(파이게임으로 리듬게임 만들기) - 02 (1) | 2020.09.19 |
4 Beats(파이게임으로 리듬게임 만들기) - 01 (0) | 2020.09.13 |
댓글