一起来学习用nodejs和CocosCreator开发网络游戏吧(六)--- 可操纵的游戏角色(下)

其实用了很长时间思考了一下一些数据运算方面放在哪里合适。当然,数值方面的运算肯定要放在服务端是正确的,本地的数值计算就会有被修改器修改、数据传输中抓包改包等作弊、外挂的问题存在,不过对于我这个小项目目前开发阶段来说,只涉及到对游戏角色移动操控这块。

在我自己所接触过的网游中,确实存在两种方式来处理角色移动数据,一个是发出操作指令,然后服务器根据操作指令计算出移动坐标再返给客户端处理,一个是本地计算移动距离,再将数据交付给服务器。这两种情况在游戏掉线的时候就能有很明显的感觉,前者掉线后,角色就无法移动了,做出的任何操作指令都不会有反馈,后者在掉线后,依然可以操纵角色在游戏中活动。

这两种方式各有利弊,前者可以杜绝各种修改数据的外挂,但是对服务器会有很大的数据计算压力。后者无法避免一些神仙外挂,但是服务器方面计算成本就会降低很多。

因为自身做过很多年的单机游戏,想学习一下网游的开发理论知识,才决定写这一系列的博客记录学习过程,所以决定使用全服务器处理数据的方案,将包括角色移动的数据计算都放到服务器中进行。

在之前的篇章中,已经做好了摇杆,在场景中摆放了个角色。在这个基础上,接下来扩展一下服务器和客户端代码,让我们可以通过摇杆操作角色,让服务器运算角色移动后的坐标并返回客户端,在客户端中表现出人物行走。同时,也能支持多个客户端同时访问,在客户端中可以看到多个角色的移动。

在确定了方案之后,就需要对服务器和客户端做出不小的改动。

首先改造一下服务器,因为大部分的数据处理都放在服务器来做,那么就需要让服务器来主导数据形式。

因为初步设计的模式是以地图为单位来创建服务器,那么每个地图都会有自己的信息,包括地图的名称,地图中npc的数据以及地图中玩家的数据。

在Server中创建MapServerModules文件夹,在里面创建mapObject.js文件,编写地图的类代码。

class MapObject {
    constructor(mapName) {
        this.mapName = mapName;//地图名称
        this.npcs = [];//地图中npc的数组
        this.players = [];//地图中玩家的数组
    }

    //客户端连接完毕,创建玩家的数据信息后将其放入地图的玩家数组中
    addPlayer(player) {
        for(let i in this.players) {
            let playerItem = this.players[i];
            if(player.getPlayerData().playerId === playerItem.getPlayerData().playerId) {
                return;
            }
        }
        this.players.push(player);
    }
    /**
     * 监听到玩家离开地图,要从数组中将玩家信息删除
     * 因为玩家离开后,需要通知其他还在连接中的玩家
     * 所以延迟从数组中删掉,是为了给其他玩家发送地图玩家数据时标记玩家退出
     */
    deletePlayer(player) {
        setTimeout(() => {
            this.players.splice(this.players.indexOf(player), 1);
        }, 1000);
    }

    //地图信息,将npc和玩家数据打包成数据集
    getMapInfo() {
        return {
            npcs: this.npcs.map((item) => {return null}),
            players: this.players.map((item) => {return item.getPlayerData()})
        }
    }
}

module.exports = MapObject;

地图类创建好后,接着创建一个角色类,里面包含了玩家的一些数据,以及相关的数据计算。当然,这些数据计算日后肯定需要抽离出来,但因为现在是一个简单的demo,暂时放在同一个类中进行处理。

在根目录的modules文件夹中,创建playerObject.js,用来编写玩家类的代码。因为自己设想的demo开发流程,还没有到加入数据库的时候,所以在这里,玩家数据初始化都使用了一些随机参数。

