游戏内核基础 一
内容纲要
游戏内核是游戏开发的核心,是对游戏开发效率提升的关键,这里写一个简单的游核。
前置
样例使用React框架和create-react-app官方脚手架,具体请查阅官方文档,此处不再赘述。
PC浏览器请使用最新版本chrome或者firefox。
移动端浏览器请使用最新系统或安装移动版chrome或firefox。
Dart版本请查阅游核 Dart版本(什么时候想起来就有可能更新);
初始化
首先,需要初始化WebGL并设置基本的循环渲染,然后绘制:
export let gl: any;
export class WEBGLUtils{
public static initialize(id?:string): HTMLCanvasElement{
let canvas: HTMLCanvasElement;
if(id){
canvas = document.getElementById(id) as HTMLCanvasElement;
if(!canvas){
throw new Error("Not find element:" + id);
}
}else{
canvas = document.createElement("canvas") as HTMLCanvasElement;
let reactApp: HTMLElement = document.getElementsByClassName("App")[0] as HTMLElement;
if(reactApp){
reactApp.appendChild(canvas);
}else{
document.body.appendChild(canvas);
}
}
gl = canvas.getContext("webgl");
if(!gl){
throw new Error("Not initialize WEBGL!");
}
return canvas;
}
}
由于使用的是react,直接使用脚手架的初始容器App,如果是angular或者vue,需要增加其他兼容即可。当然也可以不适用脚手架,直接使用namespace。
至于css样式,这里使用的是sass的scss,根据自己的使用情况自定。
实例化
然后在主类中使用:
import {gl, WEBGLUtils} from './utils/WebGL';
export default class Core{
private _canvas!: HTMLCanvasElement;
public start(): void {
this._canvas = WEBGLUtils.initialize();
gl.clearColor(0, 0, 0, 1);
this.resize();
this.loop();
}
public resize():void {
if(this._canvas){
this._canvas.width = window.innerWidth;
this._canvas.height = window.innerHeight;
}
}
}
private loop():void {
gl.clear(gl.COLOR_BUFFER_BIT);
requestAnimationFrame(this.loop.bind(this));
}
}
这个时候得到黑黑的场景:

