Advertisement

Plan movement in wego-sports game

Started by May 03, 2018 09:14 AM
0 comments, last by Mogli 6 years, 6 months ago

Hello!

As my hobby, I’m working on an ice-hockey game, using Python (pygame). It will be based on wego-turns, with each turn representing 3 seconds of “realtime playback”. In other words: both users plan their skaters’ movements and actions for the upcoming 3 seconds while the game is paused, and then, when they both have confirmed their turn, the game takes their plans, calculates what happens and finally presents the outcome of the turn to the users as a 3 seconds-replay. All frames of the action are stored so that users can re-watch the action, using rewind, forward, play functions etc.

As I'm almost totally new to programming, I’m worried about the planning phase. Although my code does achieve what I want, I’m worried that it is overly complicated and will give me lots of headaches later on. Therefore, I would be very thankful if someone more experienced in programming could take a look and/or lend me a hand and give me tips how to simplify and shorten it. I will post my code at the end of the post in the spoiler, but I fear that it might be so messed up that noone but me understands it.

Here is what the code should do:

 IDEA

  • Context: When a certain input is given while a skater is selected during the planning phase, planning mode is enabled.In planning mode, the user can set waypoints for the selected skater.
  • The higher the speed of a skater, the narrower the allowed angle to set the next waypoint needs to be. E.g. if your skater goes at full speed, you cannot perform a 90° turn within just two waypoints.
  • The number of waypoints that can be set for a skater in the planning phase depends on the skater’s speed. The greater the speed, the more waypoints can be set. The effect of speed needs to be tracked during the planning-phase itself, so that - for example - if a user sets acceleration-waypoints he will be able to set more waypoints in this very planning-phase.
  • All information for movement (between waypoints) must be stored, as it needs to be retrievable in the replay-phase.

If you have any ideas or hints how to come up with a code that achieves these things, please tell me, never mind how basic or in-depth! What follows below is my complicated attempt at it. 

 MY ATTEMPT 

Spoiler

 

While in planning mode, the function “plan_movement” (see spoiler) is called every frame of the gameloop.

All skater-instances (these are the ice-hockey-players) have these attributes:

  • Time=movement points remaining for this turn (frames)
  • A list storing all waypoints-instances that have been planned for the skater for the upcoming turn (plan_book).
  • A list storing all the move-steps for the upcoming turn (move_steps).

Waypoint-objects have these attributes: a number, a coordinate (x,y), a facing, a speed (it stores the speed of the skater at the waypoint).

Move_step-objects are movement-instructions for a skater for a single frame. They have these attributes: dx,dy,  pivot-info, link to waypoint (None or waypoint-number).

Waypoints are set at fixed intervalls. In order to set waypoints, the user has two options:

  • He can move his cursor close to the facing-vector (a line indicating the facing) of the last waypoint. If the cursor comes close enough to a legal (in terms of the angle) waypoint-position, a waypoint will be set automatically
  • The second way to set waypoints is to press the right/left keys. This will create a waypoint at the minimum allowed angle. (I left out this function in the code posted below) 

Whenever the player sets a waypoint, the game immediately translates the movement to it into move-steps:

  • It takes the speed of the last waypoint, expressed as movement distance per frame, and checks how many times this speed=distance fits into the vector leading from the old to the new waypoint. It then calculates all the small-move-step-vectors and stores them to the skater’s move_steps.
  • The number of frames that are needed to travel from one waypoint to the next is subtracted from the skater’s frames=movement points for that turn. If the user sets an “acceleration” waypoint, the number of frames is increased. For deceleration, it’s vice versa.
  • The game also calculates the difference between the facings of the the start- and end- waypoint and divides it by the number of frames used and adds the info to the move_step. S So the skater should pivot continously and fluidly when moving from one waypoint to another.

The question how to deal with excess frames (what if there are still frames left for a skater, but not enough to set a new waypoint at the fixed intervall) was solved rather pragmatically: the excess frames are lost. It shouldn’t be a big deal if you consider that the distance between two waypoints is just 50 pixels. So you don’t ever loose more than 50 pixels of movement this way. The alternative - namely allowing for a short final waypoint, overruling the regular waypoint-intervall - looked very weird and added to the complexity of the code.

