Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Another Mouse Issue?
#11
You can also control a player's fire rate by using a latching method that keeps track of a key's current state (pressed or released).

I made a quick demo to show how this can be used in a game.

I have no idea why my comments get skewed when posting using the QB tags? This happens when exporting to HTML as well. I wish someone would look into this.

Code: (Select All)
'+-----------------------------------------------------------------------------------+
'|                            Controlling Bullets                                  |
'|                                                                                  |
'|                          "Wearing the player out"                                |
'|                            ----------------------                                |
'|                                                                                  |
'| This method forces the player to release the fire button to fire the next bullet. |
'| This allows the player to rapid fire as fast as they can push the button which in |
'| turn wears the player out.                                                        |
'|                                                                                  |
'| A "latching" method is used to keep track of the fire button's current state.    |
'| This method is also used in the player's ship movements to keep the game from    |
'| registering the UP and DOWN arrow keys being pressed at the same time and the    |
'| RIGHT and LEFT arrows keys from being pressed at the same time as well.          |
'|                                                                                  |
'|                                                                                  |
'| This method can be combined with frame timers to limit the number of bullets per  |
'| second as well.                                                                  |
'|                                                                                  |
'|                        Demo by Terry Ritchie 04/25/24                            |
'+-----------------------------------------------------------------------------------+

OPTION _EXPLICIT '          force declaration of all variables

'+-----------------------+
'| Define game constants |
'+-----------------------+

CONST SCREENWIDTH% = 800 '  game screen width
CONST SCREENHEIGHT% = 600 ' game screen height
CONST BUTTONUP% = 0 '      button position is up (depressed)
CONST BUTTONDOWN% = 1 '    button position is down (pressed)

'+-----------------------------+
'| Define variables structures |
'+-----------------------------+

TYPE KEY '                  DEFINE KEYBOARD KEY
    Key AS INTEGER '        scan code of key
    Position AS INTEGER '  position of keyboard key (the "latch")
END TYPE

TYPE BUTTON '              DEFINE GAME BUTTONS
    North AS KEY '          up arrow key
    South AS KEY '          down arrow key
    East AS KEY '          right arrow key
    West AS KEY '          left arrow key
    Fire AS KEY '          space bar key
END TYPE

TYPE BULLET '              DEFINE BULLET
    Active AS INTEGER '    bullet active (y/n)
    x AS INTEGER '          center x coordinate of bullet
    y AS INTEGER '          center y coordinate of bullet
    Speed AS SINGLE '      bullet speed
END TYPE

TYPE SHIP '                DEFINE PLAYER SHIP
    x AS INTEGER '          center  x coordinate of ship
    y AS INTEGER '          center  y coordinate of ship
    MaxX AS INTEGER '      maximum x coordinate of ship \
    MaxY AS INTEGER '      maximum y coordinate of ship  \ Ship bounding box
    MinX AS INTEGER '      minimum x coordinate of ship  /
    MinY AS INTEGER '      minimum y coordinate of ship /
END TYPE

'+------------------+
'| Define variables |
'+------------------+

DIM Button AS BUTTON '            game buttons
REDIM Bullet(0) AS BULLET '      player bullet array
DIM Ship AS SHIP '                player ship

'+----------------------+
'| Initialize variables |
'+----------------------+

Button.North.Key = 18432 '        up arrow    key scan code
Button.South.Key = 20480 '        down arrow  key scan code
Button.East.Key = 19712 '        right arrow key scan code
Button.West.Key = 19200 '        left arrow  key scan code
Button.Fire.Key = 32 '            space bar  key scan code

Ship.x = 20 '                    initial ship x coordinate
Ship.y = (SCREENHEIGHT - 1) \ 2 ' initial ship y coordinate
Ship.MinX = 20 '                  ship minimum x coordinate
Ship.MinY = 20 '                  ship minimum y coordinate
Ship.MaxX = SCREENWIDTH \ 2 - 1 ' ship maximum x coordinate
Ship.MaxY = SCREENHEIGHT - 21 '  ship maximum y coordinate

