How to change the direction of a group sprite based on a condition in Phaser 3 - game-development

I'm using Phaser 3 and tried doing this:
class Bee extends Phaser.GameObjects.Sprite {
constructor(config) {
super(config.scene, config.x, config.y, 'bee');
config.scene.add.existing(this);
config.scene.physics.add.existing(this);
this.initial = [config.x, config.y];
this.x = config.x;
this.y = config.y;
this.speed = 5;
}
preUpdate() {
if (this.y < this.initial[1] - 100) {
this.speed *= -1;
}
if (this.y > this.initial[1] + 100) {
this.speed *= -1;
}
this.y += this.speed;
this.setY(this.y);
}
}
I want a Bee object to move down 100 px then move up and then repeat, but it doesn't seem to work.
In my scene I created a object:
bees = this.physics.add.group();
let bee = new Bee({scene:scene, x: 100, y:200})
bee.setScale(0.5);
bee.allowGravity = false;
bees.add(bee);
I'll have multiple Bee sprites in the game.

You could solve the up and down movement with a simple tween (https://photonstorm.github.io/phaser3-docs/Phaser.Tweens.Tween.html).
A tween changes/moves the selected property (or multiple properties) between a starting and an end position. In your case, I would use it to change the y property of the bee physic body velocity.
You can do alot with a tween, without the need to calculate speed, next position and so on. You just have to tweak the properties of the tween config, to get the desired movement/animation.
Here a working example:
(Update: Now with a few bees in the group and an 'offset' for the flight pattern)
class Bee extends Phaser.GameObjects.Sprite {
constructor(config) {
super(config.scene, config.x, config.y, 'bee');
config.scene.add.existing(this);
config.scene.physics.add.existing(this);
this.x = config.x;
this.y = config.y;
config.scene.tweens.add({
targets: this.body.velocity,
y: { from: -100, to: 100 },
ease: Phaser.Math.Easing.Quadratic.InOut,
yoyo: true,
repeat: -1,
duraton: 1000,
// just to create a interesting offset for the example
delay: Phaser.Math.Between(0,6) * 200
});
}
}
function create(){
let bees = this.physics.add.group();
for(let i=0; i < 10 ; i++){
let bee = new Bee({scene: this,
x: i * -100 ,
y: 50 });
bees.add(bee);
}
bees.setVelocity(100,0)
}
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
physics: {
default: 'arcade',
},
scene: {
create
}
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Related

Infinite draggable webgl slider

I'm new to webgl and I'm trying to imrpove this infinite draggable webgl slider:
https://codepen.io/ReGGae/pen/povjKxV
setup() {
const { ww } = store
const state = this.state
const { items, titles } = this.ui
const {
width: wrapWidth,
left: wrapDiff
} = this.el.getBoundingClientRect()
// Set bounding
state.max = -(items[items.length - 1].getBoundingClientRect().right - wrapWidth - wrapDiff)
state.min = 0
// Global timeline
this.tl = gsap.timeline({
paused: true,
defaults: {
duration: 1,
ease: 'linear'
}
})
.fromTo('.js-progress-line-2', {
scaleX: 1
}, {
scaleX: 0,
duration: 0.5,
ease: 'power3'
}, 0)
.fromTo('.js-titles', {
yPercent: 0
}, {
yPercent: -(100 - (100 / titles.length)),
}, 0)
.fromTo('.js-progress-line', {
scaleX: 0
}, {
scaleX: 1
}, 0)
// Cache stuff
for (let i = 0; i < items.length; i++) {
const el = items[i]
const { left, right, width } = el.getBoundingClientRect()
// Create webgl plane
const plane = new Plane()
plane.init(el)
// Timeline that plays when visible
const tl = gsap.timeline({ paused: true })
.fromTo(plane.mat.uniforms.uScale, {
value: 0.65
}, {
value: 1,
duration: 1,
ease: 'linear'
})
// Push to cache
this.items.push({
el, plane,
left, right, width,
min: left < ww ? (ww * 0.775) : -(ww * 0.225 - wrapWidth * 0.2),
max: left > ww ? state.max - (ww * 0.775) : state.max + (ww * 0.225 - wrapWidth * 0.2),
tl,
out: false
})
}
}
What I would like to be able to do is to perfectly loop the words with the slides. Now when dragging it fast the words don't match the pictures (currently there is one more word than the images as workaround).
Plus I would like to find a way to place links on the draggable images.
I would be very grateful for any advice.

SwiftUI: How to manage dynamic rows/columns of Views?

I am finding my first SwiftUI challenge to be a tricky one. Given a set of playing cards, display them in a way that allows the user to see the full deck while using space efficiently. Here's a simplified example:
In this case 52 cards (Views) are presented, in order of 01 - 52. They are dynamically packed into the parent view such that there is enough spacing between them to allow the numbers to be visible.
The problem
If we change the shape of the window, the packing algorithm will pack them (correctly) into a different number of rows & columns. However, when the number of rows/columns change, the card Views are out of order (some are duplicated):
In the image above, notice how the top row is correct (01 - 26) but the second row starts at 12 and ends at 52. I expect his is because the second row originally contained 12 - 22 and those views were not updated.
Additional criteria: The number of cards and the order of those cards can change at runtime. Also, this app must be able to be run on Mac, where the window size can be dynamically adjusted to any shape (within reason.)
I understand that when using ForEach for indexing, one must use a constant but I must loop through a series of rows and columns, each of which can change. I have tried adding id: \.self, but this did not solve the problem. I ended up looping through the maximum possible number of rows/columns (to keep the loop constant) and simply skipped the indices that I didn't want. This is clearly wrong.
The other alternative would be to use arrays of Identifiable structures. I tried this, but wasn't able to figure out how to organize the data flow. Also, since the packing is dependent on the size of the parent View it would seem that the packing must be done inside the parent. How can the parent generate the data needed to fulfill the deterministic requirements of SwiftUI?
I'm willing to do the work to get this working, any help understanding how I should proceed would be greatly appreciated.
The code below is a fully working, simplified version. Sorry if it's still a bit large. I'm guessing the problem revolves around the use of the two ForEach loops (which are, admittedly, a bit janky.)
import SwiftUI
// This is a hacked together simplfied view of a card that meets all requirements for demonstration purposes
struct CardView: View {
public static let kVerticalCornerExposureRatio: CGFloat = 0.237
public static let kPhysicalAspect: CGFloat = 63.5 / 88.9
#State var faceCode: String
func bgColor(_ faceCode: String) -> Color {
let ascii = Character(String(faceCode.suffix(1))).asciiValue!
let r = (CGFloat(ascii) / 3).truncatingRemainder(dividingBy: 0.7)
let g = (CGFloat(ascii) / 17).truncatingRemainder(dividingBy: 0.9)
let b = (CGFloat(ascii) / 23).truncatingRemainder(dividingBy: 0.6)
return Color(.sRGB, red: r, green: g, blue: b, opacity: 1)
}
var body: some View {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 10)
.fill(bgColor(faceCode))
.cornerRadius(8)
.frame(width: geometry.size.height * CardView.kPhysicalAspect, height: geometry.size.height)
.aspectRatio(CardView.kPhysicalAspect, contentMode: .fit)
.overlay(Text(faceCode)
.font(.system(size: geometry.size.height * 0.1))
.padding(5)
, alignment: .topLeading)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 2))
}
}
}
// A single rows of our fanned out cards
struct RowView: View {
var cards: [String]
var width: CGFloat
var height: CGFloat
var start: Int
var columns: Int
var cardWidth: CGFloat {
return height * CardView.kPhysicalAspect
}
var cardSpacing: CGFloat {
return (width - cardWidth) / CGFloat(columns - 1)
}
var body: some View {
HStack(spacing: 0) {
// Visit all cards, but only add the ones that are within the range defined by start/columns
ForEach(0 ..< cards.count) { index in
if index < columns && start + index < cards.count {
HStack(spacing: 0) {
CardView(faceCode: cards[start + index])
.frame(width: cardWidth, height: height)
}
.frame(width: cardSpacing, alignment: .leading)
}
}
}
}
}
struct ContentView: View {
#State var cards: [String]
#State var fanned: Bool = true
// Generates the number of rows/columns that meets our rectangle-packing criteria
func pack(area: CGSize, count: Int) -> (rows: Int, cols: Int) {
let areaAspect = area.width / area.height
let exposureAspect = 1 - CardView.kVerticalCornerExposureRatio
let aspect = areaAspect / CardView.kPhysicalAspect * exposureAspect
var rows = Int(ceil(sqrt(Double(count)) / aspect))
let cols = count / rows + (count % rows > 0 ? 1 : 0)
while cols * (rows - 1) >= count { rows -= 1 }
return (rows, cols)
}
// Calculate the height of a card such that a series of rows overlap without covering the corner pips
func cardHeight(frameHeight: CGFloat, rows: Int) -> CGFloat {
let partials = CGFloat(rows - 1) * CardView.kVerticalCornerExposureRatio + 1
return frameHeight / partials
}
var body: some View {
VStack {
GeometryReader { geometry in
let w = geometry.size.width
let h = geometry.size.height
if w > 0 && h > 0 { // using `geometry.size != .zero` crashes the preview :(
let (rows, cols) = pack(area: geometry.size, count: cards.count)
let cardHeight = cardHeight(frameHeight: h, rows: rows)
let rowSpacing = cardHeight * CardView.kVerticalCornerExposureRatio
VStack(spacing: 0) {
// Visit all cards as if the layout is one row per card and simply skip the rows
// we're not interested in. If I make this `0 ..< rows` - it doesn't work at all
ForEach(0 ..< cards.count) { row in
if row < rows {
RowView(cards: cards, width: w, height: cardHeight, start: row * cols, columns: cols)
.frame(width: w, height: rowSpacing, alignment: .topLeading)
}
}
}
.frame(width: w, height: 100, alignment: .topLeading)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(cards: ["01", "02", "03", "04", "05", "06", "07", "08", "09",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
"50", "51", "52"])
.background(Color.white)
.preferredColorScheme(.light)
}
}
I think you're on the right track that you need to use an Identifiable to prevent the system from making assumptions about what can be recycled in the ForEach. To that end, I've created a Card:
struct Card : Identifiable {
let id = UUID()
var title : String
}
Within the RowView, this is trivial to use:
struct RowView: View {
var cards: [Card]
var width: CGFloat
var height: CGFloat
var columns: Int
var cardWidth: CGFloat {
return height * CardView.kPhysicalAspect
}
var cardSpacing: CGFloat {
return (width - cardWidth) / CGFloat(columns - 1)
}
var body: some View {
HStack(spacing: 0) {
// Visit all cards, but only add the ones that are within the range defined by start/columns
ForEach(cards) { card in
HStack(spacing: 0) {
CardView(faceCode: card.title)
.frame(width: cardWidth, height: height)
}
.frame(width: cardSpacing, alignment: .leading)
}
}
}
}
In the ContentView, things get a little more complicated because of the dynamic rows:
struct ContentView: View {
#State var cards: [Card] = (1..<53).map { Card(title: "\($0)") }
#State var fanned: Bool = true
// Generates the number of rows/columns that meets our rectangle-packing criteria
func pack(area: CGSize, count: Int) -> (rows: Int, cols: Int) {
let areaAspect = area.width / area.height
let exposureAspect = 1 - CardView.kVerticalCornerExposureRatio
let aspect = areaAspect / CardView.kPhysicalAspect * exposureAspect
var rows = Int(ceil(sqrt(Double(count)) / aspect))
let cols = count / rows + (count % rows > 0 ? 1 : 0)
while cols * (rows - 1) >= count { rows -= 1 }
return (rows, cols)
}
// Calculate the height of a card such that a series of rows overlap without covering the corner pips
func cardHeight(frameHeight: CGFloat, rows: Int) -> CGFloat {
let partials = CGFloat(rows - 1) * CardView.kVerticalCornerExposureRatio + 1
return frameHeight / partials
}
var body: some View {
VStack {
GeometryReader { geometry in
let w = geometry.size.width
let h = geometry.size.height
if w > 0 && h > 0 { // using `geometry.size != .zero` crashes the preview :(
let (rows, cols) = pack(area: geometry.size, count: cards.count)
let cardHeight = cardHeight(frameHeight: h, rows: rows)
let rowSpacing = cardHeight * CardView.kVerticalCornerExposureRatio
VStack(spacing: 0) {
ForEach(Array(cards.enumerated()), id: \.1.id) { (index, card) in
let row = index / cols
if index % cols == 0 {
let rangeMin = min(cards.count, row * cols)
let rangeMax = min(cards.count, rangeMin + cols)
RowView(cards: Array(cards[rangeMin..<rangeMax]), width: w, height: cardHeight, columns: cols)
.frame(width: w, height: rowSpacing, alignment: .topLeading)
}
}
}
.frame(width: w, height: 100, alignment: .topLeading)
}
}
}
}
}
This loops through all of the cards and uses the unique IDs. Then, there's some logic to use the index to determine what row the loop is on and if it is the beginning of the loop (and thus should render the row). Finally, it sends just a subset of the cards to the RowView.
Note: you can look at Swift Algorithms for a more efficient method than enumerated. See indexed: https://github.com/apple/swift-algorithms/blob/main/Guides/Indexed.md
#jnpdx has provided a valid answer that is direct in its approach, which helps to understand the problem without adding additional complexity.
I have also stumbled across an alternative approach that requires more drastic changes to the structure of the code, but is more performant while also leading to more production-ready code.
To begin with, I created a CardData struct that implements the ObservableObject protocol. This includes the code to pack a set of cards into rows/columns based on a given CGSize.
class CardData: ObservableObject {
var cards = [[String]]()
var hasData: Bool {
return cards.count > 0 && cards[0].count > 0
}
func layout(cards: [String], size: CGSize) -> CardData {
// ...
// Populate `cards` with packed rows/columns
// ...
return self
}
}
This would only work if the layout code could know the frame size for which it was packing. To that end, I used .onChange(of:perform:) to track changes to the geometry itself:
.onChange(of: geometry.size, perform: { size in
cards.layout(cards: cardStrings, size: size)
})
This greatly simplifies the ContentView:
var body: some View {
VStack {
GeometryReader { geometry in
let cardHeight = cardHeight(frameHeight: geometry.size.height, rows: cards.rows)
let rowSpacing = cardHeight * CardView.kVerticalCornerExposureRatio
VStack(spacing: 0) {
ForEach(cards.cards, id: \.self) { row in
RowView(cards: row, width: geometry.size.width, height: cardHeight)
.frame(width: geometry.size.width, height: rowSpacing, alignment: .topLeading)
}
}
.frame(width: geometry.size.width, height: 100, alignment: .topLeading)
.onChange(of: geometry.size, perform: { size in
_ = cards.layout(cards: CardData.faceCodes, size: size)
})
}
}
}
In addition, it also simplifies the RowView:
var body: some View {
HStack(spacing: 0) {
ForEach(cards, id: \.self) { card in
HStack(spacing: 0) {
CardView(faceCode: card)
.frame(width: cardWidth, height: height)
}
.frame(width: cardSpacing, alignment: .leading)
}
}
}
Further improvements can be had by storing rows/columns of CardViews inside CardData rather than the card title strings. This will eliminate the need to recreate a full set of (in my case, complex) CardViews in the View code.
The final end result now looks like this:

TypeError: Cannot read property 'width' of undefined - Nuxt JS and canvas

I am trying to run a sketch on the canvas element in Nuxt JS but I am having some issues (I am completely new to Vue JS).
While I have no errors in VS Code, there is an error in the browser console:
client.js?06a0:84 TypeError: Cannot read property 'width' of undefined
at Blob.get (Blob_Point.js?a5e5:126)
at Blob.render (Blob_Point.js?a5e5:29)
below is the code for my Vue file:
<template>
<div>
<h1 class="pa-5 text-center">Blob</h1>
<canvas id="myCanvas" width="600" height="400"></canvas>
<!--<v-btn #click="drawRect">Clear</v-btn> -->
</div>
</template>
<script>
import Blob from "../assets/content/Blob_Point"
//import { Point } from "../assets/content/Blob_Point"
//import Point from "../assets/content/Blob_Point"
let oldMousePoint = { x: 0, y: 0 }
let blob = new Blob()
//let point = new Point()
let hover = false
let canvas
export default {
data() {
return {
canvas: null,
x: 0,
y: 0,
isDrawing: false,
rectWidth: 200,
//hover: false,
//oldMousePoint: { x: 0, y: 0 },
}
},
mounted() {
let canvas = document.getElementById("myCanvas")
this.canvas = canvas.getContext("2d")
},
created() {
new Blob("#C09EFF")
blob.canvas = canvas
blob.init()
blob.render()
},
methods: {
/* showCoordinates(e) {
this.x = e.offsetX
this.y = e.offsetY
},
drawLine(x1, y1, x2, y2) {
let ctx = this.vueCanvas
ctx.beginPath()
ctx.strokeStyle = "black"
ctx.lineWidth = 1
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
ctx.closePath()
},
draw(e) {
if (this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY)
this.x = e.offsetX
this.y = e.offsetY
}
},
beginDrawing(e) {
this.x = e.offsetX
this.y = e.offsetY
this.isDrawing = true
},
stopDrawing(e) {
if (this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY)
this.x = 0
this.y = 0
this.isDrawing = false
//windowWidth = 0,
}
}, */
resize() {
document.getElementById("myCanvas").width = window.innerWidth
document.getElementById("myCanvas").height = window.innerHeight
},
/*drawRect() {
this.vueCanvas.clearRect(0, 0, 600, 800)
//this.vueCanvas.beginPath()
//this.vueCanvas.rect(20, 20, this.rectWidth, 100)
//this.vueCanvas.stroke()
},*/
mouseMove(e) {
let pos = blob.center
let diff = { x: e.clientX - pos.x, y: e.clientY - pos.y }
let dist = Math.sqrt(diff.x * diff.x + diff.y * diff.y)
let angle = null
blob.mousePos = {
x: pos.x - e.clientX,
y: pos.y - e.clientY,
}
if (dist < blob.radius && hover === false) {
let vector = {
x: e.clientX - pos.x,
y: e.clientY - pos.y,
}
angle = Math.atan2(vector.y, vector.x)
hover = true
// blob.color = '#77FF00';
} else if (dist > blob.radius && hover === true) {
let vector = {
x: e.clientX - pos.x,
y: e.clientY - pos.y,
}
angle = Math.atan2(vector.y, vector.x)
hover = false
blob.color = null
}
if (typeof angle == "number") {
let nearestPoint = null
let distanceFromPoint = 100
blob.points.forEach((point) => {
if (Math.abs(angle - point.azimuth) < distanceFromPoint) {
// console.log(point.azimuth, angle, distanceFromPoint);
nearestPoint = point
distanceFromPoint = Math.abs(angle - point.azimuth)
}
})
if (nearestPoint) {
let strength = {
x: oldMousePoint.x - e.clientX,
y: oldMousePoint.y - e.clientY,
}
strength =
Math.sqrt(strength.x * strength.x + strength.y * strength.y) * 1
if (strength > 100) strength = 100
nearestPoint.acceleration = (strength / 100) * (hover ? -1 : 1)
}
}
oldMousePoint.x = e.clientX
oldMousePoint.y = e.clientY
},
},
}
</script>
<style scoped>
#myCanvas {
border: 1px solid grey;
}
</style>
and below is the Blob_Point JS file that I am importing:
/* eslint-disable */
// Blob Class
export default class Blob {
// setup function
constructor(color) {
//the objects setup
// 'this' is a reference to the current class
this.points = []
this._color = color
}
init() {
for (let i = 0; i < this.numPoints; i++) {
let point = new Point(this.divisional * (i + 1), this)
//point.acceleration = -1 + Math.random() * 2;
this.push(point)
}
}
render() {
let canvas = this.canvas
let ctx = this.ctx
let position = this.position
let pointsArray = this.points
let radius = this.radius
let points = this.numPoints
let divisional = this.divisional
let center = this.center
ctx.clearRect(0, 0, canvas.width, canvas.height)
pointsArray[0].solveWith(pointsArray[points - 1], pointsArray[1])
let p0 = pointsArray[points - 1].position
let p1 = pointsArray[0].position
let _p2 = p1
// this is the draw
ctx.beginPath()
ctx.moveTo(center.x, center.y)
ctx.moveTo((p0.x + p1.x) / 2, (p0.y + p1.y) / 2)
for (let i = 1; i < points; i++) {
pointsArray[i].solveWith(
pointsArray[i - 1],
pointsArray[i + 1] || pointsArray[0]
)
let p2 = pointsArray[i].position
var xc = (p1.x + p2.x) / 2
var yc = (p1.y + p2.y) / 2
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc)
// ctx.lineTo(p2.x, p2.y);
//ctx.fillStyle = this.color;
// ctx.fillRect(p1.x-2.5, p1.y-2.5, 5, 5);
p1 = p2
}
var xc = (p1.x + _p2.x) / 2
var yc = (p1.y + _p2.y) / 2
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc)
ctx.lineTo(_p2.x, _p2.y)
ctx.closePath()
ctx.fillStyle = this.color
ctx.fill()
ctx.strokeStyle = this.color
// ctx.stroke();
/*
ctx.fillStyle = '#000000';
if(this.mousePos) {
let angle = Math.atan2(this.mousePos.y, this.mousePos.x) + Math.PI;
}*/
//requestAnimationFrame(this.render.bind(this))
}
push(item) {
if (item instanceof Point) {
this.points.push(item)
}
}
set color(value) {
this._color = value
}
get color() {
return this._color
}
set canvas(value) {
if (
value instanceof HTMLElement &&
value.tagName.toLowerCase() === "canvas"
) {
this._canvas = canvas
this.ctx = this._canvas.getContext("2d")
}
}
get canvas() {
return this._canvas
}
set numPoints(value) {
if (value > 10) {
this._points = value
}
}
get numPoints() {
return this._points || 32
}
set radius(value) {
if (value > 0) {
this._radius = value
}
}
get radius() {
return this._radius || 300
}
set position(value) {
if (typeof value == "object" && value.x && value.y) {
this._position = value
}
}
get position() {
return this._position || { x: 0.5, y: 0.5 }
}
get divisional() {
return (Math.PI * 2) / this.numPoints
}
get center() {
return {
x: this.canvas.width * this.position.x,
y: this.canvas.height * this.position.y,
}
}
set running(value) {
this._running = value === true
}
get running() {
return this.running !== false
}
}
// Point Class
export class Point {
constructor(azimuth, parent) {
this.parent = parent
this.azimuth = Math.PI - azimuth
this._components = {
x: Math.cos(this.azimuth),
y: Math.sin(this.azimuth),
}
this.acceleration = -0.3 + Math.random() * 0.6
}
solveWith(leftPoint, rightPoint) {
this.acceleration =
(-0.3 * this.radialEffect +
(leftPoint.radialEffect - this.radialEffect) +
(rightPoint.radialEffect - this.radialEffect)) *
this.elasticity -
this.speed * this.friction
}
set acceleration(value) {
if (typeof value == "number") {
this._acceleration = value
this.speed += this._acceleration * 2
}
}
get acceleration() {
return this._acceleration || 0
}
set speed(value) {
if (typeof value == "number") {
this._speed = value
this.radialEffect += this._speed * 3
}
}
get speed() {
return this._speed || 0
}
set radialEffect(value) {
if (typeof value == "number") {
this._radialEffect = value
}
}
get radialEffect() {
return this._radialEffect || 0
}
get position() {
return {
x:
this.parent.center.x +
this.components.x * (this.parent.radius + this.radialEffect),
y:
this.parent.center.y +
this.components.y * (this.parent.radius + this.radialEffect),
}
}
get components() {
return this._components
}
set elasticity(value) {
if (typeof value === "number") {
this._elasticity = value
}
}
get elasticity() {
return this._elasticity || 0.001
}
set friction(value) {
if (typeof value === "number") {
this._friction = value
}
}
get friction() {
return this._friction || 0.0085
}
}
Any ideas on the why lines 29 and 127 of the Blob_Point.js file are throwing errors?
I attached 2 screens of the developer tools in chrome to show the errors along with the JS its pointing to.
Thanks for any help!
The main issue I can identify is here. I have added comments to the code.
Blob_Point.js
render() {
let canvas = this.canvas // this references the Blob Class (not the Vue instance) and there is no initialised property such as canvas in the class
}
To fix this main issue, you can do something like
Blob_Point.js
export default class Blob {
constructor(color, canvas) {
//the objects setup
// 'this' is a reference to the current class
this.points = []
this._color = color;
this.canvas = canvas
}
}
And then in the .vue file
.Vue
mounted() {
let canvas = document.getElementById("myCanvas");
this.canvas = canvas.getContext("2d");
blob = new Blob("#C09EFF", this.canvas); // now canvas in the Blob Class constructor will refer to the vue instance canvas
blob.canvas = canvas;
blob.init();
blob.render();
},
I have identified another issue here.
set canvas(value) {
if (
value instanceof HTMLElement &&
value.tagName.toLowerCase() === "canvas"
) {
this._canvas = canvas // there is no initialised constructor property as _canvas
this.ctx = this._canvas.getContext("2d") // there is no initialised constructor property such as _canvas
}
}
get canvas() {
return this._canvas // there is no initialised constructor property as _canvas
}

