I have a Unity WebGL player that is embedded in a React application. The React application has drag and drop tiles that can be drug and dropped onto the WebGL player. When the tiles begin to be drug unity starts raycasting so that you can tell what object in the screen you are going to drop onto. All of this works perfectly when using a mouse, but I've noticed Input.touchCount always returns 0 unless the touch originates inside the WebGL player. Does anyone know a fix to this? Been head bashing this one for a moment now...
Here is the raycasting code. Like I said, it works perfectly for a mouse... but I cannot get a touch.position returned.
public void LateUpdate()
{
if (SHOULD_CAST_RAY)
{
// always returning 0
Debug.Log(Input.touchCount);
RaycastHit hit;
Vector3 position = Input.touchSupported
&& Input.touchCount == 1
? new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0)
: Input.mousePosition;
if (Physics.Raycast(RigsCamera.ScreenPointToRay(position), out hit, CameraRigControllerScript.CameraDistanceMax * 1.5f, 1 << 10))
{
if (CURRENT_SELECTION == null)
{
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
else if (!IsAlreadySelected(hit))
{
RemoveSelectionIndication();
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
return;
}
if (CURRENT_SELECTION != null)
{
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
}
}
Also, if I touch the screen on the Unity WebGL player and then start dragging one of my React components which sends a message to Unity to start raycasting; I get atouch.position that is at the point I touched and does not move with the finger... The hell?
If you have Unity 5+ version you can use mousePosition for everything, since the Input class handles the Touch (0) and Mouse (0) exactly the same, have you tried that?
Vector3 position = Input.touchSupported && Input.touchCount == 1
? new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0)
: Input.mousePosition;
Try changing this to only
Vector3 position = Input.mousePosition;
I posted a solution on the Unity forums in-case there is any update to this thread in the future.
In WebGL, the Unity Input class does not register touch events that originate begin outside of the WebGL player. To solve this problem I used a couple boolean values that are toggled by my React components through the GameInstance.SendMessage method; as well as a Vector3 to store a value sent from React, also through SendMessage. Here are the important c# bits. If anyone has any questions, please ask and I'll walk you through the rest!
bool SHOULD_CAST_RAY;
bool USE_EXTERNAL_ORIGINATING_TOUCH_POS;
Vector3 EXTERNAL_ORIGINATING_TOUCH_POS;
public void LateUpdate()
{
if (SHOULD_CAST_RAY)
{
if (USE_EXTERNAL_ORIGINATING_TOUCH_POS && EXTERNAL_ORIGINATING_TOUCH_POS.z < 0) { return; }
RaycastHit hit;
Vector3 screenPoint = Input.mousePresent ? Input.mousePosition : Vector3.zero;
Vector3 viewportPoint = USE_EXTERNAL_ORIGINATING_TOUCH_POS ? RigsCamera.ScreenToViewportPoint(EXTERNAL_ORIGINATING_TOUCH_POS) : Vector3.zero;
if (Physics.Raycast(
USE_EXTERNAL_ORIGINATING_TOUCH_POS
? RigsCamera.ViewportPointToRay(new Vector3(viewportPoint.x, 1 - viewportPoint.y, 0))
: RigsCamera.ScreenPointToRay(screenPoint),
out hit,
CameraRigControllerScript.CameraDistanceMax * 1.5f,
1 << 10
)) {
if (CURRENT_SELECTION == null)
{
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
else if (!IsAlreadySelected(hit))
{
RemoveSelectionIndication();
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
return;
}
if (CURRENT_SELECTION != null)
{
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
}
}
// The below methods are used to control the raycasting from React through sendMessage
public void ClearExternalOriginatingTouchPosition()
{
EXTERNAL_ORIGINATING_TOUCH_POS = new Vector3(0, 0, -1f);
USE_EXTERNAL_ORIGINATING_TOUCH_POS = false;
}
public void DisableRaycasting()
{
SHOULD_CAST_RAY = false;
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
public void EnableRaycasting()
{
SHOULD_CAST_RAY = true;
}
public void SetExternalOriginatingTouchPosition(string csv)
{
string[] pos = csv.Split(',');
EXTERNAL_ORIGINATING_TOUCH_POS = new Vector3(float.Parse(pos[0]), float.Parse(pos[1]), 0);
USE_EXTERNAL_ORIGINATING_TOUCH_POS = true;
}
Related
I'm currently working on a web based Battleship game for one my Odin Project assignments. At some point I felt that the mediator pattern would be the perfect choice to deal with firing missiles for the player and the CPU. Now, my assignment encourages me to test the game thoroughly without console.log but with Jest. I've been able to test some features of the game but the mediator pattern is confusing. Mocking functions or modules is likely to be the right direction to go for but to be honest I've read heaps of guides and I've not been able to implement them (understanding mocks has been hard as well). The function notifyAttackinside EventManager has already been tested the old way with console.log.
Can anyone let me know what I'm doing wrong please?
Event Manager
export {EventManager}
const EventManager = {
gameManager: GameManager,
notifyAttack(who, coordinate){
if(!who)
throw new Error(`Unknown player`);
else
who === `CPU` ? GameManager.player.board.getAttack(coordinate) : GameManager.cpu.board.getAttack(coordinate);
GameManager.turn = who;
}
}
Game Manager
import {Player} from "./player";
export {GameManager}
const GameManager = {
turn: undefined,
player: undefined,
cpu: Player(),
}
Player
import {coordinate, GameBoard} from './gameboard';
import { EventManager } from './eventmanager';
export {Player}
const playerActions = {
eventManager: EventManager,
fire(coordinate){
this.eventManager.notifyAttack(this.name, coordinate);
}
}
function Player(name){
const player = Object.create(playerActions);
player.board = GameBoard();
name === undefined ? player.name = `CPU`: player.name = name;
return player;
}
GameBoard
import { Ship } from "./ship"
export {GameBoard, coordinate, shipOrientation, tile}
function coordinate(x,y){
const boardSize = 10;
if(x > boardSize || x < 1)
throw new Error(`X coordinate is out of boundaries`);
if(y > boardSize || y < 1)
throw new Error(`Y coordinate is out of boundaries`);
return{x:x, y:y}
}
function tile(coordinate, id){
return{coordinate: coordinate, id: id}
}
const shipOrientation = {
HORIZONTAL: Symbol(`horizontal`),
VERTICAL: Symbol(`vertical`),
}
const gameboardActions = {
placeShips(shipType, orientation, inputCoordinate){
const ship = Ship(shipType);
ship.ID = `${inputCoordinate.x},${inputCoordinate.y}`;
this.tiles.forEach(tile=>{
if(tile.coordinate.x === inputCoordinate.x && tile.coordinate.y === inputCoordinate.y)
throw new Error(`There's already an object on that input coordinate`);
})
if(orientation === shipOrientation.HORIZONTAL){
if(inputCoordinate.x + ship.length > this.size)
throw new Error(`Part of ship is out of board X boundary`);
for(let i = 0; i<ship.length; ++i)
this.tiles.push(tile(coordinate(inputCoordinate.x+i, inputCoordinate.y), `${ship.ID}`));
}else if(orientation === shipOrientation.VERTICAL){
if(inputCoordinate.y + ship.length > this.size)
throw new Error(`Part of ship is out of board Y boundary`);
for(let i = 0; i<ship.length; ++i)
this.tiles.push(tile(coordinate(inputCoordinate.x, inputCoordinate.y+i), `${ship.ID}`));
}else
throw new Error(`Undefined ship orientation`);
this.shipsLog.set(`${ship.ID}`,ship);
},
getAttack(inputCoordinate){
let isShip, ID;
this.tiles.forEach(tile=>{
if(tile.coordinate.y===inputCoordinate.y&&tile.coordinate.x===inputCoordinate.x&&tile.id){
ID = tile.id;
return isShip = true;
}
})
if(isShip){
this.shipsLog.get(ID).hit()
if(this.shipsLog.get(ID).isSunk){
this.removeShip(ID);
this.checkSunkFleet();
}
}else
this.tiles.push(tile(inputCoordinate, undefined));
},
removeShip(ID){
this.shipsLog.delete(ID);
for(let i = 0; i<this.tiles.length; ++i)
if(this.tiles[i].id===ID)
this.tiles.splice(i,1);
},
checkSunkFleet(){
this.shipsLog.size === 0 ? this.sunkFleet=true:this.sunkFleet=false;
}
}
function GameBoard (){
const gameboard = Object.create(gameboardActions);
gameboard.shipsLog = new Map();
gameboard.tiles= [];
gameboard.size= 10;
gameboard.sunkFleet = false;
return gameboard;
}
Jest test
import {GameBoard, coordinate} from "./gameboard";
import {GameManager} from './gamemanager';
import {Player} from "./player";
import {EventManager} from "./eventmanager";
GameManager.player = Player(`Pablo`);
describe(`Player set up`, ()=>{
test(`Player's name is Pablo`,()=>{
expect(GameManager.player.name).toMatch(/^[A-Z]+$/i);
});
test(`Player has a board to play with`, ()=>{
expect(GameManager.player.board).toMatchObject(GameBoard());
});
})
describe(`Player's actions`,()=>{
test(`Pablo fires a missile, he misses ship target though`, ()=>{
const myCoordinate = coordinate(5,5);
const spy = jest.spyOn(EventManager, 'notifyAttack')
GameManager.player.fire(myCoordinate);
expect(spy).toBeCalled();
expect(GameManager.cpu.getAttack).toBeCalledWith(myCoordinate);
expect(GameManager.cpu.shipsLog.has(`${myCoordinate.x}, ${myCoordinate.y}`));
})
})
So the flow goes in this way:
A Player already set up in GameManager (Pablo) fires a missile by triggering fire() inside the Player object
fire() reports EventManager who fires the missile and its coordinates
EventManager calls CPU GameBoard getAttack() method that records Pablo's missing missile
You guys might wonder why I'm using an EventManager instead of relying on GameManager. Basically, GameManager is in charge of changing turns, set up the game whereas EventManager specifically deals with the battle to prevent coupling between Player and CPU
I'd like to hear from you if you need more details for the question.
Cheers!
Well I found the answer after some struggle. Mocks are not the best idea in this situation, instead jest spies let me check if functions are called without changing the implementation like mocks so I wasn't far from the answer.
Here you go jest test code for future reference
describe(`Player's attack`,()=>{
GameManager.cpu = Player();
GameManager.cpu.board.placeShips(shipType.DESTROYER, shipOrientation.HORIZONTAL, coordinate(4,4))
let myCoordinate = coordinate(5,5);
const eventManager = jest.spyOn(EventManager, 'notifyAttack');
const cpuGetsAttack = jest.spyOn(GameManager.cpu.board, 'getAttack')
test(`Pablo fires a missile, he misses his target ship though`, ()=>{
GameManager.player.fire(myCoordinate);
expect(eventManager).toBeCalled();
expect(cpuGetsAttack).toBeCalledWith(myCoordinate);
expect(GameManager.cpu.board.shipsLog.has(`${myCoordinate.x},${myCoordinate.y}`)).toBeFalsy();
expect(GameManager.cpu.board.tiles).toContainEqual(tile(myCoordinate, undefined));
});
test(`Pablo fires a missile and hits his target ship`, ()=>{
myCoordinate.x = 4;
myCoordinate.y = 4;
GameManager.player.fire(myCoordinate);
expect(eventManager).toBeCalled();
expect(cpuGetsAttack).toBeCalledWith(myCoordinate);
expect(GameManager.cpu.board.shipsLog.has(`${myCoordinate.x},${myCoordinate.y}`)).toBeTruthy();
expect(GameManager.cpu.board.tiles).toContainEqual(tile(myCoordinate, GameManager.cpu.id));
});
});
I am working on a game app using React native and Matter.js.
I am trying to implement a system that adds points every time a bullet hits a target.
In order to do this, I am trying to use collisionStart to detect the collision.
However, even though the bullet and target collide only once, the event seems to be triggered 41 times.
This is the code:
Matter.Events.on(engine, 'collisionStart', (event) => {
let pairs = event.pairs
for (const pair of pairs) {
if (pair.bodyA.label === 'bullet' && pair.bodyB.label === 'Worm') {
console.log("target hit");
}
}
})
In the end, I'm planning to replace console.log with something that adds points. At the current moment, one collision seems like it would trigger the add points 41 times, which is obviously not ideal.
Any ideas what is happening here and how I can get this to trigger only once for one collision?
Try next example. I take it from my own project [you need little adaptd from ts to js]:
Matter.Events.on(this.starter.getEngine(), "collisionStart", function (event) {
root.collisionCheck(event, true);
});
public collisionCheck(event, ground: boolean) {
const myInstance = this;
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i !== j; ++i) {
const pair = pairs[i];
if (pair.activeContacts) {
if (pair.bodyA.label === "bullet" && pair.bodyB.label === "Worm") {
const collectitem = pair.bodyA;
this.playerDie(collectitem);
} else if (pair.bodyB.label === "bullet" && pair.bodyA.label === "Worm") {
const collectitem = pair.bodyB;
this.playerDie(collectitem);
}
// ....
}
}
}
public destroyBody = (destroyBody) => {
try {
Matter.Composite.remove(this.getWorld(), destroyBody);
} catch(err) {
console.log(err)
}
}
If you still have same problem , we can adapt also with flag PREVENT_DOUBLE_BY_1_SECOUND for example.
I making a chess game with chessboardjs and I want to check for checkmate.
How can I do this ?
I'm using that code but it isn't working.
if (game.game_over() == true) {
alert("Game Over");
}
The whole function is:
var onDragStart = function (source, piece, position, orientation) {
if (game.game_over() == true) {
alert("Game Over");
}
if (turn == 1) {
if (piece.search(/^b/) === -1) {
return false;
}
} else {
if (piece.search(/^w/) === -1) {
return false;
}
}
if (turn == 1) {
turn = 0;
} else {
turn = 1;
}
};
To check the state of a chess position, you'll need more than the chessboard representation but also a chess engine (the program which understands/enforces chess rules). A favorite for javascript is: chess.js (chessboard.js is only a graphical representation of the board and doesn't include any of the logic required to check for possible moves etc)
Like in this question How can I integrate chess.js and chessboard.js?
These libraries are often used together: https://github.com/jhlywa/chess.js/
and from there you have access to the rules of chess: https://github.com/jhlywa/chess.js/#game_over
To determine a game has ended, you have to calculate the possible legal moves for the side to move.
No legal moves for the side to move implies the game has ended.
If there are no legal moves and the king of the side to move is in check => checkmate.
Otherwise it is stalemate.
Following scenario.
I wrote a angular2 application with material2.
In my SideNav is a search input field. When a user types in it, he is redirected (via routing) to the search component, while the searched word is handed over as a routing parameter.
The search component shows all pages of the application, which contain the searched word (index in the background). Once the user clicks on the entry, he's redirected to this page, and the searched word is appended as a query parameter. I'm now trying to highlight all appearances of the searchword on the page, the user gets redirected to. At the moment i'm doing this:
subscription: ISubscription;
searchTerm: string;
constructor(private router: Router, private elementRef: ElementRef) {}
ngOnInit(): void {
this.subscription = this.router.routerState.queryParams.subscribe(queryParams => {
let searchTerm = queryParams['searchTerm'];
if (searchTerm) {
this.searchTerm = searchTerm;
} else {
this.searchTerm = null;
}
});
}
ngAfterContentInit(): void {
if (this.searchTerm && isStaticDoc) {
let regExp = new RegExp(`(${this.searchTerm})`, 'i');
this.highlightWords(this.elementRef.nativeElement, regExp);
}
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
highlightWords(node, regExp: RegExp) {
if (!node || ! regExp) {
return;
}
if (node.nodeType === 3) {
let regs = regExp.exec(node.nodeValue);
if (regs) {
let match = document.createElement('span');
match.appendChild(document.createTextNode(regs[0]));
match.classList.add('search-hl');
let after = node.splitText(regs.index);
after.nodeValue = after.nodeValue.substring(regs[0].length);
node.parentNode.insertBefore(match, after);
}
} else if (node.hasChildNodes()) {
for (let i = 0; i < node.childNodes.length; i++) {
this.highlightWords(node.childNodes[i], regExp);
}
}
}
Now the issue is, that i get an error RangeError: Maximum call stack size exceeded, which might be a hint, that the recursion level is way to deep.
I've already tried to use 3rd party libraries, bot non of them is really made to be used from angular2 and on top, the written code isn't that difficult... but its not working.
Any ideas how to stage beneath the maximum call stack size following the same or an similar approach?
tl;dr trying to highlight all appearances of searchTerm(which is passed over as a queryParam) on the page -> my approach (see code) is not
working due to max call stack size.
Edit: Using rc4 atm, upgrading soon, but this shouldn't be an issue (i guess)
Thanks to user3791775 I've come up with an solution.
highlightWords(html: string, searchTerm: string): string {
let regExp = new RegExp(`(${searchTerm})`, 'i');
let results = regExp.exec(html);
if (results) {
let before = html.substr(0, results.index);
let after = html.substr(results.index + searchTerm.length);
let indexOpenTag = before.lastIndexOf('<');
let indexCloseTag = before.lastIndexOf('>');
let indexOpenTagAfter = after.indexOf('<');
let indexCloseTagAfter = after.indexOf('>');
if (indexOpenTag <= indexCloseTag && indexOpenTagAfter <= indexCloseTagAfter) {
return `${before}<span class="search-hl">${results[0]}</span>${this.highlightWords(after, searchTerm)}`;
} else {
return `${before}${results[0]}${this.highlightWords(after, searchTerm)}`;
}
} else {
return html;
}
}
This can be used the following way
let ref = document.getElementById('my-highlicht-content');
ref.innerHtml = this.highlightWords(ref.innerHtml, this.searchTerm)
Thanks for helping!
Edit:
Had another edgecase, which made it necessary to inspect the part after the keyword as well. Updated my example.
Here i use just print the JPanel but i need to put graphics component for drawLine method. how to use it anyone please advice me. my sample code for print JPanel here.
public class Sample {
JPanel component_name = new JPanel();
public void printComponenet(){
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setJobName(" Print Component ");
pj.setPrintable (new Printable() {
public int print(Graphics pg, PageFormat pf, int pageNum){
if (pageNum > 0){
return Printable.NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) pg;
g2.translate(pf.getImageableX(), pf.getImageableY());
component_name.printAll(g2);
return Printable.PAGE_EXISTS;
}
});
if (pj.printDialog() == false)
return;
try {
pj.print();
} catch (PrinterException ex) {
// handle exception
}
}
Add
g2.drawLine(left,top,width,height);
Replacing ( or assigning to ) the four parameters with integer values you want to use. eg:
g2.drawLine(30,40,400,300);
Should work for most displays.
g2.setColor(r,g,b);
Before, if you want other than black.