Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Игра на JS эффект ночи (https://javascript.ru/forum/misc/74851-igra-na-js-ehffekt-nochi.html)

Arhitector 12.08.2018 09:28

Игра на JS эффект ночи
 
Всем привет. Решил я вообщем сделать свою небольшую игрушку, ничего особенного простенькая бродилка по карте из двухмерного массива с индексами координат спрайтов для отрисовки текстур ландшафта.

И вот стало мне интересно, как можно реализовать в игре эффект смены времени суток? Что бы вся карта очень сильно затемнялась, но в некоторых точках которые я определю на оси X и Y (например у фонаря или в точке нахождения персонажа), был небольшой радиус подсвечивающий ближайшую местность.

Хочу добиться вот такого эффекта как на этих скриншотах!

https://www.google.ru/imgres?imgurl=...&iact=c&ictx=1

https://www.google.ru/imgres?imgurl=...&iact=c&ictx=1

https://www.google.ru/imgres?imgurl=...&iact=c&ictx=1

Буду благодарен за помощь!

j0hnik 12.08.2018 10:26

я так полагаю делаете используя обычные html элементы?

Arhitector 12.08.2018 10:45

Вложений: 1
Цитата:

Сообщение от j0hnik (Сообщение 492413)
я так полагаю делаете используя обычные html элементы?

Нет, вся отрисовка на хостле canvas.
В принципе я немного продвинулся по этому направлению, написал для персонажа функцию нахождения дистанции объекта относительно персонажа и установил переменную с радиусом света, если дистанция больше, тогда поверх фона отрисовываю новый слой rgba(0,0,0,0.8) если же радиус меньше то добавляю легкое затемнение, вроде работает
var map = location.getMap("bg");

for (var i = 0 ; i < map.length; i++) {
    for (var j = 0; j < map[i].length; j++) {
        var x = (j * cellSize) + offset_map.x, y = (i * cellSize) + offset_map.y;
        
        if(character.getDistanseTo({x: x + (cellSize / 2), y: y + (cellSize / 2)}) 
                         >= character.getRadiusLight()) {
            ctx.fillStyle = "rgba(0,0,0,0.8)";
	    ctx.fillRect(x, y, cellSize, cellSize);
	}
	else {
	    ctx.fillStyle = "rgba(0,0,0,0.3)";
	    ctx.fillRect(x, y, cellSize, cellSize);
        }
    }
}

Но есть некоторые недочеты и тут так как вся система карты у меня построена на сетке 32х32 так и отрисовка немного "квадратная" получается...
Вот думаю как исправить.
Если кто подскажет красивую формулу вычета разницы между этими отступами и закрашивания соседних квадратов ровно по линии радиуса персонажа, буду безмерно благодарен!

рони 12.08.2018 18:26

canvas круг света
 
Arhitector,
мысли вслух ...
<!DOCTYPE HTML>

<html>

<head>
  <title>Untitled</title>

</head>

<body>

<canvas id="canvas" width="600" height="400" style="border:1px solid #d3d3d3;"></canvas>

<script>
var img = new Image;
img.src = "https://mdn.mozillademos.org/files/5397/rhino.jpg";
img.onload = function() {
  draw(this);
};
function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, 600, 400);
  function move(event) {
    ctx.drawImage(img, 0, 0, 600, 400);
    var x = event.layerX;
    var y = event.layerY;
    var grd = ctx.createRadialGradient(x, y, 5, x, y, 100);
    grd.addColorStop(0, "transparent");
    grd.addColorStop(1, "black");
    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, 600, 400);
  }
  canvas.addEventListener("mousemove", move);
};
</script>


</body>

</html>

Arhitector 12.08.2018 20:20

Вложений: 2
Цитата:

Сообщение от рони (Сообщение 492427)
Arhitector,
мысли вслух ...
<!DOCTYPE HTML>

<html>

<head>
  <title>Untitled</title>

</head>

<body>

<canvas id="canvas" width="600" height="400" style="border:1px solid #d3d3d3;"></canvas>

