3

I have been working on an "Asteroids" remake. However I can not get the objects to move in a random motion. I'm sure this evolves using vectors, however I do not know how to randomly generate a vector for each individual asteroid.

I am looking for a motion similar to this motion shown in this asteroid game, however I have no idea how to even start. This is my code so far:

import pygame as game
import random as r
import math

game.init()
game.display.set_caption("Asteroids")
screen = game.display.set_mode([800,600])
time=0
gameon=True
bgcolor = game.color.Color("#f6cb39")
black=game.color.Color("black")
badguy = game.image.load("asteroid.png")
badguy = game.transform.scale(badguy, (50, 50))
badguys=[]
SPAWNENEMY=10
CLOCK=11
game.time.set_timer(SPAWNENEMY,800)
game.time.set_timer(CLOCK,1000)

font=game.font.Font(None,20)
timetext=font.render("Time: 0", 0, black)

while gameon:
    screen.fill(bgcolor)
    event=game.event.poll()
    if event.type==SPAWNENEMY:
        bgbox = game.Rect(badguy.get_rect())
        bgbox.x = r.randint(50,800)
        bgbox.y = r.randint(50,600)
        badguys.append(bgbox)

    if event.type==game.QUIT:
        gameon=False;

    for bg in badguys:

        '''
        This is where I tried to put the mocment code,
        but I was unableto get randmom movments,
        only for asteroids to randomly appear or consistently
        move in one direction something like "bg.x+=2"

        '''

    for bg in badguys:
        screen.blit(badguy,(bg.x,bg.y))

    game.display.flip()

I apologize if its a little long, I don't know what else I can cut out to create an MCV.

martineau
  • 119,623
  • 25
  • 170
  • 301
Misha
  • 57
  • 5
  • For each bad guy jut initialize a random velocity vector and apply it to the position at each step, to they will follow a linear movement at a random direction –  Jan 17 '19 at 14:44
  • Spawn the meteor using random numbers for its position at the edge of the screen. You could do something like make three random numbers, one between 1-4 which will determine which edge it spawns on, the next between 1 and the resolution to determine where on the edge it will spawn, and lastly one for a random accuracy modifier. Point its vector at the center of the screen, apply the random modifier to make its "aim" imperfect. Just a guess! – KuboMD Jan 17 '19 at 14:52
  • I apologize, but I am very new to programming. I have never done anything with vectors, and I do not know how to do that. I can spaw the asteroids at the edge of the screen though. – Misha Jan 17 '19 at 15:04

2 Answers2

3

Here's how to do it using vectors. Each item in the badguy list is now a pair of items, its current position and an associated speed vector. Note that the position itself is also a vector (aka a "position vector").

Updating the current position is accomplished by simply adding each badguy's speed vector to its current position. i.e. bg[0] += bg[1].

import pygame as game
import pygame.math as math
from pygame.time import Clock
import random as r


game.init()
game.display.set_caption("Asteroids")
screen = game.display.set_mode([800, 600])

time = 0
gameon = True
bgcolor = game.color.Color("#f6cb39")
black = game.color.Color("black")
clock = Clock()

badguy = game.image.load("asteroid.png")
badguy = game.transform.scale(badguy, (50, 50))
badguys = []
SPAWNENEMY = 10
CLOCK = 11

game.time.set_timer(SPAWNENEMY, 800)
game.time.set_timer(CLOCK, 1000)

font=game.font.Font(None,20)
timetext=font.render("Time: 0", 0, black)