'+--------------------------------------------------------------------------------------------------------------------+
'| BEGIN MAIN PROGRAM                                                                                                |
'+--------------------------------------------------------------------------------------------------------------------+

SCREEN _NEWIMAGE(SCREENWIDTH, SCREENHEIGHT, 32) ' enter game graphics screen
DO '                                              begin main program loop
    _LIMIT 60 '                                  nice and smooth at 60 FPS
    CLS '                                        clear the game screen
    DisplayDirections '                          display game's directions

    '+-----------------------------------+
    '| Update the player's ship position |
    '+-----------------------------------+

    MoveShip '                                    update and draw player ship

    '+-----------------------------------------------+
    '| Test for the fire button in the down position |
    '+-----------------------------------------------+

    IF _KEYDOWN(Button.Fire.Key) THEN '          fire button pressed? (space bar key)

        '+------------------------------------------------------------------------------+
        '| Test if the fire button has been released since the last time it was pressed |
        '+------------------------------------------------------------------------------+

        IF Button.Fire.Position = BUTTONUP THEN ' yes, has fire button been released?

            '+------------------------------------------------------------+
            '| Set the fire button into the down position and fire bullet |
            '+------------------------------------------------------------+

            Button.Fire.Position = BUTTONDOWN '  yes, latch fire button in down position
            FireBullet Ship.x + 15, Ship.y '      fire a bullet from the nose of the ship
        END IF
    ELSE '                                        no, fire button is not pressed
        Button.Fire.Position = BUTTONUP '        latch fire button in up position
    END IF

    '+------------------------------------+
    '| Update the active bullet positions |
    '+------------------------------------+

    ManageBullets '                              update and draw all active bullets
    _DISPLAY '                                    update game screen with changes

LOOP UNTIL _KEYDOWN(27) '                        leave main loop when ESC pressed
SYSTEM '                                          return to the operating system

'+--------------------------------------------------------------------------------------------------------------------+
'| END MAIN PROGRAM                                                                                                  |
'+--------------------------------------------------------------------------------------------------------------------+

'+--------------------------------------------------------------------------------------------------------------------+
'| Declare subroutines and functions                                                                                  |
'+--------------------------------------------------------------------------------------------------------------------+

' ____________________________________________________________________________________________________________________
'/                                                                                                                    \
SUB DisplayDirections () '                                                                          DisplayDirections |
    ' ________________________________________________________________________________________________________________/
    '/                                                                                                                \
    '| Displays the game's directions to the player.                                                                  |
    '\________________________________________________________________________________________________________________/

    LOCATE 2, 2
    PRINT "FORCE PLAYER TO PRESS AND RELEASE FIRE KEY"
    LOCATE 4, 2
    PRINT " - ARROW KEYS TO MOVE"
    LOCATE 5, 2
    PRINT " - SPACE BAR TO FIRE"
    LOCATE 6, 2
    PRINT " - ESC TO LEAVE GAME"