<script>
var img = new Image;
img.src = "https://mdn.mozillademos.org/files/5397/rhino.jpg";
img.onload = function() {
  draw(this);
};
function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, 600, 400);
  function move(event) {
    ctx.drawImage(img, 0, 0, 600, 400);
    var x = event.layerX;
    var y = event.layerY;
    var grd = ctx.createRadialGradient(x, y, 5, x, y, 100);
    grd.addColorStop(0, "transparent");
    grd.addColorStop(1, "black");
    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, 600, 400);
  }
  canvas.addEventListener("mousemove", move);
};
</script>
</body>

</html>

Вот это действительно очень круто, спасибо. Сколько не гуглил но на createRadialGradient ни разу так и не наткнулся.

Тут возникает немного другая проблема, когда вешаю данный код на функцию при наведением мыши которая запускается при попадании курсора на холст, всё работает замечательно.

А вот когда пытаюсь привязать данное действие к координатам персонажа в функции которая запускается сразу и начинает зацикленно отрисовывать мою карту, вот тут сразу же игру ломает вот такая ошибка

Uncaught TypeError: Failed to execute 'createRadialGradient' on 'CanvasRenderingContext2D': The provided double value is non-finite.
at Object.drawMapBackground (draw.js:212)
at render (game.js:178)
at Image.water_bg.onload (game.js:211)



function render () {
	// отрисовка ландшафта, объектов персонажа и тд тд тд

	draw.drawMapBackground(); // <<<<----- сюда

	requestAnimationFrame(render); // зацикливание отрисовки
}

var draw = {
	// код код код
	// 

	drawMapBackground: function () {
		var real_pos = character.getRealCoordinates();

			var grd = ctx.createRadialGradient(real_pos.x, real_pos.y, 10, real_pos.x, real_pos.y, r);
		    grd.addColorStop(0, "transparent");
		    grd.addColorStop(1, "rgba(0,0,0,0.85)");
		    ctx.fillStyle = grd;
		    ctx.fillRect(0, 0, example.width, example.height);
	}
}

С чем это может быть связано? Сам я что то не смог разобраться, гугловский перевод аля
"Предоставляемое двойное значение не ограничено" как то совсем не помог увы :(

рони 12.08.2018 20:30

Arhitector,
по обрывкам кода сложно что-то сказать, и выше была только идея, реализовать её можно по разному.

Malleys 12.08.2018 20:34

Проверьте real_pos.x, real_pos.y и r.
Они должны быть числами(но не NaN или Infinite) и не строками.

Возможно перед вызовом createRadialGradient можно проверить?
if(!Number.isFinite(real_pos.x)) console.log("invalid real_pos.x");
if(!Number.isFinite(real_pos.y)) console.log("invalid real_pos.y");
if(!Number.isFinite(r)) console.log("invalid r");

Arhitector 12.08.2018 21:04

Цитата:

Сообщение от Malleys (Сообщение 492446)
Проверьте real_pos.x, real_pos.y и r.
Они должны быть числами(но не NaN или Infinite) и не строками.

Возможно перед вызовом createRadialGradient можно проверить?
if(!Number.isFinite(real_pos.x)) console.log("invalid real_pos.x");
if(!Number.isFinite(real_pos.y)) console.log("invalid real_pos.y");
if(!Number.isFinite(r)) console.log("invalid r");

Жаль на этом форуме нет кнопки "сто тысяч благодарностей"... вы ёё заслужили...
И правда, совсем уже видимо замотался с этим делом, голова у меня не варит, переменные то проверить забыл, всё сразу встало на места.

Я уже даже такую штуку написать успел... брр

var r = character.getRadiusLight(),
  pos = character.getCoordinates(),
  normalDrawBG = function(object) {
    var r = object.r, x = object.x, y = object.y,
    bend = Math.round(object.r / Math.PI);

    ctx.fillStyle = "rgba(0,0,0,0.85)";
    ctx.moveTo(x - r, y); 
    ctx.bezierCurveTo(x - r, (y - r) - bend, x + r, (y - r) - bend, x + r, y);
    ctx.bezierCurveTo(x + r, y + r + bend, x - r, y + r + bend, x - r, y);
};

ctx.beginPath();

normalDrawBG({x: pos.x, y: pos.y, r: r});

ctx.moveTo(0, 0); 
ctx.lineTo(0, canvas.height); 
ctx.lineTo(canvas.width, canvas.height); 
ctx.lineTo(canvas.width, 0); 
ctx.closePath();
ctx.fill();

Arhitector 12.08.2018 21:05

Цитата:

Сообщение от рони (Сообщение 492445)
Arhitector,
по обрывкам кода сложно что-то сказать, и выше была только идея, реализовать её можно по разному.

Это была чисто моя ошибка, ваш код супер, еще раз спасибо :)