while gameon:
    screen.fill(bgcolor)

    event = game.event.poll()
    if event.type == SPAWNENEMY:
        # Select a random initial position vector.
        posn = math.Vector2(r.randint(50, 800), r.randint(50, 600))

        # Create a random speed vector.
        speed = r.randint(1, 10)
        dx = r.random()*speed * r.choice((-1, 1))
        dy = r.random()*speed * r.choice((-1, 1))
        vector = math.Vector2(dx, dy)

        # Each badguy item is a [position, speed vector].
        badguys.append([posn, vector])

    if event.type == game.QUIT:
        gameon = False;

    for bg in badguys:
        # Update positions.
        bg[0] += bg[1]  # Update position using speed vector.

    for bg in badguys:
        screen.blit(badguy, bg[0])

    clock.tick(60)
    game.display.flip()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Exactly what I was looking for! Thank you so much – Misha Jan 17 '19 at 22:46
  • 1
    Misha: Glad you found it useful. The MCV in your question was very helpful. The only thing that would have made it better would have been if you uploaded the `asteroid.png` image file [somewhere](https://imgur.com) and had a link to it in your question, too. – martineau Jan 17 '19 at 23:06
1

So each asteroid in your game is represented by a Rect, stored in badguys.

With a Rect, you're able to store a postion and a size (since a Rect has the attributes x, y, width and height).

Now, you want to store additional information/state for each asteroid, so only using a Rect is not enough. You need a different data structure that holds more fields.

Since you use python, the fitting data structure is a class that is able to hold the random vector.

But let's think a little bit further. Since you use pygame, pygame already offers a class for represting your game objects, and that class is called Sprite.

So here we go (note the comments in the code):

import pygame
import random

screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()

class Asteroid(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()

        # let's create an image of an asteroid by drawing some lines
        self.image = pygame.Surface((50, 50))
        self.image.set_colorkey((11, 12, 13))
        self.image.fill((11, 12, 13))
        pygame.draw.polygon(self.image, pygame.Color('grey'), [(0, 11), (20, 0), (50, 10), (15, 22), (27, 36), (10, 50), (0, 11)], 1)

        # Let's store a copy of that image to we can easily rotate the image
        self.org_image = self.image.copy()

        # The rect is used to store the position of the Sprite
        # this is required by pygame
        self.rect = self.image.get_rect(topleft=(x, y))

        # Let's create a random vector for the asteroid
        self.direction = pygame.Vector2(0, 0) 
        while self.direction.length() == 0:
            self.direction = pygame.Vector2(random.uniform(-1, 2), random.uniform(-1, 2))

        # Also we want a constant, random speed
        self.direction.normalize_ip()
        self.speed = random.uniform(0.1, 0.3)

        # we additionaly store the position in a vector, so the math is easy
        self.pos = pygame.Vector2(self.rect.center)

        # Aaaaaaaaaand a random rotation, because why not
        self.rotation = random.uniform(-0.3, 0.3)
        self.angle = 0

    def update(self, dt):
        # movement is easy, just add the position and direction vector
        self.pos += self.direction * self.speed * dt
        self.angle += self.rotation * dt
        self.image = pygame.transform.rotate(self.org_image, self.angle)

        # update the rect, because that's how pygame knows where to draw the sprite
        self.rect = self.image.get_rect(center=self.pos)

SPAWNENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWNENEMY, 800)

asteroids = pygame.sprite.Group()
dt = 0
while True:
    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            quit()
        if e.type == SPAWNENEMY:
            asteroids.add(Asteroid(random.randint(50, 200), random.randint(50, 200)))
    screen.fill(pygame.Color('black'))
    asteroids.draw(screen)
    asteroids.update(dt)
    pygame.display.flip()
    dt = clock.tick(60)
sloth
  • 99,095
  • 21
  • 171
  • 219
  • it gives me an error "module 'pygame' has no attribute 'Vector2'" – Misha Jan 17 '19 at 15:25
  • 1
    @Misha It seems you're running an older pygame version. You could either try `pygame.math.Vector2` instead or update your pygame version. – sloth Jan 17 '19 at 15:30
  • IMO there's a lot of good advice and sample code in this answer, however much of it is likely too advanced for many to understand since it goes well beyond the question being asked — although that's not inherently a bad thing. – martineau Jun 12 '21 at 18:18