WackyToaster's Forum Posts

  • Alright final update for now. I've updated the project (same link)

    wackytoaster.at/parachute/ceilingSlopes.c3p

    I did add a velocity-based pushout that solves issue #2 mentioned above, but adds some other issues in that it sporadically does not work. So it's disabled in the project. It's kinda fun though.

    Issue #1 cannot be solved as easily as expected, the issue is not that the jump isn't executed by the behavior, but rather that the pushout actually pushes the player into the floor and thus the pushout fails. I tried changing the players height slightly during the pushout to compensate but that also did weird things. I feel like this should work though, maybe I made an error somewhere.

    Issue #3 is a bit more complex than expected, probably another can of worms that I don't wanna open at this moment. I had a solution that kind of worked but was framerate dependent, so... nah.

    For now I'm actually fine with all these 3 issues being present (for my project). It's not make or break for me. But of course, if someone happens to find a solution for them, let me know.

  • This is an unfortunate and kinda known issue of box2D (and other physics engines too). The only surefire way to prevent this is to make the collision a single, continous solid object (e.g. an invisible tiled background).

  • Ok so after checking the code again I did realize I was indeed doing all kinds of nonsense that somehow worked out lol. I revised the code, now using two raycasts and some checks and I got the jittering under control. Actually it works really well overall now, (almost) no jank and framerate independent. Thanks for the input r0j0

    Three issues remain that I think I can fix in due time.

    1. If the player jumps when wedged under a slope, the behavior will stop the player from executing the jump before my code runs. That should be a somewhat easy fix.

    2. The slopes allow the player to combine the behaviors vectorX with whatever vector the pushout creates, causing the player to exceed the usual maximum horizontal speed. Not 100% certain how I can counteract that yet. I'm thinking something like calculating the vectorX of the pushout and subtracting that from the vector of the behavior. Haven't tested that yet though.

    3. Rubbing against a slope stops the player rather than sliding along it. Not sure how yet but it should be reasonably easy to work around I think.

    Code for V2 to just replace all of player.js

    import * as Util from "./util.js";
    
    export class Player extends ISpriteInstance {
    	
    	#preTick = () => this.preTick();
    	#postTick = () => this.postTick();
    	
    	constructor() {
    		super();
    		runtime.addEventListener("pretick", this.#preTick);
    		runtime.addEventListener("tick2", this.#postTick);
    
    		this.storedY = 0;
    		this.didPushout = false;
    	}
    
    	preTick() {
    		// this ticks BEFORE behaviors
    		// TODO: Ideally I should have an input check here to see if a jump was executed because if the player is wedged under a sloped ceiling, the behavior will set vectorY to 0 and bonk the player before the slope handling kicks in
    		this.handleCeilingSlopes();
    	}
    
    	postTick() {
    		// this ticks AFTER behaviors
    		// if a pushout happened this tick, set the players vectorY to the stored vectorY. This is needed because the behavior will set the vectorY to 0 because it detected a ceiling
    		if(this.didPushout) this.behaviors.Platform.vectorY = this.storedY+this.behaviors.Platform.gravity*runtime.dt;
    	}
    
    	handleCeilingSlopes() {
    		this.didPushout = false;
    
    		// Player is not moving upwards, return
    		if (this.behaviors.Platform.vectorY >= 0) return;
    		
    		// Store current position and expected next position based on velocity
    		const old = {"x": this.x, "y": this.y};
    		const next = {"x": this.x+this.behaviors.Platform.vectorX*runtime.dt, "y": this.y+this.behaviors.Platform.vectorY*runtime.dt};
    		let maxPushoutDistance = 7; // maximum corner correction
    		const maxPushoutSlope = 1000*runtime.dt; // max slope correction
    		const minCeilingAngleTolerance = Math.PI*0.05; // maximum ceiling angle
    
    		// set position to next frame
    		this.setPosition(next.x, next.y);
    
    		let overlap = this.testOverlapSolid();
    		if(overlap) {
    			// store the behaviors current vectorY
    			this.storedY = this.behaviors.Platform.vectorY+this.behaviors.Platform.gravity*runtime.dt;
    			let testRight = false;
    			let testLeft = false;
    
    			// test push right
    			let i = 0;
    			let pushoutAngle = 0;
    			while(overlap && i < maxPushoutDistance) {
    				i++;
    				const bbox = this.getBoundingBox();
    				const ray = this.behaviors.LineOfSight.castRay(bbox.left, bbox.top+16, bbox.left, bbox.top-2); // angle defaults to 0 if no slope hit
    				let rAngle = Util.isWithinAngle(ray.normalAngle, Math.PI*0.5, minCeilingAngleTolerance) ? ray.normalAngle-Math.PI*0.5 : ray.normalAngle;
    				if(Math.cos(rAngle) < 0) rAngle = Math.PI*2;
    				if (ray.didCollide && !Util.isWithinAngle(ray.normalAngle, Math.PI*0.5, minCeilingAngleTolerance)) maxPushoutDistance = maxPushoutSlope;
    				this.x += Math.cos(rAngle);
    				this.y += Math.sin(rAngle);
    				overlap = this.testOverlapSolid();
    				pushoutAngle = rAngle;
    			}
    			testRight = {"success": false, "i": i, "x": this.x, "y": this.y, "angle": pushoutAngle};
    			if(!overlap) testRight.success = true;
    
    			// reset
    			this.setPosition(next.x, next.y);
    			overlap = true;
    			maxPushoutDistance = 7;
    			pushoutAngle = 0;
    
    			// test push left
    			i = 0;
    			while(overlap && i < maxPushoutDistance) {
    				i++;
    				const bbox = this.getBoundingBox();
    				const ray = this.behaviors.LineOfSight.castRay(bbox.right, bbox.top+16, bbox.right, bbox.top-2);
    				let rAngle = ray.didCollide ? ray.normalAngle : Math.PI; // angle defaults to Math.PI if no slope hit
    				rAngle = Util.isWithinAngle(rAngle, Math.PI*0.5, minCeilingAngleTolerance) ? rAngle+Math.PI*0.5 : rAngle;
    				if(Math.cos(rAngle) > 0) rAngle = Math.PI;
    				if (ray.didCollide && !Util.isWithinAngle(ray.normalAngle, Math.PI*0.5, minCeilingAngleTolerance)) maxPushoutDistance = maxPushoutSlope;
    				this.x += Math.cos(rAngle);
    				this.y += Math.sin(rAngle);
    				overlap = this.testOverlapSolid();
    				pushoutAngle = rAngle;
    			}
    			testLeft = {"success": false, "i": i, "x": this.x, "y": this.y, "angle": pushoutAngle};
    			if(!overlap) testLeft.success = true;
    
    			// result
    			if(!testLeft.success && !testRight.success) {
    				this.setPosition(old.x, old.y);
    			} else if(testLeft.success && testRight.success) {
    				// detect inner corner
    				if(Math.sign(Math.cos(testLeft.angle)) != Math.sign(Math.cos(testRight.angle))) {
    					this.didPushout = false;
    				} else {
    					this.didPushout = true;
    				}
    				if(testRight.i >= testLeft.i) {
    					this.setPosition(testLeft.x, testLeft.y);
    				} else {
    					this.setPosition(testRight.x, testRight.y);
    				}
    			} else if (testRight.success && !testLeft.success) {
    				this.setPosition(testRight.x, testRight.y);
    				this.didPushout = true;
    			} else if (!testRight.success && testLeft.success) {
    				this.setPosition(testLeft.x, testLeft.y);
    				this.didPushout = true;
    			}
    
    		} else {
    			// no pushout needed, just revert
    			this.setPosition(old.x, old.y);
    		}
    	}
    }
  • A more elaborate algorithm such as SAT (separating axis theorem) would give a more precise normal between polygons

    Yeah I read about SAT here and there but implementing it is a different story. The reason why I did it with a raycast was that it's a simple solution that I'm familiar with, even if not perfect. But I might have to go for "perfection" here because of the jank.

    After the ray cast and comparing the normal you push out of the solids with a loop. First you say you try to push out left then right. The push out right is not doing what you think it’s doing. You’re just moving at the opposite angle which is back into the solids.

    Uhm I do believe I have the logic correct here though. I first attempt to push out to the left, if that succeeds I keep the position and don't push out to the right anymore. And if pushing out to the left fails, I reset the position, then attempt to push out to the right. If that succeeds I keep the new position, otherwise I reset again (no pushout). Maybe not the cleanest code though but it's WIP :) I suppose a better version would be to do both attempts and see which direction needs the least pushout to work, then pick that one.

    I also see you’re using the reflection angle when doing the pushing. You possibly could try using the normal instead.

    I did try the normal angle which zooms the player along the slopes. I think I ended up using the reflection angle because it conveniently simulates a kind of friction and slows the player down more if the slop is less steep. Otherwise the player would zoom along the slope at crazy speeds.

    Nvm I was silly and my brain kind of didn't compute that the reflection angle is not what I was actually looking for. I was indeed looking for the normal angle.

    My basis for this was this tutorial for corner correction and I just kinda tacked on from there.

    youtube.com/watch

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I'm currently in the process of "modding" the platformer behavior (without hacks) to handle slopes on the ceiling in a neat way. I do believe I'm really close, but there's some jank left. So I'm posting my code here in hopes some wizard can do magic and of course in return everyone is free to use this code.

    wackytoaster.at/parachute/ceilingSlopes.c3p

    There's some jank when jumping into a place where two ceilings sort of meet. Just try around in the project you'll surely encounter it. The player then jitters around and it doesn't look too nice.

    I probably ideally also add another function to handle the slopes when falling, because when you rub the wall when falling, the player seems to have their vectorX set to 0 by the behavior because it sees a "wall"

    In any case, I hope someone can help me make this as good as possible.

  • I'd argue the easiest way would be to use javascript since javascript doesn't care about what instance types you throw at it. I'd recommend doing it this way too because the "sort z order" action is specifically designed for this purpose.

    Sort the Z order of instances of a given object according to an instance variable. This effectively removes all instances from their Z order, sorts them, then inserts the sorted sequence back in to the holes left behind. This means if the instances are spread across a range of layers, they will be sorted in the same Z order locations, maintaining the same order relative to other instances on those layers. Note this action is much faster than using an ordered For each with an action like Send to front/back, so this action should be the preferred way to sort the Z order of large numbers of instances.

    And the code part is relatively easy to understand.

    const instances = [...runtime.objects.Sprite.getAllInstances(), ...runtime.objects.Fence.getAllInstances(), ...runtime.objects.[name of object or family].getAllInstances()];
    runtime.sortZOrder(instances, (a, b) => a.y - b.y);
    

    Now all you have to do is add all the objects you intend to sort by their Y position in the layout.

  • I've been encountering this issue sporadically for a while but never have been able to reliably reproduce it. I also have encountered the issue where image points I set to specific positions don't save the new position and revert to the old position as soon as I close the frame I edited (so even when I keep the image editor open and just go to the next frame)

    I'm always cruising on the beta branch.

  • You do not have permission to view this post

  • Here's a simple method to do this.

    wackytoaster.at/parachute/tokenAlongPath.c3p

    You could also use an array to define your path but since you're a new user I think this approach is just easier.

  • Trust me I've spend multiple hours to pick out the right song. :D It's not easy. I've also worked with some music artists before, also not easy (and more expensive)

  • igortyhon Played it a little bit. Works decent, couldn't find any bugs. I think it could use some polishing in general, but especially the sound loop of the tank driving. Because it doesn't loop properly and it's one of the main sounds you hear. Also, I think you could add some music. You'll probably make at least a few bucks on crazygames, so invest that in some music. ;) You can buy on audiojungle or whereever, it's not super expensive that way.

  • Thanks for explaining ^^ I suppose it makes sense in a way.

  • DiegoM Actually the 0-1 range is only for when using it in the plugin SDK. If you use the expression in events it's 0-100. According to construct.net/en/make-games/manuals/construct-3/system-reference/system-expressions

    But I still don't understand the purpose of rgba() and rgbEx() with the values from 0-100. Not a single program I've ever used in my entire life did use 0-100 for setting RGB-colors. Except Construct. I guess it's easier to set something to 30% red, but that's such a specific thing I don't think I've ever needed that. Anyway, I'm glad there's still the variants that just take 0-255 values.

  • It was fixed for the mouse plugin, but it seems that the same fix is needed for the touch plugin. You can open an issue in the issue tracker, attach your test project for demonstration and perhaps also link to the old issue for reference.

  • It looks more specifically like this issue.

    github.com/Scirra/Construct-bugs/issues/8027

    It works as expected with the mouse plugin, but not with touch. I'd assume this is essentially the same bug.