Search code examples
winapiassemblymasmgame-loop

Win32 game loop


I'm creating simple game using Win32 API. When I click on window, a ball appear and start rolling look like bida game

My problem is when I call "InvalidateRect", my game very lag. I don't know I'm doing any thing wrong!!!

And my hPen didn't work like what I expect

Please help!!! Thank you and sorry for my bad English

.386    ; use 80386 instruction
.model flat,stdcall ; uses flat memory addressing model
option casemap:none

include C:\masm32\include\windows.inc   ; windows.inc have structures and constants
include C:\masm32\include\user32.inc 
includelib C:\masm32\lib\user32.lib ; CreateWindowEx, RegisterClassEx,...
include C:\masm32\include\kernel32.inc 
includelib C:\masm32\lib\kernel32.lib   ; ExitProcess
include C:\masm32\include\masm32.inc
includelib C:\masm32\lib\masm32.lib
include C:\masm32\include\gdi32.inc 
includelib C:\masm32\lib\gdi32.lib 

.CONST
DRAWING equ 1
WAITING equ 0
PEN_COLOR equ 00000000h ; black
PEN_SIZE equ 2
BALL_SIZE equ 35
BALL_SPEED equ 20

.DATA
ClassName db 'SimpleWinClass',0
AppName db 'Ball',0

state db WAITING

vectorX dd 6
vectorY dd -7

WIN_WIDTH dd 700
WIN_HEIGHT dd 500

.DATA?
; HINSTANCE & LPSTR typedef DWORD in windows.inc
; reserve the space for future use
hInstance HINSTANCE ?

tlpoint POINT <>
brpoint POINT <>

; use for create window
wc WNDCLASSEX <?>
msg MSG <?> ; handle message
hwnd HWND ? ; handle window procedure

hdc HDC ?
ps PAINTSTRUCT <?>

time SYSTEMTIME <?>

hPen HPEN ?

