用华为自研的仓颉(Cangjie)编程语言编写经典游戏

说明:HarmonyOS NEXT 当前官方推荐 ArkTS + ArkUI 做 UI,仓颉适合写性能敏感或通用逻辑,并支持与 ArkTS 混合开发。DevEco Studio 已内置“Cangjie Hybrid”模板,会自动生成 ArkTS ↔ 仓颉的调用桥接与示例结构,极大降低集成成本。

用华为自研的仓颉(Cangjie)编程语言编写经典游戏

一、准备工作

  1. 安装 DevEco Studio(最新版 5.x/Next)
  2. 从华为开发者联盟获取 DevEco Studio 与 HarmonyOS NEXT SDK;安装时勾选 HarmonyOS NEXT 相关组件与 Cangjie 语言支持。
  3. 设备/模拟器
  4. 提议使用 HarmonyOS NEXT 设备(打开开发者模式与 USB 调试),或在 DevEco AVD 管理器中新建 HarmonyOS NEXT 模拟器。
  5. 为什么选“仓颉 + ArkTS 混合”?
  6. 仓颉语言具备高性能、并发 GC、强安全特性,适合写游戏规则、碰撞检测、寻路等核心;ArkTS/ArkUI 专长在声明式 UI、生命周期与端能力对接。混合模板把两者接起来,ArkTS 页面可直接调用仓颉模块输出的组件/方法。

二、新建项目(Cangjie Hybrid)

  1. 打开 DevEco Studio → New Project → HarmonyOS App → 选择 Cangjie Hybrid 模板。
  2. 取名:CjSnake;包名如 com.example.cjsnake;选择 Stage 模型(HarmonyOS NEXT)。
  3. 完成向导后你会看到项目结构包含两个主要目录:
  4. cangjie/:存放 .cj 仓颉源码
  5. ets/(ArkTS):页面、路由、ArkUI 组件,以及自动生成的同名包装器 .ets,用于从 ArkTS 调用仓颉。
  6. 在官方与一线教程中都强调:页面生命周期仍由 ArkTS 驱动,仓颉更侧重组件/逻辑。

如果你是第一次接触混合工程,提议先跑模板自带 Demo,确认工程能编译、部署成功后再开始改造。

三、功能目标(贪吃蛇)

  • 核心逻辑(仓颉实现)
    • 网格世界(例如 20×28)。
    • 蛇(列表:头在前)移动、长大、撞墙/自撞判定、随机刷食物。
    • 对外暴露:init(width, height), tick(), turn(dir), getState()。
  • UI(ArkTS + ArkUI 实现)
    • 用 Canvas 绘制网格、蛇、食物。
    • 触摸/方向键控制方向;setInterval 或系统计时器驱动 tick() 刷新。
    • 失误后给出“重新开始”按钮。

四、在仓颉里写游戏逻辑(/cangjie/game/Snake.cj)

下面示例紧贴当前公开语法风格(接近 Swift/Kotlin),可直接放入 cangjie/game/ 并在同目录建 CMakeList/模块描述(Hybrid 模板一般已配置好构建脚本与导出规则)。不同版本细节可能有少许出入——以模板注释与“仓颉语言指南”文档为准。

// 文件:cangjie/game/Snake.cj

package game.snake

public enum Direction { Up, Down, Left, Right }

public class Point {

public var x: Int

public var y: Int

public init(x: Int, y: Int) { this.x = x; this.y = y }

}

public class GameState {

public var snake: List<Point>

public var food: Point

public var alive: Bool

public var score: Int

public init(snake: List<Point>, food: Point, alive: Bool, score: Int) {

this.snake = snake; this.food = food; this.alive = alive; this.score = score

}

}