Another issue I ran into was the connection between move-steps and waypoints. I want the game to track when a skater reaches a waypoint (as users will set actions like passes and shots to waypoints). Here, I also went an untidy, pragmatical way: I simply set the final movestep of each section to be on the waypoint itself.

At the end of the planning phase, the plan_books and move_steps of all skaters would be filled. The game then proceeds to the action-calculation-phase: for each frame, it moves all skaters according to their movesteps.  

 

Spoiler

 



def plan_movement(planning_phase, type=0):
    # handles movement-planning input by the player / sets waypoints and translates them into move_steps
    phase = planning_phase
    skater = phase.selected_skater

    if skater.frames > 0:
        st_wp = skater.last_wp                                                                 # ST_WP = last WP object
        st_p = st_wp.coll_rect.center                                           # ST_P = last WP coordinates (needed in this form below)
        aim_p = lo.world_pos_a(pg.mouse.get_pos(), skater.match.camera) # ending coordinates (=Mousepos translated from screen into world coord)
        wp_vec = lo.Vector(aim_p[0] - st_p[0], aim_p[1] - st_p[1], skater.match, st_p)  # WP_VEC  = vector from last WP to mousecoordinates
        if wp_vec.length > 5:                                 # needed; if vector is too small, cannot change length; crashes the game
            wp_vec.change_length(WP_INTERVALL)                                               # give wp_vec the proper length
            wp_vec_facing = give_absolute_facing(skater, wp_vec)                              # tell facing of wp_vec
            move_dir = check_angle(skater, st_wp.facing, wp_vec_facing)       # determine legality and direction of move 

            if move_dir:                                                            # if the move is a legal move in a direction:
                new_WP_coord = (st_p[0] + wp_vec.x, st_p[1] + wp_vec.y)           # determine the coordinates for the new WP
                if aim_p[0] in range(int(new_WP_coord[0]) - 10, int(new_WP_coord[0]) + 10) and aim_p[1] in range(
                                int(new_WP_coord[1]) - 10, int(new_WP_coord[1]) + 10):     # and cHeck if the mouse cursor is within 10 pixels
                    frame_speed = skater.last_wp.speed                     # the distance covered in a single frame = speed at last wp
                    if frame_speed < start_up_speed:                          # if the player was stationary, give a minimum speed
                        frame_speed = start_up_speed
                    duration = WP_INTERVALL / frame_speed                         # determine the duration of the wp/intervall

                    if duration <= skater.frames:     #  if the skater has enough frames left to pay the full duration:
                        if move_dir == 2:                                                     #BACKWARDS-SKATING (IGNORE)
                            wp_vec_facing += 180
                            if wp_vec_facing > 360:
                                wp_vec_facing -= 360
                                
                        pivot_step = (wp_vec_facing - st_wp.facing) / duration # calculate pivot-steps (facing-difference / duration)

                        frame_vec = copy.copy(wp_vec)                                 # to get a move-step-vector, copy the wp_vec ...
                        frame_vec.change_length(frame_speed)          # ... and shorten it down to the proper speed/length
                        st_p_facing = st_wp.facing
                        # GENERATE MOVE_STEPS AND WAYPOINT
                        generate_move_steps(st_p, frame_vec, pivot_step, duration, move_dir, new_WP_coord, skater, st_p_facing)
                        generate_waypoint(skater, new_WP_coord, move_dir, wp_vec, wp_vec_facing, type)

            else:
                print('illegal angle!')
    else:
        print('ALl movement spent this turn')
        phase.is_depleted = 1


def generate_move_steps(st_p, frame_vec, pivot_step, duration, move_dir, new_WP_coord, skater, st_p_facing):
# generate approaching movesteps
    absolved = 0
    while absolved < duration - 1:
        st_p = st_p  # first start-point is the last_wp startpoint, then always take last_startpoint + frame_vec as new start point
        new_facing = st_p_facing + pivot_step
        if new_facing < 0:
            new_facing = 360 - new_facing
        elif new_facing > 360:
            new_facing = new_facing - 360
        move_step = MoveStep(frame_vec.x, frame_vec.y, new_facing, None, move_dir)
        skater.move_steps.append(move_step)
        st_p = (st_p[0] + move_step.dx, st_p[1] + move_step.dy)
        st_p_facing = new_facing
        absolved += 1
        skater.frames -= 1