着色器
然后,封装WebGL着色器:
import {gl} from './../utils/WebGL';
export default class Shader{
private _name: string;
private _program?: WebGLProgram;
private _attributes: {
[name: string]: number
} = {};
public constructor(name: string, vertexSource: string, fragmentSource: string){
this._name = name;
let vertexShader = this.loadShader(vertexSource, gl.VERTEX_SHADER);
let fragmentShader = this.loadShader(fragmentSource, gl.FRAGMENT_SHADER);
this.createProgram(vertexShader, fragmentShader);
this.detectAttributes();
}
public get name(): string{
return this._name;
}
public use(): void{
gl.useProgram(this._program);
}
public getAttributeLocation(name: string): number {
if(this._attributes[name] === undefined){
throw new Error("Not find attribute " + name + "in shader named " + this._name);
}
return this._attributes[name];
}
private loadShader(source: string, shaderType: number): WebGLShader{
let shader: WebGLShader = gl.createShader(shaderType);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let err = gl.getShaderInfoLog(shader);
if(err){
throw new Error("Error compile " + this._name + ":" + err);
}
return shader;
}
private createProgram(vertexShader: WebGLShader, fragmengShader: WebGLShader): void{
this._program = gl.createProgram();
gl.attachShader(this._program, vertexShader);
gl.attachShader(this._program, fragmengShader);
gl.linkProgram(this._program);
let err = gl.getProgramInfoLog(this._program);
if(err){
throw new Error("Error compile " + this._name + ":" + err);
}
}
private detectAttributes(): void{
let attributeCount = gl.getProgramParameter(this._program, gl.ACTIVE_ATTRIBUTES);
for(let i =0; i< attributeCount; i++){
let attributeInfo: WebGLActiveInfo = gl.getActiveAttrib(this._program, i);
if(!attributeInfo){
break;
}
this._attributes[attributeInfo.name] = gl.getAttribLocation(this._program, attributeInfo.name);
}
}
}
缓冲区
接着,封装缓冲区:
import { gl } from "../utils/WebGL";
export class AttributeInfo{
public location!: number;
public size!: number;
public offset!: number;
}
export default class GLBuffer{
private _hasAttributeLocation: boolean = false;
private _elementSize!: number;
private _stride?: number;
private _buffer?: WebGLBuffer;
private _targetBufferType?: number;
private _dataType?: number;
private _mode?: number;
private _typeSize!: number;
private _data: number[] = [];
private _attributes: AttributeInfo[] = [];
/**
*
* @param elementSize 每个buffer元素大小
* @param dataType buffer数据 默认gl.FLOAT
* @param targetBufferType bufferTarget类型
* @param mode buffer mode 默认 gl.TRIANGLES
*/
public constructor(elementSize: number, dataType: number = gl.FLOAT, targetBufferType: number = gl.ARRAY_BUFFER, mode: number = gl.TRIANGLES) {
this._elementSize = elementSize;
this._dataType = dataType;
this._targetBufferType = targetBufferType;
this._mode = mode;
switch(this._dataType){
case gl.FLOAT:
case gl.INT:
case gl.UNSIGNED_INT:
this._typeSize = 4;
break;
case gl.SHORT:
case gl.UNSIGNED_SHORT:
this._typeSize = 2;
break;
case gl.BYTE:
case gl.UNSIGNED_BYTE:
this._typeSize = 1;
break;
default:
throw new Error("Unrecognized data type: " + dataType.toString());
}
this._stride = this._elementSize = this._typeSize;
this._buffer = gl.createBuffer();
}
public destroy(): void{
gl.deleteBuffer(this._buffer);
}
public bind(normalized: boolean = false): void{
gl.bindBuffer(this._targetBufferType, this._buffer);
if(this._hasAttributeLocation){
for(let it of this._attributes){
gl.vertexAttribPointer(it.location, it.size, this._dataType, normalized, this._stride, it.offset * this._typeSize);
gl.enableVertexAttribArray(it.location);
}
}
}
public unbind(): void{
for(let it of this._attributes){
gl.disableVertexAttribArray(it.location);
}
gl.bindBuffer(gl.ARRAY_BUFFER, this._buffer);
}
public addAttributeLocation(info: AttributeInfo): void{
this._hasAttributeLocation = true;
this._attributes.push(info);
}
public pushBackData(data: number[]): void{
for(let d of data){
this._data.push(d);
}
}
public upload(): void{
gl.bindBuffer(this._targetBufferType, this._buffer);
let bufferData!: ArrayBuffer;
switch(this._dataType){
case gl.FLOAT:
bufferData = new Float32Array(this._data);
break;
case gl.INT:
bufferData = new Int32Array(this._data);
break;
case gl.UNSIGNED_INT:
bufferData = new Uint32Array(this._data);
break;
case gl.SHORT:
bufferData = new Int16Array(this._data);
break;
case gl.UNSIGNED_SHORT:
bufferData = new Uint16Array(this._data);
break;
case gl.BYTE:
bufferData = new Int8Array(this._data);
break;
case gl.UNSIGNED_BYTE:
bufferData = new Uint8Array(this._data);
break;
}
gl.bufferData(this._targetBufferType, bufferData, gl.STATIC_DRAW);
}
public draw(): void{
if(this._targetBufferType === gl.ARRAY_BUFFER){
gl.drawArrays(this._mode, 0, this._data.length / this._elementSize);
}else if(this._targetBufferType === gl.ELEMENT_ARRAY_BUFFER){
gl.drawElements(this._mode, this._data.length, this._dataType, 0);
}
}
}
然后,测试功能:
//...
import Shader from './shader/Shader';
import GLBuffer, { AttributeInfo } from './buffer/GLBuffer';
export default class Core{
//...
private _shader!: Shader;
private _buffer!: GLBuffer;
public start(): void {
//...
this.loadShaders();
this._shader.use();
this.createBuffer();
this.resize();
this.loop();
}
public resize():void {
if(this._canvas){
//...
gl.viewport(0, 0, this._canvas.width, this._canvas.height);
}
}
public loadShaders(): void{
let vertexShaderSource = `
attribute vec3 a_position;
void main(){
gl_Position = vec4(a_position, 1.0);
}
`;
let fragmentShaderSource = `
precision mediump float;
uniform vec4 u_color;
void main(){
gl_FragColor = u_color;
}
`;
this._shader = new Shader("basic", vertexShaderSource, fragmentShaderSource);
}
private createBuffer(): void{
this._buffer = new GLBuffer(3);
let positionAttribute = new AttributeInfo();
positionAttribute.location = this._shader.getAttributeLocation("a_position");
positionAttribute.offset = 0;
positionAttribute.size = 3;
this._buffer.addAttributeLocation(positionAttribute);
let vertices = [//x, y, z
0, 0, 0,
0, 0.5, 0,
0.5, 0.5, 0
];
this._buffer.pushBackData(vertices);
this._buffer.upload();
this._buffer.unbind();
}
private loop():void {
gl.clear(gl.COLOR_BUFFER_BIT);
let colorPosition = this._shader.getUniformLocation("u_color");
gl.uniform4f(colorPosition, 1, 0.5, 0, 1);
this._buffer.bind();
this._buffer.draw();
let positionLocation = this._shader.getAttributeLocation("a_position");
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(this.loop.bind(this));
}
}
就能看到下面的场景了:

code enjoy! 🧐🧐🧐
作者:indeex
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。