END SUB
' ____________________________________________________________________________________________________________________
'/                                                                                                                    \
SUB MoveShip () '                                                                                            MoveShip |
    ' ________________________________________________________________________________________________________________/
    '/                                                                                                                \
    '| Updates the ship location according to player keypresses then draws the ship on the game screen.              |
    '\________________________________________________________________________________________________________________/

    SHARED Button AS BUTTON ' need access to game buttons
    SHARED Ship AS SHIP '    need access to player ship

    '+---------------------------------+
    '| Check for forward ship movement |
    '+---------------------------------+

    IF _KEYDOWN(Button.East.Key) THEN '                    forward thrust button pressed? (right arrow key)
        IF Button.West.Position = BUTTONUP THEN '          yes, is the reverse button pressed?
            Button.East.Position = BUTTONDOWN '            no, latch forward button in down position
            Ship.x = Ship.x + 5 '                          move ship forward
            IF Ship.x > Ship.MaxX THEN Ship.x = Ship.MaxX ' keep x within upper limit
        END IF
    ELSE '                                                  no, forward thrust button is not pressed
        Button.East.Position = BUTTONUP '                  latch forward thrust button in up position
    END IF

    '+---------------------------------+
    '| Check for reverse ship movement |
    '+---------------------------------+

    IF _KEYDOWN(Button.West.Key) THEN '                    reverse button pressed? (left arrow key)
        IF Button.East.Position = BUTTONUP THEN '          yes, is the forward thrust button pressed?
            Button.West.Position = BUTTONDOWN '            no, latch reverse button in down position
            Ship.x = Ship.x - 5 '                          move ship backward
            IF Ship.x < Ship.MinX THEN Ship.x = Ship.MinX ' keep x within lower limit
        END IF
    ELSE '                                                  no, reverse button is not pressed
        Button.West.Position = BUTTONUP '                  latch reverse button in up position
    END IF

    '+--------------------------------+
    '| Check for upward ship movement |
    '+--------------------------------+

    IF _KEYDOWN(Button.North.Key) THEN '                    upward button pressed? (up arrow key)
        IF Button.South.Position = BUTTONUP THEN '          yes, is the downward button pressed?
            Button.North.Position = BUTTONDOWN '            no, latch upward button in down position
            Ship.y = Ship.y - 5 '                          move ship upward
            IF Ship.y < Ship.MinY THEN Ship.y = Ship.MinY ' keep y within lower limit
        END IF
    ELSE '                                                  no, upward button is not pressed
        Button.North.Position = BUTTONUP '                  latch upward button in up position
    END IF

    '+----------------------------------+
    '| Check for downward ship movement |
    '+----------------------------------+

    IF _KEYDOWN(Button.South.Key) THEN '                    downward button pressed? (down arrow key)
        IF Button.North.Position = BUTTONUP THEN '          yes, is the upward button pressed?
            Button.South.Position = BUTTONDOWN '            no, latch downward button in down position
            Ship.y = Ship.y + 5 '                          move ship downward
            IF Ship.y > Ship.MaxY THEN Ship.y = Ship.MaxY ' keep y within upper limit
        END IF
    ELSE '                                                  no, downward button is not pressed
        Button.South.Position = BUTTONUP '                  latch downward button in up position
    END IF

    '+------------------+
    '| Draw player ship |
    '+------------------+

    LINE (Ship.x - 15, Ship.y - 10)-(Ship.x - 15, Ship.y + 10) ' draw triangular ship
    LINE -(Ship.x + 15, Ship.y)
    LINE -(Ship.x - 15, Ship.y - 10)

END SUB
' ____________________________________________________________________________________________________________________
'/                                                                                                                    \
SUB ManageBullets () '                                                                                  ManageBullets |
    ' ________________________________________________________________________________________________________________/
    '/                                                                                                                \
    '| Updates and draws all currently active bullets to the game screen.                                            |
    '\________________________________________________________________________________________________________________/

    SHARED Bullet() AS BULLET ' need access to player bullet array
    DIM Index AS INTEGER '      array index counter

    '+------------------------------------------+
    '| Scan the bullet array for active bullets |
    '+------------------------------------------+

    Index = -1 '                                                      reset index counter value
    DO '                                                              begin array search loop
        Index = Index + 1 '                                          increment array index counter
        IF Bullet(Index).Active THEN '                                is this bullet active?

            '+------------------------------------------------------+
            '| This bullet is active, update its location and speed |
            '+------------------------------------------------------+

            Bullet(Index).x = Bullet(Index).x + Bullet(Index).Speed ' yes, move bullet to the right
            Bullet(Index).Speed = Bullet(Index).Speed * 1.05 '        increase speed slightly

            '+-----------------------------------------------------+
            '| Check to see if the bullet has left the game screen |
            '+-----------------------------------------------------+

            IF Bullet(Index).x >= _WIDTH(0) - 1 THEN '                has bullet left the game screen?

                '+------------------------------------------------+
                '| This bullet has left the screen, deactivate it |
                '+------------------------------------------------+

                Bullet(Index).Active = 0 '                            yes, deactivate bullet
            ELSE '                                                    no, bullet still on game screen

                '+----------------------------------------------+
                '| This bullet is still on game screen, draw it |
                '+----------------------------------------------+

                CIRCLE (Bullet(Index).x, Bullet(Index).y), 5 '        draw the bullet
                PAINT (Bullet(Index).x, Bullet(Index).y) '            paint the bullet
            END IF
        END IF
    LOOP UNTIL Index = UBOUND(Bullet) '                              leave when all indexes checked

