0 votes

Hi, I'm currently having trouble implementing a mechanic into my first game, which is a Sokoban clone. I want it so that the player can pull boxes when they hold down the z key in addition to pushing boxes naturally.

I've implemented a basic version of the mechanic, however its very buggy, like you are unable to pull boxes if they are in contact with a wall, and the player can also get themselves stuck inside walls when trying to pull.

What I basically want to happen is for the player to pull the box when they press any key that isn't in the box's direction. However, if they do press it in the box's direction, they'll just push it like if they weren't holding down the z key to pull. How can I go about implementing this mechanic?

Here's my player code:

extends KinematicBody2D

onready var ray = $RayCast2D
onready var runTimer = $RunTimer
var grid_size = 8
var runDelay: float  = 0.5

var inputs = {
    'ui_up' : Vector2(0,-1),
    'ui_down' : Vector2(0,1),
    'ui_left' : Vector2(-1,0),
    'ui_right' : Vector2(1,0)
}

func _unhandled_input(event):
    for dir in inputs.keys():
        if event.is_action(dir) and runTimer.is_stopped():
            runTimer.stop()
            if $Tween.is_active() == false:
                move(dir)
        if event.is_action_pressed(dir):
            runTimer.start(runDelay)
            if $Tween.is_active() == false:
                move(dir)

    if event.is_action_pressed("reset"):
        get_tree().reload_current_scene()

func move(dir):
    var game = get_parent()
    var vector_pos = inputs[dir] * grid_size
    ray.cast_to = vector_pos
    if Input.is_key_pressed(KEY_Z):
        ray.cast_to = -vector_pos
    ray.force_raycast_update()
    $Tween.interpolate_property(self, "position", position, position + vector_pos, 0.1, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
    if !ray.is_colliding():
        $Tween.start()
        game.moves += 1
    else:
        var collider = ray.get_collider()
        if collider.is_in_group('box'):
            if collider.move(dir):
                $Tween.start()
                game.moves += 1

func _on_Tween_tween_all_completed():
    get_parent().check_end()

And my box code since the two objects work in tandem to work right (I based my game off of a YouTube tutorial if you couldn't tell already):

extends KinematicBody2D

onready var ray = $RayCast2D
var grid_size = 8
var inputs = {
    'ui_up' : Vector2.UP,
    'ui_down' : Vector2.DOWN,
    'ui_left' : Vector2.LEFT,
    'ui_right' : Vector2.RIGHT
}

func move(dir):
    var vector_pos = inputs[dir] * grid_size
    ray.cast_to = vector_pos
    if Input.is_key_pressed(KEY_Z):
        ray.cast_to = -vector_pos
    ray.force_raycast_update()
    $Tween.interpolate_property(
        self, "position", 
        position, position + vector_pos, 0.1, 
        Tween.TRANS_SINE, 
        Tween.EASE_IN_OUT
    )
    if !ray.is_colliding():
        $Tween.start()
        return true
    return false
Godot version 3.2.1
in Engine by (12 points)

If your game is grid-based, you don't need raycasts at all.You should make two arrays: one is map of player, walls and boxes, and second array is map of target positions. Then you just check that player can move to desired location. If this location has box, you check that player can move this box (free space in moving direction). After that you check box positions with target positions. And you add animations between player moves.
And pulling is simpler - you check that there is box nearby and player can move in desired location. Then you update map accordingly to movement direction.

1 Answer

0 votes

the original Sokoban movement is instant (with animation in between tile movement), the is no sliding or colliding, therefore the kinematic object and the physic process is not necessary, you can switch everything to animated sprites and restric movement by checking if the area around the player is free or occupied by wall or boxes.

otherwise, you can create a "physic version" of the game, since you declared both the box and the player to be kinematic bodies, i'm expecting you have the walls as static bodies which both boxes and player collide on.

in this case you can let the physic engine manage the collision, but you might want to switch the box to a RigidBody. Doing so the pushing will come automatically, and for the pulling you can simply call

func _input(event):
 if event.is_action_pressed("pulling") and box_distance<whatEverValue:
   box.apply_central_impulse(vector_toward_player)

although you will lose some fine control of the box, it is probably easier than making 2 kinematic object interact.

by (1,436 points)
edited by
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.