Форум программистов, компьютерный форум, киберфорум
Программирование игр
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.89/18: Рейтинг темы: голосов - 18, средняя оценка - 4.89
3 / 3 / 0
Регистрация: 14.07.2012
Сообщений: 86

Подскажите какие технологии лучше использовать

19.08.2022, 13:26. Показов 4355. Ответов 36
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте! У меня есть игра , которую я написал на C++/SFML , игра простая головоломка вроде тетриса.
У меня есть задача "запихнуть" эту игру на сайт, что бы играть в нее из браузера. Какие технологии лучше для этого использовать?
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
19.08.2022, 13:26
Ответы с готовыми решениями:

С чего начать написание стратегической игры и какие технологии использовать?
Всем доброго времени суток! Планирую на с++ написать небольшую игрулю-стратегию. Дана сеточная карта, и там по клеточкам перемещаем...

Какие библиотеки OpenGL лучше использовать с VB.NET и в чем разница?
В данном деле я новичок. Программирую на Visual Basic NET. Пытаюсь создать графический фрагмент. Есть три dll: 1....

Какие ЯП, IDE, фреймворки, технологии лучше использовать для преподавания начинающим программистам?
У нас в этом плане подход у препадов был простой - 1-ый семестр C++. 2-ой семестр Паскаль. дальше на чем хотите на том и пилите. второй...

36
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 16:52
Студворк — интернет-сервис помощи студентам
Скачайте и установите программу Free Texture Packer (сайт недоступен на территории РФ, надо ставить VPN, у меня плагин для браузера VeePN). Когда запустите программу, то увидите прямоугольную область слева с надписью "drag'n'drop images here":

Название: 2c91c904-1bc0-4c2a-a5c5-a154786392e7.png
Просмотров: 190

Размер: 2.7 Кб

В эту область нужно перетащить все свои спрайты для игры:



В настройках справа нужно выставить следующие значения (в "Save path" указать путь к папке: "public\assets"):



Нажмите кнопку "Export". В папке ""public\assets"" вы увидите два файла: "texture.png" и "texture.json". В "texture.png" упакованы все спрайты, а в "texture.json" находятся данные - имена спрайтов, их координаты на текстурном атласе (то есть в файле "texture.png") и ширина спрайтов. Данные из "texture.json" надо использовать, как текстурные координаты для игровых объектов. Координаты и ширина спрайтов даны в пикселях и чтобы их перевести в текстурные координаты нужно будет делить их на ширину и высоту "texture.png", то есть нормировать на единицу.

Миниатюры
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 17:04
В моём случае размер файла "texture.png" получился равным 1024x512:



Данные о размере ("w" и "h") можно найти в файле "texture.json" в разделе "meta". Кстати, в JS уже встроен парсер JSON, поэтому никаких библиотек для работы с JSON подключать не нужно:

JSON
1
2
3
4
5
6
7
8
9
10
11
  "meta": {
    "app": "http://free-tex-packer.com",
    "version": "0.6.7",
    "image": "texture.png",
    "format": "RGBA8888",
    "size": {
      "w": 1024,
      "h": 512
    },
    "scale": 1
  }