class PlayerObject {
  //构造函数中的相关初始化。
  constructor() {
    this.dt = 1 / 60; //因为游戏的设计帧率是60帧,所以服务器在计算数据的时候,也选择60帧(即每秒进行60次计算)
    this.update = null; //循环计时器

    //角色状态参数,站立,向左右方向行走
    this.State = {
      STATE_STAND: 1,
      STATE_WALK_LEFT: 2,
      STATE_WALK_RIGHT: 3
    };

    //玩家角色数据集,初始化随机的id和随机的名字
    this.playerData = {
      playerId: this.makeUUID(),
      playerName: "player" + Math.ceil(Math.random() * 100),
      playerAttribute: {
        //角色数据
        logout: false, //是否退出地图的标示符
        currentState: this.State.STATE_STAND, //角色当前状态
        moveSpeed: 150.0, //角色移动速度
        position: {
          //角色在地图中的坐标
          x: -500,
          y: -460
        },
        scale: {
          //角色动画的缩放参数,角色的面向是通过缩放参数来控制的
          x: 3,
          y: 3
        }
      }
    };
    this.connection = null; //角色的websocket连接对象
  }

  //获取到角色的相关数据,用在map中生成数据合集
  getPlayerData() {
    return this.playerData;
  }

  addConnection(connection) {
    this.connection = connection;
  }

  makeUUID() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
      var r = (Math.random() * 16) | 0,
        v = c == "x" ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }

  //接收玩家的操作,目前只有操控移动,所以这里是根据操作改变角色状态
  operation(data) {
    this.playerData.playerAttribute.currentState = data.state;
  }

  /**
   * 角色在服务器生成后,需要发送消息到客户端
   * 客户端进行角色人物动画加载,加载完成后,再通知服务器可以开始角色数据运算
   * 这里使用定时器循环计算
   */

  start() {
    this.update = setInterval(() => {
      if (!this.playerData.playerAttribute) {
        return;
      }
      switch (this.playerData.playerAttribute.currentState) {
        case this.State.STATE_STAND:
          break;
        case this.State.STATE_WALK_LEFT:
          this.walkLeft();
          break;
        case this.State.STATE_WALK_RIGHT:
          this.walkRight();
          break;
      }
      //计算完成后,通过websocket的连接对象,连同地图中全部的npc和角色信息,一并返回给客户端
      if (this.connection) {
        let map = this.connection.map;
        let mapData = map.getMapInfo();
        let data = {
            /**
             * 数据格式暂定
             * dataType 数据操作指令
             * data 数据
             */
          dataType: "GAME_PLAYER_DATA",
          data: mapData
        };
        this.connection.sendText(JSON.stringify(data));
      }
    }, this.dt * 1000);
  }

  //标记角色离开地图,停止数据计算
  end() {
    this.playerData.playerAttribute.logout = true;
    clearInterval(this.update);
  }

  //计算角色移动后的坐标,和处理面向
  walkLeft() {
    let dis = this.playerData.playerAttribute.moveSpeed * this.dt;
    this.playerData.playerAttribute.position.x =
      this.playerData.playerAttribute.position.x - dis;
    this.playerData.playerAttribute.scale.x =
      Math.abs(this.playerData.playerAttribute.scale.x) * -1;
  }

  walkRight() {
    let dis = this.playerData.playerAttribute.moveSpeed * this.dt;
    this.playerData.playerAttribute.position.x =
      this.playerData.playerAttribute.position.x + dis;
    this.playerData.playerAttribute.scale.x = Math.abs(
      this.playerData.playerAttribute.scale.x
    );
  }
}

module.exports = PlayerObject;

如此就创建好了一个简单的地图类和一个简单的角色类处理地图和角色信息。

接下来创建一下地图的服务器。在Server目录下,添加mapServer.js。

const wsServer = require("../modules/websocketServer");//引入websocketServer
const mapObject = require("./MapServerModules/mapObject");//引入地图类
const playerObject = require("../modules/playerObject");//引入角色类

const map_1 = new mapObject("map_1");//初始化地图信息