# generate final movestep on the planned WP
    last_st_p = st_p
    last_endpoint = new_WP_coord
    new_facing = st_p_facing + pivot_step
    if new_facing < 0:
        new_facing = 360 - new_facing
    elif new_facing > 360:
        new_facing = new_facing - 360
    last_movestep = MoveStep(last_endpoint[0] - last_st_p[0], last_endpoint[1] - last_st_p[1],
                             new_facing, skater.last_wp.seq_number + 1,
                             move_dir)
    skater.move_steps.append(last_movestep)
    skater.frames -= 1


def generate_waypoint(skater, new_WP_coord, move_dir, wp_vec, wp_vec_facing, type):
#FORWARDS
    if move_dir == 1:
    # glide forwards
        if type == 0:
            new_speed = skater.last_wp.speed - norm_skater_airdrag
            if new_speed < start_up_speed:
                new_speed = start_up_speed
            color = C_glide_1
    # accelerate forwards
        elif type == 1:
            new_speed = skater.last_wp.speed + norm_skater_accel * stat_accel[skater.acceleration_stat]
            if new_speed > max_speed * stat_speed[skater.speed_stat]:
                new_speed = max_speed * stat_speed[skater.speed_stat]
            color = C_accel_1
    # decelerate forwards
        elif type == 2:
            new_speed = skater.last_wp.speed - norm_skater_decel
            if new_speed < 1:
                new_speed = 0
            color = C_decel_1
    # full break forwards
        elif type == 3:
            new_speed = skater.last_wp.speed - norm_skater_fullbreak
            if new_speed < 1:
                new_speed = 0
            color = C_break_1
# BACKWARDS
    if move_dir == 2:
        # glide backwards
        if type == 0:
            new_speed = skater.last_wp.speed - norm_skater_airdrag
            if new_speed < start_up_speed:
                new_speed = start_up_speed
            color = C_glide_2
            # accelerate backwards
        elif type == 1:
            new_speed = skater.last_wp.speed + norm_skater_accel * stat_accel[skater.acceleration_stat]
            if new_speed > max_speed_bw * stat_speed[skater.speed_stat]:
                new_speed = max_speed_bw * stat_speed[skater.speed_stat]
            color = C_accel_2
            # decelerate backwards
        elif type == 2:
            new_speed = skater.last_wp.speed - norm_skater_decel
            if new_speed < 1:
                new_speed = 0
            color = C_decel_2
            # full break backwards
        elif type == 3:
            new_speed = skater.last_wp.speed - norm_skater_fullbreak
            if new_speed < 1:
                new_speed = 0
            color = C_break_2

# COMPILE AND CREATE
    wp_vec.color = color
    new_WP_seq = skater.last_wp.seq_number + 1
    new_WP = h.WayPoint(new_WP_coord, round(wp_vec_facing), skater, new_WP_seq, wp_vec, new_speed, move_dir)
    skater.plan_book.append(new_WP)
    skater.last_wp = new_WP
    skater.cur_speed = new_speed
    print('WAYPOINT', new_WP_seq,' CREATED, movedir =', move_dir)

def check_angle(skater, start_facing, end_facing):
    # returns False if the current move is not allowed for the skater at the given facing with the given speed
    # if the move is legal, returns the direction: 1 = forwards, 2 = backwards
    skater_speed_limit = 1.1-(skater.cur_speed / max_speed)
    allowed_angle = norm_skater_angle * stat_curve[skater.curve_stat] * skater_speed_limit
    angle = start_facing - end_facing
    if abs(angle) < allowed_angle/2:
        return 1
    elif abs(angle) in range (160, 201):
        return 2
    else:
        return False

 

 

 VIDEO

Spoiler

 

Here is a video that shows how the game works right now. It might give you a better idea of what I'm trying to achieve.

https://www.youtube.com/watch?v=Vlxf3VxvxnA 

 

 

This topic is closed to new replies.

Advertisement