"texture.json" целиком:

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
{
  "frames": {
    "background.png": {
      "frame": {
        "x": 1,
        "y": 1,
        "w": 500,
        "h": 350
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 500,
        "h": 350
      },
      "sourceSize": {
        "w": 500,
        "h": 350
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "wallHorizontal.png": {
      "frame": {
        "x": 503,
        "y": 1,
        "w": 200,
        "h": 20
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 200,
        "h": 20
      },
      "sourceSize": {
        "w": 200,
        "h": 20
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "wallVertical.png": {
      "frame": {
        "x": 705,
        "y": 1,
        "w": 20,
        "h": 350
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 20,
        "h": 350
      },
      "sourceSize": {
        "w": 20,
        "h": 350
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "enemy.png": {
      "frame": {
        "x": 503,
        "y": 23,
        "w": 20,
        "h": 20
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 20,
        "h": 20
      },
      "sourceSize": {
        "w": 20,
        "h": 20
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "player.png": {
      "frame": {
        "x": 525,
        "y": 23,
        "w": 20,
        "h": 20
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 20,
        "h": 20
      },
      "sourceSize": {
        "w": 20,
        "h": 20
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "coin.png": {
      "frame": {
        "x": 547,
        "y": 23,
        "w": 15,
        "h": 25
      },
      "rotated": false,
      "trimmed": false,
      "spriteSourceSize": {
        "x": 0,
        "y": 0,
        "w": 15,
        "h": 25
      },
      "sourceSize": {
        "w": 15,
        "h": 25
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    }
  },
  "meta": {
    "app": "http://free-tex-packer.com",
    "version": "0.6.7",
    "image": "texture.png",
    "format": "RGBA8888",
    "size": {
      "w": 1024,
      "h": 512
    },
    "scale": 1
  }
}
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 17:39
Пример, как загрузить "texture.json" и "texture.png". Пример выводит размер "texture.png" путём парсинга JSON:

JavaScript
1
2
3
4
const assetsJson = queue.getResult("assetsJson");
const w = assetsJson.meta.size.w;
const h = assetsJson.meta.size.h;
console.log(w, h);
PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>
    <script src="https://code.createjs.com/1.0.0/soundjs.min.js"></script>
</head>
 
<body onload="loadAssets();">
 
    <script>
        const queue = new createjs.LoadQueue();
 
        function handleFileComplete()
        {
            const assetsImage = queue.getResult("assetsImage");
            console.log(assetsImage);
 
            const assetsJson = queue.getResult("assetsJson");
            // console.log(assetsJson);
            const w = assetsJson.meta.size.w;
            const h = assetsJson.meta.size.h;
            console.log(w, h);
        }
 
        function loadAssets()
        {
            queue.on("complete", handleFileComplete);
            queue.loadFile({ id: "assetsImage", src: "assets/texture.png" });
            queue.loadFile({ id: "assetsJson", src: "assets/texture.json" });
        }
    </script>
 
</body>
 
</html>
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 18:55
Для шейдеров лучше всего иметь отдельные файлы, я обычно называю их "default.vert" и "default.frag". Для VSCode есть расширение Shader languages support for VS Code для подсветки синтаксиса языка GLSL.

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



index.html

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>
    <script src="https://code.createjs.com/1.0.0/soundjs.min.js"></script>
</head>
 
<body>
    <canvas id="renderCanvas" width="300" height="300"></canvas>
 
    <script src="js/webgl-context.js"></script>
    <script src="js/shader-program.js"></script>
    <script src="js/main.js"></script>
</body>
 
</html>
js/main.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let program;
const queue = new createjs.LoadQueue();
 
function handleFileComplete()
{
    if (!initWebGLContext("renderCanvas")) return;
 
    const vertShaderSource = queue.getResult("vertShaderSource");
    const fragShaderSource = queue.getResult("fragShaderSource");
    program = createShaderProgram(vertShaderSource, fragShaderSource);
 
    const assetsImage = queue.getResult("assetsImage");
    const assetsJson = queue.getResult("assetsJson");
}
 
function loadAssets()
{
    queue.on("complete", handleFileComplete);
    queue.loadFile({ id: "vertShaderSource", src: "assets/shaders/default.vert" });
    queue.loadFile({ id: "fragShaderSource", src: "assets/shaders/default.frag" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsJson", src: "assets/sprites/texture.json" });
}
 
loadAssets();
js/shader-program.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function createShaderProgram(vertShaderSource, fragShaderSource)
{
    const vShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vShader, vertShaderSource);
    gl.compileShader(vShader);
    let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("vert: " + gl.getShaderInfoLog(vShader));
    };
 
    const fShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fShader, fragShaderSource);
    gl.compileShader(fShader);
    ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("frag: " + gl.getShaderInfoLog(fShader));
    };
 
    const program = gl.createProgram();
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    gl.useProgram(program);
 
    return program;
}
js/webgl-context.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
gl = null;
 
function initWebGLContext(canvasName)
{
    const canvas = document.getElementById(canvasName);
    if (canvas === null)
    {
        console.log(`Failed to get a canvas element with the name "${canvasName}"`);
        return false;
    }
    gl = canvas.getContext("webgl", { alpha: false, premultipliedAlpha: false });
    return true;
}
assets/shaders/default.vert

glSlang
1
2
3
4
5
6
7
8
9
10
11
attribute vec3 aPosition;
attribute vec2 aTexCoord;
 
uniform mat4 uMvpMatrix;
varying vec2 vTexCoord;
 
void main()
{
    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}
assets/shaders/default.frag

glSlang
1
2
3
4
5
6
7
8
9
precision mediump float;
 
uniform sampler2D uSampler;
varying vec2 vTexCoord;
 
void main()
{
    gl_FragColor = texture2D(uSampler, vTexCoord);
}
Миниатюры
Вложения
Тип файла: zip preloadjs-and-shader-program.zip (35.0 Кб, 0 просмотров)
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 21:35
Пример отрисовки игровых объектов загруженных из Free Texture Packer. Парсим "texture.json". Получаем ширину и высоту "texture.png". Получаем размеры игровых объектов:

JavaScript
1
2
3
4
5
6
7
8
9
10
function loadVertexBuffers(program, assetsJson)
{
    const w = assetsJson.meta.size.w;
    const h = assetsJson.meta.size.h;
 
    const bgFrame = assetsJson.frames["background.png"].frame;
    const playerFrame = assetsJson.frames["player.png"].frame;
    const wallHorFrame = assetsJson.frames["wallHorizontal.png"].frame;
 
    /* ... */
Определяем квадраты с помощью треугольников для игровых объектов. Здесь мы может задать Pivot Point, то есть тольку, которая определят положение игрового объекта в игровом мире. Например, для заднего фона (background) удобно удобно задать Pivot Point в левом верхнем углу. Для игрового персонажа удобно задать его внизу ног. Для платформ удобно задать в середине по X и вверху по Y:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    const vertPositions = [
        // Background
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Player
        -0.5, -1, 0,
        -0.5, 0, 0,
        0.5, -1, 0,
        0.5, -1, 0,
        -0.5, 0, 0,
        0.5, 0, 0,
        // Wall Horizontal
        -0.5, 0, 0,
        -0.5, 1, 0,
        0.5, 0, 0,
        0.5, 0, 0,
        -0.5, 1, 0,
        0.5, 1, 0,
 
        // -0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, -0.5, 0,
        // 0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, 0.5, 0,
    ];
Определяем текстурные координаты из "texture.json":

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    const texCoords = [
        // Background
        bgFrame.x / w, (bgFrame.y) / h,
        bgFrame.x / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x) / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, (bgFrame.y + bgFrame.h) / h,
        // Player
        playerFrame.x / w, (playerFrame.y) / h,
        playerFrame.x / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x) / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, (playerFrame.y + playerFrame.h) / h,
        // Wall Horizontal
        wallHorFrame.x / w, (wallHorFrame.y) / h,
        wallHorFrame.x / w, (wallHorFrame.y + wallHorFrame.h) / h,
        (wallHorFrame.x + wallHorFrame.w) / w, wallHorFrame.y / h,
        (wallHorFrame.x + wallHorFrame.w) / w, wallHorFrame.y / h,
        (wallHorFrame.x) / w, (wallHorFrame.y + wallHorFrame.h) / h,
        (wallHorFrame.x + wallHorFrame.w) / w, (wallHorFrame.y + wallHorFrame.h) / h,
    ];
В main.js можно определить индекс объект в VBO:

JavaScript
1
2
3
4
5
const objIndex = {
    background: 0,
    player: 6,
    wallHorizontal: 12
};
Отрисовка игровых объектов в gameLoop:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function gameLoop()
{
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // Background
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(0, 0, 0));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(500, 350, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.background, 6);
 
    // Player
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(250, 175, -10));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(20, 20, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.player, 6);
 
    // Wall Horizontal
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(250, 175, -10));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(200, 20, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.wallHorizontal, 6);
}
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
22.08.2022, 21:46


public/index.html

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
    <title>Document</title>
 
    <script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>
    <script src="https://code.createjs.com/1.0.0/soundjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>
</head>
 
<body>
    <canvas id="renderCanvas" width="500" height="350"></canvas>
 
    <script src="js/webgl-context.js"></script>
    <script src="js/shader-program.js"></script>
    <script src="js/texture.js"></script>
    <script src="js/vertex-buffers.js"></script>
    <script src="js/main.js"></script>
</body>
 
</html>
js/main.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
let program, texture, uMvpMatrixLocation;
const queue = new createjs.LoadQueue();
 
const modelMatrix = glMatrix.mat4.create();
const viewMatrix = glMatrix.mat4.create();
const projMatrix = glMatrix.mat4.create();
const projViewMatrix = glMatrix.mat4.create();
const mvpMatrix = glMatrix.mat4.create();
 
glMatrix.mat4.ortho(projMatrix, 0, 500, 350, 0, 50, 0);
glMatrix.mat4.lookAt(viewMatrix, [0, 0, 40], [0, 0, 0], [0, 1, 0]);
glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);
 
const objIndex = {
    background: 0,
    player: 6,
    wallHorizontal: 12
};
 
function handleFileComplete()
{
    if (!initWebGLContext("renderCanvas")) return;
 
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.viewport(0, 0, gl.canvas.clientWidth, gl.canvas.clientHeight);
    gl.clearColor(0.2, 0.2, 0.2, 1);
 
    const vertShaderSource = queue.getResult("vertShaderSource");
    const fragShaderSource = queue.getResult("fragShaderSource");
    program = createShaderProgram(vertShaderSource, fragShaderSource);
    
    uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
 
    const assetsJson = queue.getResult("assetsJson");
    loadVertexBuffers(program, assetsJson);
 
    const assetsImage = queue.getResult("assetsImage");
    texture = createTexture(assetsImage);
 
    gameLoop();
}
 
function loadAssets()
{
    queue.on("complete", handleFileComplete);
    queue.loadFile({ id: "vertShaderSource", src: "assets/shaders/default.vert" });
    queue.loadFile({ id: "fragShaderSource", src: "assets/shaders/default.frag" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsJson", src: "assets/sprites/texture.json" });
}
 
function gameLoop()
{
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // Background
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(0, 0, 0));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(500, 350, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.background, 6);
 
    // Player
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(250, 175, -10));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(20, 20, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.player, 6);
 
    // Wall Horizontal
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(250, 175, -10));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(200, 20, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.wallHorizontal, 6);
}
 
loadAssets();
js/shader-program.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function createShaderProgram(vertShaderSource, fragShaderSource)
{
    const vShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vShader, vertShaderSource);
    gl.compileShader(vShader);
    let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("vert: " + gl.getShaderInfoLog(vShader));
    };
 
    const fShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fShader, fragShaderSource);
    gl.compileShader(fShader);
    ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("frag: " + gl.getShaderInfoLog(fShader));
    };
 
    const program = gl.createProgram();
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    gl.useProgram(program);
 
    return program;
}
js/texture.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
function createTexture(image)
{
    const texture = gl.createTexture();
 
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
 
    return texture;
}
js/vertex-buffers.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
function loadVertexBuffers(program, assetsJson)
{
    const w = assetsJson.meta.size.w;
    const h = assetsJson.meta.size.h;
 
    const bgFrame = assetsJson.frames["background.png"].frame;
    const playerFrame = assetsJson.frames["player.png"].frame;
    const wallHorFrame = assetsJson.frames["wallHorizontal.png"].frame;
 
    const vertPositions = [
        // Background
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Player
        -0.5, -1, 0,
        -0.5, 0, 0,
        0.5, -1, 0,
        0.5, -1, 0,
        -0.5, 0, 0,
        0.5, 0, 0,
        // Wall Horizontal
        -0.5, 0, 0,
        -0.5, 1, 0,
        0.5, 0, 0,
        0.5, 0, 0,
        -0.5, 1, 0,
        0.5, 1, 0,
 
        // -0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, -0.5, 0,
        // 0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, 0.5, 0,
    ];
 
    const texCoords = [
        // Background
        bgFrame.x / w, (bgFrame.y) / h,
        bgFrame.x / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x) / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, (bgFrame.y + bgFrame.h) / h,
        // Player
        playerFrame.x / w, (playerFrame.y) / h,
        playerFrame.x / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x) / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, (playerFrame.y + playerFrame.h) / h,
        // Wall Horizontal
        wallHorFrame.x / w, (wallHorFrame.y) / h,
        wallHorFrame.x / w, (wallHorFrame.y + wallHorFrame.h) / h,
        (wallHorFrame.x + wallHorFrame.w) / w, wallHorFrame.y / h,
        (wallHorFrame.x + wallHorFrame.w) / w, wallHorFrame.y / h,
        (wallHorFrame.x) / w, (wallHorFrame.y + wallHorFrame.h) / h,
        (wallHorFrame.x + wallHorFrame.w) / w, (wallHorFrame.y + wallHorFrame.h) / h,
    ];
 
    const vertPosBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPositions), gl.STATIC_DRAW);
 
    const aPositionLocation = gl.getAttribLocation(program, "aPosition");
    gl.vertexAttribPointer(aPositionLocation, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aPositionLocation);
 
    const texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
 
    const aTexCoordLocation = gl.getAttribLocation(program, "aTexCoord");
    gl.vertexAttribPointer(aTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aTexCoordLocation);
}
js/webgl-context.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
gl = null;
 
