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
);
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 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
I'm currently using Phaser 3, although my question isn't technically restricted to that framework, as it's more of a general JS/canvas/maths question, but:
I have a line drawn with graphics(). It’s anchored at one end, and the other end is draggable. I made a quick demo and so far, so good - you can see what I have already on CodePen.
Dragging the marker around and redrawing the line is no problem, but what I’d like is for that line to have a maximum length of 100, so even if you’re still dragging beyond that point, the line would still follow the mouse, but not get any longer than 100. Dragging inside that maximum radius, the line would shrink as normal.
I’ve put together a visual that hopefully explains it:
The issue is that I suspect this is VERY MATHS and I am very, very weak with maths. Could anyone explain like I’m five what I need to do to my code to achieve this?
Edit: Adding code in a snippet here, as requested:
var config = {
type: Phaser.AUTO,
width: 800,
height: 400,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: {
preload: preload,
create: create,
update: update
}
};
var path;
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
path = { t: 0, vec: new Phaser.Math.Vector2() };
curve = new Phaser.Curves.Line([ 400, 390, 300, 230 ]);
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
point1.setData('vector', curve.p1);
this.input.setDraggable(point1);
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
curve.getPoint(path.t, path.vec);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.55.2/phaser.min.js"></script>
You are right, you would need some math, but phaser has many helper functions, that will do the heavy lifting.
The main idea is, of this solution is
define a maxLength
get the the new point on drag, and create a real Phaser Vector2
here is some math is needed, to create the vector, just calculate destination point minus origin point
new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y) (origin point being the starting point of the desired vector, and destination point being the mouse pointer)
calculate the length of the created vector and compare it with the maxLength
if too long adjust the vector, with the handy function setLength (link to the documentation, this is where you would have needed math, but thankfully Phaser does it for us)
set the new coordinates for point1 and the curve endpoint
Here a quick demo (based on your code):
var config = {
type: Phaser.AUTO,
width: 500,
height: 170,
scene: {
preload: preload,
create: create,
update: update
}
};
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
curve = new Phaser.Curves.Line([ config.width/2, config.height - 20, config.width/2, 10 ]);
// define a length, could be a global constant
let maxLength = curve.p0.y - curve.p1.y;
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(curve.p0.x, curve.p0.y, maxLength)
.setStrokeStyle(1, 0xffffff, .5)
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > maxLength){
vector.setLength(maxLength);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
curve.p1.x = point1.x;
curve.p1.y = point1.y;
});
// NOT REALLY NEEDED
/*this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});*/
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Optional - Code Version using Phaser.GameObjects.Line:
This uses less code, and thanks to the Line GameObject (link to Documentation), you can directly use the vector to update the line, and also don't need the update function, graphics and so.
const config = {
type: Phaser.CANVAS,
width: 500,
height: 160,
scene: {
create
}
};
const game = new Phaser.Game(config);
const MAX_LINE_LENGTH = 100;
function create() {
let points = [ {x: config.width/2, y: config.height - 20}, {x: config.width/2, y: config.height - 120} ];
let point0 = this.add.circle(points[0].x, points[0].y, 6)
.setStrokeStyle(4, 0xff0000);
let point1 = this.add.circle(points[1].x, points[1].y, 6)
.setStrokeStyle(4, 0xff0000)
.setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(point0.x, point0.y, MAX_LINE_LENGTH)
.setStrokeStyle(1, 0xffffff, .5);
let line = this.add.line(points[0].x, points[0].y, 0, 0, 0, -100, 0x00ff00)
.setOrigin(0);
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > MAX_LINE_LENGTH){
vector.setLength(MAX_LINE_LENGTH);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
line.setTo(0, 0, vector.x, vector.y);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
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>
The game I'm creating doesn't require any physics, however you are able to interact when hovering over/clicking on the sprite by using sprite.setInteractive({cursor: "pointer"});, sprite.on('pointermove', function(activePointer) {...}); and similar.
I ran into some issues with the interactive area and wanted to debug it by showing the "area" that is interactable. However I could only find ways to do that that are related to Arcade Physics. Is there any way to get something like a debug outline around my interactable area without Physics?
Out-Of-The-Box, without physics, I don't know any way, but one could get this function/feature with a small helper-function. (but maybe there is something, since phaser is a really extensive framework. But I also couldn't find anything).
Something like this, could do the trick, and is reuseable:
function debugSpriteArea(scene, sprite){
let debugRect = scene.add.rectangle(
sprite.x, sprite.y,
sprite.displayWidth, sprite.displayHeight,
0xff0000).setOrigin(sprite.originX,
sprite.originY);
debugRect.setDepth(-1);
}
Here the help-function in action:
let Scene = {
preload ()
{
this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
},
create ()
{
// Animation set
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
const cody = this.add.sprite(200, 100, 'brawler')
.setOrigin(0.5);
debugSpriteArea(this, cody);
cody.play('walk');
cody.setInteractive();
this.mytext = this.add.text(10, 10, 'No Hit', { fontFamily: 'Arial' });
cody.on('pointerdown', function (pointer) {
let originXOffset = cody.displayWidth * cody.originX;
let originYOffset = cody.displayHeight * cody.originY;
let x = (pointer.x - cody.x + originXOffset ) / (cody.displayWidth / cody.width)
let y = (pointer.y - cody.y + originYOffset) / (cody.displayHeight / cody.height);
if(cody.anims && cody.anims.currentFrame){
let currentFrame = cody.anims.currentFrame;
let pixelColor = this.textures.getPixel(x, y, currentFrame.textureKey, currentFrame.textureFrame);
if(pixelColor.a > 0) {
this.mytext.text = 'hit';
} else {
this.mytext.text = 'No hit';
}
}
}, this);
}
};
function debugSpriteArea(scene, sprite){
let debugRect = scene.add.rectangle(
sprite.x, sprite.y,
sprite.displayWidth, sprite.displayHeight,
0xff0000).setOrigin(sprite.originX,
sprite.originY);
debugRect.setDepth(-1);
}
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
scene: Scene
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>