Screen wrap & Detect edges
This is a post in the
Worm series.
Other posts in this series:
- May 02, 2023 - Basic Pygame game structure
- May 03, 2023 - Add grid lines and Kinematic objects
- May 21, 2023 - Creating and Moving our player
- May 22, 2023 - Worm eats food
- May 24, 2023 - Grid Movement
- May 30, 2023 - 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.
Previous