Skip to content

Commit

Permalink
[#285] new me.collision.rayCast function for basic Line of Sight impl…
Browse files Browse the repository at this point in the history
…ementation

this one was basically coming for free after we added Quadtree +  SAT collision implementation in melonJS.

it can probably be improved or the API tweaked a bit, but it's pretty simple to use already : just cast a line, and get in return an array of intersecting entities.
  • Loading branch information
obiot committed Jan 5, 2018
1 parent a770c90 commit 8d653e0
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Version History
5.1.0
* Audio : Howler core update (2.0.7)
* Core : window.onReady is now marked as deprecated, and replaced by me.device.onReady
* Core : new me.collision.rayCast function for basic Line of Sight implementation
* Container : new `onChildChange` callback for when a child is added or removed
* Input : further improvements to multitouch support (compatibility on latest device/browser)
* Input : new me.input.setTouchAction function to enable/disable gesture by default (default to "none")
Expand Down
34 changes: 34 additions & 0 deletions examples/lineofsight/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
body {
background-color: #000;
color: #fff;

/* Allow mouse dragging. */
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
-webkit-user-select: none;
user-select: none;

/* disable touch panning/zooming */
-ms-touch-action: none;
touch-action: none;

/* Allow canvas to hit the edges of the browser viewport. */
margin: 0;
}

#screen canvas {
margin: auto;

/* Hide the gap for font descenders. */
display: block;

/* disable scaling interpolation */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
30 changes: 30 additions & 0 deletions examples/lineofsight/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>melonJS - Line Of Sight</title>
<link rel="stylesheet" type="text/css" media="screen" href="index.css">
<meta id="viewport" name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body>
<!-- Canvas placeholder -->
<div id="screen"></div>

<!-- melonJS Library -->
<script type="text/javascript" src="../../build/melonjs.js"></script>
<script type="text/javascript" src="../../plugins/debug/debugPanel.js"></script>

<!-- Game Scripts -->
<script type="text/javascript" src="js/game.js"></script>
<script type="text/javascript" src="js/entities/entities.js"></script>
<script type="text/javascript" src="js/screens/play.js"></script>

<!-- Bootstrap -->
<script type="text/javascript">
me.device.onReady(function onReady() {
game.onload();
});
</script>
</body>
</html>
115 changes: 115 additions & 0 deletions examples/lineofsight/js/entities/entities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

// a draggable square entity
game.Square = me.Entity.extend({
/**
* constructor
*/
init: function (x, y, settings) {
// ensure we do not create a default shape
//settings.shapes = [];
// call the super constructor
this._super(me.Entity, "init", [x, y, settings]);

//this.body.addShape(new me.Rect(x, y, this.width, this.height));

// status flags
this.selected = false;
this.hover = false;

// to memorize where we grab the shape
this.grabOffset = new me.Vector2d(0,0);

// turn red once touched by a line
this.color = new me.Color(0, 255, 0);

this.isColliding = false;
},

onActivateEvent: function () {
//register on mouse/touch event
me.input.registerPointerEvent("pointerdown", this, this.onSelect.bind(this));
me.input.registerPointerEvent("pointerup", this, this.onRelease.bind(this));
me.input.registerPointerEvent("pointercancel", this, this.onRelease.bind(this));
me.input.registerPointerEvent("pointermove", this, this.pointerMove.bind(this));
},

/**
* pointermove function
*/
pointerMove: function (event) {
if (this.selected) {
// follow the pointer
me.game.world.moveUp(this);
this.pos.set(event.gameX, event.gameY, this.pos.z);
this.pos.sub(this.grabOffset);
}
},


// mouse down function
onSelect : function (event) {
if (this.selected === false) {
// manually calculate the relative coordinates for the body shapes
// since only the bounding box is used by the input event manager
var parentPos = this.ancestor.getBounds().pos;
var x = event.gameX - this.pos.x - parentPos.x;
var y = event.gameY - this.pos.y - parentPos.y;

// the pointer event system will use the object bounding rect, check then with with all defined shapes
for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) {
if (shape.containsPoint(x, y)) {
this.selected = true;
break;
}
}
if (this.selected) {
this.grabOffset.set(event.gameX, event.gameY);
this.grabOffset.sub(this.pos);
this.selected = true;
}
}
return this.seleted;
},

// mouse up function
onRelease : function (/*event*/) {
this.selected = false;
// don"t propagate the event furthermore
return false;
},

/**
* update function
*/
update: function () {
return true;
},

/**
* draw the square
*/
draw: function (renderer) {
var lineWidth = 2;

if (this.isColliding === true) {
this.color.setColor(255, 0, 0);
} else {
this.color.setColor(0, 255, 0);
}

renderer.setGlobalAlpha(0.5);
renderer.setColor(this.color);
renderer.fillRect(0, 0, this.width, this.height);
renderer.setGlobalAlpha(1.0);
renderer.setLineWidth(lineWidth);
renderer.strokeRect(
lineWidth,
lineWidth,
this.width - lineWidth * 2,
this.height - lineWidth * 2
);

// reset the colliding flag
this.isColliding = false;
}
});
34 changes: 34 additions & 0 deletions examples/lineofsight/js/game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

