Scrolling Game Development Kit Forum

SGDK Version 1 => Script => Topic started by: ragingmime on 2006-02-05, 03:48:40 PM

Title: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-05, 03:48:40 PM
I've set up the wizard-based shooting script in my game, and I'm trying to find a way to use it to let the player aim and shoot with the mouse, a la Abuse.

I've been trying to use the mouse input sample script (http://sourceforge.net/docman/display_doc.php?docid=7777&group_id=9970) to make crosshairs that the player can move with the mouse, but the script doesn't work. I get the error "Object Variable or With Block variable not set" when I include this line:
HostObj.SinkObjectEvents CurrentDisplay, "Display"
What's going on? Here's the rest of the script:

Sub CurrentDisplay_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer
ProjectObj.GamePlayer.rMap.SpriteDefs("aim").X = X + .MapScrollX - .rMap.ViewLeft
ProjectObj.GamePlayer.rMap.SpriteDefs("aim").Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub


Also, I don't understand the point of the initialization script area of this sample. It has a connecteventsnow line when there's already a connecteventsnow line in the runtime script. Is this sample from an older version of the game maker? Am I missing something there?

Thanks in advance.
Title: Re: Mouse Aiming/sthooting
Post by: bluemonkmn on 2006-02-06, 06:57:53 AM
Yes, the key point you are missing is that the display object does not exist until the Player_OnPlayInit event occurs.  But ConnectEventsNow cannot execute inside an event because no events can be triggered until ConnectEventsNow has executed.  So to get around this catch-22 I implemented a solution which allows you to put multiple scripts within a single file (you all knew there had to be some reason behind this right?  Here it is!)  Multiple scripts are separated by a line that starts with "#Split" and you can transfer control from one script to another using the HostObj.StartScript property.  When StartScript is set, GameDev will transfer contron to another script at its next opportunity, clearing out everything it knows about the old script and transferring only the data that you store into the HostObj.TempStorage property (and other GameDev object properties -- but no variables, functions or events are retained).

So this is how you can connect different event handlers at different times.  The first script's main purpose is to wait until the OnPlayInit event occurs before attempting to attach to the display's events.  When OnPlayInit occurs, it transfers control to the other (main) script which hooks up event handlers to the Display object.

Is that enough to help you figure out the problem you are having?
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-06, 03:50:06 PM
Thanks! Yep, that does help: now I'm not getting any error messages when I start the game.

The crosshairs sprite (called "aim," using the template "aimtemp" and on the path "aimpath") still isn't moving, though. I've made sure that it's set as an initial instance, that the control is set to "simple (scripted)", that its movement speed is at the maximum (although I'm not sure that matters), etc. The crosshairs sprite appears when the script isn't running, but it of course doesnt' move. When the game is run with the script, the crosshairs sprite still appears but it also doesn't move when I move the mouse. The code right now is:


#Split == Runtime script (Number 1) ============
'Modified by ragingmime

Sub CurrentDisplay_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer.rMap
.Sprite("aim").X = X + .MapScrollX - .rMap.ViewLeft
.Sprite("aim").Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub
                   
HostObj.SinkObjectEvents CurrentDisplay, "Display"
HostObj.SinkObjectEvents ProjectObj.GamePlayer, "Player"
HostObj.ConnectEventsNow()


Can you see anything else that I'm doing wrong?

Thanks a lot!
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-06, 04:20:27 PM
I think that sub called "CurrentDisplay_MouseMove" should be "Display_MouseMove" though I'm not entirely sure.  If that doesn't work, try putting in a line that you know will have an effect (like "x = x / 0") inside that event.  If it crashes, you know the problem is that the sprite is not displaying after it's moved.  Otherwise, if it doesn't crash, then you know that the problem is that the event is never being called (because of the name of the sub)
Title: Re: Mouse Aiming/sthooting
Post by: bluemonkmn on 2006-02-07, 07:10:24 AM
Yes, durnurd has it right.  Let me briefly explain the parameters of "SinkObjectEvents".  The first parameter is an object that will raise events.  The second parameter determines by what name the object's events will be handled.  So when you provide "Display" as the second parameter, that means that every event handler that handles an event coming from CurrentDisplay should start with "Display_".
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-07, 01:52:21 PM
Thanks; that was probably part of the problem. It seems like the event still isn't being triggered, though, because I can put all kinds of garbage in Display_MouseMove and never get an error:
Code: [Select]