END SUB
' ____________________________________________________________________________________________________________________
'/                                                                                                                    \
SUB FireBullet (x AS INTEGER, y AS INTEGER) '                                                              FireBullet |
    ' ________________________________________________________________________________________________________________/
    '/                                                                                                                \
    '| Initiates a bullet at the supplied coordinates                                                                |
    '|                                                                                                                |
    '| x,y - the on screen coordinates where the bullet will materialize                                              |
    '\________________________________________________________________________________________________________________/

    SHARED Bullet() AS BULLET ' need access to player bullet array
    DIM Index AS INTEGER '      array index counter

    '+-------------------------------------------------------+
    '| Scan the bullet array for an index that is not active |
    '+-------------------------------------------------------+

    Index = -1 '                                                    reset index counter
    DO '                                                            begin array search loop
        Index = Index + 1 '                                        increment array index counter
    LOOP UNTIL Index = UBOUND(Bullet) OR Bullet(Index).Active = 0 ' leave when inactive found or at end of array

    '+------------------------------------------------------------------------------+
    '| Test if the index value from the search loop yielded an inactive array index |
    '+------------------------------------------------------------------------------+

    IF Bullet(Index).Active THEN '                                  was an inactive array index found?

        '+------------------------------------------------------------------------------------------------------+
        '| No inactive bullets were found in the array so the array size will need to be increased by one. The  |
        '| maximum size of the array will eventually be determined by the speed of the player's button presses. |
        '+------------------------------------------------------------------------------------------------------+

        Index = Index + 1 '                                        no, increase the array index size
        REDIM _PRESERVE Bullet(Index) AS BULLET '                  resize the array while preserving exisiting data
    END IF

    '+---------------------------------------------------+
    '| Add the new bullet's information the bullet array |
    '+---------------------------------------------------+

    Bullet(Index).x = x '                                          new bullet's x coordinate
    Bullet(Index).y = y '                                          new bullet's y coordinate
    Bullet(Index).Speed = 5 '                                      new bullet's initial speed
    Bullet(Index).Active = 1 '                                      this array index is active
    SOUND 880, .5 '                                                awesome bullet sound

END SUB
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#12
(04-25-2024, 02:16 AM)NakedApe Wrote: Steve and Terry, thank you, but I'm on a Mac, the neglected step-child platform Wink , and moving the mouse events outside the update loop is different. Doing your fix, Steve, doesn't solve the problem for me, it just made the timer not work at all 'cept for the beep. Terry, your demo doesn't work on my system either. Pete, thanks, I'll give your sub a try, but I have my doubts. bplus, I'm not using bullets here, just a laser beam to blast away. Terry, I'll try the method from your latest post - cycle counting makes sense.  Thanks, everybody.

Well, not really neglected. We do show it some love from time to time.  Big Grin The recent list of PRs included a lot of Mac-related updates.

I guess the real challenge is that many of us don't have access to Apple hardware for testing and tinkering. Personally, the only Apple development hardware I have is a hackintosh.