.CODE
start:
    ; call GetModuleHandle(null)
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms683199(v=vs.85).aspx
    push NULL
    call GetModuleHandle    ; module handle same as instance handle in Win32
    mov hInstance, eax  ; return an instance to handle in eax

    ; call WinMain(hInstance, hPrevInstance, CmdLine, CmdShow)
    ; our main function
    push SW_SHOW
    push NULL
    push NULL
    push hInstance
    call WinMain

    ; call ExitProcess
    push eax
    call ExitProcess

    ; Define WinMain 
    WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
        ; Structure in msdn, define in windows.inc
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633577(v=vs.85).aspx

        ; Load default icon
        push IDI_APPLICATION
        push NULL
        call LoadIcon
        mov wc.hIcon, eax
        mov wc.hIconSm, eax

        ; Load default cursor
        push IDC_ARROW
        push NULL
        call LoadCursor
        mov wc.hCursor, eax

        mov wc.cbSize, SIZEOF WNDCLASSEX    ; size of this structure
        mov wc.style, CS_HREDRAW or CS_VREDRAW  ; style of windows https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
        mov wc.lpfnWndProc, OFFSET WndProc  ; andress of window procedure
        mov wc.cbClsExtra, NULL 
        mov wc.cbWndExtra, NULL
        push hInstance
        pop wc.hInstance
        mov wc.hbrBackground, COLOR_WINDOW+1    ; background color, require to add 1
        mov wc.lpszMenuName, NULL
        mov wc.lpszClassName, OFFSET ClassName

        ; we register our own class, named in ClassName
        push offset wc
        call RegisterClassEx

        ; after register ClassName, we use it to create windows compond
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx
        push NULL
        push hInstance
        push NULL
        push NULL
        push WIN_HEIGHT
        push WIN_WIDTH
        push CW_USEDEFAULT
        push CW_USEDEFAULT
        push WS_OVERLAPPEDWINDOW
        push offset AppName
        push offset ClassName
        push WS_EX_CLIENTEDGE
        call CreateWindowEx

        mov hwnd, eax   ; return windows handle

        ; display window
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
        push CmdShow
        push hwnd
        call ShowWindow

        ; update window
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/dd145167(v=vs.85).aspx
        push hwnd
        call UpdateWindow

        ; Message Loop
        MESSAGE_LOOP:
            ; get message
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
            push PM_REMOVE
            push 0
            push 0
            push NULL
            push offset msg
            call PeekMessage

            ; return in eax
            ; if the function retrieves a message other than WM_QUIT, the return value is nonzero.
            ; if the function retrieves the WM_QUIT message, the return value is zero.
            cmp eax, 0
            je GAME_LOOP

            cmp msg.message, WM_QUIT
            je END_LOOP

            ; translate virtual-key messages into character messages - ASCII in WM_CHAR
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955(v=vs.85).aspx
            push offset msg
            call TranslateMessage 

            ; sends the message data to the window procedure responsible for the specific window the message is for.
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644934(v=vs.85).aspx
            push offset msg
            call DispatchMessage

        GAME_LOOP:
            ; check that is DRAWING or not?
            cmp [state], DRAWING
            jne MESSAGE_LOOP

            push offset time
            call GetSystemTime

            cmp dword ptr[time.wMilliseconds], BALL_SPEED
            jl MESSAGE_LOOP

            push TRUE
            push NULL
            push hwnd
            call InvalidateRect

            jmp MESSAGE_LOOP

        END_LOOP:
            mov eax, msg.wParam   
        ret 
    WinMain endp

    ; Handle message with switch(notification)
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
        cmp uMsg, WM_PAINT
        je ON_WM_PAINT

        cmp uMsg, WM_CREATE
        je ON_WM_CREATE

        cmp uMsg, WM_LBUTTONDOWN
        je ON_WM_LBUTTONDOWN

        cmp uMsg, WM_DESTROY
        je ON_WM_DESTROY

        cmp uMsg, WM_QUIT
        je ON_WM_DESTROY

        cmp uMsg, WM_CLOSE
        je ON_WM_DESTROY

        jmp ON_DEFAULT

        ; user close program
        ON_WM_DESTROY:
            push NULL
            call PostQuitMessage
            jmp EXIT

        ON_WM_CREATE:
            ; create a pen with specific color and size
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/dd183509(v=vs.85).aspx
            push PEN_COLOR
            push PEN_SIZE
            push PS_SOLID
            call CreatePen
            mov hPen, eax

            jmp EXIT

        ON_WM_LBUTTONDOWN:
            cmp [state], DRAWING
            je EXIT

            push lParam
            call updateXY

            ; when clicked, set state to DRAWING
            mov [state], DRAWING

            mov dword ptr[time.wMilliseconds], BALL_SPEED
            push offset time
            call SetSystemTime

            jmp EXIT

        ON_WM_PAINT:
            mov dword ptr[time.wMilliseconds], 0
            push offset time
            call SetSystemTime 

            push offset ps
            push hWnd 
            call BeginPaint
            mov hdc, eax

            ; apply pen to hdc
            push hPen
            push hdc
            call SelectObject

            call createEllipse

            push offset ps
            push hWnd
            call EndPaint

            jmp EXIT

        ON_DEFAULT:
            ; handle any message that program don't handle
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
            push lParam 
            push wParam 
            push uMsg   ; message
            push hWnd   ; windows
            call DefWindowProc

            jmp EXIT

        EXIT:
            ret
    WndProc endp

    createEllipse proc
        push brpoint.y
        push brpoint.x
        push tlpoint.y
        push tlpoint.x
        push hdc
        call Ellipse

        call moveEllipse

        mov eax, WIN_WIDTH
        cmp brpoint.x, eax
        jg MEET_RIGHT_LEFT

        mov eax, WIN_HEIGHT
        cmp brpoint.y, eax
        jg MEET_BOTTOM_TOP

        cmp tlpoint.x, 0
        jl MEET_RIGHT_LEFT

        cmp tlpoint.y, 0
        jl MEET_BOTTOM_TOP

        jmp MEET_NONE

        MEET_RIGHT_LEFT:
            neg vectorX
            jmp MEET_NONE

        MEET_BOTTOM_TOP:
            neg vectorY
            jmp MEET_NONE

        MEET_NONE:

        ret
    createEllipse endp

    moveEllipse proc
        mov eax, dword ptr[vectorX]
        mov ecx, dword ptr[vectorY]

        add tlpoint.x, eax
        add tlpoint.y, ecx
        add brpoint.x, eax
        add brpoint.y, ecx

        ret
    moveEllipse endp

    updateXY proc lParam:LPARAM
        mov eax, lParam

        ; get low word that contain x
        xor ebx, ebx
        mov bx, ax

        mov tlpoint.x, ebx
        mov brpoint.x, ebx
        add brpoint.x, BALL_SIZE

        ; get high word that contain y
        mov eax, lParam
        shr eax, 16

        mov tlpoint.y, eax
        mov brpoint.y, eax
        add brpoint.y, BALL_SIZE

        ret
    updateXY endp

end start

