Following a CGPath with SKAction without starting from beginning of CGPath - cocoa-touch

I have an SKShape node that represents an ellipse. A player is placed on the current point of a Bezier Path that is based on the ellipse's CGPath:
I have two actions that the player node can perform. The player can either follow the path clockwise or counterclockwise.
rotateCounterClockwiseAction = SKAction.followPath(counterClockwisePath.CGPath, asOffset: false, orientToPath: true, duration: 1)
rotateClockwiseAction = SKAction.followPath(clockwisePath.CGPath, asOffset: false, orientToPath: true, duration: 1)
When I start one of the actions as:
player.runAction(SKAction.repeatActionForever(rotateClockwiseAction), withKey: "ClockwiseRotation")
The player moves along the appropriate path and direction. When I stop one of the actions:
player.removeActionForKey("ClockwiseRotation")
The player stops on the path where it was last moved to. I want to be able to start either of the actions from the player's current point, and follow the same path, but right now, if I start either one of the actions again (after an action has already been started and stopped) the player jumps to the same starting point as seen in the picture and follows the path from there. How can I get the player to follow the path from the point that it is already at?

You can achieve this easily by using the speed property of SKNode.
Instead of removing and recreating the action, you can pause and resume it by adjusting the speed value. This will cause your bezier path animation to resume from its current position.
// Set the player node's speed to 0.0 causing all actions to be paused.
[playerNode setSpeed:0.0];
...
// Then when you're ready to resume set the player node's speed back to 1.0.
// This will cause the action to continue from its current position.
[playerNode setSpeed:1.0];

Related

I have a question about a YT tutorial because I wanna customize it a little

