pytheron pytheron - 1 month ago 8
Python Question

How can I extend the unit selection logic in my RTS game to apply to multiple units?

Update:
I've now made classes and objects to hold the unit positions, and I can blit several to the screen and move them all at once. However what I haven't been able to do is get the units to be selected independently. I have these lists/arrays which draw from classes:

unit_orc_grunt = Unit("Grunt",[35,70,105,140,175,210,245,280,315,350],[35,70,105,140,175,210,245,280,315,350])

positions = numpy.array([
[unit_orc_grunt.locationx[0],unit_orc_grunt.locationy[0]],
[unit_orc_grunt.locationx[1],unit_orc_grunt.locationy[1]],
[unit_orc_grunt.locationx[2],unit_orc_grunt.locationy[2]],
[unit_orc_grunt.locationx[3],unit_orc_grunt.locationy[3]],
[unit_orc_grunt.locationx[4],unit_orc_grunt.locationy[4]],
[unit_orc_grunt.locationx[5],unit_orc_grunt.locationy[5]],
[unit_orc_grunt.locationx[6],unit_orc_grunt.locationy[6]],
[unit_orc_grunt.locationx[7],unit_orc_grunt.locationy[7]],
[unit_orc_grunt.locationx[8],unit_orc_grunt.locationy[8]],
[unit_orc_grunt.locationx[9],unit_orc_grunt.locationy[9]],
])

not_selected = [
positions[0],
positions[1],
positions[2],
positions[3],
positions[4],
positions[5],
positions[6],
positions[7],
positions[8],
positions[9]
]

selected = []


Then I have this in the loop:

elif button_type[0] == 1:
for position in positions:
if numpy.any(position) == numpy.any(not_selected) and numpy.any(position) != numpy.any(selected) and mouse_position[0] >= position[0] and mouse_position[0] <= position[0] + character_width and mouse_position[1] >= position[1] and mouse_position[1] <= position[1] + character_height:
selected.append(position)
not_selected.remove(position)


The selected units would then be drawn from the list 'selected', however, I am getting an error:

The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()


------------------Original question----------------------

Currently if you left-click on the unit, it becomes 'selected' (or 'de-selected'), and a green square is drawn around it. Then when you right-click somewhere on the screen, the unit moves neatly into the square in the location that you clicked.

Also if you use the up, down, left or right keys it will scroll the screen.

import pygame
import random
pygame.init()

#Define mouse position
mouse_position_x = 525
mouse_position_y = 315

# Define colors
green = (0,255,0)
brown = (150,75,0)

#Define border position
border_x = 0
border_y = 0

#Define character selection box
def character_selection_box():
pygame.draw.line(screen,green,(character_location_x,character_location_y),(character_location_x+character_width,character_location_y),2) # Top bar
pygame.draw.line(screen,green,(character_location_x,character_location_y+character_height),(character_location_x+character_width,character_location_y+character_height),2) # Bottom bar
pygame.draw.line(screen,green,(character_location_x,character_location_y),(character_location_x,character_location_y+character_height),2) # Left bar
pygame.draw.line(screen,green,(character_location_x+character_width,character_location_y),(character_location_x+character_width,character_location_y+character_height+1),2) # Right bar

#Define round
def assign_square(n):
div = (n/35)
rou = round(div)
mul = (35*rou)
return int(mul)

#Set window
screen_width = 981
screen_height = 700
game_screen_width = 800
game_screen_height = 700
screen_size = (screen_width,screen_height)
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("Warpath")

#Set block character
character_width = 35
character_height = 35
character_location_x = 525
character_location_y = 315
movement = 1
unit_selected = 0

#Load images
orc_grunt_forward = pygame.image.load('orc_forward3.png') #(35 by 35 pixel image)
character_image = orc_grunt_forward

#Loop until the user clicks the close button
shutdown = False

#Set clock
clock = pygame.time.Clock()

#Set scroll limit
scroll_x = 0
scroll_y = 0

# ---------- Main program loop -----------
while not shutdown:

# --- Main event loop ---
for event in pygame.event.get():

# --- If quit button pressed, shutdown
if event.type == pygame.QUIT:
shutdown = True