function initWebGLContext(canvasName)
{
    const canvas = document.getElementById(canvasName);
    if (canvas === null)
    {
        console.log(`Failed to get a canvas element with the name "${canvasName}"`);
        return false;
    }
    gl = canvas.getContext("webgl", { alpha: false, premultipliedAlpha: false });
    return true;
}
public/assets/default.vert

glSlang
1
2
3
4
5
6
7
8
9
10
11
attribute vec3 aPosition;
attribute vec2 aTexCoord;
 
uniform mat4 uMvpMatrix;
varying vec2 vTexCoord;
 
void main()
{
    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}
public/assets/default.frag

glSlang
1
2
3
4
5
6
7
8
9
precision mediump float;
 
uniform sampler2D uSampler;
varying vec2 vTexCoord;
 
void main()
{
    gl_FragColor = texture2D(uSampler, vTexCoord);
}
Миниатюры
Вложения
Тип файла: zip draw-sprites.zip (36.0 Кб, 0 просмотров)
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
23.08.2022, 11:18
Организация gameLoop с помощью requestAnimationFrame

Для того чтобы организовать gameLoop можно использовать requestAnimationFrame, которая будет вызывать функцию gameLoop примерно 60 раз в секунду в примере ниже:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function gameLoop()
{
    // Указываем какую функцию вызвать следующей
    requestAnimationFrame(gameLoop);
 
    // Стираем всё с экрана путём заливки заданным ранее цветом, а
    // также очищаем буфер глубины, чтобы не было зависимости от порядка рисования
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    // Выбираем текстурный атлас
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // Задаём новые позиции объектам
    /* ... */
 
    // Рисуем все объекты заново, некоторые с новыми параметрами
    /* ... */
}
Функцию gameLoop надо вызвать один раз после инициализации игры и она будет вызываться примерно 60 раз в секунду.