in this video, https://youtu.be/klBvssJE5Qg I shows you how to spawn enemies outside of a fixed camera. (this is in GDscript by the way) How could I make this work with a moving camera? I wanna make a zombie fighting game with a moving camera and zombies spawning outside that.
I would really appreciate help with this.
I've tried researching on the internet about how to do it, but I just didn't find it.
N/A..................................
After looking at the video, I see they are using this line to spawn:
Global.instance_node(enemy_1, enemy_position, self)
This suggest to me a couple thing:
The position is probably either relative to the self passed as argument or global.
There must be an Autoload called Global that I need to check to make sure.
And the answer is in another castle video.
In the video Godot Wave Shooter Tutorial #2 - Player Shooting we find this code:
extends Node
func instance_node(node, location, parent):
var node_isntance = node.instance()
parent.add_child(node_instance)
node_instance.global_position = location
return node_instance
And thus, we are working with global coordinates global_position. Thus enemy_position is used as global coordinates.
Ok, instead of using enemy_position as global coordinates we are going to use it as local coordinates of the Camera2D (or a child of it). Which means you need a reference to the Camera2D (which I don't know where do you have it).
You could make your code in a child of the Camera2D, or take the transform of the Camera2D using a RemoteTransform2D. Either way, you could then work in its local coordinates. Thus you would do this:
Global.instance_node(enemy_1, to_global(enemy_position), self)
Or you could have a reference by exporting a NodePath (or in the newest Godot you can export a Camera2D) from your script and set it via the inspector. So you can do this:
Global.instance_node(enemy_1, camera.to_global(enemy_position), self)
Where camera is your reference to the Camera2D.
In the following section of Arena.gd:
func _on_Enemy_spawn_timer_timeout():
var enemy_position = Vector2(rand_range(-160, 670), rand_range(-90, 390))
I believe you can add the X and Y coordinates of the camera to their corresponding random ranges in the enemy position Vector2. This will displace the enemy depending on where the camera is currently located.
You can get the position of the camera with this:
get_parent().get_node("Name of your camera").position
When this is all put together:
func _on_Enemy_spawn_timer_timeout():
var enemy_position = Vector2(rand_range(-160, 670) + get_parent().get_node("Name of your camera").position.x, rand_range(-90, 390) + get_parent().get_node("Name of your camera").position.y)
Keep in mind that you might need to displace the values in the following while loop as well. I hope this helps.

Player doesn't spawn correctly in procedural generated map

I've followed "Procedural Generation in Godot: Dungeon Generation" by KidsCanCode #https://www.youtube.com/watch?v=o3fwlk1NI-w and find myself unable to debug the current problem.
This specific commit has the code, but I'll try to explain in more detail bellow.
My main scene has a Camera2D node, a generic Node2D calles Rooms and a TileMap, everything is empty.
When the script starts, it runs a
func make_room(_pos, _size):
position = _pos
size = _size
var s = RectangleShape2D.new()
s.custom_solver_bias = 0.5
s.extents = size
$CollisionShape2D.shape = s
A few times and it fills $Rooms using .add_child(r) where r is a instance of the node that has the make_room() function. It will then iterate over $Rooms.get_children() a few times to create a AStar node to link all the rooms:
The magic comes when make_map() is called after afterwards, it fills the map with non-walkable blocks and then it carves the empty spaces, which works fine too:
There is a find_start_room() that is called to find the initial room, it also sets a global variable to the Main script start_room, which is used to write 'Start' on the map using draw_string(font, start_room.position - Vector2(125,0),"start",Color(3,4,8))
When I hit 'esc' it runs this simple code to instance the player:
player = Player.instance()
add_child(player)
player.position = start_room.position + Vector2(start_room.size.x/2, start_room.size.y/2)
play_mode = true
The problem comes when spawning the player. I tried doing some 'blind' fixing, such as adding or subtracting a Vector2(start_room.size.x/2, start_room.size.y/2) to player.position to see if I could make it fall within the room, to no avail.
Turning to the debugger didn't help, as the positions expressed by the variable inspectors don't seem to mean anything.
I tried implementing a simple 'mouse click print location':
print("Mouse Click/Unclick at: ", event.position)
print("Node thing",get_node("/root/Main/TileMap").world_to_map(event.position))
And also a 'start_room' print location:
print(get_node("/root/Main/TileMap").world_to_map(start_room.position))
And a when player moves print location, written directly into the Character script:
print(get_node("/root/Main/TileMap").world_to_map(self.position))
Getting results like the ones bellow:
Mouse Click/Unclick at: (518, 293)
Node thing(16, 9)
(-142, 0)
(-147, -3)
So, the player doesn't spawn on the same position as the start_room and the mouse position information is not the same as anything else.
Why is the player now spawning correctly? How can I debug this situation?
EDIT1: User Theraot mentioned about how the RigidBody2D is doing some weird collisions, and from what I understood, changing their collision behavior should fix the whole thing.
There's a section on the code that -after generating the random rooms- it removes some of the rooms like this:
for room in $Rooms.get_children():
if randf() < cull:
room.queue_free()
else:
room.mode = RigidBody2D.MODE_STATIC
room_positions.append(Vector3(room.position.x, room.position.y, 0))
From what I understand, if the room is randomly selected it will be deleted using queue_free() OR it will be appended to a room_positions for further processing. This means if I shift all the rooms to a different collision layer, the player/character instance would be alone with the TileMap on the same collision layer.
So I just added a simple room.collision_layer = 3 changing this section of the code to
for room in $Rooms.get_children():
if randf() < cull:
room.queue_free()
else:
room.mode = RigidBody2D.MODE_STATIC
room.collision_layer = 3
room_positions.append(Vector3(room.position.x, room.position.y, 0))
It doesn't seem to have changed anything, the player still spawns outside the room.
Do you see the rooms spread outwards?
You didn't write code to move the rooms. Sure, the code gives them a random position. But even if you set their position to Vector2.ZERO they move outwards, avoiding overlaps.
Why? Because these rooms are RigidBody2D, and they do push other physics objects. Such as other rooms or the player character.
That's the problem: These rooms are RigidBody2D, and you put your KinematicBody2D player character on top of one of them. The RigidBody2D pushes it out.
The tutorial you followed is exploiting this behavior of RigidBody2Ds to spread the rooms. However you don't need these RigidBody2D after you are done populating your TileMap.
Instead, you can store the start position in a variable for later placing the player character (you don't need offsets - by the way - the position of the room is the center of the room), and then remove the RigidBody2Ds. If you want to keep the code that writes the text, you would also have to modify it, so it does not fail when the room no longer exists.
Alternatively, you can edit their collision layer and mask so they don't collide with the player character (or anything for that matter, but why would you want these RigidBody2Ds that collide with nothing?).
Addendum post edit: Collision layers and mask don't work as you expect.
First of all, the collision layer and mask are flags. The values of the layers are powers of two (1, 2, 4, 8...). So, when you set it to 3, it is the layer 1 plus the layer 2. So it still collides with a collision mask of 1.
And second, even if you changed the collision layer of the rooms to 2 (so it does not match the collision mask of 1 that the player character has). The player character still has a layer 1 which match the collision mask of the rooms.
See also the proposal Make physics layers and masks logic simple and consistent.
Thus, you would need to change the layer and mask. Both. in such way that they don't collide. For example, you can set layer and mask to 0 (which disable all collisions). The algorithm that populates the TileMap does not use the layer and mask.

Sprite in Game Maker doesn't act the way I want it to

I'm currently working on animating my player so that he behaves like he's breathing.
if(time mod 60==0){
if(image_index==0){
image_index=1;
}
else{
image_index=0;
}
}
time++;
The whole thing is put in the step event and sprite is changing every single step and it even changes to an index 2 and 3, which I haven't even used in the code.
So if anyone has some ideas why does it work like this then please tell me.
It is because the sprite you use has multiple sub-images. GameMaker will naturally iterate the image index every frame.
So first, you need to stop the animation from running with
image_speed = 0;
You have to run this line when the sprite has just been changed, so ideally just after the "sprite_index" variable is changed. If you don't change it, just set image_speed to zero in the creation code.
If you are curious, I found the answer here : How to freeze sprite animation on last frame?

Unity Change Ball Direction after hitting paddle in a Pong game (like in dx-ball)

I'm struggling with how to get the ball to change the bounce based on where it hits on the paddle. Normally in a pong game, the angle changes, depending on how far from center the ball bounces, and which direction of the center it bounces.
I managed to do something like that:
//rb = rigidbody, velOnPaddleHit = predefined float
float dist = transform.position.x - paddle.position.x;
dist = transform.position.x > paddle.position.x ? dist : -dist;
dist /= paddle.localScale.x/2;
dist *= velOnPaddleHit;
rb.addForce(dist, 0,0);
But it's just not working / it's weird.
Can anyone help me?
Edit: Here's the video showing this kind of behaviour. When the ball hits the left side of the paddle, it goes left, the velocity doesn't matter
https://www.youtube.com/watch?v=fHX_2DLDp1w
You do't change it based on where it hit the paddle. To change the direction of the ball, all you have to do is to flip the numbers in the opposite direction. So multiplying x and y axis rigidBody value by -1 and applying it to the ball should do just that.
if ball collides with the paddle,multiply the velocity of the ball's rigidBody in all axis by -1, except for the z axis or the x-axis(Depends on how you setup your scene. It could ether z or x axis but the y-axis MUST be multiplied by -1 in other to go in the other direction.
You may have a speedVariable that increases the speed of the ball each time it hits the paddle.
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "ball")
{
Vector3 tempVect = new Vector3(collision.rigidbody.velocity.x * -1, collision.rigidbody.velocity.y * -1, collision.rigidbody.velocity.z);
collision.rigidbody.velocity = tempVect;
}
}
To detect where the ball hit, the easiest way I know about is to use tiny box colliders.
Create at-least 24 box colliders. Name them from detector1 to detector24
(detector1, detector2,detector3,detector4,detector5.....)
Mark isTrigger of 24 of the box colliders true so that they wont collide with the ball.
Create a layer called "detectorLayer" and make sure that those 24 box colliders are in the "detectorLayer" layer.
Create another layer called "ballLayer" and make sure that the ball's layer is set to "ballLayer".
Attach a collider to your paddle called. Change your paddle's GameObject name to "Paddle". Create a new layer called "PaddleLayer" and make sure that "Paddle" GameObject is in the layer called "PaddleLayer".
Position those 24 colliders from left to right in order close to the paddle but make sure they are not touching the paddle or the collider of the Paddle. The image below should help you understand what I am taking about.
Writing the code should be piece a piece of cake.
bool firstColision = false;
Check If ball collides with Paddle with OnCollisionEnter.
firstColision = true;
Immediately check which of the 24-colliders the ball is touching with the OnTriggerEnter function.
Inside the OnTriggerEnter function, make sure firstColision is true before checking which of the 24 box colliders is touching the ball.
The first box collider called "detector1" is positioned on left-most side while the last box collider called "detector24" is positioned to the right-most side of the padder. You can use if if statement or the switch statement todetect which box collider the ball triggered. 24/2 = 12 so box collider named "detector24" should be positioned in the middle and should be considered the middle of the paddle.
(ACTION) After detecting which box number the ball hit from with OnTriggerEnter, now you can do whatever you want. For example, make the ball go left or right. You may have constant values that dictates how far to move the ball for each box collider detected. That's up to you. You make your own rules from here now.
Set firstColision to false to reset it.
NOTE: It doesn't have to be 24 box colliders. It can be 15 but the more you have have, the better.
If everything works as expected, you can do the final part which is to go to Edit->Project Settings->Physics and make sure that the following is true:
A. detectorLayer cannot collide with detectorLayer.
B. PaddleLayer cannot collide with detectorLayer.
This will improve performance on mobile devices.

How to make an SKNode continue in the same direction after finishing following a path

In my project I have an SKNode following a CGPathRef made with touchesMoved. What I want to have happen is once it reaches the end of the CGPathRef, it follows in the same direction that it ended in. I am using the SKAction followPath and tried to implement this by adding a line from the last point on the path (touchesEnded) all the way to the end of the scene using some previous points and a unit vector however this was not only complicated but also very inconsistent. Is there another method I can use to make it just continue on its path after it finishes the CGPath? Thanks!
I suggest storing your node's CGVector (dx, dy) which represents the direction of movement for your node. For example, if the last moving vector was 15,15 that translates to your node having moved up and to the right. You can use this value to continue moving. If the values are too low or too high, you can add a min/max filter.