//同聊天服务器一样,几个回调函数来处理连接的监听
const textCallback = (server, result, connection) => {
  let dataType = result.dataType;
  let player = null;

  //通过dataType的操作指令,执行不同的任务
  /**
   * PLAYER_SERVER_INIT 服务器角色初始化,用于客户端连接成功后,向服务器发送的命令
   * PLAYER_SERVER_INIT_OVER 服务器完成角色初始化,向客户端发送命令,要求客户端创建角色相关数据
   * PLAYER_CLIENT_INIT_OVER 客户端角色创建完成,通知服务器开始角色数据计算
   * PLAYER_OPERATION 客户端发起操作,控制角色,改变角色状态
   */
  switch (dataType) {
    case "PLAYER_SERVER_INIT"://服务器角色初始化,并添加连接对象
      player = new playerObject();
      player.addConnection(connection);
      connection["player"] = player;
      connection["map"] = map_1;
      connection.sendText(
        makeResponseData("PLAYER_SERVER_INIT_OVER", player.getPlayerData())
      );
      break;
    case "PLAYER_CLIENT_INIT_OVER"://客户端角色创建完成,开启角色数据计算,并将角色信息添加到地图信息中心
      player = connection.player;
      player.start();
      map_1.addPlayer(player);
      break;
    case "PLAYER_OPERATION"://客户端发来的操作,改变角色状态
      player = connection.player;
      player.operation(result.data);
      break;
  }
};

//通知客户端连接成功
const connectCallback = (server, result, connection) => {
  connection.sendText(makeResponseData("CONNECT_SUCCESS", null));
};

//客户端取消连接,将该客户端角色移出地图数据
const closeConnectCallback = (server, result, connection) => {
  connection.player.end();
  map_1.deletePlayer(connection.player);
};

//打包数据的公共函数
const makeResponseData = (dataType, data) => {
  return JSON.stringify({
    dataType,
    data
  });
};

module.exports = MapServer = port => {
  let callbacks = {
    textCallback: (server, result, connection) => {
      textCallback(server, result, connection);
    },
    connectCallback: (server, result, connection) => {
      connectCallback(server, result, connection);
    },
    closeConnectCallback: (server, result, connection) => {
      closeConnectCallback(server, result, connection);
    }
  };

  const mapServer = wsServer(port, callbacks);
};

因为在地图服务器的监听回调中,添加了connection的参数,所以也要修改对应的modules/websocketServer.js中相关代码,这里就不列出了。

修改index.js文件,添加服务器端口,可以随着服务器启动后启动地图服务器。

const http = require(‘http‘);
const url = require(‘url‘);
const chatServer = require(‘./Server/chatServer‘);
const mapServer = require(‘./Server/mapServer‘);
// const wsServer = require(‘./websocketServer‘);

http.createServer(function(req, res){
    var request = url.parse(req.url, true).query
    var response = {
        info: request.input ? request.input + ‘, hello world‘ : ‘hello world‘
    };
    res.setHeader("Access-Control-Allow-Origin", "*");//跨域
    res.write(JSON.stringify(response));
    res.end();
}).listen(8181);

const chat = chatServer(8183);
const map = mapServer(8184);

以上,服务器相关代码就完成了。记录的内容中可能有些许疏漏,具体可以到文章末尾列出的github地址下载代码。

接下来修改一下客户端相关代码,以便能够和服务器通信并完成操作响应。

因为服务器端口越来越多,可能在后续的地方会有相当多的地方要填写连接地址,这里将地址相关的代码提到公共类中,方便调用和统一维护。

修改Script/Common/Tools.js中的代码,添加连接地址获取。

    constructor() {
        this.webSocketServerUrl = "ws://127.0.0.1";
        this.chatPort = "8183";//端口需要和服务器保持一致
        this.testMapPort = "8184";
    }

    getChatServerUrl() {
        return this.webSocketServerUrl + ":" + this.chatPort;
    }

    getTestMapServerUrl() {
        return this.webSocketServerUrl + ":" + this.testMapPort;
    }

作为联网游戏,肯定是有多端连接进来的,那么就需要每个连接到游戏中的玩家,在地图中都要有对应的角色。所以,现在需要把之前添加的女仆长,转换成预制体,并为其编写一个Script/Player/Character.js,并拖拽到预制体上。

