Skip to content
This repository has been archived by the owner on Jan 30, 2022. It is now read-only.

Commit

Permalink
实现坦克转弯预留位置机制,优化坦克碰撞规则
Browse files Browse the repository at this point in the history
  • Loading branch information
feichao93 committed Jul 9, 2018
1 parent 818e37a commit 8bff100
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 35 deletions.
2 changes: 1 addition & 1 deletion app/ai/AITankCtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default class AITankCtx {
return { type: 'turn', direction }
} else if (this.forwardLength > 0) {
const { xy } = getDirectionInfo(tank.direction)
const movedLength = Math.abs(tank.get(xy, undefined) - this.startPos)
const movedLength = Math.abs(tank[xy] - this.startPos)
const maxDistance = this.forwardLength - movedLength
if (movedLength === this.forwardLength) {
this.forwardLength = 0
Expand Down
4 changes: 3 additions & 1 deletion app/components/BattleFieldScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export class BattleFieldContent extends React.PureComponent<Partial<State & Poin
{bullets.map((b, i) => <Bullet key={i} bullet={b} />).toArray()}
</g>
<g className="tank-layer">
{activeTanks.map(tank => <Tank key={tank.tankId} tank={tank} />).toArray()}
{activeTanks
.map(tank => <Tank key={tank.tankId} tank={tank} showReservedIndicator />)
.toArray()}
</g>
<g className="helmet-layer">
{activeTanks
Expand Down
16 changes: 14 additions & 2 deletions app/components/tanks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function calculateTankTransform(tank: TankRecord) {
type P = {
tank: TankRecord
time: number
showReservedIndicator?: boolean
}

type S = { lastTireShape: number }
Expand All @@ -157,17 +158,28 @@ export class TankClassBase extends React.Component<P, S> {
}

render() {
const { tank, time } = this.props
const { tank, time, showReservedIndicator } = this.props
const { lastTireShape } = this.state

const color = resolveTankColorConfig(tank).find(time - this.startTime)
const shape = tank.moving ? tireShapeTiming.find(time - this.startTime) : lastTireShape
const imageKey = `Tank/${tank.side}/${tank.level}/${color}/${shape}`
return (
const img = (
<Image imageKey={imageKey} transform={calculateTankTransform(tank)} width="16" height="16">
{React.createElement(resolveTankComponent(tank.side, tank.level), { color, shape })}
</Image>
)

if (DEV.RESTRICTED_AREA && showReservedIndicator) {
return (
<g>
{img}
<rect width="16" height="16" x={tank.rx} y={tank.ry} fill="yellow" opacity={0.4} />
</g>
)
} else {
return img
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions app/reducers/tanks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export default function tanks(state = Map() as TanksMap, action: Action) {
} else if (action.type === 'START_STAGE') {
return state.clear()
} else if (action.type === 'MOVE') {
const { tankId, x, y, direction } = action
return state.update(tankId, t => t.merge({ x, y, direction }))
return state.update(action.tankId, t => t.merge(action))
} else if (action.type === 'START_MOVE') {
return state.setIn([action.tankId, 'moving'], true)
} else if (action.type === 'STOP_MOVE') {
Expand Down
5 changes: 4 additions & 1 deletion app/sagas/common/spawnTank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export default function* spawnTank(tank: TankRecord, spawnSpeed = 1) {
if (!DEV.FAST) {
yield flickerSaga(tank.x, tank.y, spawnSpeed)
}
yield put({ type: 'ADD_TANK', tank })
yield put<Action>({
type: 'ADD_TANK',
tank: tank.merge({ rx: tank.x, ry: tank.y }),
})
} finally {
yield put<Action>({
type: 'REMOVE_RESTRICTED_AREA',
Expand Down
64 changes: 38 additions & 26 deletions app/sagas/directionController.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
import { put, select, take } from 'redux-saga/effects'
import { Input, TankRecord } from '../types'
import canTankMove from '../utils/canTankMove'
import { getDirectionInfo } from '../utils/common'
import { ceil8, floor8, getDirectionInfo, isPerpendicular, round8 } from '../utils/common'
import * as selectors from '../utils/selectors'
import values from '../utils/values'

// 坦克进行转向时, 需要对坐标进行处理
// 如果转向前的方向为 left / right, 则将 x 坐标转换到最近的 8 的倍数
// 如果转向前的方向为 up / down, 则将 y 坐标设置为最近的 8 的倍数
// 这样做是为了使坦克转向之后更容易的向前行驶, 因为障碍物(brick/steel/river)的坐标也总是4或8的倍数
// 但是有的时候简单的使用 round8 来转换坐标, 可能使得坦克卡在障碍物中
// 所以这里转向的时候, 需要同时尝试 floor8 和 ceil8 来转换坐标
function* getReservedTank(tank: TankRecord) {
const { xy } = getDirectionInfo(tank.direction)
const coordinate = tank[xy]
const useFloor = tank.set(xy, floor8(coordinate))
const useCeil = tank.set(xy, ceil8(coordinate))
const canMoveWhenUseFloor = yield select(canTankMove, useFloor)
const canMoveWhenUseCeil = yield select(canTankMove, useCeil)

if (!canMoveWhenUseFloor) {
return useCeil
} else if (!canMoveWhenUseCeil) {
return useFloor
} else {
return tank.set(xy, round8(coordinate))
}
}

function move(tank: TankRecord): Action.Move {
return { type: 'MOVE', tankId: tank.tankId, x: tank.x, y: tank.y, direction: tank.direction }
return {
type: 'MOVE',
tankId: tank.tankId,
x: tank.x,
y: tank.y,
rx: tank.rx,
ry: tank.ry,
direction: tank.direction,
}
}

export default function* directionController(
Expand All @@ -28,39 +59,20 @@ export default function* directionController(
yield put({ type: 'STOP_MOVE', tankId: tank.tankId })
}
} else if (input.type === 'turn') {
const { direction } = input
// 坦克进行转向时, 需要对坐标进行处理
// 如果转向UP/DOWN, 则将x坐标转换到最近的8的倍数
// 如果转向为LEFT/RIGHT, 则将y坐标设置为最近的8的倍数
// 这样做是为了使坦克转向之后更容易的向前行驶, 因为障碍物(brick/steel/river)的坐标也总是4或8的倍数
// 但是有的时候简单的使用Math.round来转换坐标, 可能使得坦克卡在障碍物中
// 所以这里转向的时候, 需要同时尝试Math.floor和Math.ceil来转换坐标
const turned = tank.set('direction', direction) // 转向之后的tank对象
// 要进行校准的坐标字段
const { xy } = getDirectionInfo(direction, true)
const n = tank.get(xy, undefined) / 8
const useFloor = turned.set(xy, Math.floor(n) * 8)
const useCeil = turned.set(xy, Math.ceil(n) * 8)
const canMoveWhenUseFloor = yield select(canTankMove, useFloor)
const canMoveWhenUseCeil = yield select(canTankMove, useCeil)
let movedTank
if (!canMoveWhenUseFloor) {
movedTank = useCeil
} else if (!canMoveWhenUseCeil) {
movedTank = useFloor
if (isPerpendicular(input.direction, tank.direction)) {
yield put(move(tank.useReservedXY().set('direction', input.direction)))
} else {
// use-round
movedTank = turned.set(xy, Math.round(n) * 8)
yield put(move(tank.set('direction', input.direction)))
}
yield put(move(movedTank))
} else if (input.type === 'forward') {
const speed = values.moveSpeed(tank)
const distance = Math.min(delta * speed, input.maxDistance || Infinity)

const { xy, updater } = getDirectionInfo(tank.direction)
const movedTank = tank.update(xy, updater(distance))
if (yield select(canTankMove, movedTank)) {
yield put(move(movedTank))
const reservedTank: TankRecord = yield getReservedTank(movedTank)
yield put(move(movedTank.merge({ rx: reservedTank.x, ry: reservedTank.y })))
if (!tank.moving) {
yield put({ type: 'START_MOVE', tankId: tank.tankId })
}
Expand Down
8 changes: 8 additions & 0 deletions app/types/TankRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const TankRecordType = Record({
hp: 1,
withPowerUp: false,

// 坦克转弯预留位置的坐标
rx: 0,
ry: 0,

// helmetDuration用来记录tank的helmet的剩余的持续时间
helmetDuration: 0,
// frozenTimeout小于等于0表示可以进行移动, 大于0表示还需要等待frozen毫秒才能进行移动
Expand All @@ -25,4 +29,8 @@ export default class TankRecord extends TankRecordType {
static fromJS(object: any) {
return new TankRecord(object)
}

useReservedXY() {
return this.merge({ x: this.rx, y: this.ry })
}
}
2 changes: 2 additions & 0 deletions app/utils/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ declare global {
tankId: TankId
x: number
y: number
rx: number
ry: number
direction: Direction
}

Expand Down
3 changes: 2 additions & 1 deletion app/utils/canTankMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ function isTankCollidedWithOtherTanks(
if (tank.tankId === otherTank.tankId) {
continue
}
const subject = asRect(otherTank)
// 判断坦克相撞时,subject 一方需要使用预留位置
const subject = asRect(otherTank.useReservedXY())
if (testCollide(subject, tankTarget, threshhold)) {
return true
}
Expand Down
14 changes: 14 additions & 0 deletions app/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,17 @@ export function* waitFor(emitter: EventEmitter, expectedType: string) {
emitter.removeListener(expectedType, callback)
}
}

export const round8 = (x: number) => Math.round(x / 8) * 8
export const floor8 = (x: number) => Math.floor(x / 8) * 8
export const ceil8 = (x: number) => Math.ceil(x / 8) * 8

export function xor(p: boolean, q: boolean) {
return (p && !q) || (!p && q)
}

export function isPerpendicular(dir1: Direction, dir2: Direction) {
const isDir1Vertical = dir1 === 'up' || dir1 === 'down'
const isDir2Vertical = dir2 === 'up' || dir2 === 'down'
return xor(isDir1Vertical, isDir2Vertical)
}
2 changes: 1 addition & 1 deletion devConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = dev => ({
'DEV.SPOT_GRAPH': false,
// 是否显示 <TankPath />
'DEV.TANK_PATH': false,
// 是否显示 <RestrictedAreaLayer />
// 是否显示 <RestrictedAreaLayer /> 与坦克的转弯保留位置指示器
'DEV.RESTRICTED_AREA': dev,
// 是否加快游戏过程
'DEV.FAST': false,
Expand Down

0 comments on commit 8bff100

Please sign in to comment.