zoukankan      html  css  js  c++  java
  • 青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 3

    继上一次介绍了《神奇的六边形》的完整游戏开发流程后可点击这里查看,这次将为大家介绍另外一款魔性游戏《跳跃的方块》的完整开发流程。

              (点击图片可进入游戏体验)

    因内容太多,为方便大家阅读,所以分多次来讲解。

    若要一次性查看所有文档,也可点击这里

    接上回(《跳跃的方块》Part 2

    (三)控制展示游戏世界

    经过前面的准备,虚拟游戏世界已经构建完成,开始着手将虚拟世界呈现出来。

    添加图形资源

    导入所有的图形资源到Assets/atlas/main@atlas下,并重新打包图集。

    创建功能按钮和界面

    在game下添加引导界面、暂停按钮(UIImage)、分数显示(UIText)、暂停界面(Node)、暂停界面下的恢复按钮(UIImage)和一个半透明层(UIImage)

    • 暂停按钮
      暂停按钮的范围为相对于父节点左上角(20, 20, 70, 70),并且需要能接受事件。

    • 分数显示
      分数显示区域为相对于父节点右上角(width - 90, 20, width - 110, 70)的范围,并设置文本向右对齐。

    • 暂停界面
      暂停界面有一个屏蔽操作用的半透明遮罩和一个恢复按钮组成。遮罩需要铺满全屏,恢复按钮以父节点的中心为锚点,向下偏移40。 遮罩和恢复按钮都需要接受事件。

    • 引导界面 引导界面提示玩家应该如何操作,以屏幕中心为描点,进行布局。

    一点关于布局的理解

    显示的内容可以看做一个矩形区域,Pivot控制节点原点在矩形区域中的位置,Anchors和偏移值(left,right,top, bottom, anchoredX, anchoredY, width, height)则控制矩形四个顶点的位置。
    所以设置时,先确定希望节点原点的位置,设置好Pivot后,根据希望的矩形设置四个顶点的位置。
    假设:父节点的高度、宽度分别为h、w。那么当四个边可以根据公式表达分别为:

    • x1 = a1 * w + b1
    • x2 = a2 * w + b2
    • y1 = c1 * h + d1
    • y2 = c2 * h + d2

    就可以通过如下设置达到希望的效果:

    • anchor中 minX = a1, maxX = a2。当a1 === a2时,设置width = -b2 - b1;否则设置left = b1,right = -b2。
    • anchor中 minY = c1, maxY = c2。当c1 === c2时,设置height = -d2 - d1; 否者设置top = d1,right = -d2。

    创建游戏世界的基础坐标系

    在前面章节中创建的game节点,是一个铺满屏幕的节点,可以理解为对应屏幕,且Pivot为(0,0),那么坐标系为从左上到右下的一个坐标系,这个坐标系和虚拟世界的不同,需要转换下。 在game节点下创建一个节点origin,把origin作为显示虚拟世界的原点。

    1. 先调整x轴的坐标系。 调整origin的width为0,MinAnchors中的minX,maxX调整为0.5。这样origin就位于父节点的水平中心上。
    2. 在调整y轴方向。 原来的y轴正方向为从上至下,而虚拟世界的却是从下至上,所以对节点进行垂直翻转(scaleY = -1)来达到预期效果。
    3. 调整y轴原点位置。 垂直翻转后,原点位置位于屏幕上边缘,通过设置AnchoredY=960将节点移动到屏幕下边缘。 最终该节点的布局参数如下:

    映射规则

    • 原点所在的y轴即为游戏的deadLine。
    • 方块在屏幕中的位置为:y - deadLine。
    • 关卡在屏幕中的位置为:-deadLine。

    创建关卡父节点

    在虚拟世界的创建过程中,分析了关卡的特性,在显示时只需要显示屏幕中的关卡,甚至连创建也不需要,并且关卡是一个连着一个,有点类似于单列表格的形式。于是这里选择使用官方插件中的TableView来实现关卡效果。 使用TableView时,需要为所有的关卡创建一个父节点,和创建方块类似,我们创建一个levels的Node节点,作为所有关卡的父节点。 布局参数如下:

    创建方块

    在origin节点下创建一个UIImage节点:brick。设置它相对于父节点的上边缘水平中心为锚点,以自己的中心为中心,旋转45度。 最终布局参数如下: 

     

    创建坐标系、关卡父节点、方块的具体操作如下:

     

    创建关卡数据适配器

    使用TableView时,还需要一个数据适配器(TableViewAdapter)来提供关卡数据。 先引入插件ExtraUI(com.qici.extraUI),建立一个脚本LevelAdapter。内容如下:

     1 var LevelAdapter = qc.defineBehaviour('qc.engine.LevelAdapter', com.qici.extraUI.TableViewAdapter, function() {
     2     var self = this;
     3 
     4     // 载入配置和游戏世界
     5     self.config = JumpingBrick.gameConfig;
     6     self.world = JumpingBrick.gameWorld;
     7 }, {
     8 });
     9 
    10 LevelAdapter.prototype.awake = function() {
    11 };
    12 
    13 /**
    14  * 获取表格大小,x、y同时只能有一个为Infinity
    15  * @return {{x: number|Infinity, y: number| Infinity}}
    16  */
    17 LevelAdapter.prototype.getTableSize = function() {
    18     // 关卡为无限的
    19     return { x: 1, y: Infinity};
    20 };
    21 
    22 /**
    23  * 根据在Table中的点返回对应的单元格
    24  * @param  {number} x - x轴坐标
    25  * @param  {number} y - y轴坐标
    26  * @return {{x: number, y: number}}} 返回点所在的单元格信息
    27  */
    28 LevelAdapter.prototype.findCellWithPos = function(x, y) {
    29     // 第一个格子为第一屏960的高度,第二个格子为第一关
    30     return { 
    31         x: 0,
    32         y: y < 960 ? 0 : (1 + Math.floor((y - 960) / this.config.levelInterval))
    33     };
    34 };
    35 
    36 /**
    37  * 获取节点的显示位置
    38  */
    39 LevelAdapter.prototype.getCellRect = function(col, row) {
    40     if (row === 0) 
    41         return new qc.Rectangle(0, 0, 100, 960);
    42     else
    43         return new qc.Rectangle(0, 960 + (row - 1) * this.config.levelInterval, 100, this.config.levelInterval);
    44 };
    45 
    46 /**
    47  * 节点处于不可见时,回收节点,
    48  * @param  {qc.Node} cell - 节点
    49  * @param  {number} col - 所在列
    50  * @param  {number} row - 所在行
    51  */
    52 LevelAdapter.prototype.revokeCell = function(cell, col, row) {
    53     // 关卡不可见时,删除已经生成的关卡数据
    54     this.world.deleteLevelInfo(row - 1);
    55 };
    56 
    57 /**
    58  * 节点处于可见时,创建节点,
    59  * @param  {qc.Node} cell - 节点
    60  * @param  {number} col - 所在列
    61  * @param  {number} row - 所在行
    62  */
    63 LevelAdapter.prototype.createCell = function(cell, col, row) {
    64     // 创建关卡时,设置关卡信息
    65     var self = this,
    66         levelInfo = self.world.getLevelInfo(row - 1);
    67     cell.levelShow.setLevelInfo(levelInfo);
    68 };
    View Code

    创建关卡的单元格处理脚本

    创建脚本LevelShow,用来控制关卡预制的显示方式。 内容如下:

     1 var LevelShow = qc.defineBehaviour('qc.engine.LevelShow', qc.Behaviour, function() {
     2     // 将脚本对象关联到节点上
     3     this.gameObject.levelShow = this;
     4 }, {
     5     leftLevel : qc.Serializer.NODE,   // 关卡左边阻挡
     6     rightLevel : qc.Serializer.NODE,  // 关卡右边阻挡
     7     block : qc.Serializer.NODE        // 阻挡块的父节点
     8 });
     9 
    10 LevelShow.prototype.onDestory = function() {
    11     // 释放关联
    12     this.gameObject.levelShow = null;
    13 };
    14 
    15 LevelShow.prototype.update = function() {
    16     var self = this,
    17         width = JumpingBrick.gameConfig.getGameWidth();
    18     // 如果是电脑浏览器打开,游戏显示的宽度可能会变化,所以需要根据屏幕宽度的变化,动态调整关卡阻挡的范围。
    19     // 防止左右两边出现空白区域
    20     if (width !== self.recordWidth) {
    21         var diff = (width - self.recordWidth) / 2;
    22         self.recordWidth = width;
    23 
    24         if (diff + self.leftLevel.width > 0) {
    25             self.leftLevel.x -= diff;
    26             self.leftLevel.width += diff;    
    27         }
    28 
    29         if (diff + self.rightLevel.width > 0) {
    30             self.rightLevel.width += diff;    
    31         }
    32     }
    33 };
    34 
    35 LevelShow.prototype.setLevelInfo = function(levelInfo) {
    36     var self = this,
    37         width = JumpingBrick.gameConfig.getGameWidth();
    38     var blockChildren = self.block.children;
    39     var blockLen = blockChildren.length;
    40 
    41     self.recordWidth = width;
    42     if (!levelInfo) {
    43         self.leftLevel.visible = self.rightLevel.visible = false;
    44         while (blockLen--) {
    45             blockChildren[blockLen].visible = false;
    46         }
    47         return;
    48     }
    49     var passArea = levelInfo.passArea,
    50         color = new qc.Color(levelInfo.color);
    51 
    52     self.leftLevel.visible = self.rightLevel.visible = true;
    53     // 设置左边阻挡
    54     self.leftLevel.x = -0.5 * width;
    55     self.leftLevel.y = passArea.y;
    56     self.leftLevel.width = passArea.x - self.leftLevel.x;
    57     self.leftLevel.height = passArea.height;
    58     self.leftLevel.colorTint = color;
    59 
    60     // 设置右边阻挡
    61     self.rightLevel.x = passArea.x + passArea.width;
    62     self.rightLevel.y = passArea.y;
    63     self.rightLevel.width = 0.5 * width - self.rightLevel.x;
    64     self.rightLevel.height = passArea.height;
    65     self.rightLevel.colorTint = color;
    66 
    67     // 确保块够用
    68     while (blockLen < levelInfo.block.length) {
    69         blockLen++;
    70         self.game.add.clone(self.leftLevel, self.block);
    71     }
    72 
    73     blockChildren = self.block.children;
    74     blockLen = blockChildren.length;
    75     var idx = -1;
    76     while (++idx < blockLen) {
    77         var blockInfo = levelInfo.block[idx];
    78         if (!blockInfo) {
    79             blockChildren[idx].visible = false;
    80         }
    81         else {
    82             blockChildren[idx].colorTint = color;
    83             blockChildren[idx].visible = true;
    84             blockChildren[idx].x = blockInfo.x;
    85             blockChildren[idx].y = blockInfo.y;
    86             blockChildren[idx].width = blockInfo.width;
    87             blockChildren[idx].height = blockInfo.height;
    88         }
    89     }
    90 };
    View Code

    创建关卡预制

    创建一个预制level,level下有三个节点:leftLevel(UIImage),rightLevel(UIImage),block(Node)。 并为其添加上一步创建的脚本LevelShow。

    构建控制脚本

    创建脚本GameControl

    创建脚本,并预设功能相关的节点,监听相关事件。具体实现如下:

     1 /**
     2  * 游戏控制,将虚拟世界投影到游戏世界,并管理暂停等处理
     3  */
     4 var GameControl = qc.defineBehaviour('qc.JumpingBrick.GameControl', qc.Behaviour, function() {
     5     var self = this;
     6 
     7     // 设置到全局中
     8     JumpingBrick.gameControl = self;
     9 
    10     // 方块
    11     self.brick = null;
    12 
    13     // 关卡的父节点,用于动态挂载关卡节点
    14     self.levelParent = null;
    15 
    16     // 开始指引界面
    17     self.startManual = null;
    18 
    19     // 暂停界面
    20     self.pausePanel = null;
    21 
    22     // 暂停按钮
    23     self.pauseButton = null;
    24 
    25     // 回到游戏按钮
    26     self.resumeButton = null;
    27 
    28     // 当前的状态
    29     self._state = 0;
    30 }, {
    31     brick: qc.Serializer.NODE,
    32     tableViewNode : qc.Serializer.NODE,
    33     scoreText: qc.Serializer.NODE,
    34     levelParent: qc.Serializer.NODE,
    35     startManual: qc.Serializer.NODE,
    36     pausePanel: qc.Serializer.NODE,
    37     pauseButton: qc.Serializer.NODE,
    38     resumeButton: qc.Serializer.NODE
    39 });
    40 
    41 /**
    42  * 初始化
    43  */
    44 GameControl.prototype.awake = function() {
    45     var self = this,
    46         config = JumpingBrick.gameConfig;
    47 
    48     // 监听节点的鼠标或者触摸按下事件
    49     self.addListener(self.gameObject.onDown, self.doPointDown, self);
    50     // 监听键盘事件
    51     self.addListener(self.game.input.onKeyDown, self.doKeyDown, self);
    52     // 监听暂停按钮
    53     self.pauseButton && self.addListener(self.pauseButton.onClick, self.doPause, self);
    54     // 监听恢复按钮
    55     self.resumeButton && self.addListener(self.resumeButton.onClick, self.doResume, self);
    56     // 监听游戏结束
    57     self.addListener(JumpingBrick.gameWorld.onGameOver, self.doGameOver, self);
    58 
    59       // 监听分数变化
    60       self.addListener(JumpingBrick.gameWorld.onScoreChanged, self.doScoreChanged, self);
    61 
    62     // 获取Brick上的结束时播放的TweenPosition
    63     self._brickTweenPosition = self.brick.getScript('qc.TweenPosition');
    64     if (self._brickTweenPosition)
    65     self.addListener(self._brickTweenPosition.onFinished, self.doGameFinished, self);
    66 
    67     // 获取levelParent上的结束时播放的TweenPosition
    68     self._levelTweenPosition = self.levelParent.getScript('qc.TweenPosition');
    69 
    70     // 根据配置初始化方块信息
    71     if (self.brick) {
    72         self.brick.width = self.brick.height = config.brickSide;
    73         self.brick.rotation = Math.PI / 4;
    74     }
    75 
    76     // 初始化
    77     self.switchState(GameControl.STATE_MANUEL);
    78 };
    79 
    80 /**
    81  * 销毁时
    82  */
    83 GameControl.prototype.onDestroy = function() {
    84    // 预生成的关卡节点清理
    85     this._blockPool = [];
    86 
    87     // 使用中的关卡节点清理
    88     this._showLevel = [];
    89 };
    View Code

    运行时状态管理

    游戏运行时,分为开始引导、游戏运行、游戏暂停、游戏结束4个状态,对这四个状态进行统一管理。代码如下:

     1 /**
     2  * 游戏开始时,指引界面状态
     3  */
     4 GameControl.STATE_MANUEL     = 0;
     5 /**
     6  * 游戏运行状态
     7  */
     8 GameControl.STATE_RUN         = 1;
     9 /**
    10  * 游戏暂停状态
    11  */
    12 GameControl.STATE_PAUSE     = 2;
    13 
    14 /**
    15  * 游戏结束处理
    16  */
    17 GameControl.STATE_GAMEOVER     = 3;
    18 
    19 /**
    20  * 切换状态
    21  */
    22 GameControl.prototype.switchState = function(state) {
    23     var self = this;
    24     self.state = state;
    25     self.startManual.visible = self.state === GameControl.STATE_MANUEL;
    26     if (self.startManual.visible) {
    27         // 进入开始引导时,必须重置游戏世界
    28         JumpingBrick.gameWorld.resetWorld();
    29         self.tableViewNode.getScript('com.qici.extraUI.TableView').revokeAllCell();
    30     }
    31 
    32      self.pausePanel.visible = self.state === GameControl.STATE_PAUSE;
    33     // 同步虚拟世界和显示
    34      self.syncWorld();
    35 };
    View Code

    处理暂停和恢复时的数据保存及恢复

     1 /**
     2  * 保存游戏
     3  */
     4 GameControl.prototype.saveGameState = function() {
     5     var self = this,
     6         gameWorld = JumpingBrick.gameWorld,
     7         data = JumpingBrick.data;
     8     if (!data)
     9         return;
    10     var saveData = gameWorld.saveGameState();
    11     data.saveGameState(saveData);
    12 };
    13 
    14 /**
    15  * 恢复游戏
    16  */
    17 GameControl.prototype.restoreGameState = function() {
    18     var self = this,
    19         gameWorld = JumpingBrick.gameWorld,
    20         data = JumpingBrick.data;
    21     if (!data)
    22         return;
    23     var saveData = data.restoreGameState();
    24     if (saveData) {
    25         gameWorld.restoreGameState(saveData);
    26         self.switchState(GameControl.STATE_PAUSE);    
    27     }
    28 };
    29 
    30 /**
    31  * 清理游戏
    32  */
    33 GameControl.prototype.clearGameState = function() {
    34     var self = this,
    35         data = JumpingBrick.data;
    36     if (!data)
    37         return;
    38     data.clearGameState();
    39 };
    View Code

    处理功能效果

    对暂停、恢复进行处理。

     1 /**
     2  * 处理暂停
     3  */
     4 GameControl.prototype.doPause = function() {
     5     var self = this;
     6     self.saveGameState();
     7     self.switchState(GameControl.STATE_PAUSE);
     8 };
     9 
    10 /**
    11  * 处理恢复
    12  */
    13 GameControl.prototype.doResume = function() {
    14     var self = this;
    15     self.clearGameState();
    16     self.switchState(GameControl.STATE_RUN);
    17 };
    View Code

    处理输入事件处理

    让游戏支持输入。

     1 **
     2  * 处理方块跳跃
     3  */
     4 GameControl.prototype.doBrickJump = function(direction) {
     5     var self = this,
     6         world = JumpingBrick.gameWorld;
     7 
     8     if (self.state === GameControl.STATE_MANUEL) {
     9         // 引导状态跳跃直接切换到运行状态
    10         self.switchState(GameControl.STATE_RUN);
    11     }
    12 
    13     world.brickJump(direction);
    14 };
    15 
    16 /**
    17  * 处理点击
    18  */
    19 GameGControl.prototype.doPointDown = function(node, event) {
    20     var self = this;
    21     if (self.state !== GameControl.STATE_MANUEL &&
    22         self.state !== GameControl.STATE_RUN) {
    23         return;
    24     }
    25     var localPoint = self.gameObject.toLocal({x: event.source.x, y: event.source.y});
    26     var halfWidth = self.gameObject.width * 0.5;
    27     self.doBrickJump(localPoint.x - halfWidth);
    28 };
    29 
    30 /**
    31  * 处理键盘
    32  */
    33 GameControl.prototype.doKeyDown = function(keycode) {
    34     var self = this;
    35     if (keycode === qc.Keyboard.LEFT || keycode === qc.Keyboard.RIGHT) {
    36         if (self.state !== GameControl.STATE_MANUEL &&
    37             self.state !== GameControl.STATE_RUN) {
    38             return;
    39         }
    40         self.doBrickJump(keycode === qc.Keyboard.LEFT ? -1 : 1);
    41     }
    42     else if (keycode === qc.Keyboard.ENTER || keycode === qc.Keyboard.SPACEBAR) {
    43         if (self.state === GameControl.STATE_RUN) {
    44             self.doPause();
    45         }
    46         else if (self.state === GameControl.STATE_PAUSE) {
    47             self.doResume();
    48         }
    49     }
    50 };
    View Code

    处理游戏世界的事件

    需要处理游戏世界反馈回来的分数变更和游戏结束事件。

     1 /**
     2  * 分数变更
     3  */
     4 GameControl.prototype.doScoreChanged = function(score) {
     5     var self = this;
     6     if (self.scoreText) {
     7         self.scoreText.text = '' + score;
     8     }
     9     JumpingBrick.data.buildShareContent(score);
    10 };
    11 
    12 /**
    13  * 处理游戏结束
    14  */
    15 GameControl.prototype.doGameOver = function(type) {
    16     var self = this;
    17     // 切换状态
    18     self.switchState(GameControl.STATE_GAMEOVER);
    19     // 播放结束动画
    20     if (type !== qc.JumpingBrick.GameWorld.GAMEOVER_DEADLINE && self._brickTweenPosition) {
    21         if (self._levelTweenPosition) {
    22             self._levelTweenPosition.setCurrToStartValue();
    23             self._levelTweenPosition.setCurrToEndValue();
    24             self._levelTweenPosition.to.x += 6;
    25             self._levelTweenPosition.to.y += 6;
    26             self._levelTweenPosition.resetToBeginning();
    27             qc.Tween.playGroup(self.levelParent, 1);
    28         }
    29         self._brickTweenPosition.setCurrToStartValue();
    30         self._brickTweenPosition.setCurrToEndValue();
    31         self._brickTweenPosition.to.y = -2 * JumpingBrick.gameConfig.brickRadius;
    32         self._brickTweenPosition.duration = Math.max(0.01, Math.sqrt(Math.abs(2 * (self._brickTweenPosition.to.y - self._brickTweenPosition.from.y) / JumpingBrick.gameConfig.gravity)));    
    33         self._brickTweenPosition.resetToBeginning();
    34         qc.Tween.playGroup(self.brick, 1);
    35     }
    36     else {
    37         self.doGameFinished();
    38     }
    39 
    40 };
    41 
    42 /**
    43  * 处理游戏完结
    44  */
    45 GameControl.prototype.doGameFinished = function() {
    46     var self = this;
    47     // 更新数据
    48     if (JumpingBrick.data)
    49         JumpingBrick.data.saveScore(JumpingBrick.gameWorld.score);
    50 
    51     // 切换到结算界面
    52     qc.Tween.stopGroup(self.brick, 1);
    53     qc.Tween.stopGroup(self.levelParent, 1);
    54     self.brick.rotation = Math.PI / 4;
    55     // 当不存在界面管理时,直接重新开始游戏
    56     if (JumpingBrick.uiManager)
    57         JumpingBrick.uiManager.switchStateTo(qc.JumpingBrick.UIManager.GameOver);
    58     else
    59         self.switchState(GameControl.STATE_MANUEL);
    60 };
    View Code

    调度游戏并同步世界显示

     1 GameControl.prototype.resetFPS = function() {
     2     var self = this;
     3     self.game.debug.total = 0;
     4     self._fpsCount = 1;
     5 };
     6 
     7 /**
     8  * 每帧更新
     9  */
    10 GameControl.prototype.update = function() {
    11     var self = this;
    12 
    13     if (self.state === GameControl.STATE_RUN) {
    14         // 只有运行状态才处理虚拟世界更新
    15         var delta = self.game.time.deltaTime * 0.001;
    16         JumpingBrick.gameWorld.updateLogic(delta);
    17         self.syncWorld();
    18     }
    19 
    20     // 帧率分析,如果当前能支持60帧则60帧调度
    21     if (self._fpsCount > 50) {
    22         var cost = self.game.debug.total / self._fpsCount;
    23         self._fpsCount = 1;
    24         self.game.debug.total = 0;
    25         if (cost < 10) {
    26             self.game.time.frameRate = 60;
    27         }
    28         else {
    29             self.game.time.frameRate = 30;
    30         }
    31     }
    32     else {
    33         self._fpsCount++;
    34     }
    35 };
    36 
    37 
    38 /**
    39  * 同步世界数据
    40  */
    41 GameControl.prototype.syncWorld = function() {
    42     var self = this,
    43         world = JumpingBrick.gameWorld;
    44 
    45     // 同步方块
    46     self.brick.x = world.x;
    47     self.brick.y = world.y - world.deadline;
    48 
    49     self.levelParent.y = -world.deadline;
    50 };
    View Code

    组合脚本

    • 保存场景,刷新编辑页面,让编辑器能载入设置的插件。
    • 将GameConfig, GameWorld, GameControl, LevelAdapter都挂载到game节点上。并设置game节点可接受输入事件。
    • 将TableView挂载到origin上
    • 为brick,levels添加游戏结束的表现效果。添加TweenPosition和TweenRotation组件。
      levels添加TweenPosition后,设置如图:

    brick添加TweenRotaion后,设置如图:

    brick添加TweenPosition后,设置如图:

    • 设置关联。

    添加脚本并设置关联的操作如下(未包含Tween添加和设置):

    测试调整

    至此,游戏部分就已经基本完成了。因为代码处理中兼容了其他模块不存在的情况,所以现在已经可以提供给其他人进行测试、反馈,然后进行优化调整了。

    下次将继续讲解如何进行游戏当中的数据处理,敬请期待!

    另外,今天是平安夜,祝大家圣诞快乐!


    圣 诞 快 乐 .★ * ★.
    .*★ *. *..*     ★
    ★ Marry X'mas     *
    ★    & ‧°∴°﹒☆°.
    ‘*.  Happy New Year *
      ‘★     ★
        ‘*..★'

    其他相关链接

    开源免费的HTML5游戏引擎——青瓷引擎(QICI Engine) 1.0正式版发布了!

    JS开发HTML5游戏《神奇的六边形》(一)

    青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 1

  • 相关阅读:
    FastJSON使用笔记
    使用mysql-connector-java出现的错误
    Maven的学习
    前端部分-CSS基础介绍
    前端知识之HTML内容
    python--使用pymyslq操作数据库
    python---反射详解
    python----re正则模块详解
    python---str和repr
    python---random模块详解
  • 原文地址:https://www.cnblogs.com/qici/p/5072206.html
Copyright © 2011-2022 走看看