public class SnakeGame {

private var w: Int

private var h: Int

private var dir: Direction = Direction.Right

private var snake: MutableList<Point> = []

private var food: Point = Point(0, 0)

private var alive: Bool = true

private var score: Int = 0

private var pendingGrow: Int = 0

public init(width: Int, height: Int) {

this.w = width; this.h = height

let cx = width / 2

let cy = height / 2

this.snake.add(Point(cx, cy))

this.snake.add(Point(cx – 1, cy))

this.snake.add(Point(cx – 2, cy))

spawnFood()

}

public fun reset() {

this.snake.clear()

let cx = this.w / 2

let cy = this.h / 2

this.snake.add(Point(cx, cy))

this.snake.add(Point(cx – 1, cy))

this.snake.add(Point(cx – 2, cy))

this.dir = Direction.Right

this.alive = true

this.score = 0

this.pendingGrow = 0

spawnFood()

}

public fun turn(d: Direction) {

// 防止直接反向

if ((this.dir == Direction.Left && d == Direction.Right) ||

(this.dir == Direction.Right && d == Direction.Left) ||

(this.dir == Direction.Up && d == Direction.Down) ||

(this.dir == Direction.Down && d == Direction.Up)) {

return

}

this.dir = d

}

public fun tick(): Bool {

if (!this.alive) { return false }

let head = this.snake[0]

var nx = head.x

var ny = head.y

switch (this.dir) {

case Direction.Left: nx -= 1

case Direction.Right: nx += 1

case Direction.Up: ny -= 1

case Direction.Down: ny += 1

}

// 撞墙

if (nx < 0 || ny < 0 || nx >= this.w || ny >= this.h) {

this.alive = false

return false

}

// 自撞

for (var i = 0; i < this.snake.size(); i += 1) {

if (this.snake[i].x == nx && this.snake[i].y == ny) {

this.alive = false

return false

}

}

// 移动

this.snake.addFirst(Point(nx, ny))

if (this.pendingGrow > 0) {

this.pendingGrow -= 1

} else {

this.snake.removeLast()

}

// 吃食物

if (this.food.x == nx && this.food.y == ny) {

this.score += 10

this.pendingGrow += 1

spawnFood()

}

return true

}

private fun spawnFood() {

// 简单随机:尝试若干次找空位

for (var t = 0; t < 128; t += 1) {

let rx = rand(this.w) // 模板一般提供随机封装;若无可换成线性同余

let ry = rand(this.h)

var occupied = false

for (var i = 0; i < this.snake.size(); i += 1) {

if (this.snake[i].x == rx && this.snake[i].y == ry) { occupied = true; break }

}

if (!occupied) { this.food = Point(rx, ry); return }

}

// 兜底

this.food = Point(0, 0)

}

public fun getState(): GameState {

// 返回一个“可序列化”的轻量状态,ArkTS 用它来绘制

return GameState(this.snake.toList(), this.food, this.alive, this.score)

}

}

// === 对 ArkTS 暴露的简单工厂(Hybrid 模板会为 public 成员生成桥接包装) ===

public object SnakeExports {

private var game: SnakeGame? = null

public fun init(width: Int, height: Int) {

game = SnakeGame(width, height)

}

public fun reset() { game?.reset() }

public fun turn(d: String) {

// ArkTS 传入 “up/down/left/right”

switch (d) {

case “up”: game?.turn(Direction.Up)

case “down”: game?.turn(Direction.Down)

case “left”: game?.turn(Direction.Left)

case “right”: game?.turn(Direction.Right)

}

}

public fun tick(): Bool { return game?.tick() ?? false }

public fun state(): GameState { return game?.getState() ?? GameState([], Point(0,0), false, 0) }

}

提示:在“Cangjie Hybrid”项目中,public 的类/方法会被编译器自动生成 同名 ArkTS 包装(一般在 ets/ 下出现一个同名 .ets,以便在 ArkTS 里 import 并直接调用),这是官方教程和社区文章演示的默认体验。

五、在 ArkTS/ArkUI 中绘制与驱动(/ets/pages/Index.ets)

ArkUI 提供 Canvas 与 2D 上下文(与 Web Canvas 语义接近),用它来分帧绘制蛇与食物即可。ArkTS 作为页面/路由与 UI 主导语言,是混合工程里调用仓颉导出方法的入口。

// 文件:ets/pages/Index.ets

import { CanvasRenderingContext2D } from ‘@ohos.graphics.drawing’;

import router from ‘@ohos.router’;

// 假设 DevEco 已为 SnakeExports 生成同名包装(导入路径以自动生成为准)

import { SnakeExports } from ‘../cangjie/SnakeExports’ // ← 实际工程请以 IDE 生成路径为准

const GRID_W: number = 20

const GRID_H: number = 28

const CELL: number = 20 // 每格像素

@Entry

@Component