# --- If mouse button pressed
elif event.type == pygame.MOUSEBUTTONDOWN: # If a mouse button is pressed
mouse_position = pygame.mouse.get_pos() # Get mouse position
button_type = pygame.mouse.get_pressed() # Check which button was pressed

# --- If left click pressed and the curser was on a character, select that character
if button_type[0] == 1 and mouse_position[0] >= character_location_x and mouse_position[0] <= character_location_x + character_width and mouse_position[1] >= character_location_y and mouse_position[1] <= character_location_y + character_height:
print("Unit selected",unit_selected)
print(button_type)
unit_selected += 1
unit_selected /= unit_selected #(Otherwise it will add up unit selected if you click more than once)
int(unit_selected)

# --- If right click pressed and a character was selected (and it's within the game screen), move the character to the location
elif button_type[2] == 1 and unit_selected == 1 and mouse_position[0] > 175:
mouse_position_x *= 0
mouse_position_y *= 0

if mouse_position[0] >= assign_square(mouse_position[0]):
mouse_position_x += assign_square(mouse_position[0])

elif mouse_position[0] <= assign_square(mouse_position[0]):
mouse_position_x -= 35
mouse_position_x += assign_square(mouse_position[0])

if mouse_position[1] >= assign_square(mouse_position[1]):
mouse_position_y += assign_square(mouse_position[1])

elif mouse_position[1] <= assign_square(mouse_position[1]):
mouse_position_y -= 35
mouse_position_y += assign_square(mouse_position[1])

# --- If left click pressed and the curser was not on a character, deselect the character
elif button_type[0] == 1 and mouse_position[0] < character_location_x or mouse_position[0] > character_location_x + character_width or mouse_position[1] < character_location_y or mouse_position[1] > character_location_y + character_height:
print("Unit not selected")
print(button_type)
unit_selected *= 0
int(unit_selected)

# --- If key pressed, scroll the screen
elif event.type == pygame.KEYDOWN:

if event.key == pygame.K_RIGHT and scroll_x > -10:
direction = "right"
character_location_x -= 35
mouse_position_x -= 35
border_x -= 35
scroll_x -= 1

if event.key == pygame.K_LEFT and scroll_x < 10:
direction = "left"
character_location_x += 35
mouse_position_x += 35
border_x += 35
scroll_x += 1

if event.key == pygame.K_UP and scroll_y < 10:
direction = "up"
character_location_y += 35
mouse_position_y += 35
border_y += 35
scroll_y += 1

if event.key == pygame.K_DOWN and scroll_y > -10:
direction = "down"
character_location_y -= 35
mouse_position_y -= 35
border_y -= 35
scroll_y -= 1

# --- Game logic ---

# --- Set character movement
if character_location_x < mouse_position_x:
character_location_x += movement
if character_location_x > mouse_position_x:
character_location_x -= movement
if character_location_y < mouse_position_y:
character_location_y += movement
if character_location_y > mouse_position_y:
character_location_y -= movement

# --- Drawing ---
screen.fill(brown) # Draw background
screen.blit(character_image,(character_location_x,character_location_y)) # Draw character

if unit_selected == 1:
character_selection_box() # Draw character selection box if unit is selected

clock.tick(30)
pygame.display.flip()

#Shutdown
if shutdown == True:
pygame.quit()


The problem is that I can't figure out how to extend this to multiple units - currently if I want to add more units I can only either manage to:

a) Move them all at once

or

b) Paste the same code multiple times adjusting the character variable (not a robust / scalable solution)

How can I adjust my code so that I have a scalable solution where:

1) I can select a single unit and move it, without moving every unit at once

2) I can select multiple units by clicking on each one individually, and move them all at once (not worrying about pathfinding right now)

I also tried using classes to achieve this but it still felt like I was copying / pasting multiple functions rather than having a robust solution.

I've removed any code that doesn't concern the issue while keeping a functioning program.

Thanks

mx0 mx0
Answer

There are few things to do:

  1. Change variables character_* to object that holds all data about the unit.
  2. Create array of units / characters. That way each unit in array can have unique position, velocity ets.
  3. Everywhere in code where you check character_*, change to for loops where you iterate over characters array to check every unit.
  4. Next step should be adding functions like move / shoot to character class, to make keypress event working for multiple units.

That should give you code where you can select multiple units (if they occupy same spot) and move them independently of deselected units.