js/SpriteKit/SKLabelNode.js
'use strict'
import SKColor from './SKColor'
import SKNode from './SKNode'
import SKLabelVerticalAlignmentMode from './SKLabelVerticalAlignmentMode'
import SKLabelHorizontalAlignmentMode from './SKLabelHorizontalAlignmentMode'
import SKBlendMode from './SKBlendMode'
/**
* @access private
* @type {string}
*/
const _defaultVertexShader =
`#version 300 es
precision mediump float;
in vec3 position;
in vec2 texcoord;
uniform float screenWidth;
uniform float screenHeight;
out vec2 v_texcoord;
void main() {
vec3 pos = position;
pos.x = (pos.x * 2.0 / screenWidth) - 1.0;
pos.y = (pos.y * 2.0 / screenHeight) - 1.0;
v_texcoord = texcoord;
gl_Position = vec4(pos, 1.0);
}
`
/**
* @access private
* @type {string}
*/
const _defaultFragmentShader =
`#version 300 es
precision mediump float;
uniform sampler2D spriteTexture;
uniform float alpha;
in vec2 v_texcoord;
out vec4 outColor;
void main() {
outColor = texture(spriteTexture, v_texcoord);
outColor.a *= alpha;
}
`
/**
* A node that displays a text label.
* @access public
* @extends {SKNode}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode
*/
export default class SKLabelNode extends SKNode {
// Creating a New Label Node
/**
* Initializes a new label object with a text string.
* @access public
* @constructor
* @param {?string} text - The text to use to initialize the label node.
* @desc The label node’s font is set to Helvetica Neue Ultra Light, 32 point.
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519612-init
*/
constructor(text) {
super()
// Configuring the Label Message
/**
* The string that the label node displays.
* @access private
* @type {?string}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519788-text
*/
this._text = null
// Configuring the Label Font
/**
* The color of the label.
* @access private
* @type {?CGColor}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1520057-fontcolor
*/
this._fontColor = new SKColor(1.0, 1.0, 1.0, 1.0)
/**
* The font used for the text in the label.
* @access private
* @type {?string}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1520129-fontname
*/
this._fontName = 'HelveticaNeue-UltraLight'
/**
* The size of the font used in the label.
* @access private
* @type {number}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1520208-fontsize
*/
this._fontSize = 32.0
// Configuring the Label’s Position
/**
* The vertical position of the text within the node.
* @access private
* @type {SKLabelVerticalAlignmentMode}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519933-verticalalignmentmode
*/
this._verticalAlignmentMode = SKLabelVerticalAlignmentMode.baseline
/**
* The horizontal position of the text within the node.
* @access private
* @type {SKLabelHorizontalAlignmentMode}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519711-horizontalalignmentmode
*/
this._horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center
// Performing Color Blending
/**
* The label’s blend color.
* @type {?CGColor}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519938-color
*/
this.color = new SKColor(1.0, 1.0, 1.0, 1.0)
/**
* A floating-point value that describes how the color is blended with the font color.
* @type {number}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519724-colorblendfactor
*/
this.colorBlendFactor = 0.0
// Blending the Label into the Framebuffer
/**
* The blend mode used to draw the label into the parent’s framebuffer.
* @type {SKBlendMode}
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519598-blendmode
*/
this.blendMode = SKBlendMode.alpha
this._canvas = document.createElement('canvas')
this._context = this._canvas.getContext('2d')
this._glContext = null
this._texture = null
this._textureUpToDate = false
/**
* @access private
* @type {WebGLProgram}
*/
this._program = null
this._vertexArrayObject = null
this._vertexBuffer = null
this._indexBuffer = null
this.text = text
}
/**
* Initializes a new label object with a specified font.
* @access public
* @param {?string} fontName - The name of the font used by the label.
* @returns {SKLabelNode} -
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519917-init
*/
static labelWithFontNamed(fontName) {
const label = new SKLabelNode()
label.fontName = fontName
return label
}
/**
* Initializes a new label object with a text string.
* @access public
* @param {?string} text - The text to use to initialize the label node.
* @returns {SKLabelNode} -
* @desc The label node’s font is set to Helvetica Neue Ultra Light, 32 point.
* @see https://developer.apple.com/documentation/spritekit/sklabelnode/1519612-init
*/
static labelWithText(text) {
return new SKLabelNode(text)
}
get text() {
return this._text
}
set text(newValue) {
this._text = newValue
this._updateCanvas()
}
get fontColor() {
return this._fontColor
}
set fontColor(newValue) {
this._fontColor = newValue
this._updateCanvas()
}
get fontName() {
return this._fontName
}
set fontName(newValue) {
this._fontName = newValue
this._updateCanvas()
}
get fontSize() {
return this._fontSize
}
set fontSize(newValue) {
this._fontSize = newValue
this._updateCanvas()
}
get verticalAlignmentMode() {
return this._verticalAlignmentMode
}
set verticalAlignmentMode(newValue) {
this._verticalAlignmentMode = newValue
this._updateCanvas()
}
get horizontalAlignmentMode() {
return this._horizontalAlignmentMode
}
set horizontalAlignmentMode(newValue) {
this._horizontalAlignmentMode = newValue
this._updateCanvas()
}
_updateCanvas() {
this._context.font = `${this._fontSize}px ${this._fontName}`
const metrics = this._context.measureText(this._text)
this._canvas.width = metrics.width
this._canvas.height = this._fontSize * 3
this._context.font = `${this._fontSize}px ${this._fontName}`
this._context.fillStyle = this._fontColor.hexColor
switch(this._verticalAlignmentMode){
case SKLabelVerticalAlignmentMode.baseline:
this._context.textBaseline = 'alphabetic'
break
case SKLabelVerticalAlignmentMode.center:
this._context.textBaseline = 'middle'
break
case SKLabelVerticalAlignmentMode.top:
this._context.textBaseline = 'top'
break
case SKLabelVerticalAlignmentMode.bottom:
this._context.textBaseline = 'bottom'
break
default:
throw new Error(`unknown vertical alignment mode: ${this._verticalAlignmentMode}`)
}
//switch(this._horizontalAlignmentMode){
// case SKLabelHorizontalAlignmentMode.center:
// this._context.textAlign = 'center'
// break
// case SKLabelHorizontalAlignmentMode.left:
// this._context.textAlign = 'left'
// break
// case SKLabelHorizontalAlignmentMode.right:
// this._context.textAlign = 'right'
// break
// default:
// throw new Error(`unknown horizontal alignment mode: ${this._horizontalAlignmentMode}`)
//}
this._context.textAlign = 'left'
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height)
this._context.fillText(this._text, 0, this._canvas.height * 0.5)
this._textureUpToDate = false
}
/**
* @access private
* @param {WebGLRenderingContext} gl -
* @param {CGRect} viewRect -
* @returns {void}
*/
_render(gl, viewRect) {
const p = this.__presentation
if(this._texture === null || this._glContext !== gl){
this._glContext = gl
this._texture = gl.createTexture()
this._textureUpToDate = false
}
if(!this._textureUpToDate){
gl.bindTexture(gl.TEXTURE_2D, this._texture)
// texImage2D(target, level, internalformat, width, height, border, format, type, source)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this._canvas.width, this._canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this._canvas)
gl.generateMipmap(gl.TEXTURE_2D)
gl.bindTexture(gl.TEXTURE_2D, null)
this._textureUpToDate = true
}
if(this._program === null){
this._program = this._createProgram(gl)
}
const program = this._program
gl.useProgram(program)
if(this._vertexArrayObject === null){
this._createVertexArrayObject(gl, program)
}
gl.bindVertexArray(this._vertexArrayObject)
gl.uniform1f(gl.getUniformLocation(program, 'screenWidth'), viewRect.size.width)
gl.uniform1f(gl.getUniformLocation(program, 'screenHeight'), viewRect.size.height)
gl.uniform1f(gl.getUniformLocation(program, 'alpha'), p.alpha)
const data = this._createVertexData()
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW)
gl.uniform1i(gl.getUniformLocation(program, 'spriteTexture'), 0)
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, this._texture)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0)
}
_createProgram(gl) {
const program = gl.createProgram()
const vsText = _defaultVertexShader
const fsText = _defaultFragmentShader
// initialize vertex shader
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vertexShader, vsText)
gl.compileShader(vertexShader)
if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
const info = gl.getShaderInfoLog(vertexShader)
throw new Error(`SKSpriteNode vertex shader compile error: ${info}`)
}
// initialize fragment shader
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragmentShader, fsText)
gl.compileShader(fragmentShader)
if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
const info = gl.getShaderInfoLog(fragmentShader)
throw new Error(`particle fragment shader compile error: ${info}`)
}
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// link program object
gl.linkProgram(program)
if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
const info = gl.getProgramInfoLog(program)
throw new Error(`program link error: ${info}`)
}
//gl.useProgram(program)
return program
}
/**
* @access private
* @param {WebGLRenderingContext} gl -
* @param {WebGLProgram} program -
* @returns {void}
*/
_createVertexArrayObject(gl, program) {
this._vertexArrayObject = gl.createVertexArray()
gl.bindVertexArray(this._vertexArrayObject)
this._vertexBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer)
const positionLoc = gl.getAttribLocation(program, 'position')
gl.bindAttribLocation(program, positionLoc, 'position')
gl.enableVertexAttribArray(positionLoc)
// idx, size, type, norm, stride, offset
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0)
const texcoordLoc = gl.getAttribLocation(program, 'texcoord')
gl.bindAttribLocation(program, texcoordLoc, 'texcoord')
gl.enableVertexAttribArray(texcoordLoc)
// idx, size, type, norm, stride, offset
gl.vertexAttribPointer(texcoordLoc, 2, gl.FLOAT, false, 20, 12)
this._indexBuffer = gl.createBuffer()
const indexData = new Uint8Array([0, 3, 2, 0, 1, 3])
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW)
}
_createVertexData() {
const p = this.__presentation._worldPosition
const w = this._canvas.width * this.__presentation.xScale
const h = this._canvas.height * this.__presentation.yScale
let left = p.x
let right = p.x
let top = p.y + h * 0.5
let bottom = p.y - h * 0.5
switch(this.__presentation._horizontalAlignmentMode){
case SKLabelHorizontalAlignmentMode.center:
left -= w * 0.5
right += w * 0.5
break
case SKLabelHorizontalAlignmentMode.left:
right += w
break
case SKLabelHorizontalAlignmentMode.right:
left -= w
break
default:
// unknown mode
break
}
const arr = [
left, top, this.__presentation._worldZPosition, 0, 0,
right, top, this.__presentation._worldZPosition, 1, 0,
left, bottom, this.__presentation._worldZPosition, 0, 1,
right, bottom, this.__presentation._worldZPosition, 1, 1
]
return new Float32Array(arr)
}
copy() {
const node = new SKLabelNode()
node._copyValue(this)
return node
}
_copyValue(src) {
super._copyValue(src)
this._text = src._text
this._fontColor = src._fontColor._copy()
this._fontName = src._fontName
this._fontSize = src._fontSize
this._verticalAlignmentMode = src._verticalAlignmentMode
this._horizontalAlignmentMode = src._horizontalAlignmentMode
this.color = src.color._copy()
this.colorBlendFactor = src.colorBlendFactor
this.blendMode = src.blendMode
this._canvas = src._canvas
this._context = src._context
//this._glContext = src._glContext
//this._texture = src._texture
//this._program = src._program
//this._vertexArrayObject = src._vertexArrayObject
//this._vertexBuffer = src._vertexBuffer
//this._indexBuffer = src._indexBuffer
}
}