cc.Class({
  extends: cc.Component,

  properties: {},

  onLoad() {
    //和服务器一样,角色的三个状态
    this.State = cc.Enum({
      STATE_STAND: 1,
      STATE_WALK_LEFT: -1,
      STATE_WALK_RIGHT: -1
    });
    this.playerData = null;
    this.currentState = this.State.STATE_STAND;
    this.animDisplay = null;
  },

  //获取角色信息
  getPlayerData() {
    return this.playerData;
  },

  //创建时,初始化角色信息
  initCharacter(data) {
    this.playerData = data;
    let playerAttribute = data.playerAttribute;
    this.animDisplay = this.getComponent(dragonBones.ArmatureDisplay);
    this.node.setPosition(
      cc.v2(playerAttribute.position.x, playerAttribute.position.y)
    );
  },

  //服务器发送过来角色数据,在这里更新
  refreshPlayerData(playerData) {
    let playerAttribute = playerData.playerAttribute;
    this.resetState(playerAttribute.currentState);
    this.node.setPosition(
      cc.v2(playerAttribute.position.x, playerAttribute.position.y)
    );
    this.node.setScale(playerAttribute.scale.x, playerAttribute.scale.y);
  },

  //根据状态切换角色动画,注意,这里切换动画的的骨骼名称、动画名称都是在龙骨编辑器里定义好的
  resetState(state) {
    if (this.currentState === state) {
      return;
    }
    switch (state) {
      case this.State.STATE_STAND:
        this.changeAnimation("SakuyaStand", "SakuyaStand", 0);
        break;
      case this.State.STATE_WALK_LEFT:
        this.changeAnimation("SakuyaWalkFront", "SakuyaWalkFront", 0);

        break;
      case this.State.STATE_WALK_RIGHT:
        this.changeAnimation("SakuyaWalkFront", "SakuyaWalkFront", 0);
        break;
    }
    this.currentState = state;
  },

  //切换动画
  changeAnimation(armatureName, animationName, playTimes, callbacks) {
    if (
      this.animDisplay.armatureName === armatureName &&
      this.animDisplay.animationName === animationName
    ) {
      return;
    }

    if (this.animDisplay.armatureName !== armatureName) {
      this.animDisplay.armatureName = armatureName;
    }

    this.animDisplay.playAnimation(animationName, playTimes);
  },

  //将角色从界面中移除
  deleteCharacter() {
    this.node.removeFromParent(true);
  }
});

然后在编辑器里配置好相关的脚本,并拖拽生成预制体。

同时,需要创建一个玩家类Script/Player/Player.js,用来处理玩家的操控数据。 并绑定到编辑器的RolePlayer层上。

