I am making a minimal Doom-style FPS game engine using Python, PyGame and Legacy PyOpenGL. I hope that the player will be able to look around in four directions - forward, backwards, left and right - using glRotatef() by pressing the left and right arrow keys.
A few problems have arisen:
A gun (a cube with a texture applied that changes the texture coordinates depending on the direction the player is facing) that should always appear 0.5 units ahead of the camera in the corresponding x and z position depending on the angle glRotatef() sets it to face towards, is moving to a strange position if I move on the x axis and then look left unless I stand dead centre in the room. The cube also appears to be static when I move left and right even though I am supplying it the x value I obtained from glGetDoublev(), and when I move forward the gun appears to be scaling even though I never implemented such functionality.
When I call
if event.type == pygame.KEYDOWN: # key pressed events
if event.key == pygame.K_LEFT:
glRotatef(-90,0,1,0)
if direction == 0:
direction = 3
else:
direction -= 1
to look to the left of the room, I occasionally get moved inside the wall and this sometimes affects the gun's position further.
I've tried adding fixed x and z variables (x_steps and z_steps) that are incremented by 0.1 every time the player moves. I'm not particularly sure why that removes the "static gun" problem but it did. However, when I rotated the camera, the same problem (of the gun moving to a strange position) still occurred.
## pygame/opengl initialisation code
def main():
pygame.init()
display = (800,600)
global displaySurface
displaySurface = pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
pygame.display.set_caption("Wolfenstein 4D")
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
gluPerspective(45, (display[0]/display[1]),0.1,50.0)
## game loop, obtaining x,y,z positions and looking around the room
def room1():
direction = 3 ## 3 = forward, 2 = left, 1 = backward, 0 = right
while True:
pos = glGetDoublev(GL_MODELVIEW_MATRIX)
x = pos[3][0]
y = pos[3][1]
z = pos[3][2]
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN: # key pressed events
if event.key == pygame.K_LEFT:
glRotatef(-90,0,1,0)
if direction == 0:
direction = 3
else:
direction -= 1
x_steps = 0
z_steps = 0
if event.key == pygame.K_RIGHT:
glRotatef(90,0,1,0)
if direction == 3:
direction = 0
else:
direction += 1
x_steps = 0
z_steps = 0
## movement code
spd = 0.1
keys = pygame.key.get_pressed()
if direction == 3:
if keys[pygame.K_a]:
glTranslatef(spd,0,0)
x_steps -= spd
if keys[pygame.K_d]:
glTranslatef(-spd,0,0)
x_steps += spd
if keys[pygame.K_w]:
glTranslatef(0,0,spd)
z_steps -= spd
if keys[pygame.K_s]:
glTranslatef(0,0,-spd)
z_steps += spd
if direction == 2:
if keys[pygame.K_a]:
glTranslatef(0,0,-spd)
x_steps += spd
if keys[pygame.K_d]:
glTranslatef(0,0,spd)
x_steps -= spd
if keys[pygame.K_w]:
glTranslatef(spd,0,0)
z_steps -= spd
if keys[pygame.K_s]:
glTranslatef(-spd,0,0)
z_steps += spd
## gun drawing code in game loop
if direction == 3:
loadTexture("gun1.png")
drawHUDGun(x,-0.1,z-0.5,3,0.1,0.1,0.1)
if direction == 2:
loadTexture("gun.png")
drawHUDGun(z-0.5,-0.1,x+0.5,2,0.1,0.1,0.1)
## gun drawing function
def drawHUDGun(x,y,z,angle,width,height,depth=0.5,color = ((1,1,1))):
vertices = (
(width+x,-height+y,-depth+z),
(width+x,height+y,-depth+z),
(-width+x,height+y,-depth+z),
(-width+x,-height+y,-depth+z),
(width+x,-height+y,depth+z),
(width+x,height+y,depth+z),
(-width+x,-height+y,depth+z),
(-width+x,height+y,depth+z)
)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBegin(GL_QUADS)
if angle == 3:
j = 0
if angle == 2:
j = 8
i = 0
for surface in surfaces:
i += 1
for vertex in surface:
glColor4f(1,1,1,1)
setTexCoord(0, texCoords, j)
if angle == 3:
if i >= 0 and i < 4:
if j < 4:
j += 1
if angle == 2:
if i == 2:
if j < 12:
j += 1
glVertex3fv(vertices[vertex])
glEnd()
glDisable(GL_BLEND)
glBegin(GL_LINES)
for edge in edges:
glColor3fv((0,1,0))
for vertex in edge:
glVertex3fv(vertices[vertex])
glEnd()
## implementation of the functions
main()
room1()
I expect the gun to appear 0.5 units ahead of the player in any direction regardless of where they are in the room, but the gun is often out of view due to being assigned incorrect x or z co-ordinates.
In legacy OpenGL there exists different current matrices. The current matrix which is affected by matrix operations can be chosen by glMatrixMode. Each matrix is organized on a stack. Matrices can be pushed and popped by glPushMatrix/glPopMatrix.
The projection matrix should be placed set to the projection matrix stack, the view and model transformations to the modelview matrix stack:
// choose projection matrix stack
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]),0.1,50.0)
// choose modelview matrix stack, for the following matrix operations
glMatrixMode(GL_MODELVIEW)
The gun should be placed in a first person view ("in front of you"). The esiest wy to achieve this is to draw the gun in viewspace, this means you've to cancel all the previous transformations to the modelview matrix. The matrix can be replace with the identity matrix by glLoadIdentity.
Save the modelview matrix onto the stack, set the identity matrix, draw the gun and finally restore the modelview matrix.e.g:
glPushMatrix()
glLoadIdentity()
drawHUDGun(x,-0.1,z-0.5,3,0.1,0.1,0.1)
glPopMatrix()
Related
I am writing a python project for a pool game where rather than using a cue stick to hit a ball straight, the user can input a math equation (This will allow the use of exponential absolute and trigonometry etc. For example: y = x, y = exp(x), y = sin(x) etc) which the cue ball will follow. However, I am not sure how to move the cue ball object along the equation of a line.
The cue ball will have a fixed velocity and friction. I have thought about using a graph and centering the origins of the graph to the balls x and y coordinates when the velocity of the cue ball is equal to 0.
The game is written mostly in Pygame, apart form the equation input box where a new window has been created in Tkinter.
If anyone has any knowledge of useful modules for using equations (Rather than just representing data or equations on a graph), that will help.
import pygame
from config import *
class Cueball:
def __init__(self, x, y):
self.x = x
self.y = y
self.velocity = pygame.math.Vector2(0,0)
self.image = pygame.image.load(path.join(CUEBALL_FOLDER, "Cueball.png"))
Here is an example where you can type in your equation and the ball position is updated. It uses the Python built-in eval() function. This function should only be used when you can trust your input. There is some very rudimentary filtering but it should not be relied upon to prevent malicious activity:
import pygame
from math import sin, tan, cos
def move(pos, eq, width, height):
"""Eval() the equation to return a new position"""
x = pos[0] + 1
if x >= width:
x = 0 # reset X position
try:
y = eval(eq[3:]) # skip "y ="
if type(y) not in (int, float):
raise Exception("Unexpected eval() return")
except:
x, y = pos # don't move if equation is invalid
if abs(y) > (height // 2): # reset position when off the screen
return 0, 0
return x, y
pygame.init()
# grab the first installed font
sys_font = pygame.font.SysFont(None, 37)
clock = pygame.time.Clock()
width, height = 320, 240
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Movement Equation")
x, y = 0, 0
eq = "y = x" # initial equation
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.TEXTINPUT:
# very limited input filtering
if event.text in "0123456789 +-/*x().sintaco":
eq += event.text
elif event.type == pygame.KEYUP:
if event.key == pygame.K_BACKSPACE:
eq = eq[:-1]
elif event.key == pygame.K_ESCAPE:
x, y = 0, 0 # reset position
# move the ball position
x, y = move((x, y), eq, width, height)
# Update text
text = sys_font.render(eq, True, pygame.Color("turquoise"))
# Graphics
screen.fill(pygame.Color("black"))
# Draw Text in the center
screen.blit(text, text.get_rect(center=screen.get_rect().center))
# Draw the ball (shift y axis so zero is in the center)
pygame.draw.circle(screen, "red", (x, height // 2 - y), 10)
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
Your question states that the ball will have a fixed velocity and friction, so I don't understand how trigonometric functions apply. It sounds like you should be using Vector2 as you indicate, but you also want to follow an equation, so that's what I've done. Perhaps it'll help you develop your own approach. If there's any take away, it could be to remove your Tkinter dependency.
You can type to add to the equation, Backspace removes the last character and Esc will reset the ball position to the origin. Once the ball moves outside of the screen, it will reset its position.
As for my previous question, I'm trying to simulate a 3d space in pygame. So far, I came up with a very simple idea that uses the third coordenate as a denominator to 'compress' (pretty sure there's some terminology here I'm not aware of) the furthest points around the center of the screen and reduce their sizes.
Could anyone suggest a simple improvement to this idea? I feel like I can just tune that denominator used for the projection (see the code) to create a way more accurate simulation.
If you run the code bellow you'll have a not-that-bad simulation of (let's say) a spaceship passing by some stars (pressing w or s). They dissapear if they get to far and a new one is created after that. But, if I apply a rotation (a or d), it becomes obvious that the simulation is not doing well, as I'm not really projecting the 3d points onto the 2d screen.
import pygame
import random
import numpy as np
pygame.init()
run=True
#screensize
screensize = (width,height)=(600,600)
center=(int(width/2),int(height/2))
screen = pygame.display.set_mode(screensize)
#delta mov
ds=0.1
do=0.0001
#Stars
points=[]
for i in range(1000):
n1 = random.randrange(-5000,5000)
n2 = random.randrange(-5000,5000)
n3 = random.randrange(-30,30)
points.append([n1,n2,n3])
while run:
pygame.time.delay(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
################## keys
keys=pygame.key.get_pressed()
if keys[pygame.K_w]:
for p in points:
p[2]-=ds
if keys[pygame.K_s]:
for p in points:
p[2]+=ds
if keys[pygame.K_a] or keys[pygame.K_d]:
if keys[pygame.K_a]:
for p in points:
p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]
else:
for p in points:
p[0]=np.cos(do)*p[0]-np.sin(do)*p[2]
p[2]=np.sin(do)*p[0]+np.cos(do)*p[2]
###############################projection###################
for p in points:
#this is to create new stars
if p[2]<=-30 or p[2]>=30:
p[0] = random.randrange(-5000,5000)
p[1] = random.randrange(-5000,5000)
p[2] =30
else:
#this is to ignore stars which are behind the ship
if p[2]<=0:
pass
else:
try:
#THIS IS THE PROJECTION I USE, I TAKE THE RADIUS BECAUSE I GUESS I'LL NEED IT... BUT I DON'T USE IT XD
r = ((p[0]**2+p[1]**2+p[2]**2)**(1/2))
pygame.draw.circle(screen,(255,255,0),(int(p[0]/p[2]+center[0]),int(p[1]/p[2]+center[1])),int(10/p[2]))
#this is to prevent division by cero and alike
except Exception as e:
pass
pygame.display.update()
screen.fill((0,0,0))
pygame.quit()
In general perspective is achieved by Homogeneous coordinates. Your approach is close to that.
I recommend to operate Cartesian coordinates, where the 3 dimensions have the same scale.
Emulate a Perspective projection when you draw the points.
This means you've to calculate the w component of the Homogeneous coordinates dependent on the depth (z coordiante) of the point (e.g. w = p[2] * 30 / 5000) and to perform a "perspective divide" of the x, y and z components by the w component, before you draw the points. e.g:
#delta mov
ds=10
do=0.01
#Stars
points=[]
for i in range(1000):
n1 = random.randrange(-5000,5000)
n2 = random.randrange(-5000,5000)
n3 = random.randrange(-5000,5000)
points.append([n1,n2,n3])
while run:
pygame.time.delay(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
################## keys
keys=pygame.key.get_pressed()
if keys[pygame.K_w]:
for p in points:
p[2]-=ds
if keys[pygame.K_s]:
for p in points:
p[2]+=ds
if keys[pygame.K_a] or keys[pygame.K_d]:
if keys[pygame.K_a]:
for p in points:
p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]
else:
for p in points:
p[0], p[2] = np.cos(do)*p[0]-np.sin(do)*p[2], np.sin(do)*p[0]+np.cos(do)*p[2]
###############################projection###################
screen.fill((0,0,0))
for p in points:
#this is to create new stars
if p[2]<=-5000 or p[2]>=5000:
p[0], p[1], p[2] = random.randrange(-5000,5000), random.randrange(-5000,5000), 5000
else:
#this is to ignore stars which are behind the ship
if p[2]<=0:
pass
else:
w = p[2] * 30 / 5000
pygame.draw.circle(screen,(255,255,0),(int(p[0]/w+center[0]),int(p[1]/w+center[1])),int(10/w))
pygame.display.update()
Furthermore, the rotation is not correct. When you do
p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]
the p[0] is changed in the first line, but the original value should be used in the 2nd line.
Do "tuple" assignment to solve the issue:
p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]
I've created a game, but I have problems controlling the jump of the player. This code is a simplified version only showing the jump problem. Keep in mind that in the game itself the FPS may vary in mid jump. I've separated the jump code so it's the easier to identify, and also with the UP/DOWN arrows you can change the FPS to make tests.
Problem
The higher the FPS the smaller the jumps are, and the lower the FPS are the higher the jumps.
Expected Result
The jump reach the same height over many different FPS. Example: 30 and 120 FPS
Code
import pygame
Screen = pygame.display.set_mode((250,300))
Clock = pygame.time.Clock()
X = 50; Y = 250
FPS = 60; Current_FPS = FPS
Move = 480 / Current_FPS; Dir = "Up"
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if pygame.key.get_pressed()[pygame.K_UP]: FPS += 10 / Current_FPS
elif pygame.key.get_pressed()[pygame.K_DOWN] and FPS > 2: FPS -= 10 / Current_FPS
pygame.display.set_caption("FPS: "+str(int(FPS)))
Screen.fill((255,255,255))
X += 120 / Current_FPS
#---------------- JUMP CODE ---------------------#
if Dir == "Up":
if Move <= 0.0: Dir = "Down"
else:
Move -= 10 / Current_FPS
Y -= Move
else:
#RESET \/
if Y >= 250:
Dir = "Up"
X = 50; Y = 250
Move = 480 / Current_FPS; Dir = "Up"
#RESET /\
else:
Move += 120 / Current_FPS
Y += Move
#--------------------------------------------------#
pygame.draw.circle(Screen,(0,0,0),(int(X),int(Y)),5)
pygame.display.update()
Current_FPS = 1000.0 / Clock.tick_busy_loop(FPS)
You should set your initial jump velocity to be independent of frame rate, i.e:
Move = 480
Note that when you update the velocity (or in your case, it looks, speed) you do need to divide by the frame rate, since you are essentially multiplying by the time interval: v ~ u + a*dt. The same applies when updating the position, so this should be Y += Move / Current_FPS.
There are a couple of other things worth mentioning. Why do you track the direction variable? Why not just have your variable Move be the velocity. That way you would just need: Y += Move / Current_FPS and have positive/negative values indicate the direction. Also, your gravity is currently a lot stronger on the way down than on the way up, but this could be entirely deliberate!
Been at this for the past few hours, trying to make a small program where an image chases the cursor around. So far I've managed to make it so that the image is directly on top of the cursor and follows it around that way. However what I need is for the image to actually "chase" the cursor, so it would need to initially be away from it then run after it until it's then on top of the mouse.
Basically hit a wall with whats going wrong and what to fix up, here's what I've gotten so far:
from __future__ import division
import pygame
import sys
import math
from pygame.locals import *
class Cat(object):
def __init__(self):
self.image = pygame.image.load('ball.png')
self.x = 1
self.y = 1
def draw(self, surface):
mosx = 0
mosy = 0
x,y = pygame.mouse.get_pos()
mosx = (x - self.x)
mosy = (y - self.y)
self.x = 0.9*self.x + mosx
self.y = 0.9*self.y + mosy
surface.blit(self.image, (self.x, self.y))
pygame.display.update()
pygame.init()
screen = pygame.display.set_mode((800,600))
cat = Cat()
Clock = pygame.time.Clock()
running = True
while running:
screen.fill((255,255,255))
cat.draw(screen)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
Clock.tick(40)
Probably not in the best shape of coding, been messing with this for just over 5 hours now. Any help is much appreciated! Thanks :)
Assuming you want the cat to move at a fixed speed, like X pixels per tick, you need to pick a new position X pixels toward the mouse cursor. (If you instead want the cat to move slower the closer it gets, you'd instead pick a position a certain % of the way between the current position and the mouse cursor. If you want it to move faster the closer it gets, you need to divide instead of multiply. And so on. But let's stick with the simple one first.)
Now, how do you move X pixels toward the mouse cursor? The usual way of describing this is: You find the unit vector in the direction from the current position to the cursor, then multiply it by X, and that gives you the steps to add. And you can reduce that to nothing fancier than a square root:
# Vector from me to cursor
dx = cursor_x - me_x
dy = cursor_y - me_y
# Unit vector in the same direction
distance = math.sqrt(dx*dx + dy*dy)
dx /= distance
dy /= distance
# speed-pixel vector in the same direction
dx *= speed
dy *= speed
# And now we move:
me_x += dx
me_y += dy
Note that me_x and me_y are going to be floating-point numbers, not integers. That's a good thing; when you move 2 pixels northeast per step, that's 1.414 pixels north and 1.414 pixels east. If you round that down to 1 pixel each step, you're going to end up moving 41% slower when going diagonally than when going vertically, which would look kind of silly.
I am trying to create a project that I have made before but apart from this time I am going to be using a play-station one remote I was given for free from my school. The problem that when I move the joystick upwards and downwards it shows the same coordinates.(If you do not understand what I mean then look at the picture below). Then also I am not sure what I wold need to put into the if statement so that it checks if the joystick is upwards or downwards. I am also having trouble thinking on how you would check if the joystick is going in no direction.
I have already tried using an if statement where if the joystick is more than one number and less than another one (the first number being in the top half of the joystick and the other number meaning that it is in the bottom half of the joystick it will move downwards. The current if statement does not give off any errors but does not work. I have tried an if statement to check if it is in the middle but I am not too sure about it.
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
# No joysticks!
print("Error, I didn't find any joysticks.")
else:
# Use joystick #0 and initialize it
joystick = pygame.joystick.Joystick(0)
joystick.init()
if pygame.joystick.Joystick(0).get_axis(0) >= -0.0 and pygame.joystick.Joystick(0).get_axis(0) <= 0.0:
player_one.speed_y = 5
elif pygame.joystick.Joystick(0).get_axis(0) > -0.1 and pygame.joystick.Joystick(0).get_axis(0) < -0.9:
player_one.speed_y = -5
elif pygame.joystick(0).get_axis(0) == 0.0:
player_one.speed_y = -5
#The first if statement checks if the joystick is up and the second one
#checks if the joystick is downwards
# the middle one checks if the if statement is in the middle (not too sure)
#player one and two speed is what gets added on each time
The actual results that are that the sprite does not move when the joystick is moved downwards.
Joystick axis
First ensure that you have a joystick, by getting the number of joysticks by pygame.joystick.get_count(). Initialize the joystick by pygame.joystick.Joystick.init:
joystick = None
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
Once a joystick is initialized, ist axis value can be get by pygame.joystick.Joystick.get_axis. The value returned by this function is in range [-1, 1]. -1 for the maximum negative tilt and +1 for the maximum positive tilt.
Note, each analog stick of a gamepad or joystick has 2 axis, one for the horizontal direction and 1 for the vertical direction. Since the amount of the value returned by get_axis() depends on the tilting of the analog stick you should multiply the speed by the value.
Further you should ignore values near 0 for the dead center, because of the inaccuracy of the hardware. This can be don by a simple check using the built in function abs(x) e.g. abs(axisval) > 0.1:
if joystick:
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
player_one.speed_x = 5 * axis_x
if abs(axis_y) > 0.1:
player_one.speed_y = 5 * axis_y
See the following simple demo app:
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
pos = [size[0]/2, size[1]/2]
speed = 5
joystick = None
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key==pygame.K_RETURN:
done = True
if joystick:
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
pos[0] += speed * axis_x
if abs(axis_y) > 0.1:
pos[1] += speed * axis_y
else:
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
print("joystick initialized")
screen.fill((0, 0, 255))
pygame.draw.rect(screen, (255,255,255), (*pos, 10, 10))
pygame.display.flip()