游戏内核基础 一

内容纲要

游戏内核是游戏开发的核心,是对游戏开发效率提升的关键,这里写一个简单的游核。

前置

样例使用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

链接:https://indeex.cc

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


发表评论

您的电子邮箱地址不会被公开。