Solution

  • It's not a good idea to use the system time for speed issues. Use your procedure moveEllipse instead and increase vectorX and vectorY for more speed. InvalidateRec should be called if there is a real update needed. You can reach that by using a timer instead of changing and checking the system time: SetTimer.

    .386    ; use 80386 instruction
    .model flat,stdcall ; uses flat memory addressing model
    option casemap:none
    
    include C:\masm32\include\windows.inc   ; windows.inc have structures and constants
    include C:\masm32\include\user32.inc
    includelib C:\masm32\lib\user32.lib ; CreateWindowEx, RegisterClassEx,...
    include C:\masm32\include\kernel32.inc
    includelib C:\masm32\lib\kernel32.lib   ; ExitProcess
    include C:\masm32\include\masm32.inc
    includelib C:\masm32\lib\masm32.lib
    include C:\masm32\include\gdi32.inc
    includelib C:\masm32\lib\gdi32.lib
    
    .CONST
    DRAWING equ 1
    WAITING equ 0
    PEN_COLOR equ 00000000h ; black
    PEN_SIZE equ 2
    BALL_SIZE equ 35
    BALL_SPEED equ 20
    
    .DATA
    ClassName db 'SimpleWinClass',0
    AppName db 'Ball',0
    
    state db WAITING
    
    vectorX dd 19
    vectorY dd -19
    
    WIN_WIDTH dd 700
    WIN_HEIGHT dd 500
    
    .DATA?
    ; HINSTANCE & LPSTR typedef DWORD in windows.inc
    ; reserve the space for future use
    hInstance HINSTANCE ?
    
    tlpoint POINT <>
    brpoint POINT <>
    
    ; use for create window
    wc WNDCLASSEX <?>
    msg MSG <?> ; handle message
    hwnd HWND ? ; handle window procedure
    
    hdc HDC ?
    ps PAINTSTRUCT <?>
    
    time SYSTEMTIME <?>
    
    hPen HPEN ?
    
    .CODE
    start:
        ; call GetModuleHandle(null)
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms683199(v=vs.85).aspx
        push NULL
        call GetModuleHandle    ; module handle same as instance handle in Win32
        mov hInstance, eax  ; return an instance to handle in eax
    
        ; call WinMain(hInstance, hPrevInstance, CmdLine, CmdShow)
        ; our main function
        push SW_SHOW
        push NULL
        push NULL
        push hInstance
        call WinMain
    
        ; call ExitProcess
        push eax
        call ExitProcess
    
        ; Define WinMain
        WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
            ; Structure in msdn, define in windows.inc
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633577(v=vs.85).aspx
    
            ; Load default icon
            push IDI_APPLICATION
            push NULL
            call LoadIcon
            mov wc.hIcon, eax
            mov wc.hIconSm, eax
    
            ; Load default cursor
            push IDC_ARROW
            push NULL
            call LoadCursor
            mov wc.hCursor, eax
    
            mov wc.cbSize, SIZEOF WNDCLASSEX    ; size of this structure
            mov wc.style, CS_HREDRAW or CS_VREDRAW  ; style of windows https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
            mov wc.lpfnWndProc, OFFSET WndProc  ; andress of window procedure
            mov wc.cbClsExtra, NULL
            mov wc.cbWndExtra, NULL
            push hInstance
            pop wc.hInstance
            mov wc.hbrBackground, COLOR_WINDOW+1    ; background color, require to add 1
            mov wc.lpszMenuName, NULL
            mov wc.lpszClassName, OFFSET ClassName
    
            ; we register our own class, named in ClassName
            push offset wc
            call RegisterClassEx
    
            ; after register ClassName, we use it to create windows compond
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx
            push NULL
            push hInstance
            push NULL
            push NULL
            push WIN_HEIGHT
            push WIN_WIDTH
            push CW_USEDEFAULT
            push CW_USEDEFAULT
            push WS_OVERLAPPEDWINDOW
            push offset AppName
            push offset ClassName
            push WS_EX_CLIENTEDGE
            call CreateWindowEx
    
            mov hwnd, eax   ; return windows handle
    
            ; display window
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
            push CmdShow
            push hwnd
            call ShowWindow
    
            ; update window
            ; https://msdn.microsoft.com/en-us/library/windows/desktop/dd145167(v=vs.85).aspx
            push hwnd
            call UpdateWindow
    
            ; Message Loop
            MESSAGE_LOOP:
                ; get message
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
                push 0
                push 0
                push NULL
                push offset msg
                call GetMessage
    
                ; return in eax
                ; if the function retrieves a message other than WM_QUIT, the return value is nonzero.
                ; if the function retrieves the WM_QUIT message, the return value is zero.
                test eax, eax
                jle END_LOOP
    
                ; translate virtual-key messages into character messages - ASCII in WM_CHAR
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955(v=vs.85).aspx
                push offset msg
                call TranslateMessage
    
                ; sends the message data to the window procedure responsible for the specific window the message is for.
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms644934(v=vs.85).aspx
                push offset msg
                call DispatchMessage
    
                jmp MESSAGE_LOOP
    
            END_LOOP:
                mov eax, msg.wParam
            ret
        WinMain endp
    
        TimerProc PROC thwnd:HWND, uMsg:UINT, idEvent:UINT, dwTime:DWORD
                push TRUE
                push NULL
                push thwnd
                call InvalidateRect
                ret
        TimerProc ENDP
    
        ; Handle message with switch(notification)
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
        WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
            cmp uMsg, WM_PAINT
            je ON_WM_PAINT
    
            cmp uMsg, WM_CREATE
            je ON_WM_CREATE
    
            cmp uMsg, WM_LBUTTONDOWN
            je ON_WM_LBUTTONDOWN
    
            cmp uMsg, WM_DESTROY
            je ON_WM_DESTROY
    
            cmp uMsg, WM_QUIT
            je ON_WM_DESTROY
    
            cmp uMsg, WM_CLOSE
            je ON_WM_DESTROY
    
            jmp ON_DEFAULT
    
            ; user close program
            ON_WM_DESTROY:
                push NULL
                call PostQuitMessage
                jmp EXIT
    
            ON_WM_CREATE:
                ; create a pen with specific color and size
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/dd183509(v=vs.85).aspx
                push PEN_COLOR
                push PEN_SIZE
                push PS_SOLID
                call CreatePen
                mov hPen, eax
    
                jmp EXIT
    
            ON_WM_LBUTTONDOWN:
                cmp [state], DRAWING
                je EXIT
    
                push lParam
                call updateXY
    
                ; when clicked, set state to DRAWING
                mov [state], DRAWING
    
                push OFFSET TimerProc
                push 20
                push 1
                push hwnd
                call SetTimer
    
    ;            mov dword ptr[time.wMilliseconds], BALL_SPEED
    ;            push offset time
    ;            call SetSystemTime
    
                jmp EXIT
    
            ON_WM_PAINT:
                mov dword ptr[time.wMilliseconds], 0
    
                push offset ps
                push hWnd
                call BeginPaint
                mov hdc, eax
    
                ; apply pen to hdc
                push hPen
                push hdc
                call SelectObject
    
                call createEllipse
    
                push offset ps
                push hWnd
                call EndPaint
    
                jmp EXIT
    
            ON_DEFAULT:
                ; handle any message that program don't handle
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
                push lParam
                push wParam
                push uMsg   ; message
                push hWnd   ; windows
                call DefWindowProc
    
                jmp EXIT
    
            EXIT:
                ret
        WndProc endp
    
        createEllipse proc
            push brpoint.y
            push brpoint.x
            push tlpoint.y
            push tlpoint.x
            push hdc
            call Ellipse
    
            call moveEllipse
    
            mov eax, WIN_WIDTH
            cmp brpoint.x, eax
            jg MEET_RIGHT_LEFT
    
            mov eax, WIN_HEIGHT
            cmp brpoint.y, eax
            jg MEET_BOTTOM_TOP
    
            cmp tlpoint.x, 0
            jl MEET_RIGHT_LEFT
    
            cmp tlpoint.y, 0
            jl MEET_BOTTOM_TOP
    
            jmp MEET_NONE
    
            MEET_RIGHT_LEFT:
                neg vectorX
                jmp MEET_NONE
    
            MEET_BOTTOM_TOP:
                neg vectorY
                jmp MEET_NONE
    
            MEET_NONE:
    
            ret
        createEllipse endp
    
        moveEllipse proc
            mov eax, dword ptr[vectorX]
            mov ecx, dword ptr[vectorY]
    
            add tlpoint.x, eax
            add tlpoint.y, ecx
            add brpoint.x, eax
            add brpoint.y, ecx
    
            ret
        moveEllipse endp
    
        updateXY proc lParam:LPARAM
            mov eax, lParam
    
            ; get low word that contain x
            xor ebx, ebx
            mov bx, ax
    
            mov tlpoint.x, ebx
            mov brpoint.x, ebx
            add brpoint.x, BALL_SIZE
    
            ; get high word that contain y
            mov eax, lParam
            shr eax, 16
    
            mov tlpoint.y, eax
            mov brpoint.y, eax
            add brpoint.y, BALL_SIZE
    
            ret
        updateXY endp
    
    end start