Класс для работы с клавиатурой

Я хочу создавать браузерные 2D/3D игры с мультиплеером, поэтому изучаю это туториал: Real Time Multiplayer in HTML5. Это лучшее, что я находил по теме игр с мультиплеером. В этом туториале объясняется работа демки, которую я развернул на Heroku: http://battle-world.herokuapp.com/?debug Можете в двух вкладках браузера её потестить. У неё лобби так устроено, что автоматически создаётся новая игра для двух новых игроков. В исходниках этого примера на GitHub из статьи есть класс для работы с клавиатурой: keyboard.js. Я сделал очень укороченную версию:

keyboard.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Keyboard
{
    constructor()
    {
        this.keys = {};
        document.addEventListener("keydown", (event) => { this.onKeyChange(event, true); }, true);
        document.addEventListener("keyup", (event) => { this.onKeyChange(event, false); }, false);
    }
 
    onKeyChange(event, pressed)
    {
        this.keys[event.key] = pressed;
    }
 
    pressed(key)
    {
        return this.keys[key];
    }
}
Для использования этого класса нужно создать экземпляр Keyboard в файле main.js:

JavaScript
1
const keyboard = new Keyboard();
Теперь в gameLoop можно добавить обработку клавиш:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function gameLoop()
{
    requestAnimationFrame(gameLoop);
 
    if (keyboard.pressed("a"))
    {
        console.log('"a" was pressed');
    }
 
    if (keyboard.pressed("ArrowLeft"))
    {
        console.log('"ArrowLeft" was pressed');
    }
 
    /* ... */
}
Не забудьте подключить keyboard.js в файле index.html в теге <body>:

PHP/HTML
1
2
3
4
5
6
7
8
9
10
<body>
    <canvas id="renderCanvas" width="500" height="340"></canvas>
 
    <script src="js/webgl-context.js"></script>
    <script src="js/shader-program.js"></script>
    <script src="js/texture.js"></script>
    <script src="js/vertex-buffers.js"></script>
    <script src="js/keyboard.js"></script>
    <script src="js/main.js"></script>
</body>
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
23.08.2022, 13:09
Создание игрового уровня с помощью программы Tiled Map Editor

Скорее всего, в вашей игре "простая головоломка вроде тетриса" не нужно расставлять статические объекты по уровню, а может быть нужно. Может быть потом вам нужно будет создавать уровни. Есть очень популярная бесплатная программа Tiled Map Editor (сокращённо - Tiled), которая позволяет создавать уровни для любых движков и фреймворков: SFML, Phaser, Pixi.js, Unity и т.д., а также для игр, создаваемых без игровых движков и фремворков. Это очень полезная технология, которая поможет сократить время создания игры.

Я взял ассеты для игры из сообщений выше из книги по Phaser: "Discover Phaser", автора Thomas Palef. В этой книге объясняются основы Tiled на примере простой игры в жанре платформер, ассеты из которой вы видели из сообщений выше. Я нарисовал с помощью Tileset уровень, как в книге:



По нарисованному уровню я создал прямоугольники на "Object Layer" и подписал их "Static" и "Hole":



Эти прямоугольники я экспортировал в JSON: "File" > "Same As..." > выбрал JSON. Получил имена прямоугольников, их координаты координаты и размеры:

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
         "objects":[
                {
                 "height":20,
                 "id":1,
                 "name":"Static",
                 "rotation":0,
                 "type":"",
                 "visible":true,
                 "width":300,
                 "x":100,
                 "y":80
                }, 
                {
                 "height":20,
                 "id":2,
                 "name":"Static",
                 "rotation":0,
                 "type":"",
                 "visible":true,
                 "width":200,
                 "x":0,
                 "y":0
                },
В Free Texture Packer я закинул квадраты для объектов с именами "Static" и "Hole" из JSON:



Добавил парсинг этих объектов в файл vertex-buffers.js:

JavaScript
1
2
3
4
5
6
7
8
9
function loadVertexBuffers(program, assetsJson)
{
    const w = assetsJson.meta.size.w;
    const h = assetsJson.meta.size.h;
 
    const bgFrame = assetsJson.frames["background.png"].frame;
    const playerFrame = assetsJson.frames["player.png"].frame;
    const staticFrame = assetsJson.frames["static.png"].frame;
    const holeFrame = assetsJson.frames["hole.png"].frame;
В этот же файл добавил новые квадраты в VBO для вершин и в VBO для текстурных координат:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    const vertPositions = [
        // Background
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Player
        -0.5, -1, 0,
        -0.5, 0, 0,
        0.5, -1, 0,
        0.5, -1, 0,
        -0.5, 0, 0,
        0.5, 0, 0,
        // Static
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Hole
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
    ];
 
    const texCoords = [
        // Background
        bgFrame.x / w, (bgFrame.y) / h,
        bgFrame.x / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x) / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, (bgFrame.y + bgFrame.h) / h,
        // Player
        playerFrame.x / w, (playerFrame.y) / h,
        playerFrame.x / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x) / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, (playerFrame.y + playerFrame.h) / h,
        // Static
        staticFrame.x / w, (staticFrame.y) / h,
        staticFrame.x / w, (staticFrame.y + staticFrame.h) / h,
        (staticFrame.x + staticFrame.w) / w, staticFrame.y / h,
        (staticFrame.x + staticFrame.w) / w, staticFrame.y / h,
        (staticFrame.x) / w, (staticFrame.y + staticFrame.h) / h,
        (staticFrame.x + staticFrame.w) / w, (staticFrame.y + staticFrame.h) / h,
        // Hole
        holeFrame.x / w, (holeFrame.y) / h,
        holeFrame.x / w, (holeFrame.y + holeFrame.h) / h,
        (holeFrame.x + holeFrame.w) / w, holeFrame.y / h,
        (holeFrame.x + holeFrame.w) / w, holeFrame.y / h,
        (holeFrame.x) / w, (holeFrame.y + holeFrame.h) / h,
        (holeFrame.x + holeFrame.w) / w, (holeFrame.y + holeFrame.h) / h,
    ];
