# The Python Game Book

code games. learn Python.

### Site Tools

en:pygame:step021

# Step 021 - Rotating toward a target

### video

Video link: http://www.youtube.com/watch?v=nfaOmkhK-V0 Bug: tank angle and turret angle is swapped in the video. The source code below is correct.

### description In the code example below, you can move both tanks (as in step020) or you can “teleport” the yellow tank toward the mouse pointer by clicking the left mouse button. The blue tank will rotate it's turrent toward the position of the yellow tank. The problem is to decide in wich direction the turret should move.

### source code discussion

In the code example below this is solved by the Tank class method aim_at_player. Note that the angle diff can result in values > than 360, so the result is divided by 360 and its modulo1), is saved. You will also note that very few lines are altered since step020, mostly the tank class got a new method:

```    def aim_at_player(self, targetnumber=0):
#print "my  pos: x:%.1f y:%.1f " % ( self.pos, self.pos)
#print "his pos: x:%.1f y:%.1f " % ( Tank.book.pos, Tank.book.pos)
deltax = Tank.book[targetnumber].pos - self.pos
deltay = Tank.book[targetnumber].pos - self.pos
angle =   math.atan2(-deltax, -deltay)/math.pi*180.0

diff = (angle - self.turretAngle - 90) %360 #reset at 360
if diff == 0:
self.turndirection = 0
elif diff > 180:
self.turndirection = 1
else:
self.turndirection = -1
return diff
```

And also the mainloop's event handler is expanded. He now checks the MOUSEBUTTON event to teleport player1 tank around:

```            # teleport player1 tank if left mousebutton is pressed
elif event.type == pygame.MOUSEBUTTONDOWN:
if pygame.mouse.get_pressed():
#left mousebutton was pressed
player1.pos=pygame.mouse.get_pos()
player1.pos=pygame.mouse.get_pos()
```
<note> User yipyip has donated a very interesting solution to this problem, using Complex numbers. See his source code here: yipyip's solution </note>

### ideas

<note tip>Do you remember the keyboard-rollover problem from step020 ? Now you could use less keyboard commands by letting both players turrets automatically rotate toward each other</note> <note tip>Can you code an Artificial_intelligence for the bow machine gun ? Let it firing as soon as it's Deflection angle is low enough:

```if -5 < tankDiffAngle < 5:
Tracer(self)```
</note>

## source code on github

To run this example you need:

021_targeting.py `pygame` Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master

