I have a top down game and I need the player to be rendered below the bottom walls, and above the top walls, and my solution would be to set the depth of individual tiles in the tilemap by the tile ID. I can't use anything involving tiled, because my game will be procedurally generated. Maybe something similar to this:
this.wallLayer.forEachTile((tile)=>{
if(tile.id === 1){
tile.setDepth(1)
}
if(tile.id === 2){
tile.setDepth(3)
}
})
if there is a similar method I could use, it would be great if someone could point me in the right direction. Thanks!
I'm not sure if this is the best solution, but i would create two maps and place one over the other.
first map + layer is incharge for collision and everything the player should cover. Set a lower depth than the player.
second map + layer is incharge to be infront of the layer.
Using the function replaceByIndex, to remove indices that should not covered the player. (link to documentation) And setting a higher depth than the player
Here a short Demo showcasing this:
(use cursor-keys to move the player)
Red-Tiles should cover the player, blue tiles should be coverd by the player.
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536 /8,
height: 34,
zoom: 6,
physics: {
default: 'arcade',
arcade: {
gravity:{ y: 0 },
debug: false
}
},
scene: {
create,
update
},
banner: false
};
function create () {
let graphics = this.make.graphics();
graphics.fillStyle(0x00ff00);
graphics.fillRect(8, 0, 8, 8);
graphics.fillStyle(0xff0000);
graphics.fillRect(16, 0, 8, 8);
graphics.fillStyle(0x0000ff);
graphics.fillRect(24, 0, 8, 8);
graphics.generateTexture('tiles', 8*4, 8);
var level = [
[1,1,1,1,1],
[1,0,3,0,1],
[1,0,0,0,1],
[1,2,2,2,1],
]
// Map for the first level
var map = this.make.tilemap({ data: level, tileWidth: 8, tileHeight: 8 });
var tiles = map.addTilesetImage('tiles');
var layer = map.createLayer(0, tiles, 0, 0);
layer.setCollision(1);
var map2 = this.make.tilemap({ data: level, tileWidth: 8, tileHeight: 8 });
var layer2 = map2.createLayer(0, tiles, 0, 0);
layer2.setDepth(10);
//remove tile that should not overlap
map2.replaceByIndex(3, -1);
this.player = this.add.circle(16,16,4, 0xffffff)
.setDepth(2);
this.physics.add.existing(this.player);
// Just setup collision with the first layer
this.physics.add.collider(layer, this.player);
this.keys = this.input.keyboard.createCursorKeys();
}
function update(){
let speed = 20;
this.player.body.setVelocity(0)
if(this.keys.up.isDown){
this.player.body.setVelocityY(-speed)
}
if(this.keys.down.isDown){
this.player.body.setVelocityY(speed)
}
if(this.keys.left.isDown){
this.player.body.setVelocityX(-speed)
}
if(this.keys.right.isDown){
this.player.body.setVelocityX(speed)
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
You might want to consider using some sort of map of tile IDs to their depths.
const tileDepthMap = {
1: 1,
2: 3,
};
or even as an array since your IDs are numerical:
const tileDepthMap = [0 /* or whatever depth for ID 0 */, 1, 3];
Then you'll be able to get the depth for an ID simply by indexing into it:
tileDepthMap[tile.id]
and you can also specify a default like this:
tileDepthMap[tile.id] ?? 0 // Default to depth of 0 if not found
Related
So I have been using the Chebyshev Distance example from the Phaser labs, and while this example was using one layer, I happen to be using two, and when i set transparency on them, the colors start leaking into each other, especially on light colors.
Is there any way to circumvent or get rid of this effect
If the problem is that you have two layers, one ontop of the other and you are making both transparent (or only the top one), and you don't want that color to pass through, the solution could be to hide the tiles on the bottom layer.
Just check in the map-tile-loop, if the tile, where you want to change the alpha, has a tile beneath it, and if so make that background tile transparent.
Here a small working demo:
(The main magic is in the updateMap function)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
preload,
create
}
};
var player;
var bgLayer;
var point1 = {x: 250, y: 31};
var isLeaking = false;
new Phaser.Game(config);
function preload (){
this.load.image('tiles', 'https://labs.phaser.io/assets/tilemaps/tiles/catastrophi_tiles_16.png');
this.load.tilemapCSV('map', 'https://labs.phaser.io/assets/tilemaps/csv/catastrophi_level2.csv');
}
function create () {
this.add.text(50, 1, ' <- Background is visible, if no tiles are ontop')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
this.infoText = this.add.text(10, 20, 'Click to toggle leaking: on')
.setOrigin(0)
.setDepth(100)
.setStyle({fontFamily: 'Arial'});
// Just creating image for second layer tiles //
let graphics = this.make.graphics();
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 16, 16);
graphics.generateTexture('tiles2', 16, 16);
// Just creating image for second layer tiles //
let map = this.make.tilemap({ key: 'map', tileWidth: 16, tileHeight: 16 });
let tileset = map.addTilesetImage('tiles');
let tileset2 = map.addTilesetImage('tiles2');
bgLayer = map.createBlankLayer('background', tileset2);
bgLayer.fill(0);
let fgLayer = map.createLayer(0, tileset, 0, 0);
// Just to show that the Background is still show if not Tile is covering
fgLayer.removeTileAt(0, 0);
fgLayer.removeTileAt(1, 0);
fgLayer.removeTileAt(2, 0);
player = this.add.rectangle(point1.x, point1.y, 5, 5, 0xffffff, .5)
.setOrigin(.5);
this.input.on('pointerdown', () => {
isLeaking = !isLeaking;
this.infoText.setText( `Click to toggle leaking: ${isLeaking?'off':'on'}` )
updateMap(map);
});
updateMap(map);
}
function updateMap (map) {
let originPoint1 = map.getTileAtWorldXY(point1.x, point1.y);
console.info(map.layers.sort((a,b) => b.depth - a.depth))
map.forEachTile(function (tile) {
var dist = Phaser.Math.Distance.Chebyshev(
originPoint1.x,
originPoint1.y,
tile.x,
tile.y
);
let bgTile = bgLayer.getTileAt(tile.x, tile.y, false)
let hideOnlyTheseTiles = [ 0, 1, 2, 3, 4]; // Indexes to hide
if( !isLeaking ){
if(hideOnlyTheseTiles.indexOf(bgTile.index) > -1){ // here yopu can select the
bgTile.setAlpha(0);
}
} else{
bgTile.setAlpha(1);
}
tile.setAlpha(1 - 0.09 * dist);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I've been working on a project found here -> https://paperambi.glitch.me/
as seen in console, all my tiles are recorded null, and therefore i cant get their index property, what is wrong with my tiles, and is there anyway to fix it?
Heres an example, when you click on tiles to get properties, its null
and if you add index, it responds with cannot find null of index.
Am I using the wrong function, or have something wrong with my tiles?
code sandbox -> https://codesandbox.io/s/31xpvv85om?hidenavigation=1&module=/js/index.js&moduleview=1&file=/js/index.js:0-3327
all code for the example
/**
* Author: Michael Hadley, mikewesthad.com
* Asset Credits:
* - Tileset by 0x72 under CC-0, https://0x72.itch.io/16x16-industrial-tileset
*/
import Phaser from "phaser";
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: "game-container",
backgroundColor: "#1d212d",
pixelArt: true,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
let controls;
let marker;
let shiftKey;
let groundLayer;
function preload() {
this.load.image(
"tiles",
"../assets/tilesets/0x72-industrial-tileset-32px-extruded.png"
);
this.load.tilemapTiledJSON(
"map",
"../assets/tilemaps/platformer-simple.json"
);
}
function create() {
const map = this.make.tilemap({ key: "map" });
const tiles = map.addTilesetImage(
"0x72-industrial-tileset-32px-extruded",
"tiles"
);
// Same setup as static layers
map.createLayer("Background", tiles);
groundLayer = map.createLayer("Ground", tiles);
map.createLayer("Foreground", tiles);
shiftKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT);
// Set up the arrows to control the camera
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
speed: 0.5
};
controls = new Phaser.Cameras.Controls.FixedKeyControl(controlConfig);
// Limit the camera to the map size
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
// Create a simple graphic that can be used to show which tile the mouse is over
marker = this.add.graphics();
marker.lineStyle(5, 0xffffff, 1);
marker.strokeRect(0, 0, map.tileWidth, map.tileHeight);
marker.lineStyle(3, 0xff4f78, 1);
marker.strokeRect(0, 0, map.tileWidth, map.tileHeight);
// Help text that has a "fixed" position on the screen
this.add
.text(
16,
16,
"Arrow keys to scroll\nLeft-click to draw tiles\nShift + left-click to erase",
{
font: "18px monospace",
fill: "#000000",
padding: { x: 20, y: 10 },
backgroundColor: "#ffffff"
}
)
.setScrollFactor(0);
}
function update(time, delta) {
controls.update(delta);
// Convert the mouse position to world position within the camera
const worldPoint = this.input.activePointer.positionToCamera(
this.cameras.main
);
// Place the marker in world space, but snap it to the tile grid. If we convert world -> tile and
// then tile -> world, we end up with the position of the tile under the pointer
const pointerTileXY = groundLayer.worldToTileXY(worldPoint.x, worldPoint.y);
const snappedWorldPoint = groundLayer.tileToWorldXY(
pointerTileXY.x,
pointerTileXY.y
);
marker.setPosition(snappedWorldPoint.x, snappedWorldPoint.y);
// Draw or erase tiles (only within the groundLayer)
if (this.input.manager.activePointer.isDown) {
if (shiftKey.isDown) {
groundLayer.removeTileAtWorldXY(worldPoint.x, worldPoint.y);
} else {
groundLayer.putTileAtWorldXY(353, worldPoint.x, worldPoint.y);
var tile = groundLayer.getTileAt(
worldPoint.x,
worldPoint.y
); //.index;
console.log(tile);
}
}
}
The problem is the function you are using getTileAt (link to the documentation), with world coordinates, there are two easy solutions:
you could use your variable pointerTileXY like:
var tile = groundLayer.getTileAt(
pointerTileXY.x,
pointerTileXY.y
);
Or you could use the function getTileAtWorldXY (link to the documentation)
var tile = groundLayer.getTileAtWorldXY(
worldPoint.x,
worldPoint.y
);
I'm working on a game in Phaser 3, and need to be able to change the collision width and height of the wall tiles to something other than the width of the images, but I can't find anything that doesn't involve Tiled, which I can't use as it's a procedurally generated game.
I found a method to change the size of a tile, and I know how to get and individual tile, but nothing to change the collision size, and the few leads I found involved the differenced between the deprecated createDynamicLayer and createStaticLayer methods. The physics property of the tile object is empty, and doesn't contain the physics body of the tile, even though I set up collision between the wall tiles and the player (arcade physics). Any suggestions? thanks!
If you don't want to use the greate application "Tiled", the easiest option would be to set the tiles that should have a partial collision to not collide, and than iterate over the Map tiles and place invisible static physics bodies ontop.
It is maybe not very elegant, but it works well, and if you don't have > 1000 partial tiles on Screen, this should not be a performance issue.
Here is a Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536 /8,
height: 42,
zoom: 4.5,
physics: {
default: 'arcade',
arcade: {
gravity:{ y: 0 },
debug: false
}
},
scene: {
create,
update
},
banner: false
};
function create () {
let graphics = this.make.graphics();
graphics.fillStyle(0x00ff00);
graphics.fillRect(8, 0, 8, 8);
graphics.fillStyle(0xff0000);
graphics.fillRect(16, 0, 8, 8);
graphics.fillStyle(0xff0000);
graphics.fillRect(24, 0, 8, 8);
graphics.generateTexture('tiles', 8*4, 8);
var level = [
[1,1,1,1,1],
[1,0,0,0,1],
[1,0,3,0,1],
[1,0,0,0,1],
[1,1,1,1,1],
]
// Map for the first level
var map = this.make.tilemap({ data: level, tileWidth: 8, tileHeight: 8 });
var tiles = map.addTilesetImage('tiles');
var layer = map.createLayer(0, tiles, 0, 0);
layer.setCollision(1);
this.player = this.add.circle(16,16,4, 0xffffff)
.setDepth(2);
this.physics.add.existing(this.player);
this.physics.add.collider(layer, this.player);
let partialCollisions = [];
map.forEachTile((tile) => {
if(tile.index == 3){
let partialCollition = this.add.circle(tile.pixelX + 4, tile.pixelY + 4, 1)
this.physics.add.existing(partialCollition, true);
partialCollisions.push(partialCollition)
}
});
this.physics.add.collider(partialCollisions, this.player);
this.keys = this.input.keyboard.createCursorKeys();
}
function update(){
let speed = 20;
this.player.body.setVelocity(0)
if(this.keys.up.isDown){
this.player.body.setVelocityY(-speed)
}
if(this.keys.down.isDown){
this.player.body.setVelocityY(speed)
}
if(this.keys.left.isDown){
this.player.body.setVelocityX(-speed)
}
if(this.keys.right.isDown){
this.player.body.setVelocityX(speed)
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I would like to randomly spawn a sprite when an enemy dies.
Example: There is a 1 in 5 chance that when an enemy dies, it drops an object (sprites that increase your HP).
Any idea how this can be done?
I did some research, but I didn't find much.
For randomness in a Phaser application, I would use the Phaser's Math helper function Between (here is the link to the documentation).
It creates a random number (whole number) from the first number to the last one (including the last number, perfect for dice).
So for 1 in 5, you just need to select one number from the interval like 5and compare it with a call to the Between function. And only if it matches, you drop/create the sprite.
Just like this:
if(Phaser.Math.Between(1, 5) === 5){
// .. drop "loot" / health-object
}
Here a small Demo:
(In this demo something could be dropped or not, depending on your luck. 20% is pretty low)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
// here the function is used to spawn enemies randomly on screen
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
enemy.setInteractive()
.on('pointerdown', () => {
// 1 in 5
if(Phaser.Math.Between(1, 5) === 5){
// Drop object
this.add.rectangle(enemy.x, enemy.y, 10, 10, 0xFFFF00);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Bonus (because I find this Phaser function especially useful):
If you want to select different loot/outcome in phaser you, could even let phaser select from a selected Array, with the function Phaser.Math.RNG.pick(...) (link to documentation)
Bonus Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
let loot = [0x00ff00, 0xffff00, 0x0000ff, 0x0, 0x0];
enemy
.setInteractive()
.on('pointerdown', () => {
// Select Colro from an Array of possibilities
let color = Phaser.Math.RND.pick(loot);
// only drop item if color is not black
if(color > 0){
this.add.rectangle(enemy.x, enemy.y, 10, 10, color);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Phaser Random functions, have the added bonus that you can create your own RandomDataGenerator with a specific seed if you want, that the random numbers, that are created, are generated in the same sequence. Great for testing and so.
For a 1/5 chance, you can use JavaScript's Math.random.
Math.random() will return a float between 0 and 1.
To not hard code this, you can use a function like the following which will return true or false given an odds (in your case 1/5)
function rollRandom(odds) {
return Math.random() < odds;
}
console.log(rollRandom(1/5))
this is my jsfiddle : jsfiddle.net/V8eKp/5/show
// START -- MOVE CAMERA
var p = new b2Vec2();
p = (ball.body.GetWorldCenter().x) * physics.scale;
pos.push(p);
var length = pos.length;
var s = (pos[length - 1] - pos[length - 2]); //in pixels
if ((halfwidth < (dw - p)) && (p > halfwidth)) {
ctx.translate(-s, 0);
}
I followed the code in here : http://www.codingowl.com/readblog.php?blogid=128
the rendering of the ball is acting weirdly and slow
i saw this example : http://www.emanueleferonato.com/2010/05/04/following-a-body-with-the-camera-in-box2d-the-smart-way/ but i didn't figure out how he do it he used 3 variables stage,x,y wish are undefined in the code he wrote.
and check out this demo by Impactjs and look how the camera move with the player movement i need this functionality : http://impactjs.com/demos/physics/
please can anyone help me in this
As long as ball.body.GetPosition().x > 48, this.current will start incrementing on each update, right? But your draw() only handles 0, 1 and 2.
Got a (kind of) working fix here: http://jsfiddle.net/u4Mv5/
You already got most of it right, just need a bit of work on the scene change logic.
My logic is, whenever the scene change, remove everything, manually shift the ball back or forward, and completely redraw the new scene.
As we are manually shifting the ball, the canvas does not need to be translated.
Draw and loop code: (Simplified)
draw: function () {
this.clear();
new Body(physics, { name: "Ground" });
ball = new Body(physics, { shape: "circle", x: ballx, y: bally, radius: 0.5 });
switch ( this.current ) {
case 0: // First scene
new Body(physics, { name: "Scene0Wall" });
new Body(physics, { name: "Scene0bricks0" });
new Body(physics, { name: "Scene0bricks1" });
break;
case 1: // Second scene
new Body(physics, { name: "Scene1bricks0" });
break;
case 2: // Third scene
new Body(physics, { name: "Scene2bricks0" });
break;
}
},
update: function () {
ballx = ball.body.GetPosition().x;
bally = ball.body.GetPosition().y;
if ( ballx > scene_width ) {
++this.current;
ballx -= scene_width;
this.draw();
}
if ( ballx < 0 && this.current > 0) {
--this.current;
ballx += scene_width;
this.draw();
}
},
This is also easier on scene layout since each scene's origin is [0,0].
I'm only preserving the balls' position, not its state and velocity, which you should implement. The scene layout (and scene cut off point) should also be adjusted to make sure the ball can continue its journey instead of getting stuck in a block.