(04-25-2024, 04:11 PM)NakedApe Wrote: Terry, I think the lack of mouse wheel support on macOS is a known issue, but maybe a740g could comment on that. bplus, you are so right. I fixed the issue without using timers. Cycle counting, which I already used elsewhere in the program, was a simple fix - duh. There's almost always another way to get a job done with QB if you think about it - and get help from the hive mind here.  Smile

The scroll and the _MOUSEMOVEMENTx stuff for macOS will be fixed in the next update (which I guess will happen soonish). We already got those merged.
Reply
#13
Thanks, Terry, for that "quick" demo. Very helpful. And thanks, a740g, for the Mac update. It'll be great having free-range mouse movement... yee haw.
Reply
#14
(04-25-2024, 01:40 AM)TerryRitchie Wrote:
(04-25-2024, 01:06 AM)SMcNeill Wrote: Mouse your mouse events outside the update loop:
LOL. Mouse your mouse events, cracked me up. I find myself doing these types of typos all the time.

But Steve is correct, the only thing that should be done within a mouse update loop is gathering scroll wheel events. Here's a demo of that:

Code: (Select All)
SCREEN _NEWIMAGE(800, 600, 32)

radius = 50
DO
    _LIMIT 60
    CLS
    LOCATE 2, 2: PRINT "_MOUSEHWEEL OUTSIDE THE UPDATE LOOP"
    LOCATE 4, 2: PRINT "Use mouse wheel to change size of circle"

    LOCATE 6, 2: PRINT "PRESS ANY KEY TO MOVE _MOUSEWHEEL INSIDE THE LOOP"
    WHILE _MOUSEINPUT: WEND
    radius = radius + _MOUSEWHEEL * 5
    IF radius < 5 THEN radius = 5
    CIRCLE (_MOUSEX, _MOUSEY), radius
    _DISPLAY
LOOP UNTIL INKEY$ <> ""
DO
    _LIMIT 60
    CLS
    LOCATE 2, 2: PRINT "_MOUSEHWEEL NOW INSIDE THE UPDATE LOOP"
    LOCATE 4, 2: PRINT "Use mouse wheel to change size of circle"
    LOCATE 6, 2: PRINT "PRESS ESC TO EXIT"
    WHILE _MOUSEINPUT
        radius = radius + _MOUSEWHEEL * 5
        IF radius < 5 THEN radius = 5
    WEND
    CIRCLE (_MOUSEX, _MOUSEY), radius
    _DISPLAY
LOOP UNTIL _KEYDOWN(27)
SYSTEM

So I have a question then about the mouse update loop and only putting MouseWheel events inside the While:Wend. I based one of my mouse routines on this, from HELP in the IDE under _MOUSEINPUT:

Code: (Select All)

DO WHILE _MOUSEINPUT 'mouse status changes only
    x = _MOUSEX
    y = _MOUSEY
    IF x > 0 AND x < 640 AND y > 0 AND y < 480 THEN
        IF _MOUSEBUTTON(2) THEN
            PSET (x, y), 15
            LOCATE 1, 1: PRINT x, y
        END IF
    END IF
LOOP

Is this not a proper approach? Is it okay to do this in a DO WHILE: LOOP, but not a WHILE:WEND?  Huh  I mean, it works well...
Reply
#15
(05-01-2024, 11:42 PM)NakedApe Wrote:
(04-25-2024, 01:40 AM)TerryRitchie Wrote:
(04-25-2024, 01:06 AM)SMcNeill Wrote: Mouse your mouse events outside the update loop:
LOL. Mouse your mouse events, cracked me up. I find myself doing these types of typos all the time.

But Steve is correct, the only thing that should be done within a mouse update loop is gathering scroll wheel events. Here's a demo of that:

Code: (Select All)
SCREEN _NEWIMAGE(800, 600, 32)

