Screen wrap & Detect edges

Wrap and follow

We finish up this mini series with this post. The basic game will be functional with these last two changes in the game. I don’t want to get into game design or polishing up this ‘demo’ to turn it into a full fun game. That I will let you try using examples from elsewhere.

Choices

In earlier experimenting I had the tail following the head but when I added the screen-wrap it broke.

So let’s add wrap first and see if that makes it simpler.

Adding wrap

Ok we use math here and I’m not going to explain much.

  • copysign tells us if the number is negative or positive.
  • abs gives a positive value (absolute value)

Finding center requires a math formula and a simple search will give you the answer you need.

tpos is tilepos - cryptic yes - you could expand the name to make clearer
############################
##### Helper Functions #####
############################

def get_tpos_from_pos(pos):
    axs = int(math.copysign(1,pos[0]))
    ays = int(math.copysign(1,pos[1]))
    return axs * int(abs(pos[0])/TILESIZE[0]), ays * int(abs(pos[1])/TILESIZE[1])

def get_pos_from_tpos(tpos):
    axs = int(math.copysign(1,tpos[0]))
    ays = int(math.copysign(1,tpos[1]))
    return axs * (abs(tpos[0]) * TILESIZE[0] + int(TILESIZE[0]/2)), ays * (abs(tpos[1]) * TILESIZE[1] + int(TILESIZE[1]/2))

def calc_current_tile_axis_center_pos(pos_axis):
    axs = int(math.copysign(1,pos_axis))
    return axs * int(abs(pos_axis)/TILESQRT) * TILESQRT + int(TILESQRT/2)

Plus I changed some global vars

#some game constants
TILESQRT = 40
TILESIZE = (TILESQRT, TILESQRT)
# Display
size = width, height = (TILESQRT*20, TILESQRT*16)
# Detect edges
def detect_edges(dir, pos):
    boundry = "none"
    right_pos = calc_current_tile_axis_center_pos(width-1)
    #print(right_pos)
    bottom_pos = calc_current_tile_axis_center_pos(height-1)
    #print(bottom_pos)


    if pos[0] <= TILESIZE[0]/2 and dir[0] < 0:
        boundry = "left"
        end_move_pos = right_pos + TILESIZE[0], pos[1]
        #print(boundry, dir, pos, end_move_pos)
        
    elif pos[0] >= right_pos and dir[0] > 0:
        boundry = "right"
        end_move_pos = (-TILESIZE[0]/2, pos[1])
        #print(boundry, dir, pos, end_move_pos)

    elif pos[1] <= TILESIZE[1]/2 and dir[1] < 0:
        boundry = "top"
        end_move_pos = pos[0], bottom_pos + TILESIZE[0]
        #print(boundry, dir, pos, end_move_pos)

    elif  pos[1] >= bottom_pos and dir[1] > 0:
        boundry = "bottom"
        end_move_pos = (pos[0], -TILESIZE[1]/2)
        #print(boundry, dir, pos, end_move_pos)

    if boundry in ["left","right","top","bottom"]:
        #print (True, dir, end_move_pos)
        return (True, dir, end_move_pos)

    return (False, dir, pos)
boundry may just be a misspelled boundary - sorry

These statements above determines if the object is headed toward the edge of the screen and translates the position of the object to the other side i.e. start 1 tile off the screen so the object can move onto the screen. The moving off the screen on the other side will be handled by a second image which we get to in the near future.

I found this TAIL follow difficult.

To keep this short I’ve taken the required code and squashed it into what you need. The original took two weeks, I don’t know where my head was at.

What I did before starting:

  • removed “get_direction_from_position_diff”
  • moved players “speed” to KinematicObject.
  • renamed “set_next_move” to “check_tile_center_reached”
  • renamed all the cryptic single letter named variables
  • moved the method “check_tile_center_reached” to the functions and renamed it “detect_center_reached”
  • removed self as a parameter and added a position parameter
  • changed the one call in code to that method to reflect the changes
All the renaming doesn't help code execution, but it really clarifies what I trying to do if the code is simple to read.

The next two commits I set up some variables and check to see what values I get, and then I added the follow function.

The tail follow looks like this

    def follow(self, object_to_follow, passed_center):
        self.dist = object_to_follow.dist
        self.final_dist = object_to_follow.final_dist
        if passed_center:
            self.prev_dir_moved = self.last_dir_moved
            self.prev_center_reached = self.last_center_reached
            self.last_center_reached = object_to_follow.prev_center_reached
            self.last_dir_moved = object_to_follow.prev_dir_moved
            self.rect.center = self.last_center_reached
            velocity = (self.last_dir_moved[0] * self.final_dist, self.last_dir_moved[1] * self.final_dist)
            #print("pc_follow", self.name, velocity)
        else:
            velocity = (self.last_dir_moved[0] * self.dist), (self.last_dir_moved[1] * self.dist)
            #print("follow", self.name, velocity)

        self.rect = self.rect.move(velocity)

The variables I set for tail are

    name = "tp"
    speed = 0.2
    last_dir_moved = [0,0]
    last_center_reached = (0,0)
    ## this is pass back info
    prev_dir_moved = [0,0]
    prev_center_reached = (0,0)
    dist = 0
    final_dist = 0
    ##

The player “Move” method/function calls the following

def move(self, dir, dt ):
    #print("NEW MOVE")
    passed_center = super().move(dir, dt, self.speed)
    object_to_follow = self
    for t in self.tailpieces:
        t.follow(object_to_follow, passed_center)
        object_to_follow = t

and if you remember, “MOVE” gets called from the loop

    player.move(dir, dt)
    if Rect.collidepoint(food.rect, player.rect.center):
        player.grow_tail(get_tpos_from_pos(food.rect.center))
        food.reposition()

You can find all the changes in the git at github.com or the specific commit. I do need to revisit this post, if someone requests it.

There is the possibility there are bugs in this code. Please look at the Git code for the final solution.

Git Repository