Arhitector 12.08.2018 21:25

Еще сразу тогда спрошу тут же, что бы темы не плодить. Можно ли добавить одновременно 2-3 и более таких эффектов на карту?

рони 12.08.2018 22:30

Arhitector,
Canvas globalCompositeOperation
http://minimal.be/lab/jQuery.eraser/
https://pixijs.io/examples/#/demos/m...der-texture.js

Rise 13.08.2018 09:39

Arhitector,
Просто непересекаемых дырок с небольшим сглаживанием по краю наделать можно так:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

<canvas id="canvas" width="400" height="250" style="border: 1px solid red"></canvas>

<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

class Light1 {
    constructor(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
}
class Dark1 {
    constructor(width, height) {
        this.width = width;
        this.height = height;
        this.lights = [];
    }
    draw(ctx) {
        ctx.save();
        ctx.beginPath();
        ctx.rect(0, 0, this.width, this.height);
        for (let light of this.lights) {
            ctx.moveTo(light.x, light.y);
            ctx.arc(light.x, light.y, light.radius, 0, 6.3);
        }
        ctx.shadowColor = 'rgba(0, 0, 0, 1)';
        ctx.shadowBlur = 20;
        ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
        ctx.fill('evenodd');
        ctx.restore();
    }
}

let dark1 = new Dark1(400, 250);
dark1.lights.push(new Light1(50, 50, 30));
dark1.lights.push(new Light1(150, 100, 50));
dark1.lights.push(new Light1(100, 160, 70));
dark1.lights.push(new Light1(300, 150, 100));

let img = new Image;
img.onload = function() {
    ctx.drawImage(img, 660, 290, 400, 250, 0, 0, 400, 250);
    dark1.draw(ctx);
};
img.src = 'https://javascript.ru/forum/attachments/misc/3976d1534094922-igra-na-js-ehffekt-nochi-game-3-jpg';
</script>
    
</body>
</html>

А так уже с градиентом наверное то что и нужно:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

<canvas id="canvas" width="400" height="250" style="border: 1px solid red"></canvas>

<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

class Light2 {
    constructor(x, y, power, degree0 = 0, degree1 = 360) {
        this.x = x;
        this.y = y;
        this.radius0 = power * 50;
        this.radius1 = power * 100;
        let degree = Math.PI / 180;
        this.angle0 = degree0 * degree;
        this.angle1 = degree1 * degree;
    }
}
class Dark2 {
    constructor(width, height) {
        let offscreen = document.createElement('canvas');
        this.ctx = offscreen.getContext('2d');
        this.width = offscreen.width = width;
        this.height = offscreen.height = height;
        this.lights = [];
    }
    draw(ctx) {
        this.ctx.clearRect(0, 0, this.width, this.height);
        this.ctx.globalCompositeOperation = 'source-over';
        this.ctx.fillStyle = 'rgba(0, 0, 0, 0.75)';
        this.ctx.fillRect(0, 0, this.width, this.height);
        this.ctx.globalCompositeOperation = 'destination-out';
        for (let light of this.lights) {
            this.ctx.moveTo(light.x, light.y);
            this.ctx.arc(light.x, light.y, light.radius1, light.angle0, light.angle1);
            let grd = this.ctx.createRadialGradient(light.x, light.y, light.radius0, light.x, light.y, light.radius1);
            grd.addColorStop(1, 'rgba(0, 0, 0, 0)');
            grd.addColorStop(0, 'rgba(0, 0, 0, 0.75)');
            this.ctx.fillStyle = grd;
            this.ctx.fill();
        }
        ctx.drawImage(this.ctx.canvas, 0, 0);
    }
}

let dark2 = new Dark2(400, 250);
dark2.lights.push(new Light2(50, 50, 0.3));
dark2.lights.push(new Light2(150, 100, 0.5));
dark2.lights.push(new Light2(100, 160, 0.7));
dark2.lights.push(new Light2(300, 150, 1, -150, -10));

let img = new Image;
img.onload = function() {
    ctx.drawImage(img, 660, 290, 400, 250, 0, 0, 400, 250);
    dark2.draw(ctx);
};
img.src = 'https://javascript.ru/forum/attachments/misc/3976d1534094922-igra-na-js-ehffekt-nochi-game-3-jpg';
</script>
    
</body>
</html>

Формула радиуса освещения от мощности (или что там у света?) взята от балды.

Arhitector 13.08.2018 15:17

Rise,
Вот это уже почти идеально подходит! Огромное спасибо :)

Rise 14.08.2018 09:11

Теперь свет можно отдельно использовать:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

<canvas id="canvas" width="400" height="250" style="border: 1px solid red"></canvas>

<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

class Light {
    constructor(x, y, power, degree0 = 0, degree1 = 360, color0 = 'rgba(255,255,255,0.75)', color1 = 'rgba(255,255,255,0)') {
        this.x = x;
        this.y = y;
        this.radius0 = power * 50;
        this.radius1 = power * 100;
        let degree = Math.PI / 180;
        this.angle0 = degree0 * degree;
        this.angle1 = degree1 * degree;
        this.color0 = color0;
        this.color1 = color1;
    }
    draw(ctx) {
        ctx.beginPath();
        ctx.moveTo(this.x, this.y);
        ctx.arc(this.x, this.y, this.radius1, this.angle0, this.angle1);
        let grd = ctx.createRadialGradient(this.x, this.y, this.radius0, this.x, this.y, this.radius1);
        grd.addColorStop(0, this.color0);
        grd.addColorStop(1, this.color1);
        ctx.fillStyle = grd;
        ctx.fill();
    }
}
class Dark {
    constructor(width, height, color = 'rgba(0,0,0,0.75)') {
        let offscreen = document.createElement('canvas');
        this.ctx = offscreen.getContext('2d');
        this.width = offscreen.width = width;
        this.height = offscreen.height = height;
        this.lights = [];
        this.color = color;
    }
    draw(ctx) {
        this.ctx.clearRect(0, 0, this.width, this.height);
        this.ctx.globalCompositeOperation = 'source-over';
        this.ctx.fillStyle = this.color;
        this.ctx.fillRect(0, 0, this.width, this.height);
        this.ctx.globalCompositeOperation = 'destination-out';
        for (let light of this.lights) light.draw(this.ctx);
        ctx.drawImage(this.ctx.canvas, 0, 0);
    }
}

let dark = new Dark(100, 250);
dark.lights.push(new Light(50, 50, 0.3));
dark.lights.push(new Light(100, 160, 0.7, 90, 270));
let light = new Light(100, 160, 0.7, -90, 90);
let sun = new Light(350, 50, 0.5, 0, 360, 'rgba(255, 255, 0, 1)', 'rgba(255, 0, 0, 0.1)');

let img = new Image;
img.onload = function() {
    ctx.drawImage(img, 660, 290, 400, 250, 0, 0, 400, 250);
    dark.draw(ctx);
    light.draw(ctx);
    sun.draw(ctx);
};
img.src = 'https://javascript.ru/forum/attachments/misc/3976d1534094922-igra-na-js-ehffekt-nochi-game-3-jpg';
</script>
    
</body>
</html>


Часовой пояс GMT +3, время: 23:06.