Need help with grid movement collisions / process priority does not seem to work
Asked Answered
F

4

0

Hello, I'm having serious troubles getting actors not to clip into each other. This is for a grid based game, so we have to detect collision before committing to move a certain distance. Tile size is 16x16. All collision shapes and shapecasts are 15x15 squares (because shapecasts recognize adjacent collision shapes).
Here's the gist: AI initiates a movement. We force a shapecast update, then do a shapecast check. If the shapecast check is successful (no collision), it initiates movement and occupies the target position with a second collision shape (which is a child node of the actor, but is held in place using inverse movement). All of this happens within the same frame. Giving 2 actors different process priorities should mean their individual _process functions run one after the other, which means that they should never clip into each other, as the first actor occupies the target position and the second actor would recognize that there is a collision box there and not initiate movement. However, if I let 2 actors that are one step size apart run into each other on the same frame (happens with step size at both full tile size and fractions), they still clip into each other. Does process priority not work like that?

Best solution I have for this problem would be to do a shapecast check and occupy the target position, wait for the next physics frame, then do another shapecast check, and only if the second shapecast is also successful do I let the actor move, otherwise remove I the occupying collision shape. But this is getting ridiculously complex. There must be something simple that I'm not getting here. Any help is welcome.

Below is the code for an NPC. This one always moves left, then right.

extends Area2D

#get tile dimensions from autoload
const tilesize = g.tilesize
const stepsize = g.stepsize

#get movement speed (/ stepsize) from autoload
var walkspeed = g.walkspeed_fast

#variables for walk movement logic
var initpos = Vector2.ZERO
var dir = Vector2.ZERO
var moving = false
var walkprog = 0.0

#variables for local timer
var timercount = 0.0
var timerstate = true

#delay in seconds between ai steps
var ai_delay = 0.0
#list of ai steps to cycle through
var ai_steps = [Vector2.LEFT,Vector2.RIGHT]
#current step in ai cycle
var ai_step = 0

#child nodes used by this script
@onready var collisionshape = $ShapeCast2D
@onready var collisiontarget = $"CollisionShape2D-Target"
@onready var animatedsprite = $AnimatedSprite2D

# Called when the node enters the scene tree for the first time.
func _ready():
	#snap actor position to grid (fail-safe, should not be necessary)
	position = position.snapped(Vector2.ONE*stepsize)
	#initial position for movement calculation 
	initpos = position
	#animate
	animatedsprite.speed_scale = 0.3
	animatedsprite.play("front")

# everything runs in the same physics frame if possible, no unnecessary delays
func _process(delta):
	timer(delta)
	ai()
	move(delta)
	
func timer(delta):
	#increment timer by delta (1 whole number per second) if active
	if timerstate == true:
		timercount += delta
	
func ai():
	#if not currently moving and if timer has passed delay value (in seconds)
	if moving == false and timerstate == true and timercount >= ai_delay:
		#set walk direction to current ai step
		dir = ai_steps[ai_step]
		
		#turn sprite
		if dir.y == 1:
			animatedsprite.play("front")
		elif dir.y == -1:
			animatedsprite.play("back")
		elif dir.x == 1:
			animatedsprite.play("right")
		elif dir.x == -1: 
			animatedsprite.play("left")
		
		#shapecast to target position
		collisionshape.target_position = dir * stepsize
		collisionshape.force_shapecast_update()
		
		#if target position is not occupied
		if !collisionshape.is_colliding():
			#enable second collision box (to occupy target position)
			collisiontarget.disabled = false
			#set initial position for calculating the walk movement
			initpos = position.snapped(Vector2.ONE*stepsize)
			#disable and reset timer
			timerstate = false
			timercount = 0
			#increment ai step
			ai_step = c.bndInc(ai_step,len(ai_steps)-1)
			#enable move function
			moving = true
			#speed up animation
			animatedsprite.speed_scale = 1


func move(delta):
	if moving == true:
		#walk progress += 1 delta increment * walk speed
		walkprog += walkspeed*delta
		
		var a = stepsize*dir
		var b = a*walkprog
		var c = tilesize/2
		#move actor by (1 delta increment * walk speed)
		position = initpos + b
		#second collision box occupies target position, must stay in place
		#current position to occupy is (walk target - walk progress + center offset)
		collisiontarget.position = a - b + Vector2(c,c)
		
		#if actor arrived at target position
		if walkprog >= 1.0:
			#player is 1 full stepsize away from beginning
			position = initpos + (stepsize*dir)
			#reset variables to wait for new input
			walkprog = 0
			moving = false
			timerstate = true
			#disable second collision box until it is needed again
			collisiontarget.disabled = true
			#slow down animation
			animatedsprite.speed_scale = 0.3

Autoload "g":

extends Node

const tilesize:float = 16.0
const stepsize:float = 8.0

const walkspeed_fast = 24.0/stepsize

Autoload "c":

extends Node

#"bounded" increment
func bndInc(value:int,limit:int,increment:int=1,wraparound:int=0):
	value +=increment
	if value > limit:
		value=wraparound
	return(value)
Forjudge answered 13/4, 2023 at 9:49 Comment(0)
F
0

Hello, I'm having serious troubles getting actors not to clip into each other. This is for a grid based game, so we have to detect collision before committing to move a certain distance. Tile size is 16x16. All collision shapes and shapecasts are 15x15 squares (because shapecasts recognize adjacent collision shapes).
Here's the gist: AI initiates a movement. We force a shapecast update, then do a shapecast check. If the shapecast check is successful (no collision), it initiates movement and occupies the target position with a second collision shape (which is a child node of the actor, but is held in place using inverse movement). All of this happens within the same frame. Giving 2 actors different process priorities should mean their individual _process functions run one after the other, which means that they should never clip into each other, as the first actor occupies the target position and the second actor would recognize that there is a collision box there and not initiate movement. However, if I let 2 actors that are one step size apart run into each other on the same frame (happens with step size at both full tile size and fractions), they still clip into each other. Does process priority not work like that?

Best solution I have for this problem would be to do a shapecast check and occupy the target position, wait for the next physics frame, then do another shapecast check, and only if the second shapecast is also successful do I let the actor move, otherwise remove I the occupying collision shape. But this is getting ridiculously complex. There must be something simple that I'm not getting here. Any help is welcome.

Because I can't get the post formatting right I'll link a pastebin:

Forjudge answered 13/4, 2023 at 9:58 Comment(0)
C
0

Saucedo I've fixed the formatting for you, if you want I can restore the topic and merge with the other one before it gets deleted with the next forum cache clearing, just let me know.

For formatting code you can either manually surround it with ``` on a line before and after the codeblock or you can hit enter 3 times then place the text cursor caret on the middle one, paste your code, highlight the code and then press the code formatting button on the editor toolbar. Or you can use github gist or pastebin link on a new line and it gets auto embedded as you've just discovered in the other topic.

Conjurer answered 13/4, 2023 at 11:4 Comment(0)
F
0

Conjurer
Thank you. Could you merge them please?

Forjudge answered 13/4, 2023 at 13:53 Comment(0)
M
0

I've only worked in 3d, but it seems like if a character is going to take up more than one grid size, it should be easy enough to know and just mark it as not usable. Are you using astar? I guess you can use navmesh for 2d also and it has obstacle avoidance. Maybe it only works on a direct overhead or something.

Mulch answered 13/4, 2023 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.