How do I scale a group in Phaser 3

In Phaser 2 we scale a simple by setting the scale property as explained in docs:
https://phaser.io/examples/v2/groups/group-scale
But there is no equivalent in Phaser v3.
The possible url https://phaser.io/examples/v3/groups/group-scale points to nothing. And if I do:
this.enemies = this.add.group();
this.enemies.scale.set(2, 2);
It throws:
Phaser v3.19.0 (WebGL | Web Audio) https://phaser.io
indexph.js:22 Uncaught TypeError: Cannot read property 'set' of undefined
What is the appropriate form to scale a group of sprites in Phaser 3?
The code below should work, I think, But it doesn't.... it doesn't scale objects that are created from the group:
preload() {
this.load.atlas("sprites", "assets/spritesheet.png", "assets/spritesheet.json")
}
create() {
this.enemies = this.add.group({
key: 'sprites' ,
setScale: { x: 0.1, y: 0.1 }
});
this.enemies.create(60, 60, 'sprites', 'hamburguer.png');
In Phaser 3, you can scale a group by modifying the GroupConfig object passed in when you declare your group.
GroupConfig API Reference. You can see also see a live demo here.
In your case, to scale this group you should simply create it like:
this.enemies = this.add.group({
setScale: { x: 2, y: 2}
});
Alternatively, you could iterate through the group once it's created and scale each child object independently.
this.enemies = this.add.group();
this.enemies.children.iterate((child) => {
child.setScale(2, 2);
});
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600, loader: {
baseURL: 'https://raw.githubusercontent.com/nazimboudeffa/assets/master/',
crossOrigin: 'anonymous'
},
scene: {
preload: preload,
create: create
},
physics: {
default: 'arcade'
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.image('alien1', 'sprites/phaser-alien.png');
this.load.image('alien2', 'sprites/alien2.png');
}
function create ()
{
this.enemies1 = this.add.group();
this.enemies2 = this.add.group();
for (let i = 0; i < 64; i++)
{
let x = Phaser.Math.Between(0, 400);
let y = Phaser.Math.Between(0, 600);
this.enemy1 = this.add.image(x, y, 'alien1');
this.enemies1.add(this.enemy1);
}
for (let i = 0; i < 64; i++)
{
let x = Phaser.Math.Between(400, 800);
let y = Phaser.Math.Between(0, 600);
this.enemy2 = this.add.image(x, y, 'alien2');
this.enemies2.add(this.enemy2);
}
console.log(this.enemies1.getLength())
//console.log(this.enemies.getChildren())
console.log(this.enemies1.getChildren()[2])
for (let i = 0; i < 64; i++)
{
this.enemies1.getChildren()[i].setScale(2);
}
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.19.0/dist/phaser.js"></script>

Keep objects looking at camera

guys I know this question has been asked several times, several different ways, but I just can get it to work. Basically I have 2d clouds, but I want the camera to rotate around an object floating above the clouds. The problem is, when im not looking a the face of the clouds u can tell that they are 2d. Soooo i want the the clouds to "look" at the camera where ever it is. I believe my problem stems from how the cloud geometry is called on to the planes, but here take a look. I put the a lookAt function with in my animate function. I hope you can point me in the right direction at least.
Three.js rev. 70...
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 0, 100);
scene.add(camera);
controls = new THREE.OrbitControls( camera );
controls.target.copy( new THREE.Vector3( 0, 0,475) );
controls.minDistance = 50;
controls.maxDistance = 200;
controls.autoRotate = true;
controls.autoRotateSpeed = .2; // 30 seconds per round when fps is 60
controls.minPolarAngle = Math.PI/4; // radians
controls.maxPolarAngle = Math.PI/2; // radians
controls.enableDamping = true;
controls.dampingFactor = 0.25;
clock = new THREE.Clock();
cloudGeometry = new THREE.Geometry();
var texture = THREE.ImageUtils.loadTexture('img/cloud10.png', null, animate);
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
var fog = new THREE.Fog(0x4584b4, -100, 3000);
cloudMaterial = new THREE.ShaderMaterial({
uniforms: {
"map": {
type: "t",
value: texture
},
"fogColor": {
type: "c",
value: fog.color
},
"fogNear": {
type: "f",
value: fog.near
},
"fogFar": {
type: "f",
value: fog.far
},
},
vertexShader: document.getElementById('vs').textContent,
fragmentShader: document.getElementById('fs').textContent,
depthWrite: false,
depthTest: false,
transparent: true
});
var plane = new THREE.Mesh(new THREE.PlaneGeometry(64, 64));
for (var i = 0; i < 8000; i++) {
plane.position.x = Math.random() * 1000 - 500;
plane.position.y = -Math.random() * Math.random() * 200 - 15;
plane.position.z = i;
plane.rotation.z = Math.random() * Math.PI;
plane.scale.x = plane.scale.y = Math.random() * Math.random() * 1.5 + 0.5;
plane.updateMatrix();
cloudGeometry.merge(plane.geometry, plane.matrix);
}
cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
scene.add(cloud);
cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloud.position.z = -8000;
scene.add(cloud);
var radius = 100;
var xSegments = 50;
var ySegments = 50;
var geo = new THREE.SphereGeometry(radius, xSegments, ySegments);
var mat = new THREE.ShaderMaterial({
uniforms: {
lightPosition: {
type: 'v3',
value: light.position
},
textureMap: {
type: 't',
value: THREE.ImageUtils.loadTexture("img/maps/moon.jpg")
},
normalMap: {
type: 't',
value: THREE.ImageUtils.loadTexture("img/maps/normal.jpg")
},
uvScale: {
type: 'v2',
value: new THREE.Vector2(1.0, 1.0)
}
},
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
});
mesh = new THREE.Mesh(geo, mat);
mesh.geometry.computeTangents();
mesh.position.set(0, 50, 0);
mesh.rotation.set(0, 180, 0);
scene.add(mesh);
}
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
function animate() {
requestAnimationFrame(animate);
light.orbit(mesh.position, clock.getElapsedTime());
cloud.lookAt( camera );
controls.update(camera);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', onWindowResize, false);
just a first guess:
the lookAt function needs Vector3 as parameter. try to use camera.position in the animate function.
cloud.lookAt( camera.position );
first of all, to build 2D objects in a scene that always faces towards the camera, you should use Sprite object, so you don't have to do anything to get this effect. (and have better performance :))
Definition from THREE.org: Sprite - a sprite is a plane in an 3d scene which faces always towards the camera.
var map = THREE.ImageUtils.loadTexture( "sprite.png" );
var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
var sprite = new THREE.Sprite( material );
scene.add( sprite );
Please check this example: http://threejs.org/examples/#webgl_points_sprites
I would absolutely agree, I would use Sprite, or even Points, but then, if assign a texture to it, it will render it square-sized. My sprites are animated, and frames cannot be packed in square tiles, cause it would take a lot of space. I might make a mesh and use this lookAt function.