Creating Your First 3D Game: Shooting Back - Digging Deeper

Shooting Back: Digging Deeper

The Digging Deeper resources are additional pages that go into more detail about the code that is developed in a FreshBrain activity. Code from the activity is highlighted and printed with a fixed-width font to make it stand out in the page. Following the code is a more detailed explanation of what the code does, and often there are references off to additional documentation where you may learn more details about the syntax and semantics of the Lua programming language as well as the supporting classes provided by the Wild Pockets system.


controlsKeymap = EventMap.new()

controlsKeymap:setEvent("keyPress-space", function() myPath:go() end)

controlsKeymap:setEvent("keyPress-leftMouse", function() shoot() end)

EventHandler.push(controlsKeymap)



This code creates, initializes, and installs an EventMap. An EventMap is a mapping between an event and an action. When the user interacts with an input device -- presses a key on the keyboard, clicks a mouse button -- an input event is generated. If that event is found in the EventMap, the associated function is run. In programming of user interactive systems, this is called Event Handling.


We have seen lines like the first one before, this is just creating a new instance of the EventMap class and assigning it to the controlsKeymap variable.


The next two lines are setting event mappings into the EventMap. The first one says "when the user presses the space bar on the keyboard, call myPath:go() via an anonymous function" (we have seen anonymous functions earlier in this tutorial). The second one does something similar for a press of the left mouse button, calling the shoot() function.


The last line installs the EventMap onto the EventHandler stack. You see the call to EventHandler.push(); this actually puts the EventMap onto the top of a "stack" of EventMaps. When the EventHandler receives an input event is goes to the top of the stack, examines the EventMap at the top of the stack, and looks to see if there is a mapping for the event. If it finds a mapping, it calls the function in the map. If it doesn't find a match, it goes to the next EventMap down the stack, and checks to see if there is a match. It does this until it either (a) finds a mapping for the event, or (b) gets to the bottom of the stack. If it gets to the bottom of the stack and doesn't find a mapping, nothing happens. This event handler stack is a nice feature in that it lets you temporarily install EventMaps rather than modifying a single, global EventMap. Let's say you get into a part of the game where you need to use left mouse for something, while ignoring all other input, but just temporarily. Rather than having to save all of the current mappings, clear out the map, set your new left-mouse mapping, and then restore all of the old mappings when you're done, you can just create a new EventMap with your new left mouse mapping, push it on the stack, and then when you're done, pop it back off of the stack.



function shoot()

local bullet = SceneManager.createObject("jasonn/bullet",

SceneManager.getCamera():getPosition())

bullet:setCollisionHandler(handleCollisions)

bullet:setVelocity(Mouse.getRay() / 2)

Timer.later(function() delete(bullet) end, 15)

end



The shoot() function shows us our first use of a local variable, the bullet that is shot. We use a local here so that each time the function is called, we have a new variable for the bullet that is being created. If we had a global, we'd only have 1 variable, and couldn't handle multiple shots at once.


The interesting new code here is the association of a collision handler with the bullet. setCollisionHandler() takes a function as an argument. Earlier, we saw anonymous functions being passed as arguments; here we see a function name. Any of those earlier calls that took an anonymous function could have been passed a function name, just as we could have used an anonymous function here. It's mostly a stylistic choice. If you have a function that may be used in several different places, it's best to write it once, name it, and pass its name to any function call that needs it. If it's short, and just used in the once place, an anonymous function is perfectly appropriate and keeps the code near to where it is used.



function handleCollisions(self, other, mode)

if other:hasKeyword("enemy") then

delete(other)

delete(self)

end

end



Last but not least is the collision handler itself. It takes 3 arguments, the first two are the objects that are colliding. Note that the first argument is self. Since the collision handler was installed on the bullet (in the bullet:setCollisionHandler() call above), self will always refer to a bullet in this function, and other will refer to the thing with which the bullet is colliding (the thing that was shot). This code just checks to see if the thing that was hit was an enemy; if so, it deletes the enemy and the bullet. If it was something else in the scene (decorations, scenery, another bullet, etc.), nothing happens, and the bullet continues on its way.