highlight: a11y-dark

多人在线射击游戏、最强摸鱼游戏
在想体验地址====>

github地址:
开发不易,多谢大哥大姐们点个start吧,点个小爱心吧

技术栈

  • canvas、socket

初始 Canvas 画布

<template><div class="main-wrap" id="main-wrap"><canvas class="canvas" ref="canvasRef" id="canvas"></canvas></div>
</template><script setup lang="ts">
const canvasRef = ref();
import { onMounted, ref } from 'vue';
// 获取可是窗大小
const { innerWidth, innerHeight } = window;
onMounted(() => {// 赋值画布大小canvasRef.value.width = innerWidth;canvasRef.value.height = innerHeight;
})
</script><style lang="scss" scoped>
.main-wrap {height: 100%;width: 100%;
}
</style>

定义玩家的模型

工厂模式走起,每出现一个玩家就通过创建一个实例就行了

export class Player {public options: any;public ctx: any;constructor(ctx: any, options: any) {// canvas 2d实例this.ctx = ctx;// 玩家属性this.options = options;// 渲染玩家this.render();}/*** 渲染*/render() {// 玩家渲染}/*** 更新位置*/update() {// 玩家更新位置}
}

这样就搞定了玩家模型了,接下来是定义子弹的模型

定义子弹的模型

同玩家模型一致

export class Bullet {public options: anypublic ctx: anyconstructor(ctx: any, options: any) {this.options = options;this.ctx = ctx;this.render();}/*** 渲染*/render() {// 子弹渲染}/*** 更新位置*/update() {// 子弹更新位置}}

工具函数 结下来会用到

/*** 随机id* @params length {number} 长度* @returns id {string} 随机id*/
export const getRandomId = (length?: number) => {return (Math.random() + new Date().getTime()).toString(32).slice(0, length || 13);
};
/*** 随机颜色 16进制* @returns #cccccc*/
export const getRandomColor = () => {return `#${random().toString(16)}${random().toString(16)}${random().toString(16)}`;
};
/*** 获取0 - 256 的随机数* @returns 随机数*/
const random = () => {return Math.floor(Math.random() * 256);
};

创建canvas 2d画布

let ctx: CanvasRenderingContext2D;
ctx = canvasRef.value.getContext('2d');
// 设置背景颜色
ctx.fillStyle = '#ccc';
// 高宽
ctx.fillRect(0, 0, innerWidth, innerHeight);

创建玩家并渲染

// 所有玩家集合
const allPlayer = new Map();/*** 创建玩家*/
const createPlayer = () => {// 判断是否存在if (allPlayer.has(id)) return;const p = new Player(ctx, {// 唯一标识id: getRandomId(10),// 随机出现的位置x: Math.round(Math.random() * innerWidth),y: Math.round(Math.random() * innerHeight),// 初始大小size: 20,// 随机玩家颜色color: getRandomColor(),// 移动速度speed: 20,// 可视窗高宽innerWidth,innerHeight,// 玩家名字text: 'A'});allPlayer.set(p.options.id, p);
};
console.log(allPlayer);

玩家搞定了,接下来就是渲染了

Player类 的render完善一下

export class Player {....../*** 渲染*/render() {const { x, y, size, color, text } = this.options;const { ctx } = this;// 开一个路径ctx.beginPath();// 画一个圆 ===> 为了简单,就已圆代替玩家ctx.arc(x, y, size, 0, 2 * Math.PI, false);// 填充颜色ctx.fillStyle = color;// 关闭该路径ctx.fill();// 设置玩家名称if (text) {ctx.font = '20px Arial';ctx.fillStyle = '#000';ctx.textAlign = 'center';ctx.fillText(text,  x+size/2-10, y+size/2-4);}}
......
}

接下来就是执行render,在 new Player的过程中就有已经执行

export class Player { 。。。。constructor(ctx: any, options: any) {。。。。。this.render(); } /** * 渲染 */ render() { // 玩家渲染}
}

看看效果,果然出现了

让玩家动起来

通过监听上下左右按键,分别执行不同的操作

keyCode 方向
37
38
39
40
onMounted(() => {。。。。。。initOperate();。。。。。。
});/*** 初始化操作监听*/
const initOperate = () => {// 键盘事件 只控制状态值window.onkeydown = function (e: KeyboardEvent) {renderElements(e.keyCode);};
};/*** 渲染所有的元素 子弹 玩家 。。。。*/
const renderElements = (keyCode?: number) => {// 清空画布clearRect();// 更新玩家allPlayer.get(player?.options.id).update(keyCode);
};

接下来实现Player中的update

export class Player {....../*** 更新位置信息* @param keyCode 键盘码值*/update(keyCode?: number) {const { x, y, speed } = this.options;// 通过keycode 改变xy的坐标信息switch (keyCode) {case 37:this.options.x = x - speed;break;case 38:this.options.y = y - speed;break;case 39:this.options.x = x + speed;break;case 40:this.options.y = y + speed;break;}// 重新渲染this.render();}......
}

看看效果 玩家已经动起来了

边缘计算 ,上下左右可视窗,不能超出

export class Player {....../*** 边缘计算* @param keyCode */verifyPosition(keyCode: number) {const { innerWidth, innerHeight, size, x, y, speed } = this.options;switch (keyCode) {case 37:return x - speed > size;case 38:return y - speed > size;case 39:return x + speed < innerWidth;case 40:return y + speed < innerHeight;default:return false;}}......
}

那在什么时候调用呢,在执行update之前就得判断是否移动外面

/*** 初始化操作监听*/
const initOperate = () => {// 键盘事件 只控制状态值window.onkeydown = function (e: KeyboardEvent) {if (player?.verifyPosition(e.keyCode)) {renderElements(e.keyCode);}};
};

搞定

玩家的移动算是搞定了

子弹

鼠标点击的方向就是子弹出来的地方

那首先就得监听鼠标点击的位置

/*** 初始化操作监听*/
const initOperate = () => {。。。。。。// 玩家点击创子弹window.onmousedown = function (e: MouseEvent) {createBullet(player as Player, e);};
};/***  创建 bullet*/
const createBullet = (player: Player, e: MouseEvent) => {const { x, y } = player.options;// 返回原点到点的线段与x轴正方向之间的平面角度const location = Math.atan2(e.clientY - y, e.clientX - x);const bullet = new Bullet(ctx, {id: getRandomId(),x,y,size: 5,color: 'red',speed: 1,location: {x: Math.cos(location) * 8,y: Math.sin(location) * 8}});allBullet.set(bullet.options.id, bullet);return bullet;
};

location计算原理

Math.atan2 api方法计算二维坐标系中任意一个点(x, y)和原点(0, 0)的连线与X轴正半轴的夹角大小。

然后根据cos sin 计算取余弦值、正弦值正数并且 *8(自定义) 得出移动的速度

子弹渲染render函数

export class Bullet {。。。。。。/*** 渲染*/render() {const { x, y, size, color } = this.options;const { ctx } = this;ctx.beginPath();ctx.arc(x, y, size, 0, 2 * Math.PI, false);ctx.fillStyle = color;ctx.fill();}/*** 更新位置*/update() {const { x, y, location } = this.options;this.options.x = x + location.x;this.options.y = y + location.y;this.render();}}

看看效果

子弹是有了,但是怎么才能让他动起来了???

requestAnimationFrame 定时器重新渲染玩家和子弹

采用requestAnimationFrame的原因很简单,每一帧执行一次,60赫兹的话 那就是1000/60 = 16.66666 毫秒渲染一次

/*** 定时任务*/
const timingTask = () => {requestAnimationFrame(timingTask);if (!ctx || !canvasRef.value) return;// 清空画布clearRect();// 重新渲染allPlayer.forEach((pl: Player) => {pl.render();});// 遍历子弹allBullet.forEach((item: Bullet) => {const { x, y } = item.options;// 边缘判断 出边界线外删除if (x >= innerWidth || x <= 0 || y >= innerHeight || y <= 0) {allBullet.delete(item.options.id);}item.update();});
};
timingTask();

看看效果怎么样

完美!!!!

下个帖子重点聊联机

上班最强摸鱼游戏-多人联机小游戏 (一)相关推荐