' ===== Initialization Script (Number 0) =======
Sub Player_OnPlayInit()
HostObj.StartScript=1
HostObj.StartScript=2
End Sub

HostObj.SinkObjectEvents ProjectObj.GamePlayer, "Player"
HostObj.ConnectEventsNow()
ProjectObj.GamePlayer.Play 16

#Split == Runtime script (Number 1) ============
'Modified by ragingmime

Sub Display_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer.rMap
sdfsddsfsa
.Sprite("aim").X = X + .MapScrollX - .rMap.ViewLeft
.Sprite("aim").X = X /0
.Sprite("aim").Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub
                   
HostObj.SinkObjectEvents CurrentDisplay, "Display"
HostObj.SinkObjectEvents ProjectObj.GamePlayer, "Player"
HostObj.ConnectEventsNow()

Script number 2 is a shooting script, which runs fine.

Do you see anything else I might be doing wrong? Thanks for all your great help.
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-07, 03:36:42 PM
As far as I'm aware, only one script can be active at a time.  You need to put them together in one script.  Just copy

Code: [Select]
Sub Display_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer.rMap
sdfsddsfsa
.Sprite("aim").X = X + .MapScrollX - .rMap.ViewLeft
.Sprite("aim").X = X /0
.Sprite("aim").Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub
                   
HostObj.SinkObjectEvents CurrentDisplay, "Display"

into the other script and get rid of the one you copied it from.
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-07, 06:36:31 PM
Okay, I did that and I got the proper error messages. Now, I'm getting another error message and I think the problem is in how I'm referring to the sprites. I guess the sprite is owned by the layer within the map, not by the map as I have it.

The code right now is:

Code: [Select]
#Split == Runtime script (Number 1) ============

Sub Display_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer
.rMap.Sprite("aim").X = X + .MapScrollX - .rMap.ViewLeft
.rMap.Sprite("aim").Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub

But apparently Sprite is owned by Layer. Layer isn't owned by rMap, becuase using .rMap.rLayer.Sprite gives me an error, as does PlayerSprite.rDef.rLayer.Sprite. I've read the reference (http://gamedev.sourceforge.net/Help/GDScrRef.htm#classlayer), but apparently I'm just not understanding something.

Thanks for all your help; I really appreciate it.
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-07, 10:23:49 PM
First of all, there is no rLayer attribute of the Map object.  You need to find the layer by giving its index.  Second of all, there is no Sprite property of a map (only of a layer) and if you're going to use it, you have to use the index.

Code: [Select]
With ProjectObj.GamePlayer.rMap.MapLayer(0).SpriteDefs(1)
   .X = X + .MapScrollX - .rMap.ViewLeft
   .Y = Y + .MapScrollY - .rMap.ViewTop
End With

where 0 is the map layer counting from the top down in the list where the sprite is, starting at 0.  and 1 is the index of the Sprite you are using.
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-07, 10:28:19 PM
And I may actually be wrong about requiring the index instead of the name for either the layer or the sprite or both (I seem to remember something about that maybe) but that's the way it is in the refference, which is by no means perfect.
Title: Re: Mouse Aiming/sthooting
Post by: bluemonkmn on 2006-02-07, 10:55:33 PM
Well, the scripting reference should be correct at least as far as data types go since it is generated from the source code.  It is correct at least with respect to this.  When you refer to a layer using Map.MapLayer you can pass a name or a number, as indicated by the "Variant" parameter type (even though the parameter's name is "Index" -- I would describe it as having "named indexes").  And when you refer to a sprite on a layer, you will see that that can only take an integer, which means you can't refer to sprites by name.  The reason is sprites don't have names.  Sprite Definitions have names, but you can have multiple sprite instances based on the same definition, so all sprite instances are just referred to by index.  Generally you will only have one instance per definition, so often times it works to loop through the collection of sprites and search for a sprite index whose definition name matches a particular value.  But when you refer to the Sprites property on a layer, you will have to use numeric indexes.
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-08, 08:36:51 AM
Ah yes.  The refference says that MapLayer gets the map by its ordinal index, but says nothing about its name (but then the example had variant as data type, so there you go)
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-08, 12:49:28 PM
Are sprite definitions the same thing as templates? I thought that, at least within the GameDev editor, both sprite templates and specific sprites are named. Are sprites within the editor technically sprite definitions (because you could create multiple sprites with script?) If that's the case, could I refer to the sprite definition by name and then refer to index 0 of that sprite defintion to get the sprite that I see in the editor? How would I do that?

If I'm controlling a sprite with script, do I have to make an instance of the sprite within the script? If I want to get at a sprite that's already there (i.e. initial instance is checked and it exists in the game even without the script running), how do I reference it/figure out its index? (I only see a field for the name in the GameDev editor). Would it be considered a sprite definition, a sprite, or something else?

Sorry to be a pain about this, it's just that this object hierarchy is a little confusing. You guys have been really helpful - thanks again!
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-08, 12:59:50 PM
Sprite templates define how a sprite works.  Sprite definitions define the specific location and existance of a sprite, and then you can use special functions to instantiate those sprite definitions as sprites.  The index of a given sprite is equal to number of sprites created before it during the course of playing a level.  This includes those created by checking the "initial instance" flag of a definition, or through special functions or script (or any way possible to create sprites).

You can't access an index of a specific sprite definition.  So if you create five "enemy" sprites, you can't say SpriteDefs("enemy").sprite(0) to access the first "enemy" sprite created.  You have to count how many sprites were created before it, and add that to the index you want.
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-08, 04:00:16 PM
Okay, I get it. Thanks!

The crosshairs now move with the mouse with this code:
Code: [Select]
Sub Display_MouseMove(Button, Shift, X, Y)
With ProjectObj.GamePlayer
.rMap.MapLayer(0).Sprite(14).X = X + .MapScrollX - .rMap.ViewLeft
.rMap.MapLayer(0).Sprite(14).Y = Y + .MapScrollY - .rMap.ViewTop
End With
End Sub
                   
HostObj.SinkObjectEvents CurrentDisplay, "Display"
HostObj.SinkObjectEvents ProjectObj.GamePlayer, "Player"
HostObj.ConnectEventsNow()

The problem I'm running into now is that when I kill an enemy, the index of my crosshairs sprite changes (there's one less sprite, so it becomes #13 instead of #14) and "I get a subscript out of range." Is there any way to find the index of a given sprite and plug that in instead of hard-coding the number? I poked around in the documentation and found a way of doing the reverse (http://gamedev.sourceforge.net/Help/GDScrRef.htm#classspritedef) - i.e. getting the sprite definition's name if you have the index - but that doesn't help me, becasue the index is variable while the name is (I assume) static.

Any suggestions? Thanks a lot... you guys are amazingly helpful!
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-08, 04:30:55 PM
If sprites are destroyed though sprite collisions and special functions, then you could make a loop that will automatically find the index every time either of those things happens, something like this:

Code: [Select]
dim AimSpriteIdx
AimSpriteIdx = 14

Sub FindAim()
   with ProjectObj.GamePlayer
      dim sprCount
      sprCount = .rMap.MapLayer(0).SpriteCount
      for (i = 0 to sprCount - 1)
         if .rMap.MapLayer(0).Sprite(i).rDef.Name = "aim" then
            AimSpriteIdx = i
            exit for
         end if
      next
      if i = sprCount then            'Uhoh, we didn't find it!
         .bQuit = true                  'Quit gracefully.  Remove this line if you don't want it to quit and have an error instead.
         msgbox "Couldn't find the targeting reticle sprite!  Searched through " + sprCount + " sprites, but no luck."
      end if
   end with
End Sub

Sub Player_OnSpecialEvent(SpecialObj)
   FindAim()
End Sub

Sub Player_OnSpritesCollide(Name, ClsASprIdx, ClsBSprIdx, CollDefIdx)
   FindAim()
End Sub
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-08, 07:20:21 PM
Durnurd, you are my hero. Thanks a million.

There are still a few quirks (e.g. if you restart the level, you'll have to make sure to run FindAim again), but otherwise it works beautifully. I'm thinking about putting aim on its own layer, which should solve all the problems. (By the way, for anyone who wants to use the themselves, make sure to remove the parentheses on the line with the "for" or else GameDev will get angry).

I have crosshairs! Hooray!
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-08, 08:16:19 PM
Oops, too much programming in Java and PHP for me :P

As for putting it on its own layer, how would that affect the shooting function?  How exactly do you "shoot" using the targeting reticle?  Does it create a sprite relative to the position of the "aim" sprite?  Is there a sprite collision between the "aim" sprite and enemy sprites?  If either of these is true, then the "aim" sprite has to be on the same layer as the other sprites, otherwise no collision is detected.

Also, If you just change the last two subs to this:

Code: [Select]
Sub Player_OnFrameStart(gameActive)
   FindAim()
End Sub

Then it will refind the targeting reticle every frame, which shouldn't cause a significant slow-down.  However, it's just cleaner only to update the index when required.  When you restart the level, does that use a special function?  Because that should run FindAim, but apparently not.  Also, some refinement could be made to the code, like not re-finding the sprite if it hasn't changed, but that wouldn't cause any improvement as far as the bugs are concerned, there's not a necessity to do so (but it would make it cleaner and possibly run faster on low-end systems).
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-08, 09:47:18 PM
I'm going to pass the x and y coordinates of the reticle to the shooting function. That function will use trigonometry and the x and y coordinates to determine the angle at which the projectile should be shot (i.e., angle = inverse tangent of y/x). So it does create a sprite (a bullet) relative to the "aim" sprite, but it only uses variables that have been defined within the script to do so.

The main reason that I want to put the "aim" sprite on its own layer is so that it won't scroll with the rest of the sprites. That way, it'll only move if the user moves the mouse. Will I take a performance hit for that?

Restarting the level uses a special function defined in the windowed map dialogue, not the full-screen editor. Maybe that's why OnSpecialEvent doesn't get triggered when I restart.
Title: Re: Mouse Aiming/sthooting
Post by: durnurd on 2006-02-08, 11:23:09 PM
Oh, I completely forgot.  Any special function you define that you want to re-find the sprite has to have the "Raise Event" flag checked (i.e. functions that delete sprites or functions that change the map).

As for the shooting function, it sounds like an effective solution if you take into account cartesian coordinates rather than vectors.  You wouldn't need an angle at all, really.  I assume that you have a player sprite on the screen that is doing the shooting towards the crosshairs?  So it would be something like

Code: [Select]
    With ProjectObj.GamePlayer
        cX = .rMap.MapLayer(0).Sprite(AimSpriteIdx).X    'Or, alternatively, this could just be cX = X and cY=Y if calling from a MouseDown event
        cY = .rMap.MapLayer(0).Sprite(AimSpriteIdx).Y    'This is assuming that the crosshairs is on a layer with a scrollrate of 0 in both directions
        pX = .PlayerSprite.X - .MapScrollX - .rMap.ViewLeft    'I'm not sure if this is entirely correct, but what I'm trying to do is get the distance the player
        pY = .PlayerSprite.Y - .MapScrollY - .rMap.ViewTop   'is from the edge of the screen, not the edge of the map, so that the distance to the crosshairs is correct
    End With
    Speed = 10                'Some constant speed (in Pixels per Frame)
    dx = cX - pX
    dy = cY - pY
    If dy = 0 Then
       SpeedX = Speed * sgn(dx)
       SpeedY = 0
    ElseIf dx = 0 Then
       SpeedX = 0
       SpeedY = Speed * sgn(dy)
    Else
        ratio = Abs(dx / dy)
        SpeedX = Abs(Speed / (1 + ratio)) * Sgn(dx)
        SpeedY = (Speed - Abs(SpeedX)) * Sgn(dy)
    End If
    'Now just get a bullet sprite and set its DX to SpeedX and its DY to SpeedY
Title: Re: Mouse Aiming/sthooting
Post by: ragingmime on 2006-02-11, 12:14:04 AM
Thanks! I've edited your code to simplify it a little and to make it work within the shooting script:

Code: [Select]
Function GetStateDeltas(DX, DY)
     With ProjectObj.GamePlayer
        cX = .rMap.MapLayer(1).Sprite(AimSpriteIdx).X    'Or, alternatively, this could just be cX = X and cY=Y if calling from a MouseDown event
        cY = .rMap.MapLayer(1).Sprite(AimSpriteIdx).Y    'This is assuming that the crosshairs is on a layer with a scrollrate of 0 in both directions
        pX = .PlayerSprite.X - .MapScrollX - .rMap.ViewLeft    'I'm not sure if this is entirely correct, but what I'm trying to do is get the distance the player
        pY = .PlayerSprite.Y - .MapScrollY - .rMap.ViewTop   'is from the edge of the screen, not the edge of the map, so that the distance to the crosshairs is correct
    End With
    DistToReticleX = cX - pX
    DistToReticleY = cY - pY
    'calculate the hypotenuse of the triangle (the other sides being thedx and thedy) so that we can find the cosine and sine of the angle that's opposite to the hypotenuse - i.e. the angle between the player and the reticle. The hypotenuse, then, is the line between the player and the targeting reticle.
    hypot = sqr(DistToReticleX * DistToReticleX + DistToReticleY * DistToReticleY)
    'get cosine
    DX = DistToReticleX/hypot
    DY = DistToReticleY/hypot
 End Function

This code makes the mouse button shoot:
Code: [Select]
Sub Display_MouseDown(Button, Shift, X, Y)
   Dim NewSpr, DX, DY
   If nShot1Count >= 3 Then Exit Sub
   Set NewSpr = ProjectObj.GamePlayer.rMap.SpriteDefs("bullet").MakeInstance
   Set arBtn1Shots(nShot1Count) = NewSpr
   nShot1Count = nShot1Count + 1
   With ProjectObj.GamePlayer.PlayerSprite
      .rDef.rLayer.AddSprite HostObj.AsObject(NewSpr)
      nExpectCount = nExpectCount + 1
      NewSpr.X = .X + (.Width - NewSpr.Width) / 2
      NewSpr.Y = .Y + (.Height - NewSpr.Height) / 2
      GetStateDeltas DX, DY
      NewSpr.DX = DX * NewSpr.rDef.Template.MoveSpeed
      NewSpr.DY = DY * NewSpr.rDef.Template.MoveSpeed
      If NewSpr.rDef.Template.StateCount = 36 Then NewSpr.CurState = RectToPolarState(DX, DY)
   End With
End Sub

Thanks again for all your help! In case you're curious, this (http://undergroundrmn.proboards40.com/index.cgi?board=program&action=display&thread=1138477373&page=1) is what I'm working on. You can see a screenshot here (http://rmime.dbzpc.com/boardstuff/screen5.GIF).