打螺丝小游戏|源码

不能传效果图,可以先看效果:星际应用中心 | 科幻游戏&工具空间站

最近爆款的打螺丝/拧螺丝 休闲小游戏,凭借极简操作、解压反馈和闯关玩法,成为通勤摸鱼神器。今天就用Cocos Creator 3.8+,从零实现一套完整的打螺丝游戏,涵盖物理交互、拖拽拧动、关卡逻辑、音效反馈全流程,新手也能跟着复刻上线。

核心玩法 :点击/拖拽螺丝,将松动的螺丝拧入对应孔位,固定木板完成闯关;拧动到位触发特效+音效,关卡递进提升难度。 技术栈:Cocos Creator 3.8.x、TypeScript、2D物理引擎、触摸/鼠标事件、Tween动画

一、项目初始化与基础配置

1.1 新建项目

  • 打开Cocos Dashboard,创建2D项目 ,命名为ScrewGame,引擎版本选择3.8.0及以上

  • 清空默认场景,新建Main场景作为游戏主场景

1.2 资源准备(极简素材,新手友好)

无需复杂美术,准备基础素材即可快速开发:

  • 螺丝素材:螺丝头部、螺丝杆(2张图,区分未拧入/拧入状态)

  • 孔位素材:螺丝孔底图、孔位边框

  • 木板素材:不同形状木板(遮挡孔位、增加闯关难度)

  • 音效素材:拧螺丝音效、成功固定音效、闯关音效

将素材导入Cocos资源管理器,分类存放至texture、audio、prefab文件夹。

1.3 场景布局搭建

  1. 创建Canvas节点,适配分辨率设为720x1280(竖屏适配手机)

  2. 新建节点分层:Bg(背景)、GameLayer(游戏核心层)、UILayer(UI层)

  3. GameLayer下创建Board(木板)、ScrewGroup(螺丝组)、HoleGroup(孔位组)

  4. UILayer下创建关卡文本、进度条、重新开始按钮

二、核心模块开发(重点)

2.1 螺丝节点与组件配置

螺丝是游戏核心交互对象,需配置物理组件+触摸事件

  1. 新建Sprite节点,命名Screw,挂载螺丝头部图片

  2. 添加核心组件

    1. RigidBody2D :类型设为Kinematic(运动学刚体,避免物理漂移)

    2. CircleCollider2D :勾选Enabled,半径适配螺丝大小,开启IsTrigger(触发检测)

    3. 自定义脚本Screw.ts,挂载到螺丝节点

2.2 螺丝孔位模块(Hole)

孔位是螺丝的目标点位,负责定位和碰撞判定:

  1. 新建Sprite节点,命名Hole,挂载孔位图片

  2. 添加CircleCollider2D,设为Trigger,用于检测螺丝是否对准

  3. 制作孔位预制体,方便批量生成关卡

2.3 木板模块(Board)

木板用于增加游戏策略性,部分孔位被木板遮挡,需按顺序拧螺丝:

  1. 新建Sprite节点,挂载木板图片

  2. 添加PolygonCollider2D(多边形碰撞体),贴合木板形状

  3. 添加RigidBody2D,类型设为Static,避免移动

三、核心脚本逻辑实现(TypeScript)

3.1 螺丝控制脚本 Screw.ts

实现触摸拖拽、拧动动画、碰撞判定、固定逻辑,完整代码如下:

复制代码
import { _decorator, Component, Node, input, Input, EventTouch, Vec3, tween, RigidBody2D, Collider2D, Contact2DType, AudioSource, assert } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Screw')
export class Screw extends Component {
    // 拧动音效
    @property(AudioSource)
    screwAudio: AudioSource = null!;
    // 固定成功音效
    @property(AudioSource)
    fixAudio: AudioSource = null!;
    // 螺丝目标孔位
    targetHole: Node = null!;
    // 是否已经固定
    isFixed: boolean = false;
    // 初始位置
    private startPos: Vec3 = Vec3.ZERO;
    // 拧动旋转角度
    private readonly rotateAngle: number = 360;

    onLoad() {
        this.startPos = this.node.position.clone();
        // 注册触摸事件
        input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
        input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
        input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
        // 注册碰撞触发
        let collider = this.getComponent(Collider2D)!;
        collider.on(Contact2DType.TRIGGER_ENTER, this.onTriggerEnter, this);
    }

    // 触摸开始:选中螺丝
    onTouchStart(event: EventTouch) {
        if (this.isFixed) return;
        let touchPos = event.getUILocation();
        let worldPos = this.node.getParent()!.getComponent(UITransform)!.convertToNodeSpaceAR(new Vec3(touchPos.x, touchPos.y));
        // 判断是否点击到螺丝
        if (Vec3.distance(this.node.position, worldPos) < 50) {
            this.node.scale = new Vec3(1.1, 1.1, 1.1);
        }
    }

    // 触摸移动:拖拽螺丝
    onTouchMove(event: EventTouch) {
        if (this.isFixed) return;
        let touchPos = event.getUILocation();
        let nodePos = this.node.getParent()!.getComponent(UITransform)!.convertToNodeSpaceAR(new Vec3(touchPos.x, touchPos.y));
        this.node.setPosition(nodePos);
    }

    // 触摸结束:判定是否拧入孔位
    onTouchEnd() {
        if (this.isFixed || !this.targetHole) {
            // 未对准,回归原位
            tween(this.node).to(0.2, { position: this.startPos, scale: Vec3.ONE }).start();
            return;
        }
        this.fixScrew();
    }

    // 碰撞检测:对准孔位
    onTriggerEnter(otherCollider: Collider2D) {
        if (otherCollider.node.name.includes('Hole') && !this.isFixed) {
            this.targetHole = otherCollider.node;
        }
    }