  1. 最强摸鱼神器:开着IDEA看股票,看小说...

    这周很多公司都开始复工了,不出意外下周应该大部分都要开始上班了吧.今天TJ冒着被各公司老板追杀的风险,给大家推荐一个上班摸鱼神器:Thief-Book. **项目名称:**Thief Book **项 ...

  2. 【转】多人联机射击游戏中的设计模式应用(一)

    为了方便大家更加系统地学习和掌握各种常用的设计模式,下面通过一个综合实例--"多人联机射击游戏"来学习如何在实际开发中综合使用设计模式. 反恐精英(Counter-Strike, ...

  3. 不用 H5,闲鱼 Flutter 如何玩转小游戏?

    简介: 最近APP游戏化成为了一个新的风口,把在游戏中一些好玩的.能吸引用户的娱乐方式或场景应用在应用当中,以达到增加用户粘性,提升DAU的效果,成本较低.同时在一些需要对用户有引导性的场景,游戏化还 ...

  4. flutter能开发游戏吗_不用 H5,闲鱼 Flutter 如何玩转小游戏?-阿里云开发者社区...

    什么是Candy引擎? Candy 是闲鱼技术团队设计开发的一款引擎: APP嵌入式的.轻量级的.易于开发.性能稳定的互动引擎: 绘制系统高度融合Flutter体系,游戏场景和Flutter UI支持 ...

  5. Unity Networking开发多人联机射击游戏

    UNet开发多人联机射击游戏 引言: Networking作为Unity官方的用于开发多人在线游戏的网络模块,开发者可以不用自己搭建网络模块的底层,通过使用Unity提供的一些相关组件,可以轻松实现简 ...

  6. 技术人玩小游戏,如何“不战而胜”

    虽然迟了一天,但还是祝各位小伙伴端午安康. 最近因为端午节到来,物业举办了一个网页小游戏,得分最高的前 N 名可以拿到奖品. 闲来无事的我参加了一下,发现自己实在是太菜了,总是玩不过别人,于是转变思路 ...

  7. 微信火柴人html5小游戏,20个好玩的微信小游戏推荐!你玩过几个?

    50000+游戏爱好者已加入我们! 每天推荐好玩游戏! 加入我们,沐沐带你发现好游戏! 只有你想不到, 没有我找不到的好游戏! 「良心好游戏推荐」 搜罗了好玩的微信小游戏大全, 模拟经营游戏.恐怖游戏 ...

  8. 多人联机射击游戏中的设计模式应用

    转:http://blog.csdn.net/lovelion/article/details/8262987 反恐精英(Counter-Strike, CS).三角洲部队.战地等多人联机射击游戏广受 ...

  9. 多人联机射击游戏中的设计模式应用(一)

    为了方便大家更加系统地学习和掌握各种常用的设计模式,下面通过一个综合实例--"多人联机射击游戏"来学习如何在实际开发中综合使用设计模式. 反恐精英(Counter-Strike, ...

最新文章

  1. 使用Redis分区将数据分割到多个Redis实例
  2. android 属性动画 弧形,CSS分层动画可以让元素沿弧形路径运动
  3. Django之MVC框架与MTV框架详解
  4. c语言编译器_学C语言写自己的K语言:编译器词法分析。
  5. java语言程序设计全国考试题,2019年12月全国计算机等级考试[Java语言程序设计]复习题及答案...
  6. 【EXLIBRIS】二十唯识白话译本【ZZ】
  7. Codeforces Round #556 (Div. 1)
  8. mysql group set,Mysql--group_concat()、group by、find_in_set()使用笔记
  9. RHEL 5 rpm包安装bind
  10. 双线性插值算法实现和opencv、matlab结果不一致问题
  11. uniapp引入阿里巴巴矢量图标库
  12. unity 控制人物模型移动
  13. 尺度、空间异质性、干扰、景观多样性、景观连接度,对其概念的理解
  14. 用手机打开word图表位置很乱_干货 | 论文格式调半天?Word攻略帮你统统都搞定...
  15. 画出属于你的最漂亮的数字时序图—WaveDrom
  16. 电脑计算机为什么不是有效程序,电脑提示“不是有效的win32应用程序”是什么原因【解决方法】...
  17. 计算机组成原理例题4.2,4.2.2 例题解析(1)
  18. java接口自动化监控_java接口自动化(三) - 手工接口测试到自动化框架设计之鸟枪换炮...
  19. 关于insight数据库价格与价值的双重选择
  20. 基于JSP的网上书城

热门文章

  1. (四)CSS前端开发面试会问到的问题有哪些?
  2. 2021外卖返利小程序饿了么美团外卖侠分销系统源码
  3. 如何使用计算机查询本机网卡信息,本机mac地址查询的三种方法
  4. Android-蓝牙sco通话
  5. 微信小程序全局配置分享指定标题、图片、路径
  6. 16天记住7000考研单词(第一天)
  7. 对y_pred强制二分类
  8. Counting Cards 函数实现21点算法
  9. Node.js获取AJAX参数Demo
  10. VC API常用函数简单例子大全(1-89)