cc.Class({
  extends: cc.Component,

  properties: {
    //将摇杆绑定到Player,记得在编辑器中将对应模块拖入哦
    joyStick: {
      default: null,
      type: cc.Node
    },
    //角色动画的预制体,记得在编辑器中将对应模块拖入哦
    playerCharacterPrefab: {
      default: null,
      type: cc.Prefab
    }
  },

  // LIFE-CYCLE CALLBACKS:

  onLoad() {
    //获得摇杆
    this.joyStickController = this.joyStick.getComponent("JoyStick");

    //同样在Player里面添加一下状态管理,避免在状态未改变时频繁像服务器发送操作指令
    this.State = cc.Enum({
      STATE_STAND: 1,
      STATE_WALK_LEFT: -1,
      STATE_WALK_RIGHT: -1
    });
    this.currentState = this.State.STATE_STAND;
    this.character = null;
    this.playerData = null;
    this.wsServer = null;
  },

  start() {},

  //服务器通知客户端初始化玩家角色时,通过服务器返回的角色数据,用预制体创建Character角色并放入到场景中,同时发送创建完成消息给服务器
  initCharacter(data, wsServer) {
    this.wsServer = wsServer;
    this.playerData = data;
    this.character = cc.instantiate(this.playerCharacterPrefab);
    this.node.addChild(this.character);
    this.character.getComponent("Character").initCharacter(data);
    this.sendDataToServer("PLAYER_CLIENT_INIT_OVER", null);
  },

  //获取玩家控制的角色信息
  getPlayerData() {
      return this.playerData;
  },

  //这个循环是用来监听摇杆状态的,当摇杆达到操作要求后,改变角色状态并发送到服务器
  update(dt) {
    if (!this.playerData) {
      return;
    }
    let radian = this.joyStickController._radian;
    if (radian != -100) {
      if (-0.5 <= radian && radian <= 0.5) {
        this.changeState(this.State.STATE_WALK_RIGHT);
      } else if (
        (2.5 <= radian && radian <= Math.PI) ||
        (-1 * Math.PI <= radian && radian <= -2.5)
      ) {
        this.changeState(this.State.STATE_WALK_LEFT);
      } else {
        this.changeState(this.State.STATE_STAND);
      }
    } else {
      this.changeState(this.State.STATE_STAND);
    }
  },

  changeState(state) {
    //这里就是状态未改变时不要发消息给服务器
    if(this.currentState === state) {
        return ;
    }
    this.sendDataToServer("PLAYER_OPERATION", {state: state});
  },

  //接收到服务器返回的数据,更新角色状态。玩家操纵的角色在这里单独处理,其他客户端玩家控制的角色,可以直接操作Character来处理。
  refreshPlayerData(playerData) {
    this.character.getComponent("Character").refreshPlayerData(playerData);
    this.currentState = playerData.playerAttribute.state;
  },

  //封装一下向服务器发送消息的函数
  sendDataToServer(dataType, data) {
    if (this.wsServer.readyState === WebSocket.OPEN) {
      let wsServerData = {
        dataType,
        data
      };
      this.wsServer.send(JSON.stringify(wsServerData));
    }
  }
});

接下来创建一个地图相关的脚本,绑定到Canvas上,相当于客户端处理地图数据的脚本。

Script/SceneComponent/MapScript.js

import Tools from ‘../Common/Tools‘;

cc.Class({
  extends: cc.Component,

  properties: {
      //绑定角色控制,记得在编辑器中拖入相关模块
      player: {
          default: null,
          type: cc.Node
      },
      //绑定预制体,用来创建其他客户端玩家角色数据,记得在编辑器中拖入相关模块
      playerCharacterPrefab: {
        default: null,
        type: cc.Prefab
      },
      //绑定角色们该在的图层,记得在编辑器中拖入相关模块
      playerLayer: {
          default: null,
          type: cc.Node
      }
  },

  // LIFE-CYCLE CALLBACKS:

  onLoad() {
    //非玩家控制角色和npc角色数据合集
    this.otherPlayers = [];
    this.npcs = [];
    //地图的websocket
    this.mapWS = new WebSocket(Tools.getTestMapServerUrl());
    this.mapWS.onmessage = event => {
        let dataObject = JSON.parse(event.data);
        let dataType = dataObject.dataType;
        let data = dataObject.data;

        //连接成功
        if(dataType === "CONNECT_SUCCESS") {
            this.openMap();
        }
        //服务器完成创建后,将角色数据发送给客户端,客户端根据数据创建角色
        if(dataType === "PLAYER_SERVER_INIT_OVER") {
            if(this.player) {
                this.player.getComponent("Player").initCharacter(data, this.mapWS);
            }
        }
        //服务器发送回来的游戏过程中的全部角色数据
        if(dataType === "GAME_PLAYER_DATA") {
            let npcs = data.npcs;
            let players = data.players;

            //npc数据处理,以后拓展
            for(let i in npcs) {
                let npc = npcs[i];
            }

            //玩家角色处理
            for(let i in players) {
                let player = players[i];
                //返回的数据中,如果是玩家操控的角色,单独处理
                if(player.playerId === this.player.getComponent("Player").getPlayerData().playerId) {
                    this.player.getComponent("Player").refreshPlayerData(player);
                    continue;
                }
                //遍历其他角色数据,判断是否是新加入的角色,不是的话,刷新角色数据
                let isNew = true;
                for(let k in this.otherPlayers) {
                    let otherPlayer = this.otherPlayers[k];
                    if(player.playerId === otherPlayer.getComponent("Character").getPlayerData().playerId) {
                        //判断已加入角色是否为退出地图状态,是的话删数据色,不是的话更新角色
                        if(!player.playerAttribute.logout) {
                            otherPlayer.getComponent("Character").refreshPlayerData(player);
                        }else {
                            otherPlayer.getComponent("Character").deleteCharacter();
                            this.otherPlayers.splice(k, 1);
                            k--;
                        }
                        isNew = false;
                        break;
                    }
                }
                //如果是新加入角色,是的话,创建角色并添加到地图中
                if(isNew && !player.playerAttribute.logout) {
                    let otherPlayer = cc.instantiate(this.playerCharacterPrefab);
                    this.playerLayer.addChild(otherPlayer);
                    otherPlayer.getComponent("Character").initCharacter(player);
                    this.otherPlayers.push(otherPlayer);
                }
            }
        }
    };
  },

  start() {

  },

  openMap() {
    if (this.mapWS.readyState === WebSocket.OPEN) {
        let mapWSData =  {
            dataType: "PLAYER_SERVER_INIT",
            data: null
        }
        this.mapWS.send(JSON.stringify(mapWSData));
    }
  },

  // update (dt) {},
});