Парсю JSON в файле main.js в функции handleFileComplete() и сохраняю объекты уровня в objects:

JavaScript
1
2
3
4
5
6
7
8
9
    const level00Json = queue.getResult("level00Json");
    const layers = level00Json.layers;
    for (let i = 0; i < layers.length; i++)
    {
        if (layers[i].name == "Object Layer 1")
        {
            objects = layers[i].objects;
        }
    }
В файл main.js добавил индексы этих объектов для gl.drawArrays:

JavaScript
1
2
3
4
5
6
const objIndex = {
    background: 0,
    player: 6,
    static: 12,
    hole: 18
};
В gameLoop() рисую эти объекты выбирая их по индексу:

JavaScript
1
2
3
gl.drawArrays(gl.TRIANGLES, objIndex.static, 6);
/* ... */
gl.drawArrays(gl.TRIANGLES, objIndex.hole, 6);
А координаты и размеры объектов беру из массива objects:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    // Static
    for (let i = 0; i < objects.length; i++)
    {
        if (objects[i].name === "Static")
        {
            glMatrix.mat4.identity(modelMatrix);
            glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].x, objects[i].y, -10));
            glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].width, objects[i].height, 1));
            glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
            gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
            gl.drawArrays(gl.TRIANGLES, objIndex.static, 6);
        }
        else if (objects[i].name === "Hole")
        {
            glMatrix.mat4.identity(modelMatrix);
            glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].x, objects[i].y, -10));
            glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].width, objects[i].height, 1));
            glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
            gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
            gl.drawArrays(gl.TRIANGLES, objIndex.hole, 6);
        }
    }
Миниатюры
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
23.08.2022, 13:30
Демка к сообщению выше:



index.html

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
    <title>Document</title>
 
    <script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>
    <script src="https://code.createjs.com/1.0.0/soundjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/planck-js@0.3.31/dist/planck.min.js"></script>
</head>
 
<body>
    <canvas id="renderCanvas" width="500" height="340"></canvas>
 
    <script src="js/webgl-context.js"></script>
    <script src="js/shader-program.js"></script>
    <script src="js/texture.js"></script>
    <script src="js/vertex-buffers.js"></script>
    <script src="js/keyboard.js"></script>
    <script src="js/main.js"></script>
</body>
 
</html>
keboard.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Keyboard
{
    constructor()
    {
        this.keys = {};
        document.addEventListener("keydown", (event) => { this.onKeyChange(event, true); }, true);
        document.addEventListener("keyup", (event) => { this.onKeyChange(event, false); }, false);
    }
 
    onKeyChange(event, pressed)
    {
        this.keys[event.key] = pressed;
    }
 
    pressed(key)
    {
        return this.keys[key];
    }
}
main.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
let program, texture, uMvpMatrixLocation, objects;
const queue = new createjs.LoadQueue();
const keyboard = new Keyboard();
 
const modelMatrix = glMatrix.mat4.create();
const viewMatrix = glMatrix.mat4.create();
const projMatrix = glMatrix.mat4.create();
const projViewMatrix = glMatrix.mat4.create();
const mvpMatrix = glMatrix.mat4.create();
 
glMatrix.mat4.ortho(projMatrix, 0, 500, 340, 0, 50, 0);
glMatrix.mat4.lookAt(viewMatrix, [0, 0, 40], [0, 0, 0], [0, 1, 0]);
glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);
 
const objIndex = {
    background: 0,
    player: 6,
    static: 12,
    hole: 18
};
 
const worldScale = 30;
const world = new planck.World({ gravity: planck.Vec2(0, 9.8) });
 
function handleFileComplete()
{
    if (!initWebGLContext("renderCanvas")) return;
 
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.viewport(0, 0, gl.canvas.clientWidth, gl.canvas.clientHeight);
    gl.clearColor(0.2, 0.2, 0.2, 1);
 
    const vertShaderSource = queue.getResult("vertShaderSource");
    const fragShaderSource = queue.getResult("fragShaderSource");
    program = createShaderProgram(vertShaderSource, fragShaderSource);
 
    uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
 
    const assetsJson = queue.getResult("assetsJson");
    loadVertexBuffers(program, assetsJson);
 
    const assetsImage = queue.getResult("assetsImage");
    texture = createTexture(assetsImage);
 
    const level00Json = queue.getResult("level00Json");
    const layers = level00Json.layers;
    for (let i = 0; i < layers.length; i++)
    {
        if (layers[i].name == "Object Layer 1")
        {
            objects = layers[i].objects;
        }
    }
 
    gameLoop();
}
 
function loadAssets()
{
    queue.on("complete", handleFileComplete);
    queue.loadFile({ id: "vertShaderSource", src: "assets/shaders/default.vert" });
    queue.loadFile({ id: "fragShaderSource", src: "assets/shaders/default.frag" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsImage", src: "assets/sprites/texture.png" });
    queue.loadFile({ id: "assetsJson", src: "assets/sprites/texture.json" });
    queue.loadFile({ id: "level00Json", src: "assets/levels/level00.json" });
}
 
function gameLoop()
{
    requestAnimationFrame(gameLoop);
 
    if (keyboard.pressed("a"))
    {
        console.log('"a" was pressed');
    }
 
    if (keyboard.pressed("ArrowLeft"))
    {
        console.log('"ArrowLeft" was pressed');
    }
 
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // Background
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(0, 0, 0));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(500, 350, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.background, 6);
 
    // Player
    glMatrix.mat4.identity(modelMatrix);
    glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(250, 175, -10));
    glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(20, 20, 1));
    glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
    gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
    gl.drawArrays(gl.TRIANGLES, objIndex.player, 6);
 
    // Static
    for (let i = 0; i < objects.length; i++)
    {
        if (objects[i].name === "Static")
        {
            glMatrix.mat4.identity(modelMatrix);
            glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].x, objects[i].y, -10));
            glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].width, objects[i].height, 1));
            glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
            gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
            gl.drawArrays(gl.TRIANGLES, objIndex.static, 6);
        }
        else if (objects[i].name === "Hole")
        {
            glMatrix.mat4.identity(modelMatrix);
            glMatrix.mat4.translate(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].x, objects[i].y, -10));
            glMatrix.mat4.scale(modelMatrix, modelMatrix, glMatrix.vec3.fromValues(objects[i].width, objects[i].height, 1));
            glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
            gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
            gl.drawArrays(gl.TRIANGLES, objIndex.hole, 6);
        }
    }
}
 
