I am starting out with pymunk and have a static floor with a dynamic object (ball). When the ball falls onto the floor there is an initial overlap where the ball is absorbed into the floor before slowly being pushed back up.
Why does this happen? Is this normal?
import pygame
import pymunk
import pymunk.pygame_util
pygame.init()
#game window
SCREEN_WIDTH, SCREEN_HEIGHT = 600, 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
#pymunk space
space = pymunk.Space()
space.gravity = (0, 981)
clock = pygame.time.Clock()
fps = 60
dt = 1/60
draw_options = pymunk.pygame_util.DrawOptions(screen)
def create_floor(space, width, height, pos):
body = pymunk.Body(body_type = pymunk.Body.STATIC)
body.position = pos
shape = pymunk.Poly.create_box(body, (width, height))
space.add(body, shape)
def create_ball(space, radius, mass):
body = pymunk.Body()
body.position = (300, 250)
shape = pymunk.Circle(body, radius)
shape.mass = mass
shape.color = (255, 0, 0, 100)
space.add(body, shape)
return shape
ball = create_ball(space, 30, 10)
create_floor(space, SCREEN_WIDTH, 20, (SCREEN_WIDTH / 2, SCREEN_HEIGHT - 10))
run = True
while run:
clock.tick(fps)
space.step(dt)
screen.fill((255, 255, 255))
space.debug_draw(draw_options)
#event handler
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.update()
pygame.quit()
Yes, this is normal. There are however things that can be done to minimize this behavior:
The easiest method is to call space.step() with a smaller timestep (and instead call it more times). Replace space.step(dt) with something like this:
for _ in range(10):
space.step(dt/10)
Its also possible to limit the maximum speed that objects move with. The slower they move, the less overlap there will be. Finally, you can also try to adjust the collision_bias parameter on the space.
Related
I have a function that spawns little balls, randomly positioned, on the screen. The problem I face is that I want to distribute the balls randomly, but when I do so, some balls spawn on top of each other. I want to exclude all the positions that are already taken (and maybe a buffer of a few pixels around the balls), but I don't know how to do so. I worked around this by giving the balls a Physicsbody, so they move off from one another if they happen to spawn on top of each other. But I want them to not spawn on top of each other in the first place. My code for now is the following:
spawnedBalls = [Ball]()
level = Int()
func setupLevel() {
let numberOfBallsToGenerate = level * 2
let boundary: CGFloat = 26
let rightBoundary = scene!.size.width - boundary
let topBoundary = scene!.size.height - boundary
while spawnedBalls.count < numberOfBallsToGenerate {
let randomPosition = CGPoint(x: CGFloat.random(in: boundary...rightBoundary), y: CGFloat.random(in: boundary...topBoundary))
let ball = Ball()
ball.position = randomPosition
ball.size = CGSize(width: 32, height: 32)
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width)
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 1
spawnedBalls.append(ball)
self.addChild(ball)
}
}
I don't know if this problem should be solved by having an array that stores all taken positions, or if I should use some kind of FiledNode, where occupied space can be sort of subtracted, but sadly I am unfamiliar with FieldNodes, so I don't know if that's the right way to face the problem.
Step 1) Replace
let randomPosition = ....
with
let randomPosition = randomPositionInOpenSpace()
Step 2) Write the randomPositionInOpenSpace function:
Idea is:
a) generate a random position
b) is it in open space? if so return that
c) repeat until OK
Then Step 3) write the 'is it in open space' function
For that you need to know if the proposed coordinate is near any of the other balls. For circles, you can test the distance between their centers is greater than (radiuses + margins). Distance between centers is pythagoras: sqrt of the x delta squared plus the y delta squared.
I'm trying to use a spotlight in my scene and add shadows to an object. However, I noticed that when I increase the spotInnerAngle, the shadow of the object changes significantly. Here's an example:
Both of shadows in these images look quite different – does anyone know why increasing the spot angle is causing the shadow to be less apparent?
This is the code I'm using to create a spotlight/add shadows to my scene:
let spotLight = SCNNode()
spotLight.light = SCNLight()
spotLight.light?.type = SCNLight.LightType.spot
spotLight.light?.spotInnerAngle = 120
spotLight.light?.spotOuterAngle = 120
spotLight.light?.color = UIColor.white
spotLight.light?.castsShadow = true
spotLight.light?.automaticallyAdjustsShadowProjection = true
spotLight.light?.shadowSampleCount = 32
spotLight.light?.shadowRadius = 8
spotLight.light?.shadowMode = .deferred
spotLight.light?.shadowMapSize = CGSize(width: 2048, height: 2048)
spotLight.light?.shadowColor = UIColor.black.withAlphaComponent(1)
spotLight.position = SCNVector3(x: 0,y: 5,z: 0)
spotLight.eulerAngles = SCNVector3(-Float.pi / 2, 0, 0)
SceneKit's engine calculates shadows slightly differently than 3D software apps do, like Maya or 3dsMax. In SceneKit framework the position and scale of your Spotlight as well as its value of cone angle are crucial for shadow generating. The main rule is the following: when area of spotlight's ray in SceneKit becomes greater, the shadow edges become more obscure (aka blurry).
Here's a properties you have to take into consideration when using spotlight:
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .spot
lightNode.rotation = SCNVector4(x: 0, y: 0, z: 0, w: 1)
lightNode.castsShadow = true
/* THESE SEVEN SPOTLIGHT PROPERTIES AFFECT SHADOW'S APPEARANCE */
lightNode.position = SCNVector3(0, 10, 0)
lightNode.scale = SCNVector3(7, 7, 7)
lightNode.light?.spotOuterAngle = 120
lightNode.light?.shadowRadius = 10
lightNode.light?.zNear = 0
lightNode.light?.zFar = 1000000
lightNode.light?.shadowSampleCount = 20
lightNode.light?.shadowColor = UIColor(write: 0, alpha: 0.75)
lightNode.light?.shadowMode = .deferred
scene.rootNode.addChildNode(lightNode)
Also, I recommend you use Ambient Light with very low Intensity for lighting up dark areas on your 3D models.
Hope this helps.
From Apple's documentation:
"[spotInnerAngle] determines the width of the fully illuminated area."
The default value of this property is 0, which means only the center of the area illuminated by the spotlight is lit at full intensity.
Increasing the inner angle will increase the area that is lit at full intensity, thus adding more light to the scene. In your case, this additional light was decreasing the visible shadow from that angle.
I'm trying to create a ball that automatically moves forward in a random direction once it is created. I've tried creating a random angle from 0-360 and having the node rotate and then having an impulse applied to the node, but the node simply stays there once it is created, so I can't tell if it is just the impulse that is not working or if the rotate isn't even working.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
this is just to create the enemy, but i dont know how to apply the random direction and forward movement.
enemy.size = CGSize(width: 20, height:20)
enemy.position = CGPoint(x: frame.width/2, y: frame.height/2)
enemy.color = UIColor(red:255.0,green:0.0,blue:0.0,alpha:1.0)
enemy.colorBlendFactor = 1.0
enemy.physicsBody = SKPhysicsBody(circleOfRadius:10)
enemy.physicsBody?.affectedByGravity = false
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.restitution = 1.0
enemy.physicsBody?.friction = 0.0
enemy.physicsBody?.linearDamping = 0.0
self.addChild(enemy)
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
enemy.physicsBody!.applyForce(vec)
this is my edited code.The enemy ball now moves when it is hit by another ball, but I want the enemy ball to just automatically move on its own. Right now it stays in the place in the middle of the screen until hit.
Like Gary already pointed out in the comments you wanna be sure that you have created an added a physicsBody to your SKNode. If you haven't, some neat documentation can be found here.
Now for applying a random force. You basically want to generate a random CGVector and use applyForce on your physics body.
To generate a random vector:
let vec = CGVector(dx: CGFloat(arc4random_uniform(100)) / 50.0, dy: CGFloat(arc4random_uniform(100)) / 50.0)
arc4random_uniform creates a random number between 0 and your upper bound which is passed as a parameter. Then I divide it a bit in order to get a number between 0 and 2. You should tweak that to your needs.
Then just use enemy.physicsBody!.applyForce(vec). Note my use of ! mark. Be sure that you have created and applied a physics body otherwise this will crash.
I would like to implement a scoreboard using a tkinter.
and i want make if the distance between the object bullet and the object enemy is less than 10, I want to increase the score by 10.
How do I add code?
Thank you in advance.
from tkinter import *
import time
import random
WIDTH = 800
HEIGHT = 800
class Ball:
def __init__(self, canvas, color, size, x, y, xspeed, yspeed):
self.canvas = canvas
self.color = color
self.size = size
self.x = x
self.y = y
self.xspeed = xspeed
self.yspeed = yspeed
self.id = canvas.create_oval(x, y, x+size, y+size, fill=color)
def move(self):
self.canvas.move(self.id, self.xspeed, self.yspeed)
(x1, y1, x2, y2) = self.canvas.coords(self.id)
(self.x, self.y) = (x1, y1)
if x1 <= 0 or x2 >= WIDTH:
self.xspeed = - self.xspeed
if y1 <= 0 or y2 >= HEIGHT:
self.yspeed = - self.yspeed
bullets = []
def fire(event):
bullets.append(Ball(canvas, "red", 10, 150, 250, 10, 0))
def up(event):
spaceship.yspeed-=1
def down(event):
spaceship.yspeed+=1
window = Tk()
canvas = Canvas(window, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.bind("<Button-1>", fire)
window.bind("<Up>",up)
window.bind("<Down>",down)
spaceship = Ball(canvas, "green", 100, 100, 200, 0, 0)
enemy = Ball(canvas, "red", 100, 500, 200, 5, 0)
while True:
for bullet in bullets:
bullet.move()
if (bullet.x+bullet.size) >= WIDTH:
canvas.delete(bullet.id)
bullets.remove(bullet)
enemy.move()
spaceship.move()
window.update()
time.sleep(0.03)
There are many ways to improve your program but I will concentrate on the collision aspect only.
Tkinter canvas has several methods; find_closest, find_enclosed and find_overlapping (See here) to allow you to detect where objects are in relation to each other. find_closest would be my first choice.
find_closest should take the x, y coordinates of the 'enemy' and a 'halo distance' (your less than 10 pixels). This will return the id's of objects nearby. If one of those objects is a bullet, then add 10 points to the score.
Some other things to fix/work on
You don't have a tkinter.mainloop. You should
Your method for moving the spaceship by changing the speed rather than the xy coords is poor and ends up with a very fast moving spaceship.
I have a sprite with a physics body.
To move this sprite, in the update() function I continually set the velocity of the sprite to 150 units to go right, and -150 to go left. (See attached code.)
I have a second physics body that I'd like to have follow the first sprite. The coordinates of this second physics body are at the bottom of the first one, and 20 points to the right.
I'd like the second physics body to always follow the first, with the offset.
My code mostly works but I've noticed that the distance between the first and second bodies varies slightly, and I want there to be no variation. When moving right, the distance between them is compressed a little. When moving left, the distance between them increases a little. See a video HERE: https://www.youtube.com/watch?v=K9FhIdMwp7k
Here is the code I'm using:
override func update(_ currentTime: TimeInterval) {
switch playerDirection {
case "stopped":
playerSpeed = 0.0
case "right":
playerSpeed = 150.0
case "left":
playerSpeed = -150.0
default:
print("default")
}
// ball is the first sprite and footBall is the second sprite:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
footBall.position = CGPoint(x: (ball.position.x + 20), y: ball.position.y - (ball.size.height / 2) + 4)
myCam.position = CGPoint(x: round(ball.position.x), y: self.size.height / 2)
...
I've been playing around with using override func didSimulatePhysics() without success, also.
I did a different test that just does away with using velocity to move the player, and instead directly increment the ball.position.x (ball = player) and footBall.position.x and when I do this everything is perfectly aligned. But if I take this route I'll have to change how my game physics work elsewhere in the game, which I'd prefer to ignore.
Thanks for having a look.
Would it meet your requirements if you add the second node as a child of the first? That is, instead of having both at the same level:
scene.addChild(ball)
scene.addChild(footBall)
replace with:
scene.addChild(ball)
ball.addChild(footBall)
and just set the footBall position as the offset you need:
footBall.position = CGPoint(x: 20, y: - (ball.size.height / 2) + 4)
then any time the ball moves the footBall will also move as it is a child node, so you can remove the manual position update code for the footBall.
I used SKPhysicsJoint to get around the problem. Here's my working code (testing at this point, so still kinda sloppy, but it works in principle) :
//add a nice player
ball = SKSpriteNode(texture:spriteArray2[0])
ball.size = CGSize(width: 150, height: 48)
//ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.height / 2)
ball.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 16, height: 48))
ball.zPosition = 1
ball.physicsBody?.affectedByGravity = true
ball.position = CGPoint(x: 80.0, y: 200.0)
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = UInt32(2 | 32 | 256) //2 = floors, 32 and 256= doors.
ball.physicsBody?.contactTestBitMask = 0
ball.physicsBody?.isDynamic = true
ball.physicsBody?.restitution = 0.0
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.mass = 60.0
ball.physicsBody?.friction = 0.00
addChild(ball)
//add a nice ball for when jumping
footBall = SKSpriteNode(color: SKColor.blue, size: CGSize(width: 24, height: 16))
footBall.position = ball.position
footBall.name = "footBall"
footBall.zPosition = 1
footBall.physicsBody = SKPhysicsBody(circleOfRadius: 4)
footBall.physicsBody?.affectedByGravity = false
footBall.physicsBody?.categoryBitMask = 16
footBall.physicsBody?.collisionBitMask = 2 // floors
footBall.physicsBody?.contactTestBitMask = UInt32(32 | 64 | 128 | 256) // the doors bear snake
footBall.physicsBody?.isDynamic = true
footBall.physicsBody?.restitution = 0.0
//footBall.physicsBody?.allowsRotation = false
//footBall.physicsBody?.mass = 60.0
//footBall.physicsBody?.friction = 0.00
addChild(footBall)
let myCGPoint = ball.position // sets joint position
let myJoint = SKPhysicsJointFixed.joint(withBodyA: ball.physicsBody!, bodyB: footBall.physicsBody!, anchor: myCGPoint)
scene?.physicsWorld.add(myJoint)
...
then down in the update loop I set velocity for the player as so:
ball.physicsBody?.velocity = CGVector(dx: playerSpeed, dy: ball.physicsBody!.velocity.dy)
In my original code, in my update loop I also adjusted the position of the footBall to match the ball (player) position as needed, but in the revised code, because of the joint being used, by moving the ball the footBall moves along with it without any need to otherwise apply any force or velocity or change the x position of the footBall. The footBall just tags along for the ride, which works.
At first, this new method didn't work correctly, and I discovered through trial and error that the reason why is that "footBall.physicsBody?.allowsRotation = false" cannot be specified as a property on footBall. By uncommenting this line it works fine, however.
The point of this exercise is to use the footBall as a jumping test point for when the player's feet are stretched out during a jump. So the footBall is thus slightly forward of the main player's body. Then I'll dynamically turn on or off this footBall as needed for jumps.
Somewhat related are the following pages on "foot sensor":
http://gamedevwithoutacause.com/?p=1076
http://www.iforce2d.net/b2dtut/jumpability