最后,因为更改了一些角色信息,将玩家uuid和角色名放在了服务器生成,那么在本地删除相关代码,暂时屏蔽掉聊天界面的服务。后续,会在多场景切换中,如何保持聊天内容不被清除的开发期,再次启用!

Script/UIComponent/ChatComponent/ChatScript.js

  onLoad() {
    this.chatItems = [];
    this.chatItemNodePool = new cc.NodePool();
  },

  start() {},

启动服务器和客户端,可以看到角色加载完成,尝试在屏幕左边拖动,试试角色是否可以移动。

可以看到,向右拖拽摇杆,可以发现女仆长切换成行走姿态并向右移动。松开摇杆,恢复到站立状态。

接下来,保持这个页面,再打开一个新的页面,发现不仅在旧的页面中,出现了新加入的角色,在新的界面中,也可以看到之前已加入的角色。

尝试在右边的页面中操作角色,可以发现,左边界面中的角色也跟着同步移动了。

接下来关掉左边的界面,可以在右边界面中看到,左边页面中先打开控制的角色消失了。

如果成功看见了上面的过程,那么证明服务器和客户端之间的socket连接成功的运行了起来。那么,一个最简单的网游demo便成功完成了开发。

在后续的demo开发计划中,将会创建两个场景,并提供场景切换点,实现多地图的交互。

文章中的代码:

客户端: https://github.com/MythosMa/CocosCreator_ClientTest.git

服务端: https://github.com/MythosMa/NodeJS_GameServerTest.git

原文地址:https://www.cnblogs.com/mythosma/p/12101556.html

时间: 2024-10-04 15:49:39

一起来学习用nodejs和CocosCreator开发网络游戏吧(六)--- 可操纵的游戏角色(下)的相关文章

一起来学习用nodejs和CocosCreator开发网络游戏吧(五)--- 云服务器的搭建

为了学习websocket和CocosCreator结合开发游戏,把服务器运行在本地始终感觉缺少点什么,而且不能真正多端测试通信,于是趁着腾讯云年底促销(真的不是在打广告),99块钱一年,买了一台CentOS主机,把学习项目的服务器部署在这台云服务器上,也体验一把真正意义上的网络游戏. 因为没有任何经验,在这里记录一下整个安装部署流程,以免日后使用又忘记了. 购买好自己的服务器后,登录是需要密码的.因为不知道创建好的服务器登录密码,首先需要更改一下登录密码. 在实例=>更多=>密码/密钥=&g

