Search Results

Tuesday, January 25, 2011

Using Python in the Blender Game Engine - Part 3

Hey. So, welcome back to the 'Using Python in the Blender Game Engine' set of tutorials about using the Blender Game Engine to make a game while utilizing Python as the scripting language. In this part, we'll investigate implementing walls to help us make a world.

If you recall (or not), the last part of this tutorial series dealt with cleaning up our code - also, there was a supplemental exercise in which you were asked to implement vertical movement by using the up and down arrow keys. That should have been simple enough for you to do yourself, so I will pick up from that point. At this point, we have an initialization of our Player object's code, and we update the Player every frame, moving it about or checking for information.




As you can see, we've changed the look of the world around. The lamp is now a Hemisphere lamp, giving a nicer, more even lighting setup for our level. The player is now orange, and there's a wall around the level (the wall is taller in the source). The wall has been given a property, wall, which designates the wall as being solid. This will be how we tell what objects are solid that the Player (and other objects) cannot walk through. There's no difference to the Player's logic brick setup, just his source code. So, let's take a look at the source code for this part, shall we?



---------------------------------------------------------------------------------------------------------------------------

from bge import logic
from bge import events

def Player():
    cont = logic.getCurrentController()
    obj = cont.owner
   
    motion = cont.actuators['Motion']
   
    key = logic.keyboard.events
    kbleft = key[events.LEFTARROWKEY]
    kbright = key[events.RIGHTARROWKEY]
    kbup = key[events.UPARROWKEY]
    kbdown = key[events.DOWNARROWKEY]
   
    def Init():
        if not 'init' in obj:
            obj['init'] = 1
   
    def Update():
   
        movespd = 0.2
        mx = 0.0
        my = 0.0
       
        if kbleft > 0:
            mx = -movespd
        elif kbright > 0:
            mx = movespd
       
        if kbup > 0:
            my = movespd
        elif kbdown > 0:
            my = -movespd
           
        pos = obj.position    # Get the Player's current position
        topos = pos.copy()    # Make a copy of the Player's current position
        topos.x += mx        # And offset the copy by the movement value for the X-axis
       
        if obj.rayCast(topos, pos, 0, 'wall', 1, 1)[0] != None:    # We just collided with something on the X-axis
            mx = 0    # So stop movement on the X-axis
           
        pos = obj.position    # Get the Player's current position
        topos = pos.copy()    # Make a copy of the Player's current position
        topos.y += my        # And offset the copy by the movement value for the Y-axis

        if obj.rayCast(topos, pos, 0, 'wall', 1, 1)[0] != None:    # We just collided with something on the Y-axis
            my = 0  # So stop movement on the Y-axis
       
        motion.dLoc = [mx, my, 0.0]
        cont.activate(motion)
           
    Init()
    Update()


---------------------------------------------------------------------------------------------------------------------------
Now, then - as you can see, there are some changes to the code, but nothing especially difficult to comprehend. We added the movement on the Y-axis by using the up and down arrow keys before. We also added in movement on a variable basis, rather than just using numbers. Finally, this time, we'll work on adding in collision checking for the walls. So, the first change is this line: 

 movespd = 0.2

Here in this line, we create a local movement variable that we will move the Player by - this way, we can have the Player move a uniform amount easily. We could even change how fast the Player moves under certain circumstances by altering this variable later.

        if kbleft > 0:
            mx = -movespd
        elif kbright > 0:
            mx = movespd
       
        if kbup > 0:
            my = movespd
        elif kbdown > 0:
            my = -movespd


Now, the movement on the keyboard inputs move by the movespd variable, which is 0.2. The real changes come up next.

pos = obj.position    # Get the Player's current position
        topos = pos.copy()    # Make a copy of the Player's current position
        topos.x += mx        # And offset the copy by the movement value for the X-axis
       
        if obj.rayCast(topos, pos, 0, 'wall', 1, 1)[0] != None:    # We just collided with something on the X-axis
            mx = 0    # So stop movement on the X-axis


This time, we see quite a big change. This code (and the next portion) deal with collision. It performs raycasts to determine if there are objects with the property described in the direction described.This code is basically duplicated again for the Y-axis, so I will only explain the first part (the X-Axis collision check).

The first line creates a simple local variable that references the Player object's position. The next line creates a new local variable, topos, that copies the Player's position - this will be the ending position of our raycast function to test for collisions. Next, we add to the X position of the ending position (topos), the movement values we have from the keyboard input. Next, we use the object's rayCast function to test for collisions. The function's arguments are as follows:
1) Ending position of the ray to be tested for collisions.
2) Starting position of the ray to be tested for collisions.
3) Distance of the ray - if the distance is 0, then the ray will end on the ending position of the raycast. Otherwise, the ray will have the specific distance entered.
4) The property to test for collisions - since we are trying to find out if we are moving into a wall, we test for objects that have a wall property. If the property is blank (just quotes), then it will test for any solid object.
5) Whether to return the face normal of the face contacted by the rayCast function.
6) Whether to use X-Ray - with X-Ray mode on, it will look only for objects with the specified property. Otherwise, it will stop on first object.

It's not too complex - if you've noticed, though, we use two separate rayCast functions, one for each axis we move on. Why do that? We test separately because by doing so, we separate stopping for each axis, and so can slide on walls as the movements are separated. If we used one rayCast function, we would have to stop on any collision, regardless of what angle we approach the collision from.

Well, that's all of the changes in the source code! For the next tutorial part, we'll look into fixing the only bug that's arisen - collision only happens from the center of the Player object, and so it intersects the walls partially before collision. There's also a bug of being able to go through corners of walls, though this is simply a by-product of the first bug. Download the source code for part 3 of Using Python with the Blender Game Engine here. So far, so good!

10 comments:

  1. what about assigning a radar or near property to the object and work with the messages they send?

    ReplyDelete
  2. You could work with radar / near sensors. This is a Python tutorial, and so I'm just showing a way to implement this with Python. However, near sensors are a bit heavy on the physics, if I recall, and you'd require at least four sensors to equate to the rayCast functions.

    ReplyDelete
  3. Thanks for the tutorial - this line:
    motion = cont.actuators['Motion']
    isn't working for me. I tried print(getCurrentController().actuators) and it was empty:
    []
    Any thoughts? Thanks again.

    ReplyDelete
  4. Are you sure you connected the actuator to the controller?

    ReplyDelete
  5. Oh - should have been more careful. Thanks, it's working now.

    ReplyDelete
  6. thank very much, really nice tutorials for a beginner like me, just one question,i cant find part 4?

    ReplyDelete
  7. No problem. Part 4 is here:

    http://solarlune-gameup.blogspot.com/2011/02/using-python-in-blender-game-engine.html

    You can find a list of BGE tutorials by clicking that button under the Tutorials section at the right, near the top of the page.

    ReplyDelete
  8. how would you make it so the player can walk on the wall kind of like a wall ride?

    ReplyDelete
  9. @Le gars qui écrit des choses - Well, it depends on how it would be implemented. You could just suspend the gravity while wall running, in the simplest form.

    ReplyDelete
  10. Great tutorial. I don't understand why the topos variable is chosen for the end of the ray? Why is the offset value significant and why wouldn't you just use a constant. It seems to me you're saying, "shoot out a ray that as long as I have traveled so far on this axis'. Apologies if this sounds abrupt. It's not meant to. - Ian

    ReplyDelete