Godot: How to limit player movement to certain tiles only? - game-engine

I'm trying to make a 'Stone Age' clone.
And I'm trying to figure a way to limit my player movement to certain tiles. Meaning that a player can move only on certain tiles.
What I did so far is make two TileMaps one for 'walk-able floor' and one for 'un walk-able floor'. I put them on different collision layers and I have my player only collide with the 'un walk-able floor'. So the player is prevented from entering it:
Here (https://i.imgur.com/sPGCS0V.png): The walkable floor is the brown tiles and the unwalkable are the ones that say 'FLOOR'.
This method is a problem because I have platforms I want to move my player on. (The tiles with the arrows on them) But If all the background is unwalkable, my player will collide with it when he'll be on a platform, so he won't be able to use the platforms to move...
Is there a better way to do this?

I found a solution on this guide and slightly modified it. In a nutshell the solution is to use ray-cast in the direction of the movement and see if there is a collision.
What I did was create a tilemap and call all the tiles that allow player movement 'walkable_[something]'. In the player script where I detect the collision from the ray cast I check the name of the tile the raycast collided with. If the name begins with 'walkable' I allow movement and in all other cases I don't.
onready var ray = $RayCast2D
var speed = 256 # big number because it's multiplied by delta
var tile_size = 64 # size in pixels of tiles on the grid
var last_position = Vector2() # last idle position
var target_position = Vector2() # desired position to move towards
var movedir = Vector2() # move direction
func _process(delta):
# MOVEMENT
if ray.is_colliding():
var collision = ray.get_collider()
if collision is TileMap:
var tile_name = get_tile(collision)
if !(tile_name.begins_with("Walkable")):
position = last_position
target_position = last_position
else:
position += speed * movedir * delta
if position.distance_to(last_position) >= tile_size - speed * delta: # if we've moved further than one space
position = target_position # snap the player to the intended position
else:
position += speed * movedir * delta
if position.distance_to(last_position) >= tile_size - speed * delta: # if we've moved further than one space
position = target_position # snap the player to the intended position
This is the get_tile() function, it gets the string name of the tile the ray cast collided with. Because the raycast collision returns the whole tile map we need to know which specific tile we collided with. So I used the position of the player + the direction of the ray cast vector to figure out the correct tile. First I added half a tile in width and height of a tile size because the player position is considered the top left corner of the player node. For example if the player is a square of size 2x2 and he's positioned on (0,0). It means it's top left corner is on 0,0. Which means that 1,1 is the center of the square.
func get_tile(tile_map):
if tile_map is TileMap:
var v = Vector2(tile_size / 2 , tile_size /2)
var tile_pos = tile_map.world_to_map(position + v + ray.cast_to)
var tile_id = tile_map.get_cellv(tile_pos)
if (tile_id == -1):
return "None"
else:
return tile_map.tile_set.tile_get_name(tile_id)
For the sake of completion here is a pastebin of the full player script.

Related

How to make a new created line of specific length?

I need the line I am drawing to be of specific length. I can detect when it reaches the required length but if the user moves the mouse faster it could make the line longer than intended.
Heres a video of the issue I am having, when I move the mouse slowly works great but speed gives issue: https://www.youtube.com/watch?v=4wkYcbG78TE
Here is the code where I create and detect the length of the line.
if Input.is_action_pressed("Left_click"): #This checks the distance between last vector and the mouse vector
#points_array[-1] = get_global_mouse_position() # Gets the last position of the array and sets the mouse cords
var length = clamp(points_array[-1].distance_to(get_global_mouse_position()),0,20)
if length == 20: # if its long enough create a new line and set it to mouse position
var cords = get_global_mouse_position()
print(cords)
points_array.append(cords)
When the mouse moved too much, you could add multiple points to fill the gap, always at the correct distance, of course.
That is, while the length from the last point to the position of the mouse is larger than the distance you want, add another point at the appropriate distance.
Thus, a while loop:
if Input.is_action_pressed("Left_click"):
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > distance:
var cords = # ???
points_array.append(cords)
A little vector algebra will figure out where to place that point. Starting from the last added point, you want to go in the direction from it to the position of the mouse. What distance to go? well, it the length you want.
if Input.is_action_pressed("Left_click"):
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > distance:
var last_point = points_array[-1]
var cords = last_point + last_point.direction_to(mouse_pos) * distance
points_array.append(cords)
I believe that should work.

Kinetic Theory Model

Edit: I've now fixed the problem I asked about. The spheres were leaving the box in the corners, where the if statements (in the while loop shown below) got confused. In the bits of code that reverse the individual components of velocity on contact with walls, some elif statements were used. When elif is used (as far as I can tell) if the sphere exceeds more than one position limit at a time, the program only reverses the velocity component for one of them. This is rectified when replacing elif with simply if. I'm not sure if I quite understand the reason behind this, so hopefully someone cleverer than I will comment such information, but for now, if anyone has the same problem, I hope my limited input helps!
Some context first:
I'm trying to build a model of the kinetic theory of gases in VPython, as a revision exercise for my (Physics) degree. This involves me building a hollow box and putting a bunch of spheres in it, randomly positioned throughout the box. I then need to assign each of the spheres its own random velocity and then use a loop to adjust the position of each sphere with reference to its velocity vector and a time step.
The spheres should also undergo elastic collisions with each wall and all other spheres.
When a sphere meets a wall in the x-direction, its x-velocity component is reversed and similarly in the y and z directions.
When a sphere meets another sphere, they swap velocities.
Currently, my code works so far as creating the right number of spheres and distributing them randomly and giving each sphere its own random velocity. The spheres also move as they should, except for collisions. The spheres should all stay inside the box as they should bounce off all the walls. They appear to be bouncing off each other, however, occasionally a sphere or two will go straight through the box.
I am extremely new to programming and I don't quite understand what's going on here or why it's happening but I'd be very grateful if someone could help me.
Below is the code I have so far (I've tried to comment what I'm doing at each step):
##########################################################
# This code is meant to create an empty box and then create
# a certain number of spheres (num_spheres) that will sit inside
# the box. Each sphere will then be assigned a random velocity vector.
# A loop will then adjust the position of each sphere to make them
# move. The spheres will undergo elastic collisions with the box walls
# and also with the other spheres in the box.
##########################################################
from visual import *
import random as random
import numpy as np
num_spheres = 15
fps = 24 #fps of while loop (later)
dt = 1.0/fps #time step
l = 40 #length of box
w = 2 #width of box
radius = 0.5 #radius of spheres
##########################################################
# Creating an empty box with sides length/height l, width w
wallR = box(pos = (l/2.0,0,0), size=(w,l,l), color=color.white, opacity=0.25)
wallL = box(pos = (-l/2.0,0,0), size=(w,l,l), color=color.white, opacity=0.25)
wallU = box(pos = (0,l/2.0,0), size=(l,w,l), color=color.white, opacity=0.25)
wallD = box(pos = (0,-l/2.0,0), size=(l,w,l), color=color.white, opacity=0.25)
wallF = box(pos = (0,0,l/2.0), size=(l,l,w), color=color.white, opacity=0.25)
wallB = box(pos = (0,0,-l/2.0), size=(l,l,w), color=color.white, opacity=0.25)
#defining a function that creates a list of 'num_spheres' randomly positioned spheres
def create_spheres(num):
global l, radius
particles = [] # Create an empty list
for i in range(0,num): # Loop i from 0 to num-1
v = np.random.rand(3)
particles.append(sphere(pos= (3.0/4.0*l) * (v - 0.5), #pos such that spheres are inside box
radius = radius, color=color.red, index=i))
# each sphere is given an index for ease of referral later
return particles
#defining a global variable = the array of velocities for the spheres
velarray = []
#defining a function that gives each sphere a random velocity
def velocity_spheres(sphere_list):
global velarray
for sphere in spheres:
#making the sign of each velocity component random
rand = random.randint(0,1)
if rand == 1:
sign = 1
else:
sign = -1
mu = 10 #defining an average for normal distribution
sigma = 0.1 #defining standard deviation of normal distribution
# 3 random numbers form the velocity vector
vel = vector(sign*random.normalvariate(mu, sigma),sign*random.normalvariate(mu, sigma),
sign*random.normalvariate(mu, sigma))
velarray.append(vel)
spheres = create_spheres(num_spheres) #creating some spheres
velocity_spheres(spheres) # invoking the velocity function
while True:
rate(fps)
for sphere in spheres:
sphere.pos += velarray[sphere.index]*dt
#incrementing sphere position by reference to its own velocity vector
if abs(sphere.pos.x) > (l/2.0)-w-radius:
(velarray[sphere.index])[0] = -(velarray[sphere.index])[0]
#reversing x-velocity on contact with a side wall
elif abs(sphere.pos.y) > (l/2.0)-w-radius:
(velarray[sphere.index])[1] = -(velarray[sphere.index])[1]
#reversing y-velocity on contact with a side wall
elif abs(sphere.pos.z) > (l/2.0)-w-radius:
(velarray[sphere.index])[2] = -(velarray[sphere.index])[2]
#reversing z-velocity on contact with a side wall
for sphere2 in spheres: #checking other spheres
if sphere2 != sphere:
#making sure we aren't checking the sphere against itself
if abs(sphere2.pos-sphere.pos) < (sphere.radius+sphere2.radius):
#if the other spheres are touching the sphere we are looking at
v1 = velarray[sphere.index]
#noting the velocity of the first sphere before the collision
velarray[sphere.index] = velarray[sphere2.index]
#giving the first sphere the velocity of the second before the collision
velarray[sphere2.index] = v1
#giving the second sphere the velocity of the first before the collision
Thanks again for any help!
The elif statements within the while loop in the code given in the original question are/were the cause of the problem. The conditional statement, elif, is only applicable if the original, if, condition is not satisfied. The circumstance wherein a sphere meets the corner of the box satisfies at least two of the conditions for reversing velocity components. This means that, while one would expect (at least) two velocity components to be reversed, only one is. That is, the direction specified by the if statement is reversed, whereas the component(s) mentioned in the elif statement(s) are not, as the first condition has been satisfied and, hence, the elif statements are ignored.
If each elif is changed to be a separate if statement, the code works as intended.

Spritekit top down turning character movement

I'm creating a top down 'racer' game if you will in Spritekit. However, I'm already stuck in creating the gameplay. You control a car from a top down view which is always driving at a constant speed. The player can press two buttons to turn the car left or right. The speed has to remain constant, but the zRotation of the car has to change. Changing the zRotation isn't the problem, but defining the new velocity of the car is.
I'm currently working with Vectors, so say every time the player presses the 'turnLeft' button the zRotation of the car changes with 45 degrees and a constant velocity of 20, the new velocity would be:
player.physicsBody.velocity = CGVectorMake(14.14, 14.14);
Given by using the sin and cos of 45 degrees and sum of vectors giving the constant speed 20 ( triangle with two 45 degrees angles and one side of 20).
However, I have no idea how I should make this variable every time the player holds the turnLeft button, and if I should use actions or the update function. It should be possible for the player to drive a circle if he chooses to hold the turnLeft button forever. Any help is appreciated! Thanks.
To calculate the components of the velocity you basically need to do what you stated, except use the zRotation of your sprite in place of 45 degrees. For example:
let speed: CGFloat = 20 // The constant speed.
// Work out the components of velocity:
let dx = speed * cos(sprite.zRotation)
let dy = speed * sin(sprite.zRotation)
sprite.physicsBody!.velocity = CGVector(dx: dx, dy: dy)
As an improvement, it would be better to add this code as an initialiser of CGVector, for reusability:
extension CGVector {
init(length: CGFloat, angle: CGFloat) {
self.dx = length * cos(angle)
self.dy = length * sin(angle)
}
}
You can then use this like so:
sprite.physicsBody!.velocity = CGVector(length: speed, angle: sprite.zRotation)
Hope that answers your question.

Calculating 2D resultant forces for vehicles in games

I am trying to calculate the forces that will act on circular objects in the event of a collision. Unfortunately, my mechanics is slightly rusty so i'm having a bit of trouble.
I have an agent class with members
vector position // (x,y)
vector velocity // (x,y)
vector forward // (x,y)
float radius // radius of the agent (all circles)
float mass
So if we have A,B:Agent, and in the next time step the velocity is going to change the position. If a collision is going to occur I want to work out the force that will act on the objects.
I know Line1 = (B.position-A.position) is needed to work out the angle of the resultant force but how to calculate it is baffling me when I have to take into account current velocity of the vehicle along with the angle of collision.
arctan(L1.y,L1.x) is am angle for the force (direction can be determined)
sin/cos are height/width of the components
Also I know to calculate the rotated axis I need to use
x = cos(T)*vel.x + sin(T)*vel.y
y = cos(T)*vel.y + sin(T)*vel.x
This is where my brain can't cope anymore.. Any help would be appreciated.
As I say, the aim is to work out the vector force applied to the objects as I have already taken into account basic physics.
Added a little psudocode to show where I was starting to go with it..
A,B:Agent
Agent {
vector position, velocity, front;
float radius,mass;
}
vector dist = B.position - A.position;
float distMag = dist.magnitude();
if (distMag < A.radius + B.radius) { // collision
float theta = arctan(dist.y,dist.x);
flost sine = sin(theta);
float cosine = cos(theta);
vector newAxis = new vector;
newAxis.x = cosine * dist .x + sine * dist .y;
newAxis.y = cosine * dist .y - sine * dist .x;
// Converted velocities
vector[] vTemp = {
new vector(), new vector() };
vTemp[0].x = cosine * agent.velocity.x + sine * agent.velocity.y;
vTemp[0].y = cosine * agent.velocity.y - sine * agent.velocity.x;
vTemp[1].x = cosine * current.velocity.x + sine * current.velocity.y;
vTemp[1].y = cosine * current.velocity.y - sine * current.velocity.x;
Here's to hoping there's a curious maths geek on stack..
Let us assume, without loss of generality, that we are in the second object's reference frame before the collision.
Conservation of momentum:
m1*vx1 = m1*vx1' + m2*vx2'
m1*vy1 = m1*vy1' + m2*vy2'
Solving for vx1', vy1':
vx1' = vx1 - (m2/m1)*vx2'
vy1' = vy1 - (m2/m1)*vy2'
Secretly, I will remember the fact that vx1'*vx1' + vy1'*vy1' = v1'*v1'.
Conservation of energy (one of the things elastic collisions give us is that angle of incidence is angle of reflection):
m1*v1*v1 = m1*v1'*v1' + m2*v2'+v2'
Solving for v1' squared:
v1'*v1' = v1*v1 - (m2/m1)v2'*v2'
Combine to eliminate v1':
(1-m2/m1)*v2'*v2' = 2*(vx2'*vx1+vy2'*vy1)
Now, if you've ever seen a stationary poolball hit, you know that it flies off in the direction of the contact normal (this is the same as your theta).
v2x' = v2'cos(theta)
v2y' = v2'sin(theta)
Therefore:
v2' = 2/(1-m2/m1)*(vx1*sin(theta)+vy1*cos(theta))
Now you can solve for v1' (either use v1'=sqrt(v1*v1-(m2/m1)*v2'*v2') or solve the whole thing in terms of the input variables).
Let's call phi = arctan(vy1/vx1). The angle of incidence relative to the tangent line to the circle at the point of intersection is 90-phi-theta (pi/2-phi-theta if you prefer). Add that again for the reflection, then convert back to an angle relative to the horizontal. Let's call the angle of incidence psi = 180-phi-2*theta (pi-phi-2*theta). Or,
psi = (180 or pi) - (arctan(vy1/vx1))-2*(arctan(dy/dx))
So:
vx1' = v1'sin(psi)
vy1' = v1'cos(psi)
Consider: if these circles are supposed to be solid 3D spheres, then use a mass proportional to radius-cubed for each one (note that the proportionality constant cancels out). If they are supposed to be disklike, use mass proportional to radius-squared. If they are rings, just use radius.
Next point to consider: Since the computer updates at discrete time events, you actually have overlapping objects. You should back out the objects so that they don't overlap before computing the new location of each object. For extra credit, figure out the time that they should have intersected, then move them in the new direction for that amount of time. Note that this time is just the overlap / old velocity. The reason that this is important is that you might imagine a collision that is computed that causes the objects to still overlap (causing them to collide again).
Next point to consider: to translate the original problem into this problem, just subtract object 2's velocity from object 1 (component-wise). After the computation, remember to add it back.
Final point to consider: I probably made an algebra error somewhere along the line. You should seriously consider checking my work.

Detecting Special touch on the iphone

I was asking myself if there are examples online which covers how you can for instance detect shapes in touch gestures.
for example a rectangle or a circle (or more complex a heart .. )
or determine the speed of swiping (over time ( like i'm swiping my iphone against 50mph ))
For very simple gestures (horizontal vs. vertical swipe), calculate the difference in x and y between two touches.
dy = abs(y2 - y1)
dx = abs(x2 - x1)
f = dy/dx
An f close to zero is a horizontal swipe. An f close to 1 is a diagonal swipe. And a very large f is a vertical swipe (keep in mind that dx could be zero, so the above won't yield valid results for all x and y).
If you're interested in speed, pythagoras can help. The length of the distance travelled between two touches is:
l = sqrt(dx*dx + dy*dy)
If the touches happened at times t1 and t2, the speed is:
tdiff = abs(t2 - t1)
s = l/tdiff
It's up to you to determine which value of s you interpret as fast or slow.
You can extend this approach for more complex figures, e.g. your square shape could be a horizontal/vertical/horizontal/vertical swipe with start/end points where the previous swipe stopped.
For more complex figures, it's probably better to work with an idealized shape. One could consider a polygon shape as the ideal, and check if a range of touches
don't have too high a distance to their closest point on the pologyon's outline, and
all touches follow the same direction along the polygon's outline.
You can refine things further from there.
There does exist other methods for detecting non-simple touches on a touchscreen. Check out the $1 unistroke gesture recognizer at the University of Washington. http://depts.washington.edu/aimgroup/proj/dollar/
It basically works like this:
Resample the recorded path into a fixed number of points that are evenly spaced along the path
Rotating the path so that the first point is directly to the right of the path’s center of mass
Scaling the path (non-uniformly) to a fixed height and width
For each reference path, calculating the average distance for the corresponding points in the input path. The path with the lowest average point distance is the match.
What’s great is that the output of steps 1-3 is a reference path that can be added to the array of known gestures. This makes it extremely easy to give your application gesture support and create your own set of custom gestures, as you see fit.
This has been ported to iOS by Adam Preble, repo on github:
http://github.com/preble/GLGestureRecognizer