```#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
021_targeting.py
demo of tank game with rotating turrets
url: http://thepythongamebook.com/en:part2:pygame:step021
author: horst.jens@spielend-programmieren.at

the player2 tank rotate it's turrent always to player1 tank
you can "teleport" player1 tank by mouse click

special thanks to Jonathan Persson for motivation to write this
"""

import pygame
import random
import math

class Config(object):
""" a class to hold all game constants that may be modded by the user"""
fullscreen = False
width = 640
height = 480
fps = 100
xtiles = 30 # how many grid tiles for x axis
ytiles = 20 # how many grid tiles for y axis
title = "Esc: quit"

class Text(pygame.sprite.Sprite):
""" a helper class to write text on the screen """
number = 0
book = {}
def __init__(self, pos, msg):
self.number = Text.number # get a unique number
Text.number += 1 # prepare number for next Textsprite
Text.book[self.number] = self # store myself into the book
pygame.sprite.Sprite.__init__(self, self.groups)
self.pos = [0.0,0.0]
self.pos = pos
self.pos = pos
self.msg = msg
self.changemsg(msg)

def update(self, seconds):
pass

def changemsg(self,msg):
self.msg = msg
self.image = write(self.msg)
self.rect = self.image.get_rect()
self.rect.centerx = self.pos
self.rect.centery = self.pos

class Bullet(pygame.sprite.Sprite):
""" a big projectile fired by the tank's main cannon"""
side = 7 # small side of bullet rectangle
vel = 180 # velocity
mass = 50
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.boss = boss
self.dx = 0
self.dy = 0
self.angle = 0
self.color = self.boss.color
self.dx += self.boss.dx
self.dy += self.boss.dy # add boss movement
self.pos = self.boss.pos[:] # copy (!!!) of boss position
#self.pos = self.boss.pos   # uncomment this linefor fun effect
self.calculate_origin()
self.update() # to avoid ghost sprite in upper left corner,
# force position calculation.

""" drawing the bullet and rotating it according to it's launcher"""
self.radius = Bullet.side # for collision detection
self.angle += self.boss.turretAngle
self.mass = Bullet.mass
self.vel = Bullet.vel
image = pygame.Surface((Bullet.side * 2, Bullet.side)) # rect 2 x 1
image.fill((128,128,128)) # fill grey
pygame.draw.rect(image, self.color, (0,0,int(Bullet.side * 1.5), Bullet.side)) # rectangle 1.5 length
pygame.draw.circle(image, self.color, (int(self.side *1.5) ,self.side/2), self.side/2) #  circle
image.set_colorkey((128,128,128)) # grey transparent
self.image0 = image.convert_alpha()
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()

def calculate_origin(self):
# - spawn bullet at end of turret barrel instead tank center -
# cannon is around Tank.side long, calculatet from Tank center
# later subtracted 20 pixel from this distance
# so that bullet spawns closer to tank muzzle

def update(self, seconds=0.0):
# ---- kill if too old ---
self.kill()
# ------ calculate movement --------
self.pos += self.dx * seconds
self.pos += self.dy * seconds
# ----- kill if out of screen
if self.pos < 0:
self.kill()
elif self.pos > Config.width:
self.kill()
if self.pos < 0:
self.kill()
elif self.pos > Config.height:
self.kill()
#------- move -------
self.rect.centerx = round(self.pos,0)
self.rect.centery = round(self.pos,0)

class Tracer(Bullet):
"""Tracer is nearly the same as Bullet, but smaller
and with another origin (bow MG rect instead cannon.
Tracer inherits all methods of Bullet, but i overwrite
side = 15 # long side of bullet rectangle
vel = 200 # velocity
mass = 10
color = (200,0,100)
def __init__(self, boss, turret=False):
self.turret = turret
Bullet.__init__(self,boss ) # this line is important

"""overwriting the method because there are some differences
between a tracer and a main gun bullet"""
self.radius = Tracer.side # for collision detection
self.angle = 0
self.angle += self.boss.tankAngle
if self.turret:
self.angle = self.boss.turretAngle
self.mass = Tracer.mass
self.vel = Tracer.vel
image = pygame.Surface((Tracer.side, Tracer.side / 4)) # a line
image.fill(self.boss.color) # fill yellow ?
pygame.draw.rect(image, (0,0,0), (Tracer.side * .75, 0, Tracer.side, Tracer.side / 4)) # red dot at front
image.set_colorkey((128,128,128)) # grey transparent
self.image0 = image.convert_alpha()
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
if self.turret:
# turret mg
else:
# bow mg

def calculate_origin(self):
"""overwriting because another point of origin is needed"""
# - spawn bullet at end of machine gun muzzle (bow or turret)
if self.turret:
else:

class Tank(pygame.sprite.Sprite):
""" A Tank, controlled by the Player with Keyboard commands.
This Tank draw it's own Turret (including the main gun)
and it's bow rectangle (slit for Tracer Machine Gun"""
side = 100 # side of the quadratic tank sprite
recoiltime = 0.75 # how many seconds  the cannon is busy after firing one time
mgrecoiltime = 0.2 # how many seconds the bow mg (machine gun) is idle
turretTurnSpeed = 50 # turret
tankTurnSpeed = 80 # tank
movespeed = 80
#maxrotate = 360 # maximum amount of degree the turret is allowed to rotate
book = {} # a book of tanks to store all tanks
number = 0 # each tank gets his own number
# keys for tank control, expand if you need more tanks
#          player1,        player2    etc
firekey = (pygame.K_k, pygame.K_DOWN)
mgfirekey = (pygame.K_LCTRL, pygame.K_KP_ENTER)
mg2firekey = (pygame.K_i, pygame.K_UP)
turretLeftkey = (pygame.K_j, pygame.K_LEFT)
turretRightkey = (pygame.K_l, pygame.K_RIGHT)
forwardkey = (pygame.K_w, pygame.K_KP8)
backwardkey = (pygame.K_s, pygame.K_KP5)
tankLeftkey = (pygame.K_a, pygame.K_KP4)
tankRightkey = (pygame.K_d, pygame.K_KP6)
color = ((200,200,0), (0,0,200))
#msg = ["wasd LCTRL, ijkl", "Keypad: 4852, ENTER, cursor"]

def __init__(self, startpos = (150,150), angle=0):
self.number = Tank.number # now i have a unique tank number
Tank.number += 1 # prepare number for next tank
Tank.book[self.number] = self # store myself into the tank book
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.pos = [startpos, startpos] # x,y
self.dx = 0
self.dy = 0
self.ammo = 30 # main gun
self.mgammo = 500 # machinge gun
self.color = Tank.color[self.number]
self.turretAngle = angle #turret facing
self.tankAngle = angle # tank facing
self.msg =  "tank%i: x:%i y:%i facing: turret:%i tank:%i"  % (self.number, self.pos, self.pos, self.turretAngle, self.tankAngle )
Text((Config.width/2, 30+20*self.number), self.msg) # create status line text sprite
self.firekey = Tank.firekey[self.number] # main gun
self.mgfirekey = Tank.mgfirekey[self.number] # bow mg
self.mg2firekey = Tank.mg2firekey[self.number] # turret mg
self.turretLeftkey = Tank.turretLeftkey[self.number] # turret
self.turretRightkey = Tank.turretRightkey[self.number] # turret
self.forwardkey = Tank.forwardkey[self.number] # move tank
self.backwardkey = Tank.backwardkey[self.number] # reverse tank
self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
# painting facing north, have to rotate 90° later
image = pygame.Surface((Tank.side,Tank.side)) # created on the fly
image.fill((128,128,128)) # fill grey
if self.side > 10:
pygame.draw.rect(image, self.color, (5,5,self.side-10, self.side-10)) #tank body, margin 5
pygame.draw.rect(image, (90,90,90), (0,0,self.side/6, self.side)) # track left
pygame.draw.rect(image, (90,90,90), (self.side-self.side/6, 0, self.side,self.side)) # right track
pygame.draw.rect(image, (255,0,0), (self.side/6+5 , 10, 10, 5)) # red bow rect left
#pygame.draw.rect(image, (255,0,0), (self.side/2 - 5, 10, 10, 5)) # red bow rect middle
pygame.draw.circle(image, (255,0,0), (self.side/2,self.side/2), self.side/3 , 2) # red circle for turret
image = pygame.transform.rotate(image,-90) # rotate so to look east
self.image0 = image.convert_alpha()
self.image = image.convert_alpha()
self.rect = self.image0.get_rect()
#---------- turret ------------------
self.firestatus = 0.0 # time left until cannon can fire again
self.mgfirestatus = 0.0 # time until mg can fire again
self.mg2firestatus = 0.0 # time until turret mg can fire again
self.turndirection = 0    # for turret
self.tankturndirection = 0
self.movespeed = Tank.movespeed
self.turretTurnSpeed = Tank.turretTurnSpeed
self.tankTurnSpeed = Tank.tankTurnSpeed
Turret(self) # create a Turret for this tank

def update(self, seconds):
# no need for seconds but the other sprites need it
if self.firestatus > 0:
self.firestatus -= seconds # cannon will soon be ready again
if self.firestatus <0:
self.firestatus = 0 #avoid negative numbers
if self.mgfirestatus > 0:
self.mgfirestatus -= seconds # bow mg will soon be ready again
if self.mgfirestatus <0:
self.mgfirestatus = 0 #avoid negative numbers
if self.mg2firestatus > 0:
self.mg2firestatus -= seconds # turret mg will soon be ready again
if self.mg2firestatus <0:
self.mg2firestatus = 0 #avoid negative numbers

# ------------ keyboard --------------
pressedkeys = pygame.key.get_pressed()
# -------- turret manual rotate ----------
self.turndirection = 0    #  left / right turret rotation
if self.number == 1:   # only for tank2
self.aim_at_player()       # default aim at player0
else:
if pressedkeys[self.turretLeftkey]:
self.turndirection += 1
if pressedkeys[self.turretRightkey]:
self.turndirection -= 1

#---------- tank rotation ---------
self.tankturndirection = 0 # reset left/right rotation
if pressedkeys[self.tankLeftkey]:
self.tankturndirection += 1
if pressedkeys[self.tankRightkey]:
self.tankturndirection -= 1

# ---------------- rotate tank ---------------
self.tankAngle += self.tankturndirection * self.tankTurnSpeed * seconds # time-based turning of tank
# angle etc from Tank (boss)
oldcenter = self.rect.center
oldrect = self.image.get_rect() # store current surface rect
self.image  = pygame.transform.rotate(self.image0, self.tankAngle)
self.rect = self.image.get_rect()
self.rect.center = oldcenter
# if tank is rotating, turret is also rotating with tank !
# -------- turret autorotate ----------
self.turretAngle += self.tankturndirection * self.tankTurnSpeed * seconds  + self.turndirection * self.turretTurnSpeed * seconds # time-based turning
# ---------- fire cannon -----------
if (self.firestatus ==0) and (self.ammo > 0):
if pressedkeys[self.firekey]:
self.firestatus = Tank.recoiltime # seconds until tank can fire again
Bullet(self)
self.ammo -= 1
#self.msg =  "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire bow mg ---------------
if (self.mgfirestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mgfirekey]:
self.mgfirestatus = Tank.mgrecoiltime
Tracer(self, False) # turret mg = False
self.mgammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire turret mg ---------------
if (self.mg2firestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mg2firekey]:
self.mg2firestatus = Tank.mgrecoiltime # same recoiltime for both mg's
Tracer(self, True) # turret mg = True
self.mgammo -= 1
#self.msg =  "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# ---------- movement ------------
self.dx = 0
self.dy = 0
self.forward = 0 # movement calculator
if pressedkeys[self.forwardkey]:
self.forward += 1
if pressedkeys[self.backwardkey]:
self.forward -= 1
# if both are pressed togehter, self.forward becomes 0
if self.forward == 1:
if self.forward == -1:
# ------------- check border collision ---------------------
self.pos += self.dx * seconds
self.pos += self.dy * seconds
if self.pos + self.side/2 >= Config.height:
self.pos = Config.height - self.side/2
self.dy = 0 # crash into border
elif self.pos -self.side/2 <= 0:
self.pos = 0 + self.side/2
self.dy = 0

self.rect.centerx = round(self.pos, 0) #x
self.rect.centery = round(self.pos, 0) #y
self.msg =  "tank%i: x:%i y:%i facing: turret:%i tank:%i"  % (self.number, self.pos, self.pos, self.turretAngle, self.tankAngle )
Text.book[self.number].changemsg(self.msg)

def aim_at_player(self, targetnumber=0):
#print "my  pos: x:%.1f y:%.1f " % ( self.pos, self.pos)
#print "his pos: x:%.1f y:%.1f " % ( Tank.book.pos, Tank.book.pos)
deltax = Tank.book[targetnumber].pos - self.pos
deltay = Tank.book[targetnumber].pos - self.pos
angle =   math.atan2(-deltax, -deltay)/math.pi*180.0

diff = (angle - self.turretAngle - 90) %360 #reset at 360
if diff == 0:
self.turndirection = 0
elif diff > 180:
self.turndirection = 1
else:
self.turndirection = -1
return diff

class Turret(pygame.sprite.Sprite):
"""turret on top of tank"""
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.boss = boss
self.side = self.boss.side

self.images = {} # how much recoil after shooting, reverse order of apperance
self.images = self.draw_cannon(0)  # idle position
self.images = self.draw_cannon(1)
self.images = self.draw_cannon(2)
self.images = self.draw_cannon(3)
self.images = self.draw_cannon(4)
self.images = self.draw_cannon(5)
self.images = self.draw_cannon(6)
self.images = self.draw_cannon(7)
self.images = self.draw_cannon(8)  # position of max recoil
self.images = self.draw_cannon(4)
self.images = self.draw_cannon(0) # idle position

def update(self, seconds):
# painting the correct image of cannon
if self.boss.firestatus > 0:
self.image = self.images[int(self.boss.firestatus / (Tank.recoiltime / 10.0))]
else:
self.image = self.images
# --------- rotating -------------
# angle etc from Tank (boss)
oldrect = self.image.get_rect() # store current surface rect
self.image  = pygame.transform.rotate(self.image, self.boss.turretAngle)
self.rect = self.image.get_rect()
# ---------- move with boss ---------
self.rect = self.image.get_rect()
self.rect.center = self.boss.rect.center

def draw_cannon(self, offset):
# painting facing right, offset is the recoil
image = pygame.Surface((self.boss.side * 2,self.boss.side * 2)) # created on the fly
image.fill((128,128,128)) # fill grey
pygame.draw.circle(image, (255,0,0), (self.side,self.side), 22, 0) # red circle
pygame.draw.circle(image, (0,255,0), (self.side,self.side), 18, 0) # green circle
pygame.draw.rect(image, (255,0,0), (self.side-10, self.side + 10, 15,2)) # turret mg rectangle
pygame.draw.rect(image, (0,255,0), (self.side-20 - offset,self.side - 5, self.side - offset,10)) # green cannon
pygame.draw.rect(image, (255,0,0), (self.side-20 - offset,self.side - 5, self.side - offset,10),1) # red rect
image.set_colorkey((128,128,128))
return image

# ---------------- End of classes --------------------

#------------ defs ------------------
return (radians / math.pi) * 180.0

return degrees * (math.pi / 180.0)

def write(msg="pygame is cool"):
"""helper function for the Text sprite"""
myfont = pygame.font.SysFont("None", 28)
mytext = myfont.render(msg, True, (0,0,0))
mytext = mytext.convert_alpha()
return mytext

def main():
"""the game itself"""
pygame.init()
screen=pygame.display.set_mode((Config.width,Config.height))
#screenrect = screen.get_rect()
background = pygame.Surface((screen.get_size()))
#backgroundrect = background.get_rect()

background.fill((128,128,255)) # fill grey light blue:(128,128,255)
# paint a grid of white lines
for x in range(0,Config.width,Config.width/Config.xtiles): #start, stop, step
pygame.draw.line(background, (255,255,255), (x,0), (x,Config.height))
for y in range(0,Config.height,Config.height/Config.ytiles): #start, stop, step
pygame.draw.line(background, (255,255,255), (0,y), (Config.width,y))
# paint upper rectangle to have background for text
pygame.draw.rect(background, (128,128,255), (0,0,Config.width, 70))

background = background.convert()
screen.blit(background, (0,0)) # delete all
clock = pygame.time.Clock()    # create pygame clock object
FPS = Config.fps               # desired max. framerate
playtime = 0

tankgroup = pygame.sprite.Group()
bulletgroup = pygame.sprite.Group()

Tank._layer = 4   # base layer
Bullet._layer = 7 # to prove that Bullet is in top-layer
Tracer._layer = 5 # above Tank, but below Turret
Turret._layer = 6 # above Tank & Tracer
Text._layer = 3   # below Tank

#assign default groups to each sprite class
Tank.groups = tankgroup, allgroup
Turret.groups = allgroup
Bullet.groups = bulletgroup, allgroup
Text.groups = allgroup
player1 = Tank((150,250), 90) # create  first tank, looking north
player2 = Tank((450,250), 90) # create second tank, looking south

status3 = Text((Config.width/2, 10), "difference angle (from blue to yellow): %i" % Tank.book.aim_at_player())
mainloop = True
while mainloop:
status3.changemsg( "difference angle (from blue to yellow): %i" % Tank.book.aim_at_player() )
milliseconds = clock.tick(Config.fps)  # milliseconds passed since last frame
seconds = milliseconds / 1000.0 # seconds passed since last frame (float)
playtime += seconds

for event in pygame.event.get():
if event.type == pygame.QUIT:
# pygame window closed by user
mainloop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # exit game
# teleport player1 tank if left mousebutton is pressed
elif event.type == pygame.MOUSEBUTTONDOWN:
if pygame.mouse.get_pressed():
#left mousebutton was pressed
player1.pos=pygame.mouse.get_pos()
player1.pos=pygame.mouse.get_pos()

pygame.display.set_caption("%s FPS: %.2f playtime: %.1f " % ( Config.title,clock.get_fps(), playtime))
#screen.blit(background, (0,0)) # delete all
allgroup.clear(screen, background) # funny effect if you outcomment this line
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip() # flip the screen 30 times a second
return 0

if __name__ == '__main__':
main()

```

1)
the part remaining after a division by 360. See the modulo operator % 