radius = 50
DO
    _LIMIT 60
    CLS
    LOCATE 2, 2: PRINT "_MOUSEHWEEL OUTSIDE THE UPDATE LOOP"
    LOCATE 4, 2: PRINT "Use mouse wheel to change size of circle"

    LOCATE 6, 2: PRINT "PRESS ANY KEY TO MOVE _MOUSEWHEEL INSIDE THE LOOP"
    WHILE _MOUSEINPUT: WEND
    radius = radius + _MOUSEWHEEL * 5
    IF radius < 5 THEN radius = 5
    CIRCLE (_MOUSEX, _MOUSEY), radius
    _DISPLAY
LOOP UNTIL INKEY$ <> ""
DO
    _LIMIT 60
    CLS
    LOCATE 2, 2: PRINT "_MOUSEHWEEL NOW INSIDE THE UPDATE LOOP"
    LOCATE 4, 2: PRINT "Use mouse wheel to change size of circle"
    LOCATE 6, 2: PRINT "PRESS ESC TO EXIT"
    WHILE _MOUSEINPUT
        radius = radius + _MOUSEWHEEL * 5
        IF radius < 5 THEN radius = 5
    WEND
    CIRCLE (_MOUSEX, _MOUSEY), radius
    _DISPLAY
LOOP UNTIL _KEYDOWN(27)
SYSTEM

So I have a question then about the mouse update loop and only putting MouseWheel events inside the While:Wend. I based one of my mouse routines on this, from HELP in the IDE under _MOUSEINPUT:

Code: (Select All)

DO WHILE _MOUSEINPUT 'mouse status changes only
    x = _MOUSEX
    y = _MOUSEY
    IF x > 0 AND x < 640 AND y > 0 AND y < 480 THEN
        IF _MOUSEBUTTON(2) THEN
            PSET (x, y), 15
            LOCATE 1, 1: PRINT x, y
        END IF
    END IF
LOOP

Is this not a proper approach? Is it okay to do this in a DO WHILE: LOOP, but not a WHILE:WEND?  Huh  I mean, it works well...

It's completely unnecessary.   Let's take a few quick code snippets as an example:

DO
  WHILE _MOUSEINPUT
    x = _MOUSEX
  WEND
  _LIMIT 10
LOOP

and compare that to:
DO
  _WHILE _MOUSEINPUT
  WEND
  x = _MOUSEX
  _LIMIT 10
LOOP


Now, let's grab our mouse and drag it all across the screen from point 0,0 to point 1000,1000 in exactly 1 second.  Since we're using the Steve Virtual Mouse(tm) which updates 100 times per second, we can calculate exactly how far that mouse moves in .1 seconds.

0,0 to 10,10
to 20,20
to 30,30
to 40,40
to 50,50
to 60,60
to 70,70
to 80,80
to 90,90
to 100,100

.... and since we're running our main program with a _LIMIT 10, we know we're only going to get .1 second worth of data in each of the main loops that those WHILE... WEND are going to process.

Now, the first routine runs, and it...
WHILE _MOUSEINPUT:
assigns x the value of 10
assigns x the value of 20
assigns x the value of 30
assigns x the value of 40
assigns x the value of 50
assigns x the value of 60
assigns x the value of 70
assigns x the value of 80
assigns x the value of 90
assigns x the value of 100
WEND  (we're now out of cached data)
x is a value of 100!


And the second routine runs and it:
WHILE _MOUSEINPUT
WEND (update all those values in the background)
x = 100 (the last value which _mouseinput updated for us)


Now, which of those are going to run smoother and perform better??
Reply
#16
Yeah, Steve pointed the issue out perfectly with a good example.
There are two ways to write error-free programs; only the third one works.
QB64 Tutorial
Reply
#17
Here's where we've talked about all this before.  See if the conversation here helps to explain things for you: https://qb64phoenix.com/forum/showthread...9#pid12349
Reply
#18
Thanks. That helps mucho and the older thread too. I've got some rearranging to do!
Reply




Users browsing this thread: 1 Guest(s)