struct Index {

private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D()

private timer?: number

private alive: boolean = true

private score: number = 0

aboutToAppear() {

SnakeExports.init(GRID_W, GRID_H)

}

aboutToDisappear() {

if (this.timer) clearInterval(this.timer)

}

build() {

Column({ space: 12 }) {

Text(`贪吃蛇(仓颉逻辑 + ArkUI 绘制)`).fontSize(20).fontWeight(FontWeight.Bold)

Text(`得分:${this.score}`).fontSize(16)

// 方向控制(也可用手势)

Row({ space: 8 }) {

Button(‘↑’).onClick(() => this.turn(‘up’))

Button(‘↓’).onClick(() => this.turn(‘down’))

Button(‘←’).onClick(() => this.turn(‘left’))

Button(‘→’).onClick(() => this.turn(‘right’))

Button(this.alive ? ‘暂停’ : ‘重新开始’)

.onClick(() => this.toggle())

}

Canvas(this.ctx)

.width(GRID_W * CELL)

.height(GRID_H * CELL)

.onReady(() => this.start())

.gesture(

PanGesture()

.onActionEnd((event: GestureEvent) => {

// 简单根据偏移判断方向

const dx = event.offsetX; const dy = event.offsetY;

if (Math.abs(dx) > Math.abs(dy)) this.turn(dx > 0 ? ‘right’ : ‘left’)

else this.turn(dy > 0 ? ‘down’ : ‘up’)

})

)

}.padding(16)

}

private start() {

// 60~120ms 一步,兼顾可玩性

this.timer = setInterval(() => {

const ok = SnakeExports.tick()

const s = SnakeExports.state()

this.alive = s.alive

this.score = s.score

this.draw(s)

if (!ok || !this.alive) {

clearInterval(this.timer); this.timer = undefined

}

}, 100)

}

private toggle() {

if (this.alive) {

// 暂停:停止计时

if (this.timer) { clearInterval(this.timer); this.timer = undefined }

this.alive = false

} else {

// 重新开始

SnakeExports.reset()

this.alive = true

this.start()

}

}

private turn(d: ‘up’|’down’|’left’|’right’) {

SnakeExports.turn(d)

}

private draw(state: any) {

const ctx = this.ctx

// 背景

ctx.clearRect(0, 0, GRID_W * CELL, GRID_H * CELL)

// 画网格(可选)

for (let x = 0; x < GRID_W; x++) {

for (let y = 0; y < GRID_H; y++) {

ctx.strokeRect(x * CELL, y * CELL, CELL, CELL)

}

}

// 食物

ctx.fillRect(state.food.x * CELL, state.food.y * CELL, CELL, CELL)

// 蛇

for (let i = 0; i < state.snake.length; i++) {

const p = state.snake[i]

ctx.fillRect(p.x * CELL + 2, p.y * CELL + 2, CELL – 4, CELL – 4)

}

if (!state.alive) {

ctx.fillText(‘Game Over’, 20, 40)

}

}

}

ArkUI/ArkTS 的 Canvas、页面装饰器与组件语法属于官方常见范式;若你使用的是更新的 ArkUI API,请以 IDE 智能提示为准进行微调。

六、运行与调试

  1. 连接设备或启动模拟器(HarmonyOS NEXT)。
  2. DevEco Studio 工具栏选择运行目标 → Run。
  3. 若首次运行时间较长:AOT 编译 + 部署属于正常现象。
  4. 在页面中点击方向按钮或滑动操作,即可操控;控制台可输出调试信息(你也可以在仓颉侧添加日志)。

七、常见问题(踩坑速查)

  • ArkTS 找不到仓颉导出类/方法
    • 确认方法/类为 public,并且放在 Hybrid 模板识别的 cangjie/ 模块;重新构建后,IDE 会在 ets/ 下生成/刷新同名包装。
  • 编译报错:语法/关键字不识别
    • 仓颉版本更新较快,细节会调整;请对照 “仓颉语言指南/Spec” 更新语法(如集合类型、可空类型、控制流关键字等)。
  • Canvas API 少量差异
    • ArkUI 版本不同,CanvasRenderingContext2D 的方法名可能有细微出入(如 strokeRect/fillRect/fillText 等);以当前 ArkUI 文档/IDE 提示为准。
  • 性能优化
    • 网格大或步进频率高时,可在仓颉侧做脏矩形/区域更新、减少 ArkTS ↔ 仓颉状态拷贝;逻辑仍放在仓颉侧,只把必要绘制数据返回 ArkTS。

八、扩展方向(可选)

  • 难度曲线:每吃 5 个食物将 tick 间隔减 5ms。
  • 音效与振动:在 ArkTS 侧接入系统能力(音频、震动)。
  • 存档/排行榜:ArkTS 侧读写首选项/云端;仓颉只聚焦状态机与规则。
  • 从“组件混入”到“UI 组件”:仓颉也可实现自定义可复用组件,由 ArkTS 页面引入。

九、目录结构参考

CjSnake/

├─ cangjie/

│ └─ game/

│ └─ Snake.cj

├─ ets/

│ ├─ pages/

│ │ └─ Index.ets

│ └─ cangjie/ ← DevEco 自动生成的仓颉包装(只读)

├─ AppScope/

└─ build-profile.json5 / module.json5 / hvigorfiles …

资料与进一步学习

  • 华为开发者联盟·仓颉语言(总览、指南、招募、下载入口)——了解仓颉的定位、特性与 DevEco 集成。
  • Cangjie 官方文档站(白皮书 / 语言规范 / 入门)——查看最新语法与标准库能力。
  • ArkTS / ArkUI 基础——理解声明式 UI 与 Canvas 绘制,再与仓颉混合。
  • 混合开发实践文章(Cangjie Hybrid 项目结构、调用方式要点)——操作路径与模板行为讲解。
  • 生态与迁移综述(HarmonyOS NEXT 迁移/开发指南)——对照平台演进与最佳实践。

结语

本文用仓颉写规则 + ArkTS/ArkUI 画 UI的混合方式实现了一个完整可玩的《贪吃蛇》。你可以把这套骨架直接复用到其它经典小游戏(例如 俄罗斯方块、打砖块、扫雷):只需把仓颉侧的状态机与碰撞规则替换,ArkTS 的 Canvas 绘制与输入处理几乎保持不变。祝你在 HarmonyOS NEXT 的仓颉世界里玩得开心、写得飞快!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容