Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
commit
24aaa4582f
@ -213,13 +213,13 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateUserGroup(user: User): void {
|
private updateUserGroup(user: User): void {
|
||||||
user.group?.updatePosition();
|
|
||||||
user.group?.searchForNearbyUsers();
|
|
||||||
|
|
||||||
if (user.silent) {
|
if (user.silent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = user.group;
|
const group = user.group;
|
||||||
|
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
||||||
|
|
||||||
if (group === undefined) {
|
if (group === undefined) {
|
||||||
// If the user is not part of a group:
|
// If the user is not part of a group:
|
||||||
// should he join a group?
|
// should he join a group?
|
||||||
@ -229,12 +229,11 @@ export class GameRoom {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
|
||||||
|
|
||||||
if (closestItem !== null) {
|
if (closestItem !== null) {
|
||||||
if (closestItem instanceof Group) {
|
if (closestItem instanceof Group) {
|
||||||
// Let's join the group!
|
// Let's join the group!
|
||||||
closestItem.join(user);
|
closestItem.join(user);
|
||||||
|
closestItem.setOutOfBounds(false);
|
||||||
} else {
|
} else {
|
||||||
const closestUser: User = closestItem;
|
const closestUser: User = closestItem;
|
||||||
const group: Group = new Group(
|
const group: Group = new Group(
|
||||||
@ -249,32 +248,95 @@ export class GameRoom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the user is part of a group:
|
let hasKickOutSomeone = false;
|
||||||
// should he leave the group?
|
let followingMembers: User[] = [];
|
||||||
let noOneOutOfBounds = true;
|
|
||||||
group.getUsers().forEach((foreignUser: User) => {
|
const previewNewGroupPosition = group.previewGroupPosition();
|
||||||
if (foreignUser.group === undefined) {
|
|
||||||
return;
|
if (!previewNewGroupPosition) {
|
||||||
|
this.leaveGroup(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.hasFollowers() || user.following) {
|
||||||
|
followingMembers = user.hasFollowers()
|
||||||
|
? group.getUsers().filter((currentUser) => currentUser.following === user)
|
||||||
|
: group.getUsers().filter((currentUser) => currentUser.following === user.following);
|
||||||
|
|
||||||
|
// If all group members are part of the same follow group
|
||||||
|
if (group.getUsers().length - 1 === followingMembers.length) {
|
||||||
|
let isOutOfBounds = false;
|
||||||
|
|
||||||
|
// If a follower is far away from the leader, "outOfBounds" is set to true
|
||||||
|
for (const member of followingMembers) {
|
||||||
|
const distance = GameRoom.computeDistanceBetweenPositions(
|
||||||
|
member.getPosition(),
|
||||||
|
previewNewGroupPosition
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distance > this.groupRadius) {
|
||||||
|
isOutOfBounds = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.setOutOfBounds(isOutOfBounds);
|
||||||
}
|
}
|
||||||
const usrPos = foreignUser.getPosition();
|
}
|
||||||
const grpPos = foreignUser.group.getPosition();
|
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos);
|
// Check if the moving user has kicked out another user
|
||||||
|
for (const headMember of group.getGroupHeads()) {
|
||||||
|
if (!headMember.group) {
|
||||||
|
this.leaveGroup(headMember);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headPosition = headMember.getPosition();
|
||||||
|
const distance = GameRoom.computeDistanceBetweenPositions(headPosition, previewNewGroupPosition);
|
||||||
|
|
||||||
if (distance > this.groupRadius) {
|
if (distance > this.groupRadius) {
|
||||||
if (foreignUser.hasFollowers() || foreignUser.following) {
|
hasKickOutSomeone = true;
|
||||||
// If one user is out of the group bounds BUT following, the group still exists... but should be hidden.
|
break;
|
||||||
// We put it in 'outOfBounds' mode
|
}
|
||||||
group.setOutOfBounds(true);
|
}
|
||||||
noOneOutOfBounds = false;
|
|
||||||
} else {
|
/**
|
||||||
this.leaveGroup(foreignUser);
|
* If the current moving user has kicked another user from the radius,
|
||||||
}
|
* the moving user leaves the group because he is too far away.
|
||||||
|
*/
|
||||||
|
const userDistance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), previewNewGroupPosition);
|
||||||
|
|
||||||
|
if (hasKickOutSomeone && userDistance > this.groupRadius) {
|
||||||
|
if (user.hasFollowers() && group.getUsers().length === 3 && followingMembers.length === 1) {
|
||||||
|
const other = group
|
||||||
|
.getUsers()
|
||||||
|
.find((currentUser) => !currentUser.hasFollowers() && !currentUser.following);
|
||||||
|
if (other) {
|
||||||
|
this.leaveGroup(other);
|
||||||
|
}
|
||||||
|
} else if (user.hasFollowers()) {
|
||||||
|
this.leaveGroup(user);
|
||||||
|
for (const member of followingMembers) {
|
||||||
|
this.leaveGroup(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-create a group with the followers
|
||||||
|
const newGroup: Group = new Group(
|
||||||
|
this.roomUrl,
|
||||||
|
[user, ...followingMembers],
|
||||||
|
this.groupRadius,
|
||||||
|
this.connectCallback,
|
||||||
|
this.disconnectCallback,
|
||||||
|
this.positionNotifier
|
||||||
|
);
|
||||||
|
this.groups.add(newGroup);
|
||||||
|
} else {
|
||||||
|
this.leaveGroup(user);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (noOneOutOfBounds && !user.group?.isEmpty()) {
|
|
||||||
group.setOutOfBounds(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.group?.updatePosition();
|
||||||
|
user.group?.searchForNearbyUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void {
|
public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void {
|
||||||
|
@ -59,6 +59,39 @@ export class Group implements Movable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of users of the group, ignoring any "followers".
|
||||||
|
* Useful to compute the position of the group if a follower is "trapped" far away from the the leader.
|
||||||
|
*/
|
||||||
|
getGroupHeads(): User[] {
|
||||||
|
return Array.from(this.users).filter((user) => user.group?.leader === user || !user.following);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview the position of the group but don't update it
|
||||||
|
*/
|
||||||
|
previewGroupPosition(): { x: number; y: number } | undefined {
|
||||||
|
const users = this.getGroupHeads();
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
users.forEach((user: User) => {
|
||||||
|
const position = user.getPosition();
|
||||||
|
x += position.x;
|
||||||
|
y += position.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
x /= users.length;
|
||||||
|
y /= users.length;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the barycenter of all users (i.e. the center of the group)
|
* Computes the barycenter of all users (i.e. the center of the group)
|
||||||
*/
|
*/
|
||||||
@ -66,19 +99,15 @@ export class Group implements Movable {
|
|||||||
const oldX = this.x;
|
const oldX = this.x;
|
||||||
const oldY = this.y;
|
const oldY = this.y;
|
||||||
|
|
||||||
let x = 0;
|
|
||||||
let y = 0;
|
|
||||||
// Let's compute the barycenter of all users.
|
// Let's compute the barycenter of all users.
|
||||||
this.users.forEach((user: User) => {
|
const newPosition = this.previewGroupPosition();
|
||||||
const position = user.getPosition();
|
|
||||||
x += position.x;
|
if (!newPosition) {
|
||||||
y += position.y;
|
return;
|
||||||
});
|
|
||||||
x /= this.users.size;
|
|
||||||
y /= this.users.size;
|
|
||||||
if (this.users.size === 0) {
|
|
||||||
throw new Error("EMPTY GROUP FOUND!!!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { x, y } = newPosition;
|
||||||
|
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
|
|
||||||
@ -97,10 +126,12 @@ export class Group implements Movable {
|
|||||||
if (!this.currentZone) return;
|
if (!this.currentZone) return;
|
||||||
|
|
||||||
for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
|
for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
|
||||||
|
// Todo: Merge two groups with a leader
|
||||||
if (user.group || this.isFull()) return; //we ignore users that are already in a group.
|
if (user.group || this.isFull()) return; //we ignore users that are already in a group.
|
||||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition());
|
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition());
|
||||||
if (distance < this.groupRadius) {
|
if (distance < this.groupRadius) {
|
||||||
this.join(user);
|
this.join(user);
|
||||||
|
this.setOutOfBounds(false);
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,4 +207,8 @@ export class Group implements Movable {
|
|||||||
this.outOfBounds = true;
|
this.outOfBounds = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get getOutOfBounds() {
|
||||||
|
return this.outOfBounds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import "jasmine";
|
import "jasmine";
|
||||||
import {PositionNotifier} from "../src/Model/PositionNotifier";
|
import { PositionNotifier } from "../src/Model/PositionNotifier";
|
||||||
import {User, UserSocket} from "../src/Model/User";
|
import { User, UserSocket } from "../src/Model/User";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../src/RoomManager";
|
import { ZoneSocket } from "../src/RoomManager";
|
||||||
|
|
||||||
|
|
||||||
describe("PositionNotifier", () => {
|
describe("PositionNotifier", () => {
|
||||||
it("should receive notifications when player moves", () => {
|
it("should receive notifications when player moves", () => {
|
||||||
@ -13,28 +12,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
enterTriggered = true;
|
300,
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
300,
|
||||||
moveTriggered = true;
|
(thing: Movable) => {
|
||||||
}, (thing: Movable) => {
|
enterTriggered = true;
|
||||||
leaveTriggered = true;
|
},
|
||||||
}, () => {},
|
(thing: Movable, position: PositionInterface) => {
|
||||||
() => {});
|
moveTriggered = true;
|
||||||
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
|
leaveTriggered = true;
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
x: 500,
|
1,
|
||||||
y: 500,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
x: -9999,
|
2,
|
||||||
y: -9999,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: -9999,
|
||||||
|
y: -9999,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
||||||
@ -47,21 +77,21 @@ describe("PositionNotifier", () => {
|
|||||||
bottom: 500
|
bottom: 500
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
// Move inside the zone
|
// Move inside the zone
|
||||||
user2.setPosition({x:501, y:500, direction: 'down', moving: false});
|
user2.setPosition({ x: 501, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(true);
|
expect(moveTriggered).toBe(true);
|
||||||
moveTriggered = false;
|
moveTriggered = false;
|
||||||
|
|
||||||
// Move out of the zone in a zone that we don't track
|
// Move out of the zone in a zone that we don't track
|
||||||
user2.setPosition({x: 901, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 901, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -69,7 +99,7 @@ describe("PositionNotifier", () => {
|
|||||||
leaveTriggered = false;
|
leaveTriggered = false;
|
||||||
|
|
||||||
// Move back in
|
// Move back in
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
expect(leaveTriggered).toBe(false);
|
expect(leaveTriggered).toBe(false);
|
||||||
@ -89,28 +119,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable, fromZone: Zone|null ) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
enterTriggered = true;
|
300,
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
300,
|
||||||
moveTriggered = true;
|
(thing: Movable, fromZone: Zone | null) => {
|
||||||
}, (thing: Movable) => {
|
enterTriggered = true;
|
||||||
leaveTriggered = true;
|
},
|
||||||
}, () => {},
|
(thing: Movable, position: PositionInterface) => {
|
||||||
() => {});
|
moveTriggered = true;
|
||||||
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
|
leaveTriggered = true;
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
x: 500,
|
1,
|
||||||
y: 500,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
x: 0,
|
2,
|
||||||
y: 0,
|
"test",
|
||||||
moving: false,
|
"10.0.0.2",
|
||||||
direction: 'down'
|
{
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
moving: false,
|
||||||
|
direction: "down",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const listener = {} as ZoneSocket;
|
const listener = {} as ZoneSocket;
|
||||||
positionNotifier.addZoneListener(listener, 0, 0);
|
positionNotifier.addZoneListener(listener, 0, 0);
|
||||||
@ -126,14 +187,12 @@ describe("PositionNotifier", () => {
|
|||||||
positionNotifier.enter(user1);
|
positionNotifier.enter(user1);
|
||||||
positionNotifier.enter(user2);
|
positionNotifier.enter(user2);
|
||||||
|
|
||||||
|
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
|
|
||||||
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -184,4 +243,4 @@ describe("PositionNotifier", () => {
|
|||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
@ -1,6 +1,32 @@
|
|||||||
{.section-title.accent.text-primary}
|
{.section-title.accent.text-primary}
|
||||||
|
|
||||||
# API Camera functions Reference
|
# API Camera functions Reference
|
||||||
|
|
||||||
|
### Start following player
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.camera.followPlayer(smooth: boolean): void
|
||||||
|
```
|
||||||
|
Set camera to follow the player. Set `smooth` to true for smooth transition.
|
||||||
|
|
||||||
|
### Set spot for camera to look at
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.camera.set(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
lock: boolean = false,
|
||||||
|
smooth: boolean = false,
|
||||||
|
): void
|
||||||
|
```
|
||||||
|
|
||||||
|
Set camera to look at given spot.
|
||||||
|
Setting `width` and `height` will adjust zoom.
|
||||||
|
Set `lock` to true to lock camera in this position.
|
||||||
|
Set `smooth` to true for smooth transition.
|
||||||
|
|
||||||
### Listen to camera updates
|
### Listen to camera updates
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -58,6 +58,27 @@ WA.onInit().then(() => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get the position of the player
|
||||||
|
```
|
||||||
|
WA.player.getPosition(): Promise<Position>
|
||||||
|
```
|
||||||
|
The player's current position is available using the `WA.player.getPosition()` function.
|
||||||
|
|
||||||
|
`Position` has the following attributes :
|
||||||
|
* **x (number) :** The coordinate x of the current player's position.
|
||||||
|
* **y (number) :** The coordinate y of the current player's position.
|
||||||
|
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
You need to wait for the end of the initialization before calling `WA.player.getPosition()`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
WA.onInit().then(() => {
|
||||||
|
console.log('Position: ', WA.player.getPosition());
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Get the user-room token of the player
|
### Get the user-room token of the player
|
||||||
|
|
||||||
```
|
```
|
||||||
|
11
front/src/Api/Events/CameraFollowPlayerEvent.ts
Normal file
11
front/src/Api/Events/CameraFollowPlayerEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isCameraFollowPlayerEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
smooth: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to make the camera follow player.
|
||||||
|
*/
|
||||||
|
export type CameraFollowPlayerEvent = tg.GuardedType<typeof isCameraFollowPlayerEvent>;
|
16
front/src/Api/Events/CameraSetEvent.ts
Normal file
16
front/src/Api/Events/CameraSetEvent.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isCameraSetEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
width: tg.isOptional(tg.isNumber),
|
||||||
|
height: tg.isOptional(tg.isNumber),
|
||||||
|
lock: tg.isBoolean,
|
||||||
|
smooth: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to change the camera position.
|
||||||
|
*/
|
||||||
|
export type CameraSetEvent = tg.GuardedType<typeof isCameraSetEvent>;
|
@ -9,8 +9,8 @@ export const isGameStateEvent = new tg.IsInterface()
|
|||||||
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
||||||
tags: tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
variables: tg.isObject,
|
variables: tg.isObject,
|
||||||
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
|
|
||||||
playerVariables: tg.isObject,
|
playerVariables: tg.isObject,
|
||||||
|
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
/**
|
/**
|
||||||
|
@ -28,10 +28,12 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent";
|
|||||||
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
||||||
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
||||||
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
||||||
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
|
||||||
import { isColorEvent } from "./ColorEvent";
|
|
||||||
import { isPlayerPosition } from "./PlayerPosition";
|
import { isPlayerPosition } from "./PlayerPosition";
|
||||||
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
||||||
|
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
||||||
|
import type { CameraSetEvent } from "./CameraSetEvent";
|
||||||
|
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
||||||
|
import { isColorEvent } from "./ColorEvent";
|
||||||
import { isGetPropertyEvent } from "./GetPropertyEvent";
|
import { isGetPropertyEvent } from "./GetPropertyEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
@ -44,6 +46,8 @@ export interface TypedMessageEvent<T> extends MessageEvent {
|
|||||||
export type IframeEventMap = {
|
export type IframeEventMap = {
|
||||||
loadPage: LoadPageEvent;
|
loadPage: LoadPageEvent;
|
||||||
chat: ChatEvent;
|
chat: ChatEvent;
|
||||||
|
cameraFollowPlayer: CameraFollowPlayerEvent;
|
||||||
|
cameraSet: CameraSetEvent;
|
||||||
openPopup: OpenPopupEvent;
|
openPopup: OpenPopupEvent;
|
||||||
closePopup: ClosePopupEvent;
|
closePopup: ClosePopupEvent;
|
||||||
openTab: OpenTabEvent;
|
openTab: OpenTabEvent;
|
||||||
|
@ -33,6 +33,8 @@ import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Store
|
|||||||
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
|
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
|
||||||
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
||||||
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
||||||
|
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent";
|
||||||
|
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
query: IframeQueryMap[T]["query"],
|
query: IframeQueryMap[T]["query"],
|
||||||
@ -56,6 +58,12 @@ class IframeListener {
|
|||||||
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||||
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _cameraSetStream: Subject<CameraSetEvent> = new Subject();
|
||||||
|
public readonly cameraSetStream = this._cameraSetStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject();
|
||||||
|
public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable();
|
||||||
|
|
||||||
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||||
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
@ -205,6 +213,10 @@ class IframeListener {
|
|||||||
this._hideLayerStream.next(payload.data);
|
this._hideLayerStream.next(payload.data);
|
||||||
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||||
this._setPropertyStream.next(payload.data);
|
this._setPropertyStream.next(payload.data);
|
||||||
|
} else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) {
|
||||||
|
this._cameraSetStream.next(payload.data);
|
||||||
|
} else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) {
|
||||||
|
this._cameraFollowPlayerStream.next(payload.data);
|
||||||
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||||
scriptUtils.sendAnonymousChat(payload.data);
|
scriptUtils.sendAnonymousChat(payload.data);
|
||||||
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||||
|
@ -17,6 +17,27 @@ export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdven
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public set(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
lock: boolean = false,
|
||||||
|
smooth: boolean = false
|
||||||
|
): void {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "cameraSet",
|
||||||
|
data: { x, y, width, height, lock, smooth },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public followPlayer(smooth: boolean = false): void {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "cameraFollowPlayer",
|
||||||
|
data: { smooth },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: "onCameraUpdate",
|
type: "onCameraUpdate",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { ChatEvent } from "../Events/ChatEvent";
|
|
||||||
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
|
@ -1,28 +1,49 @@
|
|||||||
import { Easing } from "../../types";
|
import { Easing } from "../../types";
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import type { Box } from "../../WebRtc/LayoutManager";
|
import type { Box } from "../../WebRtc/LayoutManager";
|
||||||
import type { Player } from "../Player/Player";
|
import { hasMovedEventName, Player } from "../Player/Player";
|
||||||
import type { WaScaleManager } from "../Services/WaScaleManager";
|
import { WaScaleManager, WaScaleManagerEvent, WaScaleManagerFocusTarget } from "../Services/WaScaleManager";
|
||||||
import type { GameScene } from "./GameScene";
|
import type { GameScene } from "./GameScene";
|
||||||
|
|
||||||
export enum CameraMode {
|
export enum CameraMode {
|
||||||
Free = "Free",
|
/**
|
||||||
|
* Camera looks at certain point but is not locked and will start following the player on his movement
|
||||||
|
*/
|
||||||
|
Positioned = "Positioned",
|
||||||
|
/**
|
||||||
|
* Camera is actively following the player
|
||||||
|
*/
|
||||||
Follow = "Follow",
|
Follow = "Follow",
|
||||||
|
/**
|
||||||
|
* Camera is focusing on certain point and will not break this focus even on player movement
|
||||||
|
*/
|
||||||
Focus = "Focus",
|
Focus = "Focus",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CameraManagerEvent {
|
||||||
|
CameraUpdate = "CameraUpdate",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CameraManagerEventCameraUpdateData {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class CameraManager extends Phaser.Events.EventEmitter {
|
export class CameraManager extends Phaser.Events.EventEmitter {
|
||||||
private scene: GameScene;
|
private scene: GameScene;
|
||||||
private camera: Phaser.Cameras.Scene2D.Camera;
|
private camera: Phaser.Cameras.Scene2D.Camera;
|
||||||
private cameraBounds: { x: number; y: number };
|
private cameraBounds: { x: number; y: number };
|
||||||
private waScaleManager: WaScaleManager;
|
private waScaleManager: WaScaleManager;
|
||||||
|
|
||||||
private cameraMode: CameraMode = CameraMode.Free;
|
private cameraMode: CameraMode = CameraMode.Positioned;
|
||||||
|
|
||||||
private restoreZoomTween?: Phaser.Tweens.Tween;
|
private restoreZoomTween?: Phaser.Tweens.Tween;
|
||||||
private startFollowTween?: Phaser.Tweens.Tween;
|
private startFollowTween?: Phaser.Tweens.Tween;
|
||||||
|
|
||||||
private cameraFollowTarget?: { x: number; y: number };
|
private playerToFollow?: Player;
|
||||||
private cameraLocked: boolean;
|
private cameraLocked: boolean;
|
||||||
|
|
||||||
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
constructor(scene: GameScene, cameraBounds: { x: number; y: number }, waScaleManager: WaScaleManager) {
|
||||||
@ -41,7 +62,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
this.scene.game.events.off("wa-scale-manager:refresh-focus-on-target");
|
this.scene.game.events.off(WaScaleManagerEvent.RefreshFocusOnTarget);
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,52 +70,96 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
return this.camera;
|
return this.camera;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enterFocusMode(
|
/**
|
||||||
focusOn: { x: number; y: number; width: number; height: number },
|
* Set camera view to specific destination without changing current camera mode. Won't work if camera mode is set to Focus.
|
||||||
margin: number = 0,
|
* @param setTo Viewport on which the camera should set the position
|
||||||
duration: number = 1000
|
* @param duration Time for the transition im MS. If set to 0, transition will occur immediately
|
||||||
): void {
|
*/
|
||||||
|
public setPosition(setTo: WaScaleManagerFocusTarget, duration: number = 1000): void {
|
||||||
|
if (this.cameraMode === CameraMode.Focus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setCameraMode(CameraMode.Positioned);
|
||||||
|
this.waScaleManager.saveZoom();
|
||||||
|
this.camera.stopFollow();
|
||||||
|
|
||||||
|
const currentZoomModifier = this.waScaleManager.zoomModifier;
|
||||||
|
const zoomModifierChange = this.getZoomModifierChange(setTo.width, setTo.height);
|
||||||
|
|
||||||
|
if (duration === 0) {
|
||||||
|
this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange;
|
||||||
|
this.camera.centerOn(setTo.x, setTo.y);
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
|
this.playerToFollow?.once(hasMovedEventName, () => {
|
||||||
|
if (this.playerToFollow) {
|
||||||
|
this.startFollowPlayer(this.playerToFollow, duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.camera.pan(setTo.x, setTo.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
||||||
|
if (this.cameraMode === CameraMode.Positioned) {
|
||||||
|
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
|
}
|
||||||
|
if (progress === 1) {
|
||||||
|
this.playerToFollow?.once(hasMovedEventName, () => {
|
||||||
|
if (this.playerToFollow) {
|
||||||
|
this.startFollowPlayer(this.playerToFollow, duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set camera to focus mode. As long as the camera is in the Focus mode, its view cannot be changed.
|
||||||
|
* @param setTo Viewport on which the camera should focus on
|
||||||
|
* @param duration Time for the transition im MS. If set to 0, transition will occur immediately
|
||||||
|
*/
|
||||||
|
public enterFocusMode(focusOn: WaScaleManagerFocusTarget, margin: number = 0, duration: number = 1000): void {
|
||||||
this.setCameraMode(CameraMode.Focus);
|
this.setCameraMode(CameraMode.Focus);
|
||||||
this.waScaleManager.saveZoom();
|
this.waScaleManager.saveZoom();
|
||||||
this.waScaleManager.setFocusTarget(focusOn);
|
this.waScaleManager.setFocusTarget(focusOn);
|
||||||
|
|
||||||
this.cameraLocked = true;
|
this.cameraLocked = true;
|
||||||
|
|
||||||
this.unlockCameraWithDelay(duration);
|
this.unlockCameraWithDelay(duration);
|
||||||
this.restoreZoomTween?.stop();
|
this.restoreZoomTween?.stop();
|
||||||
this.startFollowTween?.stop();
|
this.startFollowTween?.stop();
|
||||||
const marginMult = 1 + margin;
|
|
||||||
const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor(
|
|
||||||
focusOn.width * marginMult,
|
|
||||||
focusOn.height * marginMult
|
|
||||||
);
|
|
||||||
const currentZoomModifier = this.waScaleManager.zoomModifier;
|
|
||||||
const zoomModifierChange = targetZoomModifier - currentZoomModifier;
|
|
||||||
this.camera.stopFollow();
|
this.camera.stopFollow();
|
||||||
this.cameraFollowTarget = undefined;
|
this.playerToFollow = undefined;
|
||||||
this.camera.pan(
|
|
||||||
focusOn.x + focusOn.width * 0.5 * marginMult,
|
const currentZoomModifier = this.waScaleManager.zoomModifier;
|
||||||
focusOn.y + focusOn.height * 0.5 * marginMult,
|
const zoomModifierChange = this.getZoomModifierChange(focusOn.width, focusOn.height, 1 + margin);
|
||||||
duration,
|
|
||||||
Easing.SineEaseOut,
|
if (duration === 0) {
|
||||||
true,
|
this.waScaleManager.zoomModifier = currentZoomModifier + zoomModifierChange;
|
||||||
(camera, progress, x, y) => {
|
this.camera.centerOn(focusOn.x, focusOn.y);
|
||||||
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.camera.pan(focusOn.x, focusOn.y, duration, Easing.SineEaseOut, true, (camera, progress, x, y) => {
|
||||||
|
this.waScaleManager.zoomModifier = currentZoomModifier + progress * zoomModifierChange;
|
||||||
|
if (progress === 1) {
|
||||||
|
// NOTE: Making sure the last action will be centering after zoom change
|
||||||
|
this.camera.centerOn(focusOn.x, focusOn.y);
|
||||||
}
|
}
|
||||||
);
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public leaveFocusMode(player: Player, duration = 0): void {
|
public leaveFocusMode(player: Player, duration = 0): void {
|
||||||
this.waScaleManager.setFocusTarget();
|
this.waScaleManager.setFocusTarget();
|
||||||
this.unlockCameraWithDelay(duration);
|
this.unlockCameraWithDelay(duration);
|
||||||
this.startFollow(player, duration);
|
this.startFollowPlayer(player, duration);
|
||||||
this.restoreZoom(duration);
|
this.restoreZoom(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public startFollow(target: object | Phaser.GameObjects.GameObject, duration: number = 0): void {
|
public startFollowPlayer(player: Player, duration: number = 0): void {
|
||||||
this.cameraFollowTarget = target as { x: number; y: number };
|
this.playerToFollow = player;
|
||||||
this.setCameraMode(CameraMode.Follow);
|
this.setCameraMode(CameraMode.Follow);
|
||||||
if (duration === 0) {
|
if (duration === 0) {
|
||||||
this.camera.startFollow(target, true);
|
this.camera.startFollow(player, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY };
|
const oldPos = { x: this.camera.scrollX, y: this.camera.scrollY };
|
||||||
@ -104,17 +169,18 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
duration,
|
duration,
|
||||||
ease: Easing.SineEaseOut,
|
ease: Easing.SineEaseOut,
|
||||||
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
||||||
if (!this.cameraFollowTarget) {
|
if (!this.playerToFollow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const shiftX =
|
const shiftX =
|
||||||
(this.cameraFollowTarget.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue();
|
(this.playerToFollow.x - this.camera.worldView.width * 0.5 - oldPos.x) * tween.getValue();
|
||||||
const shiftY =
|
const shiftY =
|
||||||
(this.cameraFollowTarget.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue();
|
(this.playerToFollow.y - this.camera.worldView.height * 0.5 - oldPos.y) * tween.getValue();
|
||||||
this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY);
|
this.camera.setScroll(oldPos.x + shiftX, oldPos.y + shiftY);
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
this.camera.startFollow(target, true);
|
this.camera.startFollow(player, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -140,6 +206,18 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
return this.cameraLocked;
|
return this.cameraLocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getZoomModifierChange(width?: number, height?: number, multiplier: number = 1): number {
|
||||||
|
if (!width || !height) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const targetZoomModifier = this.waScaleManager.getTargetZoomModifierFor(
|
||||||
|
width * multiplier,
|
||||||
|
height * multiplier
|
||||||
|
);
|
||||||
|
const currentZoomModifier = this.waScaleManager.zoomModifier;
|
||||||
|
return targetZoomModifier - currentZoomModifier;
|
||||||
|
}
|
||||||
|
|
||||||
private unlockCameraWithDelay(delay: number): void {
|
private unlockCameraWithDelay(delay: number): void {
|
||||||
this.scene.time.delayedCall(delay, () => {
|
this.scene.time.delayedCall(delay, () => {
|
||||||
this.cameraLocked = false;
|
this.cameraLocked = false;
|
||||||
@ -166,6 +244,7 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
ease: Easing.SineEaseOut,
|
ease: Easing.SineEaseOut,
|
||||||
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
onUpdate: (tween: Phaser.Tweens.Tween) => {
|
||||||
this.waScaleManager.zoomModifier = tween.getValue();
|
this.waScaleManager.zoomModifier = tween.getValue();
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -177,13 +256,28 @@ export class CameraManager extends Phaser.Events.EventEmitter {
|
|||||||
|
|
||||||
private bindEventHandlers(): void {
|
private bindEventHandlers(): void {
|
||||||
this.scene.game.events.on(
|
this.scene.game.events.on(
|
||||||
"wa-scale-manager:refresh-focus-on-target",
|
WaScaleManagerEvent.RefreshFocusOnTarget,
|
||||||
(focusOn: { x: number; y: number; width: number; height: number }) => {
|
(focusOn: { x: number; y: number; width: number; height: number }) => {
|
||||||
if (!focusOn) {
|
if (!focusOn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.camera.centerOn(focusOn.x + focusOn.width * 0.5, focusOn.y + focusOn.height * 0.5);
|
this.camera.centerOn(focusOn.x, focusOn.y);
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.camera.on("followupdate", () => {
|
||||||
|
this.emit(CameraManagerEvent.CameraUpdate, this.getCameraUpdateEventData());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCameraUpdateEventData(): CameraManagerEventCameraUpdateData {
|
||||||
|
return {
|
||||||
|
x: this.camera.worldView.x,
|
||||||
|
y: this.camera.worldView.y,
|
||||||
|
width: this.camera.worldView.width,
|
||||||
|
height: this.camera.worldView.height,
|
||||||
|
zoom: this.camera.scaleManager.zoom,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ import type { ActionableItem } from "../Items/ActionableItem";
|
|||||||
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
||||||
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
||||||
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
||||||
import { CameraManager } from "./CameraManager";
|
import { CameraManager, CameraManagerEvent, CameraManagerEventCameraUpdateData } from "./CameraManager";
|
||||||
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent";
|
||||||
import type { Character } from "../Entity/Character";
|
import type { Character } from "../Entity/Character";
|
||||||
|
|
||||||
@ -75,6 +75,7 @@ import { playersStore } from "../../Stores/PlayersStore";
|
|||||||
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
import { contactPageStore } from "../../Stores/MenuStore";
|
import { contactPageStore } from "../../Stores/MenuStore";
|
||||||
|
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
||||||
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||||
|
|
||||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||||
@ -91,7 +92,6 @@ import { MapStore } from "../../Stores/Utils/MapStore";
|
|||||||
import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore";
|
import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator";
|
import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator";
|
||||||
import Camera = Phaser.Cameras.Scene2D.Camera;
|
import Camera = Phaser.Cameras.Scene2D.Camera;
|
||||||
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -586,7 +586,7 @@ export class GameScene extends DirtyScene {
|
|||||||
waScaleManager
|
waScaleManager
|
||||||
);
|
);
|
||||||
biggestAvailableAreaStore.recompute();
|
biggestAvailableAreaStore.recompute();
|
||||||
this.cameraManager.startFollow(this.CurrentPlayer);
|
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
||||||
|
|
||||||
this.animatedTiles.init(this.Map);
|
this.animatedTiles.init(this.Map);
|
||||||
this.events.on("tileanimationupdate", () => (this.dirty = true));
|
this.events.on("tileanimationupdate", () => (this.dirty = true));
|
||||||
@ -860,7 +860,12 @@ export class GameScene extends DirtyScene {
|
|||||||
if (focusable && focusable.value === true) {
|
if (focusable && focusable.value === true) {
|
||||||
const zoomMargin = zone.properties?.find((property) => property.name === "zoom_margin");
|
const zoomMargin = zone.properties?.find((property) => property.name === "zoom_margin");
|
||||||
this.cameraManager.enterFocusMode(
|
this.cameraManager.enterFocusMode(
|
||||||
zone,
|
{
|
||||||
|
x: zone.x + zone.width * 0.5,
|
||||||
|
y: zone.y + zone.height * 0.5,
|
||||||
|
width: zone.width,
|
||||||
|
height: zone.height,
|
||||||
|
},
|
||||||
zoomMargin ? Math.max(0, Number(zoomMargin.value)) : undefined
|
zoomMargin ? Math.max(0, Number(zoomMargin.value)) : undefined
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@ -1143,6 +1148,21 @@ export class GameScene extends DirtyScene {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(
|
||||||
|
iframeListener.cameraSetStream.subscribe((cameraSetEvent) => {
|
||||||
|
const duration = cameraSetEvent.smooth ? 1000 : 0;
|
||||||
|
cameraSetEvent.lock
|
||||||
|
? this.cameraManager.enterFocusMode({ ...cameraSetEvent }, undefined, duration)
|
||||||
|
: this.cameraManager.setPosition({ ...cameraSetEvent }, duration);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(
|
||||||
|
iframeListener.cameraFollowPlayerStream.subscribe((cameraFollowPlayerEvent) => {
|
||||||
|
this.cameraManager.leaveFocusMode(this.CurrentPlayer, cameraFollowPlayerEvent.smooth ? 1000 : 0);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.iframeSubscriptionList.push(
|
this.iframeSubscriptionList.push(
|
||||||
iframeListener.playSoundStream.subscribe((playSoundEvent) => {
|
iframeListener.playSoundStream.subscribe((playSoundEvent) => {
|
||||||
const url = new URL(playSoundEvent.url, this.MapUrlFile);
|
const url = new URL(playSoundEvent.url, this.MapUrlFile);
|
||||||
@ -1155,30 +1175,31 @@ export class GameScene extends DirtyScene {
|
|||||||
this.iframeSubscriptionList.push(
|
this.iframeSubscriptionList.push(
|
||||||
iframeListener.trackCameraUpdateStream.subscribe(() => {
|
iframeListener.trackCameraUpdateStream.subscribe(() => {
|
||||||
if (!this.firstCameraUpdateSent) {
|
if (!this.firstCameraUpdateSent) {
|
||||||
this.cameras.main.on("followupdate", (camera: Camera) => {
|
this.cameraManager.on(
|
||||||
const cameraEvent: WasCameraUpdatedEvent = {
|
CameraManagerEvent.CameraUpdate,
|
||||||
x: camera.worldView.x,
|
(data: CameraManagerEventCameraUpdateData) => {
|
||||||
y: camera.worldView.y,
|
const cameraEvent: WasCameraUpdatedEvent = {
|
||||||
width: camera.worldView.width,
|
x: data.x,
|
||||||
height: camera.worldView.height,
|
y: data.y,
|
||||||
zoom: camera.scaleManager.zoom,
|
width: data.width,
|
||||||
};
|
height: data.height,
|
||||||
if (
|
zoom: data.zoom,
|
||||||
this.lastCameraEvent?.x == cameraEvent.x &&
|
};
|
||||||
this.lastCameraEvent?.y == cameraEvent.y &&
|
if (
|
||||||
this.lastCameraEvent?.width == cameraEvent.width &&
|
this.lastCameraEvent?.x == cameraEvent.x &&
|
||||||
this.lastCameraEvent?.height == cameraEvent.height &&
|
this.lastCameraEvent?.y == cameraEvent.y &&
|
||||||
this.lastCameraEvent?.zoom == cameraEvent.zoom
|
this.lastCameraEvent?.width == cameraEvent.width &&
|
||||||
) {
|
this.lastCameraEvent?.height == cameraEvent.height &&
|
||||||
return;
|
this.lastCameraEvent?.zoom == cameraEvent.zoom
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastCameraEvent = cameraEvent;
|
||||||
|
iframeListener.sendCameraUpdated(cameraEvent);
|
||||||
|
this.firstCameraUpdateSent = true;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
this.lastCameraEvent = cameraEvent;
|
|
||||||
iframeListener.sendCameraUpdated(cameraEvent);
|
|
||||||
this.firstCameraUpdateSent = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
iframeListener.sendCameraUpdated(this.cameras.main);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,8 @@ export enum WaScaleManagerEvent {
|
|||||||
RefreshFocusOnTarget = "wa-scale-manager:refresh-focus-on-target",
|
RefreshFocusOnTarget = "wa-scale-manager:refresh-focus-on-target",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WaScaleManagerFocusTarget = { x: number; y: number; width?: number; height?: number };
|
||||||
|
|
||||||
export class WaScaleManager {
|
export class WaScaleManager {
|
||||||
private hdpiManager: HdpiManager;
|
private hdpiManager: HdpiManager;
|
||||||
private scaleManager!: ScaleManager;
|
private scaleManager!: ScaleManager;
|
||||||
@ -16,7 +18,7 @@ export class WaScaleManager {
|
|||||||
private actualZoom: number = 1;
|
private actualZoom: number = 1;
|
||||||
private _saveZoom: number = 1;
|
private _saveZoom: number = 1;
|
||||||
|
|
||||||
private focusTarget?: { x: number; y: number; width: number; height: number };
|
private focusTarget?: WaScaleManagerFocusTarget;
|
||||||
|
|
||||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||||
@ -72,11 +74,13 @@ export class WaScaleManager {
|
|||||||
if (!this.focusTarget) {
|
if (!this.focusTarget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.zoomModifier = this.getTargetZoomModifierFor(this.focusTarget.width, this.focusTarget.height);
|
if (this.focusTarget.width && this.focusTarget.height) {
|
||||||
|
this.zoomModifier = this.getTargetZoomModifierFor(this.focusTarget.width, this.focusTarget.height);
|
||||||
|
}
|
||||||
this.game.events.emit(WaScaleManagerEvent.RefreshFocusOnTarget, this.focusTarget);
|
this.game.events.emit(WaScaleManagerEvent.RefreshFocusOnTarget, this.focusTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFocusTarget(targetDimensions?: { x: number; y: number; width: number; height: number }): void {
|
public setFocusTarget(targetDimensions?: WaScaleManagerFocusTarget): void {
|
||||||
this.focusTarget = targetDimensions;
|
this.focusTarget = targetDimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +113,7 @@ export class WaScaleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFocusTarget(): { x: number; y: number; width: number; height: number } | undefined {
|
public getFocusTarget(): WaScaleManagerFocusTarget | undefined {
|
||||||
return this.focusTarget;
|
return this.focusTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ const initPromise = queryWorkadventure({
|
|||||||
setMapURL(gameState.mapUrl);
|
setMapURL(gameState.mapUrl);
|
||||||
setTags(gameState.tags);
|
setTags(gameState.tags);
|
||||||
setUuid(gameState.uuid);
|
setUuid(gameState.uuid);
|
||||||
|
setUserRoomToken(gameState.userRoomToken);
|
||||||
globalState.initVariables(gameState.variables as Map<string, unknown>);
|
globalState.initVariables(gameState.variables as Map<string, unknown>);
|
||||||
player.state.initVariables(gameState.playerVariables as Map<string, unknown>);
|
player.state.initVariables(gameState.playerVariables as Map<string, unknown>);
|
||||||
setUserRoomToken(gameState.userRoomToken);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const wa = {
|
const wa = {
|
||||||
|
188
maps/tests/CameraApi/camera_api_test.json
Normal file
188
maps/tests/CameraApi/camera_api_test.json
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":30,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":30,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"script.php"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteAllowApi",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":30,
|
||||||
|
"id":6,
|
||||||
|
"name":"furnitures",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"height":30,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":115.775966464835,
|
||||||
|
"id":1,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"MS Shell Dlg 2",
|
||||||
|
"pixelsize":21,
|
||||||
|
"text":"Set Viewport - Move the camera to the given point. It will be locked if Lock checkbox is checked",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":315.4375,
|
||||||
|
"x":68.4021076998051,
|
||||||
|
"y":160.733918128655
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":115.776,
|
||||||
|
"id":4,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"MS Shell Dlg 2",
|
||||||
|
"pixelsize":21,
|
||||||
|
"text":"Follow Player - Camera will start following the player if doesn't do this already",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":315.438,
|
||||||
|
"x":64.7309301350722,
|
||||||
|
"y":256.224715416861
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":30.8202477876106,
|
||||||
|
"id":8,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"MS Shell Dlg 2",
|
||||||
|
"pixelsize":21,
|
||||||
|
"text":"x: 16 y: 16",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":111.991330228225,
|
||||||
|
"x":39.0206367023754,
|
||||||
|
"y":2.47529762459247
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":30.8202,
|
||||||
|
"id":9,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"MS Shell Dlg 2",
|
||||||
|
"pixelsize":21,
|
||||||
|
"text":"x: 464 y: 432",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":149.997520726595,
|
||||||
|
"x":483.920662086633,
|
||||||
|
"y":417.193532976246
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"height":113.333333333333,
|
||||||
|
"id":10,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"MS Shell Dlg 2",
|
||||||
|
"pixelsize":21,
|
||||||
|
"text":"Top: 256\/512 Center: 496\/655 Width: 480 Height: 286",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":172,
|
||||||
|
"x":256,
|
||||||
|
"y":512
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":7,
|
||||||
|
"nextobjectid":11,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"script.php"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteAllowApi",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}],
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.7.2",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"..\/tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":"1.6",
|
||||||
|
"width":30
|
||||||
|
}
|
50
maps/tests/CameraApi/script.php
Normal file
50
maps/tests/CameraApi/script.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script src="<?php echo $_SERVER["FRONT_URL"] ?>/iframe_api.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
//@ts-ignore
|
||||||
|
WA.camera.onCameraUpdate((worldView) => console.log(worldView));
|
||||||
|
WA.onInit().then(() => {
|
||||||
|
console.log('After WA init');
|
||||||
|
const setCameraButton = document.getElementById('setCameraButton');
|
||||||
|
const followPlayerButton = document.getElementById('followPlayerButton');
|
||||||
|
const xField = document.getElementById('x');
|
||||||
|
const yField = document.getElementById('y');
|
||||||
|
const widthField = document.getElementById('width');
|
||||||
|
const heightField = document.getElementById('height');
|
||||||
|
const smoothField = document.getElementById('smooth');
|
||||||
|
const lockField = document.getElementById('lock');
|
||||||
|
|
||||||
|
setCameraButton.addEventListener('click', () => {
|
||||||
|
WA.camera.set(
|
||||||
|
parseInt(xField.value),
|
||||||
|
parseInt(yField.value),
|
||||||
|
widthField.value ? parseInt(widthField.value) : undefined,
|
||||||
|
heightField.value ? parseInt(heightField.value) : undefined,
|
||||||
|
lockField.checked,
|
||||||
|
smoothField.checked,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
followPlayerButton.addEventListener('click', () => {
|
||||||
|
WA.camera.followPlayer(smoothField.checked);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
X: <input type="text" id="x" value="496" /><br/>
|
||||||
|
Y: <input type="text" id="y" value="655" /><br/>
|
||||||
|
width: <input type="text" id="width" value="480" /><br/>
|
||||||
|
height: <input type="text" id="height" value="286" /><br/>
|
||||||
|
Smooth: <input type="checkbox" id="smooth" value=1 /><br/>
|
||||||
|
Lock: <input type="checkbox" id="lock" value=1 /><br/>
|
||||||
|
|
||||||
|
<button id="setCameraButton">Set Camera</button>
|
||||||
|
<button id="followPlayerButton">Follow Player</button>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -219,6 +219,14 @@
|
|||||||
<a href="#" class="testLink" data-testmap="Metadata/setTiles.json" target="_blank">Test set tiles</a>
|
<a href="#" class="testLink" data-testmap="Metadata/setTiles.json" target="_blank">Test set tiles</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="testLink" data-testmap="CameraApi/camera_api_test.json" target="_blank">Test camera API</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<input type="radio" name="test-variables"> Success <input type="radio" name="test-variables"> Failure <input type="radio" name="test-variables" checked> Pending
|
<input type="radio" name="test-variables"> Success <input type="radio" name="test-variables"> Failure <input type="radio" name="test-variables" checked> Pending
|
||||||
|
Loading…
Reference in New Issue
Block a user