loadAssets();
shader-program.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function createShaderProgram(vertShaderSource, fragShaderSource)
{
    const vShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vShader, vertShaderSource);
    gl.compileShader(vShader);
    let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("vert: " + gl.getShaderInfoLog(vShader));
    };
 
    const fShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fShader, fragShaderSource);
    gl.compileShader(fShader);
    ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
    if (!ok)
    {
        console.log("frag: " + gl.getShaderInfoLog(fShader));
    };
 
    const program = gl.createProgram();
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    gl.useProgram(program);
 
    return program;
}
texture.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
function createTexture(image)
{
    const texture = gl.createTexture();
 
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
 
    return texture;
}
vertex-buffers.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
function loadVertexBuffers(program, assetsJson)
{
    const w = assetsJson.meta.size.w;
    const h = assetsJson.meta.size.h;
 
    const bgFrame = assetsJson.frames["background.png"].frame;
    const playerFrame = assetsJson.frames["player.png"].frame;
    const staticFrame = assetsJson.frames["static.png"].frame;
    const holeFrame = assetsJson.frames["hole.png"].frame;
 
    const vertPositions = [
        // Background
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Player
        -0.5, -1, 0,
        -0.5, 0, 0,
        0.5, -1, 0,
        0.5, -1, 0,
        -0.5, 0, 0,
        0.5, 0, 0,
        // Static
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
        // Hole
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 0, 0,
        0, 1, 0,
        1, 1, 0,
 
        // Wall Horizontal
        // -0.5, 0, 0,
        // -0.5, 1, 0,
        // 0.5, 0, 0,
        // 0.5, 0, 0,
        // -0.5, 1, 0,
        // 0.5, 1, 0,
 
        // -0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, -0.5, 0,
        // 0.5, -0.5, 0,
        // -0.5, 0.5, 0,
        // 0.5, 0.5, 0,
    ];
 
    const texCoords = [
        // Background
        bgFrame.x / w, (bgFrame.y) / h,
        bgFrame.x / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x + bgFrame.w) / w, bgFrame.y / h,
        (bgFrame.x) / w, (bgFrame.y + bgFrame.h) / h,
        (bgFrame.x + bgFrame.w) / w, (bgFrame.y + bgFrame.h) / h,
        // Player
        playerFrame.x / w, (playerFrame.y) / h,
        playerFrame.x / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x + playerFrame.w) / w, playerFrame.y / h,
        (playerFrame.x) / w, (playerFrame.y + playerFrame.h) / h,
        (playerFrame.x + playerFrame.w) / w, (playerFrame.y + playerFrame.h) / h,
        // Static
        staticFrame.x / w, (staticFrame.y) / h,
        staticFrame.x / w, (staticFrame.y + staticFrame.h) / h,
        (staticFrame.x + staticFrame.w) / w, staticFrame.y / h,
        (staticFrame.x + staticFrame.w) / w, staticFrame.y / h,
        (staticFrame.x) / w, (staticFrame.y + staticFrame.h) / h,
        (staticFrame.x + staticFrame.w) / w, (staticFrame.y + staticFrame.h) / h,
        // Hole
        holeFrame.x / w, (holeFrame.y) / h,
        holeFrame.x / w, (holeFrame.y + holeFrame.h) / h,
        (holeFrame.x + holeFrame.w) / w, holeFrame.y / h,
        (holeFrame.x + holeFrame.w) / w, holeFrame.y / h,
        (holeFrame.x) / w, (holeFrame.y + holeFrame.h) / h,
        (holeFrame.x + holeFrame.w) / w, (holeFrame.y + holeFrame.h) / h,
    ];
 
    const vertPosBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPositions), gl.STATIC_DRAW);
 
    const aPositionLocation = gl.getAttribLocation(program, "aPosition");
    gl.vertexAttribPointer(aPositionLocation, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aPositionLocation);
 
    const texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
 
    const aTexCoordLocation = gl.getAttribLocation(program, "aTexCoord");
    gl.vertexAttribPointer(aTexCoordLocation, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aTexCoordLocation);
}
webgl-context.js

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
gl = null;
 
function initWebGLContext(canvasName)
{
    const canvas = document.getElementById(canvasName);
    if (canvas === null)
    {
        console.log(`Failed to get a canvas element with the name "${canvasName}"`);
        return false;
    }
    gl = canvas.getContext("webgl", { alpha: false, premultipliedAlpha: false });
    return true;
}
default.vert

glSlang
1
2
3
4
5
6
7
8
9
10
11
attribute vec3 aPosition;
attribute vec2 aTexCoord;
 
uniform mat4 uMvpMatrix;
varying vec2 vTexCoord;
 
void main()
{
    gl_Position = uMvpMatrix * vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
}
default.frag

glSlang
1
2
3
4
5
6
7
8
9
precision mediump float;
 
uniform sampler2D uSampler;
varying vec2 vTexCoord;
 
void main()
{
    gl_FragColor = texture2D(uSampler, vTexCoord);
}
Минатюры
Вложения
Тип файла: zip tiled-and-keyboard.zip (37.0 Кб, 3 просмотров)
1
3 / 3 / 0
Регистрация: 14.07.2012
Сообщений: 86
28.08.2022, 04:29  [ТС]
8Observer8, спасибо большое, все ваши сообщения я внимательно читаю, и на всякий случай даже сохраняю в PDF, вы мне очень помогли! Еще раз спасибо!
1
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
28.08.2022, 15:58
Спасибо! Очень приятно. Я у меня есть один подарок для вас. Вам нужно будет совершить всего несколько простых действие, а эффект будет значительный. Речь пойдёт об использовании TypeScript вместо JavaScript. TS включает в себя JS, добавляет в JS типы, проверку типов, enum, модификаторы доступа класса: public/private/protected, ключевое слово interface, то есть "контракт", как в C# и других языках, и многое другое. Одно из самых лучших добавлений - будет нормальный IntelliSense, то есть автодополнение кода в VSCode. Надеюсь, вы уже установили VSCode и Node.js. Я сделал маленький пример, который вы можете копировать для новых проектов и писать сразу на TS без каких-либо лишних действий.

Скачайте пример: typescript-webgl-example.zip (259.8 Кб)