    // 固定螺丝:拧动动画+音效
    fixScrew() {
        this.isFixed = true;
        this.targetHole.active = false;
        // 播放拧动音效
        this.screwAudio.play();
        // 拧动旋转+缩放动画
        tween(this.node)
            .to(0.3, { eulerAngles: new Vec3(0, 0, this.rotateAngle), scale: Vec3.ONE })
            .call(() => {
                this.fixAudio.play();
                // 通知游戏管理器更新进度
                GameManager.instance.checkLevelComplete();
            })
            .start();
    }

    onDestroy() {
        input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
        input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
        input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
    }
}

3.2 游戏管理器 GameManager.ts

全局控制关卡进度、螺丝计数、闯关判定、重启游戏,采用单例模式:

复制代码
import { _decorator, Component, Node, Label, find, warn } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('GameManager')
export class GameManager extends Component {
    // 单例实例
    static instance: GameManager = null!;
    // 当前关卡螺丝总数
    @property
    totalScrew: number = 0;
    // 已固定螺丝数
    fixedScrew: number = 0;
    // 关卡文本
    @property(Label)
    levelLabel: Label = null!;
    // 通关提示节点
    @property(Node)
    completeNode: Node = null!;

    onLoad() {
        // 单例初始化
        if (GameManager.instance) {
            this.destroy();
            return;
        }
        GameManager.instance = this;
        this.completeNode.active = false;
    }

    // 初始化关卡数据
    initLevel(level: number, screwCount: number) {
        this.levelLabel.string = `第${level}关`;
        this.totalScrew = screwCount;
        this.fixedScrew = 0;
        this.completeNode.active = false;
    }

    // 螺丝固定后更新计数,判定通关
    checkLevelComplete() {
        this.fixedScrew++;
        if (this.fixedScrew >= this.totalScrew) {
            this.levelComplete();
        }
    }

    // 通关逻辑
    levelComplete() {
        this.completeNode.active = true;
        warn("关卡通关!");
    }

    // 重新开始游戏
    restartGame() {
        find("GameLayer")!.children.forEach(child => {
            if (child.name.includes("Screw")) {
                child.getComponent(Screw)!.resetScrew();
            }
        });
        this.initLevel(1, 5);
    }
}

3.3 孔位脚本 Hole.ts

复制代码
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('Hole')
export class Hole extends Component {
    // 孔位是否被占用
    isUsed: boolean = false;
}

四、进阶优化与效果增强

4.1 解压反馈升级

  • 动画优化 :螺丝拧入时添加弹性缩放+旋转,增强手感

  • 音效分层:拧动过程循环音效,固定成功触发短促音效

  • 视觉反馈:固定后螺丝变色,添加粒子特效

4.2 关卡难度递进

  • 增加螺丝数量、木板遮挡层数

  • 加入顺序拧螺丝逻辑(必须先拧指定螺丝,否则木板无法固定)

  • 添加限时闯关模式,提升可玩性

4.3 性能优化

  • 螺丝、孔位使用预制体复用,减少节点创建销毁

  • 关闭多余物理碰撞,仅保留触发检测

  • 音频对象复用,避免频繁创建AudioSource

五、打包发布与适配

  1. 平台适配 :Canvas适配模式选择SHOW_ALL,兼容手机横竖屏

  2. 微信小游戏发布

    1. 构建平台选择微信小游戏,填写AppID

    2. 压缩资源、开启代码混淆,减少包体积

    3. 使用微信开发者工具预览、上传发布

  3. Web发布:构建为Web平台,部署至静态服务器,嵌入网页引流

六、常见问题排坑

  • 螺丝拖拽漂移:将RigidBody2D设为Kinematic,关闭重力

  • 碰撞检测失效:检查Collider2D是否开启Trigger,层级是否正确

  • 音效不播放:音频格式改为mp3/ogg,AudioSource挂载到节点

  • 微信内无法交互:开启触摸事件兼容,适配微信浏览器环境

七、总结与拓展

这款打螺丝游戏核心是物理交互+极简操作,通过Cocos的2D物理、事件系统和动画系统,快速实现解压玩法。在此基础上,还能拓展:

  • 加入拆螺丝反向玩法

  • 新增螺丝刀道具,升级拧螺丝效率

相关推荐
kyh10033811201 个月前
微信小游戏《找茬找汉字闯关王》源码赠送
microsoft·微信·微信小程序·微信小游戏·找茬小游戏·微信找茬消除
kyh10033811201 个月前
微信小游戏《找茬找汉字闯关王》开发实战:送全部源码
microsoft·微信·微信小程序·小程序·微信小游戏·汉字找茬找梗
王大锤43911 个月前
微信小游戏真机调试
微信小游戏
kyh10033811202 个月前
第二个微信小游戏《汉字碰碰消》上线啦!
微信·微信小程序·微信小游戏·去水印微信小程序·养了个羊
kyh10033811202 个月前
汉字消除微信小游戏实现教程
微信·微信小游戏·小游戏源码·消除小游戏
kyh10033811203 个月前
答题流量主小程序源码+后台题库管理系统源码
微信·微信小游戏·微信刷题·刷题小程序·刷题程序源码
kyh10033811203 个月前
可商用去水印微信小程序源码(免费获取全部源码)
微信小程序·小程序·微信小游戏·去水印·去水印工具·微信去水印小程序
世洋Blog3 个月前
Unity开发微信小游戏-减少WASM包体大小
unity·游戏引擎·wasm·微信小游戏
世洋Blog3 个月前
Unity开发微信小游戏-合理的规划使用YooAsset
unity·c#·微信小游戏