一起来学习用nodejs和CocosCreator开发网络游戏吧(四)--- 可操纵的游戏角色(上)

游戏不可或缺的一个内容就是操作,尤其是对于RPG类型的游戏来讲.所以,对于websocket来说,能够实时体现数据交互的一个重要方法就是可操纵游戏角色在场景中的状态变化,能够让所有连接到服务器的客户端都能够看得到. 这次就先做一个能操作角色的场景吧. 首先,给聊天窗口加一个按钮,可以用来隐藏聊天界面(调整了一下大小,所以加个按钮控制界面以免影响操作). 加一个UILayout,用来承载所有UI界面上的内容,然后把ChatLayout放到UILayout上,接下来把ChatLayout移出到界面外

spring mvc开发入门实例demo源代码下载,很适合新手入门学习用。

原文:spring mvc开发入门实例demo源代码下载,很适合新手入门学习用. 源代码下载:http://www.zuidaima.com/share/1550463469046784.htm Eclipse + Maven + Spring MVC - Simple Example 源代码框架截图:

学习用CMake来编写Qt程序

最近开始学习CMake,因为项目需求需要用到Qt,自带的qmake会出现许多问题(比如文件修改之后有时候qmake不会侦测到不会重新编译,需要手动去编译等),于是开始尝试使用CMake来编写Qt程序,顺便学习一下怎么用CMake来使用find_package,也算给自己一次学习的机会. 切入正题,CMake对于一些有名的库都有自带文件夹中Modules里.cmake文件查询的支持,比如你需要编写Qt程序,你就可以去cmake_dir/Moudles/查找 FindQt4.cmake这个文件,里面

学习用5W1H来管理自己的项目/工作

学习用5W1H来管理自己的项目/工作 ? 最近开始需要系统化的思维模型,这只是一个开始,一下用脑图的形式来简介5W1H的具体内容: 先写xmind思维树的文本导出,后面附上图片.^ _ ^ 5W1H ????WHAT? ????????首先定义是什么? ????????5W1H分析法是一种分析问题的方法,在解决问题时可以得到广泛的应用 ????????内容包括What?.where?.when?.who?.why?.how? ????WHERE? ????????用在哪里? ????????管理

NodeJS+Express+MySQL开发小记(2):服务器部署

http://borninsummer.com/2015/06/17/notes-on-developing-nodejs-webapp/ NodeJS+Express+MySQL开发小记(1)里讲过在本地搭建 NodeJS 网站的若干细节.本人最近在阿里云服务器上面按最低配租了4个月的云服务器,所以想试着把这个项目部署到云上.云服务器操作系统是Ubuntu 14.04 LTS.之前一直在Windows下做开发,对于Linux下的环境搭建.配置还不是很熟悉,搭建的过程中学到很多东西. 本文简单记

使用nodejs进行WEB开发

这里,准备从零开始用nodejs实现一个微博系统.功能包括路由控制.页面模板.数据库访问.用户注册.登录.用户会话等内容. 将会介绍Express框架.MVC设计模式.ejs模板引擎以及MongoDB数据库的操作. 准备工作 使用http模块,Express框架, nodejs的抽象如此之差,把不该有的细节暴露给开发者.你可以用它做任何HTTP服务器能做的事情,不仅仅是做一个网站,甚至实现一个HTTP代理服务器都行. Express是WEB开发框架. 实现: 为HTTP模块提供了更高层的接口,

循序渐进学.Net Core Web Api开发系列【8】:访问数据库(基本功能)

系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇讨论如何连接数据库,包括连接SQL Server 和 连接MySQL,然后做一些基本的数据操作. 二.连接SQL Server 首先通过NuGet添加相关的包: 新建一个实体类: public class Product { [Key] public string Code { get; set; } p

循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作

系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一些常用的数据库操作,包括:条件查询.排序.分页.事务等基本数据库操作.试验的数据库为MySQL. 二.条件查询1.查询所有记录 List<Article> articles = _context.Articles.ToList<Article>(); 2.根据主键进行查询 Articl