Установите эти пакеты глобально командой в консоли (это нужно сделать только один раз):

npm i -g browserify nodemon parcel typescript uglify-js
Я включил в пример библиотеки Box2D и glMatrix. Если Box2D вам пока не нужен, то удалите строку "@box2d/core": "^0.9.0" в файле "package.json" перед продолжением.

В папке примера, где лежит "package.json" нужно ввести команду для установки локально модулей перечисленных в "package.json" (это нужно делать для каждого нового проекта):

npm install
или
npm i
Можно из консоли сразу запустить VSCode командой:

code .
В VSCode нажмите Ctrl+` для открытия терминала. В правом верхнем углу терминала нажмите кнопку "Split Terminal":

Название: 8a19079e-156d-43ad-80ad-fc9a2ba944a0.png
Просмотров: 168

Размер: 900 байт

Запустится два терминала. В левом терминале нужно набрать команду "npm run dev", а в правом "http-server -c-1":



Перейдите в браузер. Откройте в браузере консоль разработчика, в Chrome: Ctrl+Shift+J. Наберите в адресной строке браузера: localhost:8080 и вы увидите вывод программы на TS в консоль браузера.

Для сборки в Release, с сжатием bundle.js с помощью библиотеки UglifyJS нужно завершить работы левой консоли с Parcel, нажав кнопку с корзиной справа от терминалов:

Название: b04fbf20-79b1-41ec-9cfe-6ca7e9ebdf9a.png
Просмотров: 168

Размер: 1.0 Кб

Нужно снова нажать "Split Terminal" и ввести команду: npm run release Теперь вы можете закинуть папку "public" на какой-нибудь хостинг, например, на бесплатные GitHub Pages или Heroku. На Heroku можно сделать серверную часть с базой данных и веб-сокетами, а на GitHub Pages нельзя писать свою серверную часть.

Вывод примера в консоль браузера:

Название: 7a02eed0-6848-45d2-aeab-7b0894922211.png
Просмотров: 168

Размер: 4.2 Кб

Что делает пример:

JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { b2World } from "@box2d/core";
import MyClass from "./my-class";
import { myFunction1, myFunction2 } from "./my-functions";
import { gl, initWebGLContext } from "./webgl-context";
 
const world = b2World.Create({ x: 0, y: 9.8 });
console.log("gravity:", world.GetGravity());
 
myFunction1();
myFunction2();
 
const myClassInstance = new MyClass("my string");
const s = myClassInstance.getString();
console.log(s);
 
function gameLoop()
{
    requestAnimationFrame(gameLoop);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
}
 
function init()
{
    if (!initWebGLContext("renderCanvas")) return;
 
    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.viewport(0, 0, gl.canvas.clientWidth, gl.canvas.clientHeight);
    gl.clearColor(0.2, 0.3, 0.4, 1.0);
 
    gameLoop();
}
 
init();
Вложения
Тип файла: zip typescript-webgl-example.zip (259.8 Кб, 0 просмотров)
1
3 / 3 / 0
Регистрация: 14.07.2012
Сообщений: 86
01.09.2022, 15:57  [ТС]
8Observer8, Спасибо, теперь мне не придется начинать с чистого листа, и задача теперь не кажется нерешаемой, спасибо за то что уделили мне такое огромное время!
1
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
01.09.2022, 18:58
dnscheb, начните писать свой код на JS (конечно, копируйте и мой код тоже) прямо в браузере в этой песочнице: https://plnkr.co/ Туда можно закинуть звуки и спрайты. Например, сделайте, чтобы один из ваших спрайтов выводился на экран и проигрывался один из звуковых файлов. Сохраните проект в песочнице и скиньте ссылку на него здесь в этой теме. Лучше если вы загрузите текстурный атлас и json-файл к нему, созданные с помощью Free Texture Packer, как я показал в этом сообщении. Наверняка ваши спрайты имею размеры некратные двойки в какой-то степени, а WebGL 1.0 умеет работать только с такими спрайтами, чтобы и ширина и высота были кратными двойки в какой-нибудь степени, а в Free Texture Packer есть галочка "Power of two". Если возникнет проблема, например, спрайт не рисуется, то тем более публикуйте на https://plnkr.co/ я найду проблему.
0
3 / 3 / 0
Регистрация: 14.07.2012
Сообщений: 86
01.09.2022, 19:11  [ТС]
8Observer8, мне потребуется время на изучение JS, я его уже начал изучать
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
01.09.2022, 19:45
dnscheb, изучайте прямо во время переписывания игры. Не надо ждать начала работы, пока прочитаете книгу. В нём такие же конструкции, как на С++: if, for, while и т.д. Если что-то непонятно, можно как справочник использовать туториал Метанита: https://metanit.com/web/javascript/ Да, я понимаю, HTML5-теги в новинку.

Например, есть задача. Нужно вывести холст размером 300x300 закрашенный зелёным цветом. Эта задача решена у меня в примерах выше. Вы заглядываете в index.html. Видите, как создаётся холст заданного размера. Лучше смотреть на код, запоминать и пытаться написать самому в https://plnkr.co/, тогда со временем он запомнится и вы не будете тратить лишнее время на поиск в каком проекте он у вас был. Я открыл https://plnkr.co/ написал:

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
 
<html>
  <head>
    <title>My Game</title>
  </head>
 
  <body>
    <canvas width="300" height="300"></canvas>
  </body>
</html>
Но мне надо получить ссылку на <canvas> в коде на JS, поэтому я пишу внутри тега <canvas>: id = "renderCanvas", чтобы по id получить ссылку на элемент <canvas>:

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!doctype html>
 
<html>
  <head>
    <title>My Game</title>
  </head>
 
  <body>
    <canvas id="renderCanvas" width="300" height="300"></canvas>
 
    <script>
      const canvas = document.getElementById("renderCanvas");
      console.log(canvas.id);
    </script>
  </body>
</html>
В этой строке:

JavaScript
1
const canvas = document.getElementById("renderCanvas");
getElementById - говорит "взять элемент с таким-то id"

Вторая строка выводит значение id, которое равно "renderCanvas":
JavaScript
1
console.log(canvas.id);
Теперь для закраски холста зелёным цветом нужно взять контекст "webgl", установить цвет заливки в зелёный и залить холст вызовом gl.clear():

PHP/HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!doctype html>
 
<html>
  <head>
    <title>My Game</title>
  </head>
 
  <body>
    <canvas id="renderCanvas" width="300" height="300"></canvas>
 
    <script>
      const canvas = document.getElementById("renderCanvas");
      const gl = canvas.getContext("webgl");
      gl.clearColor(0, 1, 0, 1);
      gl.clear(gl.COLOR_BUFFER_BIT);
    </script>
  </body>
</html>
Я даю ссылку на сохранённый пример: https://plnkr.co/edit/LOEp7Waymg0g26An?preview А так же, по правилам форума, публикую код песочницы, как я сделал выше, либо, если файлов несколько и я не хочу публиковать те файлы заново, я сохраняю проект в архив из Plunker, для этого нажимаю правой кнопкой по "Project" и выбираю "Download as...":

Название: e0b31878-0a49-4d94-9f2e-3045b0f1261e.png
Просмотров: 157

Размер: 7.9 Кб

Будет создан архив, который я прикрепляю к сообщению:
Вложения
Тип файла: zip Project.zip (340 байт, 0 просмотров)
0
3 / 3 / 0
Регистрация: 14.07.2012
Сообщений: 86
01.09.2022, 20:07  [ТС]
8Observer8, ок, попробую
0
9953 / 2954 / 497
Регистрация: 05.10.2013
Сообщений: 8,075
Записей в блоге: 242
23.10.2022, 22:03
Пример из сообщения #29 переделал под Rollup. Rollup позволяет собирать в один файл bundle.js, который можно сжать с помощью Uglify. Для запуска примера нужно установить Node.js, а далее установить следующие пакеты глобально с помощью пакетного менеджера NPM, который установится вместе с Node.js:

npm i -g rollup
npm i -g uglify-js
Или одной командой:

npm i -g rollup uglify-js
В файле package.json я прописал команды для сборки в Debug и Release:

JSON
1
2
3
4
5
    "dev": "rollup -cwm",
    "del-bundle-map": "del /f /q /s .\\public\\js\\bundle.js.map",
    "bundle": "rollup -cm",
    "minify": "uglifyjs public/js/bundle.js -o public/js/bundle.js",
    "release": "npm run bundle && npm run minify && npm run del-bundle-map"
Если ввести команду:

npm run dev
то Rollup сделает bundle.js и будет ждать изменений в файлах проекта. При сохранении изменений какого-нибудь файла проекта Rullup автоматически создаст bundle.js Ошибки в браузере будут выводиться с корректными номерами строк. В браузере можно ставить точки останова в инструментах разработчика.

Если ввести команду:

npm run release
то будет создан сжатый bundle.js, в котором будут удалены проблемы, отступы, имена переменных и функций будут сокращены до минимума (до одного или нескольких символов) и который будет быстрее загружаться, а исходники будут в этом случае нечитабельными.

А ещё убрал зависимость от устаревших библиотек Preload.js и Sound.js. Заменил Preload.js на fetch, а Sound.js можно заменить на Web Audio API

Демка в песочнице

Вложения
Тип файла: zip rollup-js.zip (44.3 Кб, 2 просмотров)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
23.10.2022, 22:03

Какие технологии использовать для работы с XML
Здравствуйте. Подскажите какие технологии использовать в проекте? Программа должна выполнять обработку xml файлов. Доступ к программе...

Какие технологии подойдут лучше всего для связки JS и mySQL?
Здравствуйте, я студент, скоро у меня сессия и курсовая робота по теме баз данных (mySQL). Темой моей курсовой работы я выбрал...

Какие технологии нужно использовать в клиент-серверном приложении
Я хотел бы написать приложение с возможностью отправления/получения данных на сервер (который находится на моем компьютере), но я...

Какие технологии использовать при разработке данного продутка
Добрый день! Мне нужно сделать на сайте конструктор рубашек для одного местного бренда. Видел на этом сайте...

Подскажите, какие технологии понадобятся?
Всем привет, возник такой вопрос Какие знания, технологии, язык программирования подойдут для написания программы типа Skype? И как...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
37
Ответ Создать тему
Новые блоги и статьи
Сукцессия 20. Война автоботов. Делаем ставки. Лёд против "пш-пш!".
anaschu 11.07.2026
Битва ИИ в ризосфере: Как мы скрестили жесткие ОДУ, закон Фика и «подземный шантаж» Уважаемые коллеги! Хочу поделиться уникальным опытом ведения междисциплинарной компьютерной разработки. Прямо. . .
Сукцессия 19. Какие выводы я сделал из модели? Часть 2.Внутримикоризный стехиометрический буфер и хроно-дискретные зоны экспансии
anaschu 11.07.2026
Био-Инженерный Ликбез: Архитектура Модели Ризосферы Математический аппарат моделирования
Сукцессия 18. Какие выводы я сделал из войны грибов с деревьями? Часть 1- рассказ и немного про термины
anaschu 11.07.2026
Био-Инженерный Ликбез: Архитектура Модели Ризосферы Математический аппарат моделирования Система ОДУ описывает динамику изменения массы различных компонентов системы во времени. Ключевой. . .
Просветление в 5 секунд
kumehtar 10.07.2026
Андрей Веретенников выдал фразу недавно: "Вам не обязательно кем-то себя считать"
Эксперимент с картинкой-обложкой на CodinGame
Adler 10.07.2026
Решил на днях провести эксперимент. У меня есть аккаунт на CodinGame, ТОП-3 по РФ среди 2200 участников (в разделе соревнования по программированию ИИ-ботов). Там есть обложка, куда можно влепить. . .
GitDOS: Когда я решил скрестить MS-DOS, GitHub и Claude
MaGz GoLd 08.07.2026
Привет, Киберфорум! Хочу поделиться своим проектом, который, надеюсь, вас заинтересует. GitDOS — это веб-приложение, которое позволяет запускать DOS в браузере с тремя крутыми фичами: диски живут в. . .
Учебный мини "цифровой двойник" = (Сервер = Java + Flask_питон )+(3D = Гугловскаая 4T nvideo + google colab) +( WMS = Odoo на Docker)
anaschu 07.07.2026
Увидел в требованиях к вакансии создателя имитационных моделей связку (REst + Api + WMS + Anylogic + питон + 3D Nvideo), и решил посмотреть, что это такое. Из этого всего я работал более менее. . .
Море в объективе
kumehtar 06.07.2026
Хотел написать много проникновенных слов, но передумал. Оно само - говорит. Кто слышит тот слышит.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru