14 Creeper

Using what we learned in the previous chapter 13 Textures let’s create a custom texture to make a Creeper like object that will change it’s apperance. The Creeper will count down and disapperas when the count down reaches zero. Copy tutorial 00_flat_world.py to a new file with the following command, replacing TVR with your initials:

cp 00_flat_world.py 14_creeper_part_1_TVR.py

New Textures

First open the file “story_textures.png” and look at all the new textures we’ve added to the file. There are some for a basic green creeper that will be two blocks tall and some for the countdown, where the creeper turns red and displays the number of seconds remaining until it disappears.

_images/story_textures.png

Make TEXTURE_PATH point at this file as well by going to approximately line 76 and changing the TEXTURE_PATH statement to say TEXTURE_PATH = 'story_textures.png'.

TEXTURE_PATH = 'story_textures.png'

This file is a different size compared to the previous files we’ve used, so we need to adjust how we get each texture block to make sure each texture is displayed properly. Go line 52 which reads def tex_coord(x, y, n=4): and change it so that n defaults to 8. The line should read:

def tex_coord(x, y, n=8):

Previously we had texture files that were 4 by 4 groups of textures, but “story_textures.png” is 8 by 8. n just represents the dimensions of the file and tells the code how many pieces to split the overall image into to get each individual texture.

Adding the Creeper to the World - Part 1

First, let’s add some variables to keep track of the creeper. Go to the end of the Model class’ __init__ function and just before the call to self._initialize() add the following variables:

        self.creeper_time = 0
        self.creeper_status = False
        self.creeper_pos = (0, 0, 0)

creeper_time will help us track how long the creeper has existed and the state of the countdown clock. creeper_status wil simply state whether or not a creeper is in the world. creeper_pos will store the position where the creeper is located.

Next we need to define all the textures needed for the creeper. Go to line 83 where we defined TEXTURE_PATH and add the following texture definitions below it:

CREEPER_HEAD = tex_coords((4, 1), (4, 1), (4, 0))
CREEPER_BODY = tex_coords((4, 1), (4, 1), (4, 1))

CR_HEAD = tex_coords((4, 4), (4, 4), (4, 2))
CR_1 = tex_coords((4, 4), (4, 4), (0, 4))
CR_2 = tex_coords((4, 4), (4, 4), (1, 4))
CR_3 = tex_coords((4, 4), (4, 4), (2, 4))
CR_4 = tex_coords((4, 4), (4, 4), (3, 4))
CR_5 = tex_coords((4, 4), (4, 4), (4, 3))

Now go down to on_mouse_press and replace it with the following:

    def on_mouse_press(self, x, y, button, modifiers):
        """ Called when a mouse button is pressed. See pyglet docs for button
        amd modifier mappings.

        Parameters
        ----------
        x, y : int
            The coordinates of the mouse click. Always center of the screen if
            the mouse is captured.
        button : int
            Number representing mouse button that was clicked. 1 = left button,
            4 = right button.
        modifiers : int
            Number representing any modifying keys that were pressed when the
            mouse button was clicked.

        """
        if self.exclusive:
            vector = self.get_sight_vector()
            block, previous = self.model.hit_test(self.position, vector)
            if (button == mouse.RIGHT) or \
                    ((button == mouse.LEFT) and (modifiers & key.MOD_CTRL)):
                # ON OSX, control + left click = right click.
                if self.model.creeper_status:
                    self.model.remove_block(self.model.creeper_pos)
                    self.model.creeper_pos = (self.model.creeper_pos[0],
                                              self.model.creeper_pos[1] + 1,
                                              self.model.creeper_pos[2])
                    self.model.remove_block(self.model.creeper_pos)
                    self.model.creeper_status = False
                if previous and not self.model.creeper_status:
                    self.model.add_block(previous, CREEPER_BODY)
                    previous = (previous[0], previous[1] + 1, previous[2])
                    self.model.add_block(previous, CREEPER_HEAD)
                    self.model.creeper_pos = (previous[0], previous[1] - 1, previous[2])
                    self.model.creeper_time = time.time()
                    self.model.creeper_status = True
            elif button == pyglet.window.mouse.LEFT and block:
                texture = self.model.world[block]
                if texture != STONE:
                    self.model.remove_block(block)
        else:
            self.set_exclusive_mouse(True)

The two new sections of code are the inner most if statements. Let’s walk through them a little bit.

The first says if self.model.creeper_status. This block handles if the user tries to build another creeper when one is already actively counting down. If there already is an active creeper, remove it from the world and set creeper_status to false.

The second part then says if previous and not self.model.creepre_Status, so this section of code only runs if a block is selected and there isn’t already an active creeper. If both these conditions are met, then we want to add a creeper, record the time the creeper was built to start the countdown, and set creeper_status to True.

Now if you run the code, you will be able to build a creeper by pressing the right mouse button. Notice though that you can only ever have one creeper at a time.

Start the Countdown - Part 2

Now let’s make the creeper countdown to mark how much longer it will be there. Copy tutorial 14_creeper_part_1.py to a new file with the following command, replacing TVR with your initials:

cp 14_creeper_part_1.py 14_creeper_part_2_TVR.py

Start with adding a new function that will make the decisions regarding what type of block the creeper should have. Below the on_draw function at line 832, add the function below of creeper_countdown.

    def creeper_countdown(self):
        t = time.time()
        t_creeper = self.model.creeper_time
        diff = t - t_creeper
        pos = self.model.creeper_pos
        if diff > 1:
            if pos in self.model.world:
                self.model.remove_block(pos)
            pos = (pos[0], pos[1] + 1, pos[2])
            if pos in self.model.world:
                self.model.remove_block(pos)
        if diff > 6 and self.model.creeper_status:
            self.model.creeper_status = False
        elif diff > 5:
            self.model.add_block(self.model.creeper_pos, CR_1)
            self.model.add_block(pos, CR_HEAD)
        elif diff > 4:
            self.model.add_block(self.model.creeper_pos, CR_2)
            self.model.add_block(pos, CR_HEAD)
        elif diff > 3:
            self.model.add_block(self.model.creeper_pos, CR_3)
            self.model.add_block(pos, CR_HEAD)
        elif diff > 2:
            self.model.add_block(self.model.creeper_pos, CR_4)
            self.model.add_block(pos, CR_HEAD)
        elif diff > 1:
            self.model.add_block(self.model.creeper_pos, CR_5)
            self.model.add_block(pos, CR_HEAD)

This funciton takes the difference between the time of when the creeper was built and the current time and uses this difference to decide what numbered block the body of the creeper should display.

Now go to the on_draw function and add a call to creeper_countdown so that if creeper_status is True, this function will be called on_draw should then read as below:

    def on_draw(self):
        """ Called by pyglet to draw the canvas.

        """
        self.clear()
        self.set_3d()
        glColor3d(1, 1, 1)
        if self.model.creeper_status:
            self.creeper_countdown()
        self.model.batch.draw()
        self.draw_focused_block()
        self.set_2d()
        self.draw_label()
        self.draw_reticle()

Now you can build a creeper as if you were going to build a regular block and watch it turn red and countdown before disappearing.

_images/basic_creeper.png