hexus / phaser-arcade-slopes Goto Github PK
View Code? Open in Web Editor NEW:triangular_ruler: A Phaser CE plugin that brings sloped tile collision handling to the Arcade Physics engine
License: MIT License
:triangular_ruler: A Phaser CE plugin that brings sloped tile collision handling to the Arcade Physics engine
License: MIT License
Lots of calculations lazily use .clone()
and new
.
Improve by using arrays for pooling.
As per phaserjs/phaser-ce#94 and the road map, improvements can be made to tunnelling issues that can occur with small sprites.
In the linked issue, it looks like the projectiles are only colliding with the tile face and not the entire tile.
The linked issue is caused by heuristics preventing collisions, and because the square tile has no "preferred axis" to separate on, no separation seems to occur. This is rather wrong! Pre v0.3.0, this could be solved by improving heuristics or simply not using them for square tiles.
In the case of the half tiles in the GIF below, the separation is responding correctly but without invoking collision callback, which is wrong.
There are two issues here that will be addressed by planned features in the v0.3.0 roadmap:
For 0.2.0 we want to at least make sure that the collisions and their callbacks are responding correctly; even if no separation occurs, it's still a collision the user could need to know about.
Deciding the response vector in these situations is tricky.
Really great plugin just got one issue.
Every time the player is near a wall (FULL) and i press to the direction of the wall it do wall jump. Is any way to prevent this?
Thanks
Create a wiki page that lists all the tile slope types and how to create your own mappings for custom tile sets.
Having to work out the numbers for map.setCollisionBetween()
is a bit of a pain, so it would be great if the plugin could set this on the map objects itself. It makes the API even friendlier.
I've also noticed that internal edges on tile objects (collideUp
, etc) don't get flagged correctly if map.setCollisionBetween()
is called after creating layers. This should be looked into.
When a tilemap layer's fixedToCamera
property is false, Arcade Physics works with offset tile maps, and everything looks okay as long as the camera isn't moved.
Support this in Arcade Slopes by passing through the tilemap layer to collision solvers.
A note on Phaser's tilemap layers:
Phaser's TilemapLayer implementation does thoroughly contradicts the fixedToCamera
property with lines like these in its postUpdate()
and _render*
methods:
if (this.fixedToCamera)
{
this.position.x = (this.game.camera.view.x + this.cameraOffset.x) / this.game.camera.scale.x;
this.position.y = (this.game.camera.view.y + this.cameraOffset.y) / this.game.camera.scale.y;
}
this._scrollX = this.game.camera.view.x * this.scrollFactorX / this.scale.x;
this._scrollY = this.game.camera.view.y * this.scrollFactorY / this.scale.y;
So no matter where the TilemapLayer sprite is positioned, it will always scroll tiles with the camera. There is no way to override this without changing these methods.
This can be fixed by introducing a separation between the TilemapLayer's sprite position and its scrolling position, which will also help to fix mismatched collision as soon as the camera moves.
Although there's a .d.ts file, it's not clear how the arcade-slopes API can be used in Typescript. Can you please provide sample code to enable and use the Slopes #plugin?
Somehow I've only just realised that the mapping for 64px+ ninja physics debug tilesets are different from the 32px layout!
Define a ninja2
or ninja64
mapping.
Hi Hexus,
I'm really enjoying you're slopes plugin, its exactly what I needed for my game.
I'm trying to implement jumping through a platform from underneath. I believe normally for a tilemap you can just set the parameter collideDown
to false
for each tile. Since your plugin uses tilemaps, I've tried doing this but still get collisions from below so I can't jump through. Is there a specific way to turn off down collisions for your slopes plugin or am I doing something wrong? Thanks!
Hello,
planned a update to phaser 3?
I'd like to suggest the introduction of additional curved slopes like the ones below:
I understand that collision bodies for the sloped tiles are generated from polygons which are made of straight edges, but I think with some interpolation, curved slopes can also be generated programmatically.
The formula for the curved part of the 4 tiles above are:
where c(x) = x^2 / 4w
and w is the width of the tile (e.g. 16, 32, 64, 128 etc.)
Note: I'm assuming that the Y axis points downwards, as per normal display convention.
When generating the curved slope, x would be sweeped from 0 to w by a step of 1 and y would be computed accordingly. For practical purposes, x can be incremented by a larger step, e.g. by w / 4 to reduce the number of sides.
An additional point for tile 1 and 4, or two points for 2 and 3, would need to be generated for the lower corner(s).
I can give you the formulas for the 4 tiles going down right, but as you might imagine, they are simple variants of tiles 1 to 4.
Notice how 2 and 3 have 45 degree slopes at one end, which makes it possible to combine them with straight 45 degree slopes.
Flatter versions (1/2 height) can be generated for transitioning on to straight 22.5 degree slopes as well, since their edge would end at halfway up the second tile with a tangent corresponding to the 22.5 slopes, so the second 22.5 straight slope can be placed next to it for a smooth transition.
These could be also extended to curved ceiling tiles, but they're not really that useful.
I could try extending your library myself, but I'm not sure I understand the rest of your code!
What are your thoughts on this?
The slopes variable added to the Body class is a dynamic object. These are unusable in typescript as they will throw errors when trying to access their member variables(unless you use ['array'] access). This commit I created some classes in the typescript definition so that the variables are properly accessible: IkonOne@df06dc6
The added classes are BodySlopes
and BodySlopesSat
. I think that an approach like this is ok for now. If you agree with this approach, I can convert the objects in the JS code to classes as well as you mentioned the possibility of here: https://github.com/hexus/phaser-arcade-slopes/blob/master/src/ArcadeSlopes/Facade.js#L87
The two classes needed could possibly go in a new file called BodyMods.js or something.
Also, since the sat
variable is only used to store the responses, maybe simply having a collision
or response
variable might be a better fit.
Is there a way to make sure that the sprite that is colliding with the slopes rotates in the direction to where the slope is?
Scenario: Sprite is a roller coaster cart. As it goes up the hill i want the cart to stay attached to the ground so that sprite needs to rotate to match the slope. Same for going downhill.
Am I provided with information in my collision callback that might help me achieve the correct rotation?
Tiles disappear, seems like an initial window size is set and on resize it doesn't get updated.
Implement the rendering of debug geometry for:
The crossed out tasks will be saved for a later version.
tile.properties.type
)UPDATE
See down the conversation, the collide behavior looks like it's correct but you can't use overlap yet.
#7 (comment)
OLD
The bullets hit the 50% slope and slide up.
The bullets go straight through and if you're shooting through a 10x10 the bullet will enter one side and collide with the opposite side.
This is how I create 50 reusable bullets.
this.bullets = this.game.add.group()
this.bullets.createMultiple(50, 'bullet')
this.bullets.setAll('checkWorldBounds', true)
this.bullets.setAll('outOfBoundsKill', true)
this.physics.arcade.enable(this.bullets)
this.game.slopes.enable(this.bullets)
This is how I fire a bullet.
let bullet = this.rootScope.bullets.getFirstDead()
bullet.bulletId = Guid()
bullet.damage = this.damage
bullet.weaponId = this.meta.id
bullet.height = this.bulletHeight
bullet.width = this.bulletWidth
bullet.alpha = 0
bullet.body.gravity.y = -1800
// Add a touch of tile padding for the collision detection
bullet.body.tilePadding.x = 50
bullet.body.tilePadding.y = 1
bullet.reset(x, y)
let pointerAngle = this.rootScope.game.physics.arcade.moveToPointer(bullet, this.bulletSpeed)
bullet.rotation = pointerAngle
setTimeout(() => {
bullet.alpha = this.bulletAlpha !== undefined ? this.bulletAlpha : 1
}, 40)
This is how I collide bullets with the slope ground.
this.physics.arcade.collide(this.bullets, this.ground, function(bullet) {
bullet.kill()
}, null, this)
Normally I use overlap though.
this.physics.arcade.overlap(this.bullets, this.ground, function(bullet) {
bullet.kill()
}, null, this)
but this has been happening.
Create some original tilesets at different sizes for developers to use, along with a mapping preset (#9).
I'm trying to set up a basic Phaser CE project with the arcade-slopes plugin. I have a Phaser 3 project where I'm handling sloped platforms with MatterJS, but Arcade has some methods that would be very useful for this project, so I want to compare slope-handling in CE with the plugin to slope-handling in Phaser 3 with Matter.
I'm running the bare-bones Webpack template from the Phaser CE repo and I'm running the project with node through VS Code. I used the provided arcadeslopes tileset and followed the instructions on the main repo page for the plugin for setting up.
I have run into these two issues, and I'm not sure if it's a bug or just me making novice mistakes, LOL. (I've been learning Phaser3 for about half a year, and this is my first time using CE.)
Here are screenshots of the dev tools console, and you can view the complete source code at this Drive folder.
With plugin-related lines of code commented out
With no collider
With collider
Could you please enlighten me as to what's going wrong here?
Implement corner settings for collision pulling.
For example, bottomLeft
would pull in the case of these collision vectors (facing in): overlapN.x < 0 && overlapN.y > 0
.
This allows, in the example of a platformer with gravity, to pull a body into slopes but not on flat surfaces below (i.e. pull.bottomLeft
, pull.bottomRight
, but not pull.down
).
Ideally for 0.1.1, but 0.2.0 would be fine.
Recent versions of Phaser have seen an implementation circular physics bodies for collision with other sprites, but not tilemaps.
SAT.js can resolve circle vs. polygon collisions, so this seems like a fitting new feature for the plugin.
For whatever reason I fly up and gravity appears to bring me down through the ground.
It's hard to reproduce but tends to happen repeatedly when I fall very quickly.
http://www.youtube.com/watch?v=F_SRvUVgpWo
I tried adding the below code but to no avail:
// We need to enable physics on the player
this.physics.arcade.enable(this.player)
this.player.body.setSize(GameConsts.PLAYER_BODY_WIDTH, GameConsts.PLAYER_BODY_HEIGHT, 105, 10)
this.game.slopes.enable(this.player)
// Add a touch of tile padding for the collision detection
this.player.body.tilePadding.x = 10
this.player.body.tilePadding.y = 10
The heuristics are pretty painful now, when it comes to maintainability and reliability. It's time to use SAT.js more wisely.
This is part of the 0.3.0 roadmap, and the item looks like this:
The discussion here proposes ignoring collisions that occur with internal edges, which is a nice solution, and in fact what the heuristics do already.
After some experimentation in the ignormals branch (hah) at the same time as tinkering with SAT.js, this turns out to work exactly the same way, except it resurfaces the issue with skipped collisions for small and fast projects between tile seams (#38).
The direction I want to take this is using SAT.js to first determine if there is any collision, and instead of using the shortest separation that it gives back, use the shortest separation that isn't an ignored axis, which are indeed now flagged as part of each tile's polygon edge normals.
I tried to do this by just skipping flagged normals in SAT.js to let it fall back to the smallest non-ignored normal, but the results were identical, which means either:
This can probably be achieved outside of SAT.js with a custom testPolygonPolygon()
function.
Note: Question / answer session transcribed on to an issue to help out others with a similar problem.
============================================
Hi,
What parameters do I need to set to prevent the player sprite from sliding down a slope. In my code, even the slightest of slops causes the player to slide down to flat ground. In your demo code, the player sprite slides of a bit but eventually stops, even on 45 degree slopes. I have tried to use the same physics params as per your demo but I still get the slipping effect.
What should I do to prevent slipping? There may be a answer but I'm new to using Phaser and my last game programming stint was with XNA.
Thanks & Regards
Colin
============================================
Hey Colin,
The solution in the demo is to use X-axis drag on the body, as well as the "minimum Y axis" feature that's mentioned in here in the readme.
This feature separates collision bodies on the Y axis only where appropriate, so that the sliding does not occur.
The sliding is actually caused by the separating axis theorem, which looks for the shortest possible way for one shape to no longer intersect another. I looked into a solution for this for early versions of the plugin and settled on this "minimum Y axis" approach that I found somewhere buried in a forum.
A second solution that I've been using in a game I'm developing is to simply turn off gravity for the player body whenever it is touching anything beneath itself (player.body.touching.down).
I hope this helps, please feel free to get in touch if you have any more trouble with this. It pestered me for a while and it was important, for me, that the plugin could at least attempt to help with this problem.
Cheers,
Chris
============================================
Hi Chris,
Thanks for your response. I had already set the preferY for SAT testing and also had body drag set as per the demo's setting. What eventually helped was setting the friction coefficients to 0.2 on the sprite's body. The 0.2 setting was just right to have the player slow down to a stop on 0.5 tangent slopes but still slide down gradually on 1.0 tangent (45 degree) slopes.
I also had a problem with the player loosing momentum when walking on the slopes, but I solved this using high acceleration values (3000) coupled with a relatively ow max velocity (250) on the horizontal axis. This resulted in a responsive sprite that can climb slopes steadily with only a slight slowdown.
Once again, thanks a lot! You've been very helpful.
Cheers! :)
Colin
============================================
Hey Colin,
I'm glad to hear it! That's very similar to what I ended up doing in the demo too, tweaking values until I found the behaviour I needed and landing around there.
Balancing friction, drag, acceleration and max velocity against the gravity applied to the character is the way to go for quite a natural look and feel.
I'm still looking into the best ways for the plugin to provide no struggle when ascending slopes, or explicitly controlling said struggle, but for my current project I've simply been adjusting gravity with quite some success. And, to be fair, such collisions could end up very game specific, so for now I've left it to the user until I can think up something magical (if ever).
Cheers,
Chris
Happens on https://rangersteve.io when shooting. Maybe I should be specifying special slope properties just for bullets?
How I'm shooting the bullets
let bullet = this.bullets.getFirstDead()
bullet.body.gravity.y = -GameConsts.BULLET_GRAVITY // 850
bullet.height = 2
bullet.width = 40
bullet.reset(x, y)
let pointerAngle = this.game.physics.arcade.moveToPointer(bullet, currentWeapon.bulletSpeed) // currentWeapon.bulletSpeed === 2300
bullet.rotation = pointerAngle
How I'm creating the bullets
this.bullets = this.game.add.group()
this.bullets.createMultiple(30, 'bullet')
this.bullets.setAll('checkWorldBounds', true)
this.bullets.setAll('outOfBoundsKill', true)
this.physics.arcade.enable(this.bullets)
this.game.slopes.enable(this.bullets)
this.bullets.forEach(function(bullet) {
bullet.body.height = 15
bullet.body.width = 15
bullet.height = 2
bullet.width = 40
// Define some shortcuts to some useful objects
var body = bullet.body
// Update player body properties
body.drag.x = GameConsts.SLOPE_FEATURES.dragX
body.drag.y = GameConsts.SLOPE_FEATURES.dragY
body.bounce.x = GameConsts.SLOPE_FEATURES.bounceX
body.bounce.y = GameConsts.SLOPE_FEATURES.bounceY
// Update player body Arcade Slopes properties
body.slopes.friction.x = GameConsts.SLOPE_FEATURES.frictionX
body.slopes.friction.y = GameConsts.SLOPE_FEATURES.frictionY
body.slopes.preferY = GameConsts.SLOPE_FEATURES.minimumOffsetY
body.slopes.pullUp = GameConsts.SLOPE_FEATURES.pullUp
body.slopes.pullDown = GameConsts.SLOPE_FEATURES.pullDown
body.slopes.pullLeft = GameConsts.SLOPE_FEATURES.pullLeft
body.slopes.pullRight = GameConsts.SLOPE_FEATURES.pullRight
body.slopes.snapUp = GameConsts.SLOPE_FEATURES.snapUp
body.slopes.snapDown = GameConsts.SLOPE_FEATURES.snapDown
body.slopes.snapLeft = GameConsts.SLOPE_FEATURES.snapLeft
body.slopes.snapRight = GameConsts.SLOPE_FEATURES.snapRight
}, this)
GameConsts just in case
// Physics
MAX_VELOCITY_X: 500,
MAX_VELOCITY_Y: 1000, // Max velocity before player starts going through the ground.
BULLET_GRAVITY: 850,
// Slope Plugin
SLOPE_FEATURES: {
acceleration: 1500,
bounceX: 0,
bounceY: 0,
debug: 0,
dragX: 1000,
dragY: 100,
enableGravity: true,
frictionX: 0,
frictionY: 0,
gravity: 1100,
jump: 450,
minimumOffsetY: 1,
pullDown: 0,
pullLeft: 0,
pullRight: 0,
pullUp: 0,
snapDown: 0,
snapLeft: 0,
snapRight: 0,
snapUp: 0
},
Video example of https://rangersteve.io
https://streamable.com/koh2
Just a consts file
// Slope Plugin
SLOPE_FEATURES: {
acceleration: 1500,
bounceX: 0,
bounceY: 0,
debug: 0,
dragX: 1300,
dragY: 0,
enableGravity: true,
frictionX: 0,
frictionY: 0,
gravity: 1100,
jump: 400,
minimumOffsetY: 1,
pullDown: 0,
pullLeft: 0,
pullRight: 0,
pullUp: 0,
snapDown: 0,
snapLeft: 0,
snapRight: 0,
snapUp: 0
},
Where I set the player's slope very similarly to how it's done in the demo.
const body = this.player.body
this.physics.arcade.gravity.y = GameConsts.SLOPE_FEATURES.gravity
// Add a touch of tile padding for the collision detection
body.tilePadding.x = 1
body.tilePadding.y = 1
// Set player minimum and maximum movement speed
body.maxVelocity.x = GameConsts.MAX_VELOCITY_X
body.maxVelocity.y = GameConsts.MAX_VELOCITY_Y
// Add drag to the player that slows them down when they are not accelerating
body.drag.x = GameConsts.SLOPE_FEATURES.dragX
body.drag.y = GameConsts.SLOPE_FEATURES.dragY
// Update player body Arcade Slopes properties
body.slopes.friction.x = GameConsts.SLOPE_FEATURES.frictionX
body.slopes.friction.y = GameConsts.SLOPE_FEATURES.frictionY
body.slopes.preferY = GameConsts.SLOPE_FEATURES.minimumOffsetY
body.slopes.pullUp = GameConsts.SLOPE_FEATURES.pullUp
body.slopes.pullDown = GameConsts.SLOPE_FEATURES.pullDown
body.slopes.pullLeft = GameConsts.SLOPE_FEATURES.pullLeft
body.slopes.pullRight = GameConsts.SLOPE_FEATURES.pullRight
body.slopes.snapUp = GameConsts.SLOPE_FEATURES.snapUp
body.slopes.snapDown = GameConsts.SLOPE_FEATURES.snapDown
body.slopes.snapLeft = GameConsts.SLOPE_FEATURES.snapLeft
body.slopes.snapRight = GameConsts.SLOPE_FEATURES.snapRight
I'm not sure what I did wrong here, but my player sprite falls straight through the floor. When I enable debugging, it appears the bodies are only being created for some tiles and not others.
I've uploaded the code to https://github.com/dave-kennedy/Platformer.
The Typescript definitions are out of date after the new features have been added.
@IkonOne, would you be interested in making a PR for this?
I can totally tackle it myself but I have no dev environment for Typescript (never used it), so I couldn't verify their correctness.
It's caused trouble for users in the past and hasn't proven particularly useful anyway.
To be removed for 0.3.0.
Can you please add the tmx file to the demo repo?
Bower is used to pull in SAT.js only. Use npm. โ
Currently, tiles have each bounding box edge flagged if they are internal, but no such check is done for the vectors that the actual polygon comprises of.
This would be useful for restricting testable edges when raycasting, especially with ray bouncing.
There's an issue with heuristics that skip collisions between tiles.
Small enough objects can slip between tiles, as seen here:
Reproduce and fix the hell out of it without touching SAT.js for the time being.
This is related to #36. Disabling heuristics fixes the issue so that's likely where the it lies.
Here's a minimal example that reproduces the behavior. Screen-recording the jitter in action didn't work out, but here's two screenshots showing two instances of the tile junctions where it happens. What could be causing this?
I'm currently using the Arcade Slopes tileset (32px) provided in this repository. However, the tileset is showing some bizarre behavior; in particular, the first tile (full-block) is inconsistent in being collide-able.
Below is an example in Tiled of using the tileset in a map:
However, when seen in the actual game, the selection of full-blocks on the top-right is not registered for collisions at all. Also, while the half-blocks are registered for collision when used in the house, it does not register for collision when placed next to a full-block (as seen in the stack of logs next to the house.)
However, the tiles on the stack of logs WILL register for collision if there are other tiles directly adjacent to it. Below is an example in Tiled where I place more vertical half-blocks near the logs:
In this case, when seen in the game, the collisions work as intended (although the tiles in the top-right are still not registered):
I'm not sure if this is a bug, or if there is a specific criteria that needs to be required for full-blocks (and other blocks) to be registered for collision. Below is the relevant code for your reference. Here's the actual link to the repository if more information is needed. I'm also using Phaser CE (v2.8.4).
Game State:
preload() {
this.game.load.image('sprite', 'assets/sprites/sprite.png');
this.game.load.spritesheet('arcade-slopes-32', 'assets/map/arcade-slopes-32.png', 32, 32);
this.game.load.tilemap('map', 'assets/map/example_map.json', null, Phaser.Tilemap.TILED_JSON);
this.game.load.spritesheet('tileset', 'assets/map/tilesheet.png', 32, 32);
}
create() {
...
const map = this.game.add.tilemap('map');
map.addTilesetImage('tilesheet', 'tileset');
map.addTilesetImage('collisions', 'arcade-slopes-32');
for (let i = 0; i < map.layers.length; i++) {
if (map.layers[i].name !== 'collisions') {
map.createLayer(i);
}
}
this.collisionLayer = map.createLayer('collisions');
map.setCollisionBetween(0, 34, true, this.collisionsLayer);
this.game.slopes.convertTilemapLayer(this.collisionLayer, 'arcadeslopes');
this.collisionLayer.debug = true;
}
update() {
...
if (player && player.sprite) {
this.game.physics.arcade.collide(player.sprite, this.collisionLayer, () => {
console.log("Hey");
});
}
}
}
It seems like the wasTouching flags don't propagate between updates correctly. They're always false
.
Investigate and fix this.
I've tried to shim phaser-arcade-slopes in my game. I keep getting a:
SAT is not defined
A more full description is found here: http://www.richardhealy.co.uk/blog/phaser-issue-help
And code is found at:
Arcade Physics bodies update their bounds automatically.
Do the same for Arcade Slopes polygons.
See Phaser.Physics.Arcade.Body.updateBounds()
. This could be the right internal method to override or extend.
Update the the demo page to use Bootstrap and datgui.
Add some consistent navigation between the demo and the tileset generator.
I noticed npm package not update for 3 months.
Hope it will update with new release. Thanks~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.