/* Game namespace */
var game = {
// Run on page load.
"onload" : function () {
// Initialize the video.
if (!me.video.init(800, 600, {wrapper : "screen", scale : "auto", renderer : me.video.CANVAS})) {
alert("Your browser does not support HTML5 canvas.");
return;
}

// set the "Play/Ingame" Screen Object
me.state.set(me.state.PLAY, new game.PlayScreen());

// add some keyboard shortcuts
me.event.subscribe(me.event.KEYDOWN, function (action, keyCode /*, edge */) {

// toggle fullscreen on/off
if (keyCode === me.input.KEY.F) {
if (!me.device.isFullscreen) {
me.device.requestFullscreen();
} else {
me.device.exitFullscreen();
}
}
});

// render hitbox int the debug panel
me.debug.renderHitBox = true;

// switch to PLAY state
me.state.change(me.state.PLAY);
}
};
79 changes: 79 additions & 0 deletions examples/lineofsight/js/screens/play.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
game.PlayScreen = me.ScreenObject.extend({
/**
* action to perform on state change
*/
onResetEvent: function() {
var rectSize = 150;

// clear the background
me.game.world.addChild(new me.ColorLayer("background", "black"), 0);

// add a few shapes
me.game.world.addChild(new game.Square(50, 50, {width: rectSize, height: rectSize}), 1);
me.game.world.addChild(new game.Square(50, 400, {width: rectSize, height: rectSize}), 1);
me.game.world.addChild(new game.Square(300, 150, {width: rectSize, height: rectSize}), 1);
me.game.world.addChild(new game.Square(300, 350, {width: rectSize, height: rectSize}), 1);
me.game.world.addChild(new game.Square(600, 200, {width: rectSize, height: rectSize}), 1);
me.game.world.addChild(new game.Square(600, 400, {width: rectSize, height: rectSize}), 1);

me.game.repaint();

// display the current pointer coordinates on top of the pointer arrow
// and some helper text at the bottom of the viewport
me.game.world.addChild(new (me.Renderable.extend({
init: function() {
this._super(me.Renderable, 'init', [0, 0, 10, 10]);
this.font = new me.Font("Arial", 10, "#FFFFFF");
this.font.textAlign = "center";
this.fontHeight = this.font.measureText(me.video.renderer, "DUMMY").height;
},
update : function (dt) {
return true;
},
draw: function(renderer) {
var x = Math.round(me.input.pointer.gameWorldX);
var y = Math.round(me.input.pointer.gameWorldY);

this.font.draw (
renderer,
"( " + x + "," + y + " )",
x,
y - this.fontHeight);

this.font.draw (
renderer,
"drag the square to check for intersection witht the line",
150,
me.game.viewport.height - this.fontHeight
);
}
})), 10);

// basic renderable that cast a ray across the world
me.game.world.addChild(new (me.Renderable.extend({
init: function() {
this._super(me.Renderable, 'init', [0, 0, 10, 10]);
this.line = new me.Line(0, 0, [
new me.Vector2d(0, 0),
new me.Vector2d(me.game.viewport.width, me.game.viewport.height)
]);

},
update : function (dt) {
var result = me.collision.rayCast(this.line);

if (result.length > 0) {
for (i = 0; i < result.length; i++) {
// update the object isColliding flag
result[i].isColliding = true;
}
}
return true;
},
draw: function(renderer) {
renderer.setColor("red");
renderer.drawShape(this.line);
}
})), 10);
}
});
Loading

0 comments on commit 8d653e0

Please sign in to comment.